diff --git a/src/renderer/src/pages/settings/MCPSettings/SyncServersPopup.tsx b/src/renderer/src/pages/settings/MCPSettings/SyncServersPopup.tsx index f25a1c8735..ee86520144 100644 --- a/src/renderer/src/pages/settings/MCPSettings/SyncServersPopup.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/SyncServersPopup.tsx @@ -8,6 +8,7 @@ import styled from 'styled-components' import { getModelScopeToken, saveModelScopeToken, syncModelScopeServers } from './modelscopeSyncUtils' import { getTokenFluxToken, saveTokenFluxToken, syncTokenFluxServers, TOKENFLUX_HOST } from './providers/tokenflux' +import { getTokenLanYunToken, LANYUN_KEY_HOST, saveTokenLanYunToken, syncTokenLanYunServers } from './providers/lanyun' // Provider configuration interface interface ProviderConfig { @@ -45,6 +46,17 @@ const providers: ProviderConfig[] = [ getToken: getTokenFluxToken, saveToken: saveTokenFluxToken, syncServers: syncTokenFluxServers + }, + { + key: 'lanyun', + name: '蓝耘科技', + description: '蓝耘科技云平台 MCP 服务', + discoverUrl: 'https://mcp.lanyun.net', + apiKeyUrl: LANYUN_KEY_HOST, + tokenFieldName: 'tokenLanyunToken', + getToken: getTokenLanYunToken, + saveToken: saveTokenLanYunToken, + syncServers: syncTokenLanYunServers } ] diff --git a/src/renderer/src/pages/settings/MCPSettings/providers/lanyun.ts b/src/renderer/src/pages/settings/MCPSettings/providers/lanyun.ts new file mode 100644 index 0000000000..acd424de90 --- /dev/null +++ b/src/renderer/src/pages/settings/MCPSettings/providers/lanyun.ts @@ -0,0 +1,178 @@ +import type { MCPServer } from '@renderer/types' +import i18next from 'i18next' + +// Token storage constants and utilities +const TOKEN_STORAGE_KEY = 'tokenLanyunToken' +export const TOKENLANYUN_HOST = 'https://mcp.lanyun.net' +export const LANYUN_MCP_HOST = TOKENLANYUN_HOST + '/mcp/manager/selectListByApiKey' +export const LANYUN_KEY_HOST = TOKENLANYUN_HOST + '/#/manage/apiKey' + +export const saveTokenLanYunToken = (token: string): void => { + localStorage.setItem(TOKEN_STORAGE_KEY, token) +} + +export const getTokenLanYunToken = (): string | null => { + return localStorage.getItem(TOKEN_STORAGE_KEY) +} + +export const clearTokenLanYunToken = (): void => { + localStorage.removeItem(TOKEN_STORAGE_KEY) +} + +export const hasTokenLanYunToken = (): boolean => { + return !!getTokenLanYunToken() +} + +interface TokenLanYunServer { + id: string + /** + * locales 字段用于存储多语言信息。 + * 其中 key(lang)为语言代码(如 'zh', 'en'), + * value 为该语言下的 name 和 description。 + * 例如: + * { + * "zh": { name: "文档处理工具", description: "..." }, + * "en": { name: "Document Processor", description: "..." } + * } + */ + locales?: { + [lang: string]: { + description?: string + name?: string + } + } + chineseName?: string + description?: string + operationalUrls?: { url: string }[] + tags?: string[] + logoUrl?: string +} + +interface TokenLanYunSyncResult { + success: boolean + message: string + addedServers: MCPServer[] + errorDetails?: string +} + +// Function to fetch and process TokenLanYun servers +export const syncTokenLanYunServers = async ( + token: string, + existingServers: MCPServer[] +): Promise => { + const t = i18next.t + + try { + const response = await fetch(LANYUN_MCP_HOST, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + } + }) + + // Handle authentication errors + if (response.status === 401 || response.status === 403) { + clearTokenLanYunToken() + return { + success: false, + message: t('settings.mcp.sync.unauthorized', 'Sync Unauthorized'), + addedServers: [] + } + } + + // Handle server errors + if (response.status === 500 || !response.ok) { + return { + success: false, + message: t('settings.mcp.sync.error'), + addedServers: [], + errorDetails: `Status: ${response.status}` + } + } + + // Process successful response + const data = await response.json() + if (data.code === 401) { + return { + success: false, + message: t('settings.mcp.sync.unauthorized', 'Sync Unauthorized'), + addedServers: [], + errorDetails: `Status: ${response.status}` + } + } + if (data.code === 500) { + return { + success: false, + message: t('settings.mcp.sync.error'), + addedServers: [], + errorDetails: `Status: ${response.status}` + } + } + + const servers: TokenLanYunServer[] = data.data || [] + + if (servers.length === 0) { + return { + success: true, + message: t('settings.mcp.sync.noServersAvailable', 'No MCP servers available'), + addedServers: [] + } + } + + // Transform Token servers to MCP servers format + const addedServers: MCPServer[] = [] + console.log('TokenLanYun servers:', servers) + for (const server of servers) { + try { + if (!server.operationalUrls?.[0]?.url) continue + + // If any existing server id contains '@lanyun', clear them before adding new ones + // if (existingServers.some((s) => s.id.startsWith('@lanyun'))) { + // for (let i = existingServers.length - 1; i >= 0; i--) { + // if (existingServers[i].id.startsWith('@lanyun')) { + // existingServers.splice(i, 1) + // } + // } + // } + // Skip if server already exists after clearing + if (existingServers.some((s) => s.id === `@lanyun/${server.id}`)) continue + + const mcpServer: MCPServer = { + id: `@lanyun/${server.id}`, + name: + server.chineseName || server.locales?.zh?.name || server.locales?.en?.name || `LanYun Server ${server.id}`, + description: server.description || '', + type: 'sse', + baseUrl: server.operationalUrls[0].url, + command: '', + args: [], + env: {}, + isActive: true, + provider: '蓝耘科技', + providerUrl: server.operationalUrls[0].url, + logoUrl: server.logoUrl || '', + tags: server.tags ?? (server.chineseName ? [server.chineseName] : []) + } + + addedServers.push(mcpServer) + } catch (err) { + console.error('Error processing LanYun server:', err) + } + } + + return { + success: true, + message: t('settings.mcp.sync.success', { count: addedServers.length }), + addedServers + } + } catch (error) { + console.error('TokenLanyun sync error:', error) + return { + success: false, + message: t('settings.mcp.sync.error'), + addedServers: [], + errorDetails: String(error) + } + } +}