diff --git a/src/renderer/src/pages/settings/MCPSettings/SyncServersPopup.tsx b/src/renderer/src/pages/settings/MCPSettings/SyncServersPopup.tsx index 55936e1ede..fdde4f49e3 100644 --- a/src/renderer/src/pages/settings/MCPSettings/SyncServersPopup.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/SyncServersPopup.tsx @@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next' import styled from 'styled-components' import { getModelScopeToken, saveModelScopeToken, syncModelScopeServers } from './modelscopeSyncUtils' +import { getAI302Token, saveAI302Token, syncAi302Servers } from './providers/302ai' import { getTokenLanYunToken, LANYUN_KEY_HOST, saveTokenLanYunToken, syncTokenLanYunServers } from './providers/lanyun' import { getTokenFluxToken, saveTokenFluxToken, syncTokenFluxServers, TOKENFLUX_HOST } from './providers/tokenflux' @@ -57,6 +58,17 @@ const providers: ProviderConfig[] = [ getToken: getTokenLanYunToken, saveToken: saveTokenLanYunToken, syncServers: syncTokenLanYunServers + }, + { + key: '302ai', + name: '302AI', + description: '302AI 平台 MCP 服务', + discoverUrl: 'https://302.ai', + apiKeyUrl: 'https://dash.302.ai/apis/list', + tokenFieldName: 'token302aiToken', + getToken: getAI302Token, + saveToken: saveAI302Token, + syncServers: syncAi302Servers } ] diff --git a/src/renderer/src/pages/settings/MCPSettings/providers/302ai.ts b/src/renderer/src/pages/settings/MCPSettings/providers/302ai.ts new file mode 100644 index 0000000000..8a8cb10cd9 --- /dev/null +++ b/src/renderer/src/pages/settings/MCPSettings/providers/302ai.ts @@ -0,0 +1,118 @@ +import { nanoid } from '@reduxjs/toolkit' +import type { MCPServer } from '@renderer/types' +import i18next from 'i18next' + +// Token storage constants and utilities +const TOKEN_STORAGE_KEY = 'ai302_token' +export const AI302_HOST = 'https://api.302.ai/mcp' + +export const saveAI302Token = (token: string): void => { + localStorage.setItem(TOKEN_STORAGE_KEY, token) +} + +export const getAI302Token = (): string | null => { + return localStorage.getItem(TOKEN_STORAGE_KEY) +} + +export const clearAI302Token = (): void => { + localStorage.removeItem(TOKEN_STORAGE_KEY) +} + +export const hasAI302Token = (): boolean => { + return !!getAI302Token() +} + +interface Ai302SyncResult { + success: boolean + message: string + addedServers: MCPServer[] + errorDetails?: string +} + +// Function to fetch and process 302ai servers +export const syncAi302Servers = async (token: string, existingServers: MCPServer[]): Promise => { + const t = i18next.t + + try { + const response = await fetch(`${AI302_HOST}/v1/mcps/list?baseUrl=https://api.302.ai/custom-mcp/mcp`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': token + } + }) + + // Handle authentication errors + if (response.status === 401 || response.status === 403) { + clearAI302Token() + 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() + const servers: MCPServer[] = data.mcps || [] + console.log('servers', servers) + + if (servers.length === 0) { + return { + success: true, + message: t('settings.mcp.sync.noServersAvailable', 'No MCP servers available'), + addedServers: [] + } + } + + // Transform TokenFlux servers to MCP servers format + const addedServers: MCPServer[] = [] + + for (const server of servers) { + try { + // Skip if server already exists + if (existingServers.some((s) => s.id === `@302ai/${server.name}`)) continue + const mcpServer: MCPServer = { + id: `@302ai/${server.name}`, + name: server.name || `302ai Server ${nanoid()}`, + description: server.description || '', + type: server.type, + baseUrl: server.baseUrl, + isActive: server.isActive, + provider: server.provider, + providerUrl: server.providerUrl, + tags: server.tags, + logoUrl: server.logoUrl + } + + addedServers.push(mcpServer) + } catch (err) { + console.error('Error processing 302ai server:', err) + } + } + + return { + success: true, + message: t('settings.mcp.sync.success', { count: addedServers.length }), + addedServers + } + } catch (error) { + console.error('302ai sync error:', error) + return { + success: false, + message: t('settings.mcp.sync.error'), + addedServers: [], + errorDetails: String(error) + } + } +}