mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-24 02:20:10 +08:00
feat(MCPRouter): add MCPRouter provider support and integration
- Introduced MCPRouter provider with token management and server synchronization functionalities. - Added MCPRouter logo to settings page for visual representation. - Updated provider configuration to include MCPRouter details and API interactions. - Implemented functions for saving, retrieving, and clearing MCPRouter tokens, along with server synchronization logic.
This commit is contained in:
parent
bfef0c5580
commit
44b2b859da
BIN
src/renderer/src/assets/images/providers/mcprouter.webp
Normal file
BIN
src/renderer/src/assets/images/providers/mcprouter.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@ -2,6 +2,7 @@ import { ArrowLeftOutlined } from '@ant-design/icons'
|
|||||||
import Ai302ProviderLogo from '@renderer/assets/images/providers/302ai.webp'
|
import Ai302ProviderLogo from '@renderer/assets/images/providers/302ai.webp'
|
||||||
import BailianProviderLogo from '@renderer/assets/images/providers/bailian.png'
|
import BailianProviderLogo from '@renderer/assets/images/providers/bailian.png'
|
||||||
import LanyunProviderLogo from '@renderer/assets/images/providers/lanyun.png'
|
import LanyunProviderLogo from '@renderer/assets/images/providers/lanyun.png'
|
||||||
|
import MCPRouterProviderLogo from '@renderer/assets/images/providers/mcprouter.webp'
|
||||||
import ModelScopeProviderLogo from '@renderer/assets/images/providers/modelscope.png'
|
import ModelScopeProviderLogo from '@renderer/assets/images/providers/modelscope.png'
|
||||||
import TokenFluxProviderLogo from '@renderer/assets/images/providers/tokenflux.png'
|
import TokenFluxProviderLogo from '@renderer/assets/images/providers/tokenflux.png'
|
||||||
import DividerWithText from '@renderer/components/DividerWithText'
|
import DividerWithText from '@renderer/components/DividerWithText'
|
||||||
@ -72,7 +73,8 @@ const MCPSettings: FC = () => {
|
|||||||
tokenflux: <ProviderIcon src={TokenFluxProviderLogo} alt="TokenFlux" />,
|
tokenflux: <ProviderIcon src={TokenFluxProviderLogo} alt="TokenFlux" />,
|
||||||
lanyun: <ProviderIcon src={LanyunProviderLogo} alt="Lanyun" />,
|
lanyun: <ProviderIcon src={LanyunProviderLogo} alt="Lanyun" />,
|
||||||
'302ai': <ProviderIcon src={Ai302ProviderLogo} alt="302AI" />,
|
'302ai': <ProviderIcon src={Ai302ProviderLogo} alt="302AI" />,
|
||||||
bailian: <ProviderIcon src={BailianProviderLogo} alt="Bailian" />
|
bailian: <ProviderIcon src={BailianProviderLogo} alt="Bailian" />,
|
||||||
|
mcprouter: <ProviderIcon src={MCPRouterProviderLogo} alt="MCPRouter" />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import type { MCPServer } from '@renderer/types'
|
|||||||
import { getAI302Token, saveAI302Token, syncAi302Servers } from './302ai'
|
import { getAI302Token, saveAI302Token, syncAi302Servers } from './302ai'
|
||||||
import { getBailianToken, saveBailianToken, syncBailianServers } from './bailian'
|
import { getBailianToken, saveBailianToken, syncBailianServers } from './bailian'
|
||||||
import { getTokenLanYunToken, LANYUN_KEY_HOST, saveTokenLanYunToken, syncTokenLanYunServers } from './lanyun'
|
import { getTokenLanYunToken, LANYUN_KEY_HOST, saveTokenLanYunToken, syncTokenLanYunServers } from './lanyun'
|
||||||
|
import { getMCPRouterToken, saveMCPRouterToken, syncMCPRouterServers } from './mcprouter'
|
||||||
import { getModelScopeToken, MODELSCOPE_HOST, saveModelScopeToken, syncModelScopeServers } from './modelscope'
|
import { getModelScopeToken, MODELSCOPE_HOST, saveModelScopeToken, syncModelScopeServers } from './modelscope'
|
||||||
import { getTokenFluxToken, saveTokenFluxToken, syncTokenFluxServers, TOKENFLUX_HOST } from './tokenflux'
|
import { getTokenFluxToken, saveTokenFluxToken, syncTokenFluxServers, TOKENFLUX_HOST } from './tokenflux'
|
||||||
|
|
||||||
@ -73,5 +74,16 @@ export const providers: ProviderConfig[] = [
|
|||||||
getToken: getBailianToken,
|
getToken: getBailianToken,
|
||||||
saveToken: saveBailianToken,
|
saveToken: saveBailianToken,
|
||||||
syncServers: syncBailianServers
|
syncServers: syncBailianServers
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'mcprouter',
|
||||||
|
name: 'MCP Router',
|
||||||
|
description: 'MCP Router 平台 MCP 服务',
|
||||||
|
discoverUrl: 'https://mcprouter.co',
|
||||||
|
apiKeyUrl: 'https://mcprouter.co/settings/keys',
|
||||||
|
tokenFieldName: 'mcprouterToken',
|
||||||
|
getToken: getMCPRouterToken,
|
||||||
|
saveToken: saveMCPRouterToken,
|
||||||
|
syncServers: syncMCPRouterServers
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -0,0 +1,157 @@
|
|||||||
|
import { loggerService } from '@logger'
|
||||||
|
import { nanoid } from '@reduxjs/toolkit'
|
||||||
|
import type { MCPServer } from '@renderer/types'
|
||||||
|
import i18next from 'i18next'
|
||||||
|
|
||||||
|
const logger = loggerService.withContext('MCPRouterSyncUtils')
|
||||||
|
|
||||||
|
// Token storage constants and utilities
|
||||||
|
const TOKEN_STORAGE_KEY = 'mcprouter_token'
|
||||||
|
export const MCPROUTER_HOST = 'https://mcprouter.co'
|
||||||
|
|
||||||
|
export const saveMCPRouterToken = (token: string): void => {
|
||||||
|
localStorage.setItem(TOKEN_STORAGE_KEY, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getMCPRouterToken = (): string | null => {
|
||||||
|
return localStorage.getItem(TOKEN_STORAGE_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clearMCPRouterToken = (): void => {
|
||||||
|
localStorage.removeItem(TOKEN_STORAGE_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hasMCPRouterToken = (): boolean => {
|
||||||
|
return !!getMCPRouterToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MCPRouterServer {
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
name: string
|
||||||
|
author_name?: string
|
||||||
|
title?: string
|
||||||
|
description?: string
|
||||||
|
content?: string
|
||||||
|
server_key: string
|
||||||
|
config_name: string
|
||||||
|
server_url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MCPRouterSyncResult {
|
||||||
|
success: boolean
|
||||||
|
message: string
|
||||||
|
addedServers: MCPServer[]
|
||||||
|
updatedServers: MCPServer[]
|
||||||
|
errorDetails?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to fetch and process MCPRouter servers
|
||||||
|
export const syncMCPRouterServers = async (
|
||||||
|
token: string,
|
||||||
|
existingServers: MCPServer[]
|
||||||
|
): Promise<MCPRouterSyncResult> => {
|
||||||
|
const t = i18next.t
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://api.mcprouter.to/v1/list-servers', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'HTTP-Referer': 'https://cherry-ai.com',
|
||||||
|
'X-Title': 'Cherry Studio'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle authentication errors
|
||||||
|
if (response.status === 401 || response.status === 403) {
|
||||||
|
clearMCPRouterToken()
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: t('settings.mcp.sync.unauthorized', 'Sync Unauthorized'),
|
||||||
|
addedServers: [],
|
||||||
|
updatedServers: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle server errors
|
||||||
|
if (response.status === 500 || !response.ok) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: t('settings.mcp.sync.error'),
|
||||||
|
addedServers: [],
|
||||||
|
updatedServers: [],
|
||||||
|
errorDetails: `Status: ${response.status}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process successful response
|
||||||
|
const data = await response.json()
|
||||||
|
const servers: MCPRouterServer[] = data.data?.servers || []
|
||||||
|
|
||||||
|
if (servers.length === 0) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: t('settings.mcp.sync.noServersAvailable', 'No MCP servers available'),
|
||||||
|
addedServers: [],
|
||||||
|
updatedServers: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform MCPRouter servers to MCP servers format
|
||||||
|
const addedServers: MCPServer[] = []
|
||||||
|
const updatedServers: MCPServer[] = []
|
||||||
|
|
||||||
|
for (const server of servers) {
|
||||||
|
try {
|
||||||
|
// Check if server already exists using server_key
|
||||||
|
const existingServer = existingServers.find((s) => s.id === `@mcprouter/${server.server_key}`)
|
||||||
|
|
||||||
|
const mcpServer: MCPServer = {
|
||||||
|
id: `@mcprouter/${server.server_key}`,
|
||||||
|
name: server.title || server.name || `MCPRouter Server ${nanoid()}`,
|
||||||
|
description: server.description || '',
|
||||||
|
type: 'streamableHttp',
|
||||||
|
baseUrl: server.server_url,
|
||||||
|
isActive: true,
|
||||||
|
provider: 'MCPRouter',
|
||||||
|
providerUrl: `https://mcprouter.co/${server.server_key}`,
|
||||||
|
logoUrl: '',
|
||||||
|
tags: [],
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingServer) {
|
||||||
|
// Update existing server with corrected URL and latest info
|
||||||
|
updatedServers.push(mcpServer)
|
||||||
|
} else {
|
||||||
|
// Add new server
|
||||||
|
addedServers.push(mcpServer)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Error processing MCPRouter server:', err as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalServers = addedServers.length + updatedServers.length
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: t('settings.mcp.sync.success', { count: totalServers }),
|
||||||
|
addedServers,
|
||||||
|
updatedServers
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('MCPRouter sync error:', error as Error)
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: t('settings.mcp.sync.error'),
|
||||||
|
addedServers: [],
|
||||||
|
updatedServers: [],
|
||||||
|
errorDetails: String(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user