diff --git a/src/renderer/src/assets/images/providers/mcprouter.webp b/src/renderer/src/assets/images/providers/mcprouter.webp
new file mode 100644
index 0000000000..7d6557fafc
Binary files /dev/null and b/src/renderer/src/assets/images/providers/mcprouter.webp differ
diff --git a/src/renderer/src/pages/settings/MCPSettings/index.tsx b/src/renderer/src/pages/settings/MCPSettings/index.tsx
index 6e4ac3d2af..6adb64ca23 100644
--- a/src/renderer/src/pages/settings/MCPSettings/index.tsx
+++ b/src/renderer/src/pages/settings/MCPSettings/index.tsx
@@ -2,6 +2,7 @@ import { ArrowLeftOutlined } from '@ant-design/icons'
import Ai302ProviderLogo from '@renderer/assets/images/providers/302ai.webp'
import BailianProviderLogo from '@renderer/assets/images/providers/bailian.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 TokenFluxProviderLogo from '@renderer/assets/images/providers/tokenflux.png'
import DividerWithText from '@renderer/components/DividerWithText'
@@ -72,7 +73,8 @@ const MCPSettings: FC = () => {
tokenflux: ,
lanyun: ,
'302ai': ,
- bailian:
+ bailian: ,
+ mcprouter:
}
return (
diff --git a/src/renderer/src/pages/settings/MCPSettings/providers/config.ts b/src/renderer/src/pages/settings/MCPSettings/providers/config.ts
index efe46be5fb..6b094536e1 100644
--- a/src/renderer/src/pages/settings/MCPSettings/providers/config.ts
+++ b/src/renderer/src/pages/settings/MCPSettings/providers/config.ts
@@ -3,6 +3,7 @@ import type { MCPServer } from '@renderer/types'
import { getAI302Token, saveAI302Token, syncAi302Servers } from './302ai'
import { getBailianToken, saveBailianToken, syncBailianServers } from './bailian'
import { getTokenLanYunToken, LANYUN_KEY_HOST, saveTokenLanYunToken, syncTokenLanYunServers } from './lanyun'
+import { getMCPRouterToken, saveMCPRouterToken, syncMCPRouterServers } from './mcprouter'
import { getModelScopeToken, MODELSCOPE_HOST, saveModelScopeToken, syncModelScopeServers } from './modelscope'
import { getTokenFluxToken, saveTokenFluxToken, syncTokenFluxServers, TOKENFLUX_HOST } from './tokenflux'
@@ -73,5 +74,16 @@ export const providers: ProviderConfig[] = [
getToken: getBailianToken,
saveToken: saveBailianToken,
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
}
]
diff --git a/src/renderer/src/pages/settings/MCPSettings/providers/mcprouter.ts b/src/renderer/src/pages/settings/MCPSettings/providers/mcprouter.ts
new file mode 100644
index 0000000000..a993ce7383
--- /dev/null
+++ b/src/renderer/src/pages/settings/MCPSettings/providers/mcprouter.ts
@@ -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 => {
+ 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)
+ }
+ }
+}