From 86cdd3c44d0a64e735e8326bf1ab063ee1324718 Mon Sep 17 00:00:00 2001 From: zerob13 Date: Tue, 12 Aug 2025 15:49:10 +0800 Subject: [PATCH 1/6] wip: add mcp market --- docs/mcprouter-market.md | 54 ++++ src/main/presenter/mcpPresenter/index.ts | 46 +++ .../mcpPresenter/mcprouterManager.ts | 122 ++++++++ .../components/settings/McpBuiltinMarket.vue | 262 ++++++++++++++++++ .../src/components/settings/McpSettings.vue | 17 ++ src/renderer/src/i18n/en-US/mcp.json | 18 ++ src/renderer/src/i18n/ja-JP/mcp.json | 18 ++ src/renderer/src/i18n/zh-CN/mcp.json | 18 ++ src/renderer/src/i18n/zh-CN/routes.json | 3 +- src/renderer/src/router/index.ts | 9 + src/shared/presenter.d.ts | 23 ++ 11 files changed, 589 insertions(+), 1 deletion(-) create mode 100644 docs/mcprouter-market.md create mode 100644 src/main/presenter/mcpPresenter/mcprouterManager.ts create mode 100644 src/renderer/src/components/settings/McpBuiltinMarket.vue diff --git a/docs/mcprouter-market.md b/docs/mcprouter-market.md new file mode 100644 index 000000000..c9b55b733 --- /dev/null +++ b/docs/mcprouter-market.md @@ -0,0 +1,54 @@ +MCPRouter 市场集成说明 + +本文档说明 DeepChat 如何集成 MCPRouter 市场,并提供实施步骤。 + +后端(主进程) + +- 新增 `src/main/presenter/mcpPresenter/mcprouterManager.ts` + - listServers(page, limit): 调用 `https://api.mcprouter.to/v1/list-servers` + - Headers: `Content-Type: application/json`, `HTTP-Referer: deepchatai.cn`, `X-Title: DeepChat` + - getServer(serverKey): 调用 `https://api.mcprouter.to/v1/get-server` + - 需要 `Authorization: Bearer ` + - installServer(serverKey): 将返回的 `server_url` 转换为 MCP `http` 类型(Streamable HTTP)配置,写入本地: + - baseUrl = server_url + - customHeaders = { 'Content-Type': 'application/json', 'Authorization': 'Bearer ', 'HTTP-Referer': 'deepchatai.cn', 'X-Title': 'DeepChat' } + - icons 使用随机 emoji +- 在 McpPresenter 暴露: + - listMcpRouterServers(page, limit) + - installMcpRouterServer(serverKey) + - getMcpRouterApiKey() / setMcpRouterApiKey(key)(持久化到 ConfigPresenter 的 app-settings) + +配置持久化 + +- 使用 ConfigPresenter.setSetting('mcprouterApiKey', key) 持久化 API Key。 +- 所有来自 MCPRouter 的安装均复用该 Key。 + +前端(渲染层) + +- 新增页面 `src/renderer/src/components/settings/McpBuiltinMarket.vue` + - Grid/Gallery 样式列表,虚拟/无限滚动:滚动接近底部自动加载下一页,直到无更多数据。 + - 顶部提供 API Key 输入框与“获取密钥”按钮(跳转到 MCPRouter Key 页面)。 + - 每个卡片提供“安装”按钮: + - 若未配置 Key,先提醒; + - 调用主进程的 installMcpRouterServer(server_key) 安装。 +- 路由:`/settings/mcp-market`,在 SettingsTabView 菜单中显示为“内置MCP市场”。 +- McpSettings.vue 增加“浏览内置 MCP 市场”按钮。 + +i18n + +- 在 zh-CN/mcp.json 增加 market 文案键;在 zh-CN/routes.json 增加 settings-mcp-market。 + +使用步骤 + +1. 打开 设置 -> MCP -> 点击“浏览内置 MCP 市场”。 +2. 首次安装前,点击“获取密钥”并在 MCPRouter 控制台创建 API Key,复制粘贴到输入框并保存。 +3. 在市场页选择需要的服务,点击“安装”。 +4. 安装完成后回到 MCP 服务器列表,即可看到新增的 HTTP 类型服务,按需启用与启动。 + +注意事项 + +- 遵循 Electron 最佳实践,所有网络请求在主进程完成,渲染层通过 IPC 调用 Presenter。 +- 不破坏现有 MCP 配置结构;安装的服务以 http 类型保存,customHeaders 精确包含上文所述四个头。 +- 若接口返回空列表,前端展示“暂无服务/没有更多了”。 + + diff --git a/src/main/presenter/mcpPresenter/index.ts b/src/main/presenter/mcpPresenter/index.ts index 2e7981eb8..66a57eddd 100644 --- a/src/main/presenter/mcpPresenter/index.ts +++ b/src/main/presenter/mcpPresenter/index.ts @@ -12,6 +12,7 @@ import { } from '@shared/presenter' import { ServerManager } from './serverManager' import { ToolManager } from './toolManager' +import { McpRouterManager } from './mcprouterManager' import { eventBus, SendTarget } from '@/eventbus' import { MCP_EVENTS, NOTIFICATION_EVENTS } from '@/events' import { IConfigPresenter } from '@shared/presenter' @@ -83,6 +84,8 @@ export class McpPresenter implements IMCPPresenter { private toolManager: ToolManager private configPresenter: IConfigPresenter private isInitialized: boolean = false + // McpRouter + private mcprouter?: McpRouterManager constructor(configPresenter?: IConfigPresenter) { console.log('Initializing MCP Presenter') @@ -90,6 +93,12 @@ export class McpPresenter implements IMCPPresenter { this.configPresenter = configPresenter || presenter.configPresenter this.serverManager = new ServerManager(this.configPresenter) this.toolManager = new ToolManager(this.configPresenter, this.serverManager) + // init mcprouter manager + try { + this.mcprouter = new McpRouterManager(this.configPresenter) + } catch (e) { + console.warn('[MCP] McpRouterManager init failed:', e) + } // 监听自定义提示词服务器检查事件 eventBus.on(CONFIG_EVENTS.CUSTOM_PROMPTS_SERVER_CHECK_REQUIRED, async () => { @@ -181,6 +190,43 @@ export class McpPresenter implements IMCPPresenter { } } + // =============== McpRouter marketplace APIs =============== + async listMcpRouterServers( + page: number, + limit: number + ): Promise<{ + servers: Array<{ + uuid: string + 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 + }> + }> { + if (!this.mcprouter) throw new Error('McpRouterManager not available') + const data = await this.mcprouter.listServers(page, limit) + return { servers: data && data.servers ? data.servers : [] } + } + + async installMcpRouterServer(serverKey: string): Promise { + if (!this.mcprouter) throw new Error('McpRouterManager not available') + return this.mcprouter.installServer(serverKey) + } + + async getMcpRouterApiKey(): Promise { + return this.configPresenter.getSetting('mcprouterApiKey') || '' + } + + async setMcpRouterApiKey(key: string): Promise { + this.configPresenter.setSetting('mcprouterApiKey', key) + } + private scheduleBackgroundRegistryUpdate(): void { setTimeout(async () => { try { diff --git a/src/main/presenter/mcpPresenter/mcprouterManager.ts b/src/main/presenter/mcpPresenter/mcprouterManager.ts new file mode 100644 index 000000000..e42deeb65 --- /dev/null +++ b/src/main/presenter/mcpPresenter/mcprouterManager.ts @@ -0,0 +1,122 @@ +import { IConfigPresenter, MCPServerConfig } from '@shared/presenter' + +type McpRouterListResponse = { + code: number + message: string + data?: { + servers: Array<{ + uuid: string + 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 + }> + } +} + +type McpRouterGetResponse = { + code: number + message: string + data?: { + 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 + } +} + +const LIST_ENDPOINT = 'https://api.mcprouter.to/v1/list-servers' +const GET_ENDPOINT = 'https://api.mcprouter.to/v1/get-server' + +export class McpRouterManager { + constructor(private readonly configPresenter: IConfigPresenter) {} + + private getCommonHeaders(): Record { + return { + 'Content-Type': 'application/json', + 'HTTP-Referer': 'deepchatai.cn', + 'X-Title': 'DeepChat' + } + } + + async listServers(page: number, limit: number): Promise { + const res = await fetch(LIST_ENDPOINT, { + method: 'POST', + headers: this.getCommonHeaders(), + body: JSON.stringify({ page, limit }) + }) + if (!res.ok) throw new Error(`McpRouter list failed: HTTP ${res.status}`) + const json = (await res.json()) as McpRouterListResponse + if (json.code !== 0) throw new Error(json.message || 'List servers error') + return json.data || { servers: [] } + } + + async getServer(serverKey: string): Promise { + const apiKey = this.configPresenter.getSetting('mcprouterApiKey') || '' + if (!apiKey) throw new Error('McpRouter API key missing') + const headers = { + ...this.getCommonHeaders(), + Authorization: `Bearer ${apiKey}` + } + const res = await fetch(GET_ENDPOINT, { + method: 'POST', + headers, + body: JSON.stringify({ server: serverKey }) + }) + if (!res.ok) throw new Error(`McpRouter get failed: HTTP ${res.status}`) + const json = (await res.json()) as McpRouterGetResponse + if (json.code !== 0 || !json.data) throw new Error(json.message || 'Get server error') + return json.data + } + + private pickRandomEmoji(): string { + const emojis = ['🧩', '🛠️', '⚙️', '🚀', '🔧', '🧪', '📦', '🛰️', '🧠', '🔌', '📡', '🗂️'] + const idx = Math.floor(Math.random() * emojis.length) + return emojis[idx] + } + + /** + * Install a server from McpRouter to local MCP config as HTTP (Streamable) server + */ + async installServer(serverKey: string): Promise { + const detail = await this.getServer(serverKey) + if (!detail) throw new Error('Server detail not found') + + const apiKey = this.configPresenter.getSetting('mcprouterApiKey') || '' + if (!apiKey) throw new Error('McpRouter API key missing') + + // Build MCPServerConfig + const config: MCPServerConfig = { + command: '', + args: [], + env: {}, + descriptions: detail.description || detail.title || detail.name, + icons: this.pickRandomEmoji(), + autoApprove: ['all'], + disable: false, + type: 'http', + baseUrl: detail.server_url, + customHeaders: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${apiKey}`, + 'HTTP-Referer': 'deepchatai.cn', + 'X-Title': 'DeepChat' + } + } + + const serverName = detail.config_name || detail.server_key || detail.name + return await this.configPresenter.addMcpServer(serverName, config) + } +} diff --git a/src/renderer/src/components/settings/McpBuiltinMarket.vue b/src/renderer/src/components/settings/McpBuiltinMarket.vue new file mode 100644 index 000000000..8c9c8fc65 --- /dev/null +++ b/src/renderer/src/components/settings/McpBuiltinMarket.vue @@ -0,0 +1,262 @@ + + + + + diff --git a/src/renderer/src/components/settings/McpSettings.vue b/src/renderer/src/components/settings/McpSettings.vue index 3cf1eed77..9338fa6d7 100644 --- a/src/renderer/src/components/settings/McpSettings.vue +++ b/src/renderer/src/components/settings/McpSettings.vue @@ -168,6 +168,16 @@ {{ $t('settings.mcp.higressMarket') }} + + @@ -206,9 +216,11 @@ import { useMcpStore } from '@/stores/mcp' import { useLanguageStore } from '@/stores/language' import { useToast } from '@/components/ui/toast' import { MCP_MARKETPLACE_URL, HIGRESS_MCP_MARKETPLACE_URL } from '../mcp-config/const' +import { useRouter } from 'vue-router' const { t } = useI18n() const languageStore = useLanguageStore() +const router = useRouter() const mcpStore = useMcpStore() const { toast } = useToast() @@ -438,4 +450,9 @@ const openMcpMarketplace = () => { const openHigressMcpMarketplace = () => { window.open(HIGRESS_MCP_MARKETPLACE_URL, '_blank') } + +// 打开内置 MCP 市场 +const openBuiltinMarket = () => { + router.push('/settings/mcp-market') +} diff --git a/src/renderer/src/i18n/en-US/mcp.json b/src/renderer/src/i18n/en-US/mcp.json index 2e328d3e2..1e31d1b8e 100644 --- a/src/renderer/src/i18n/en-US/mcp.json +++ b/src/renderer/src/i18n/en-US/mcp.json @@ -246,5 +246,23 @@ "mcpDisabled": "MCP is disabled", "getPromptFailed": "Failed to get prompt", "readResourceFailed": "Failed to read resource" + }, + "market": { + "browseBuiltin": "Browse Built-in MCP Market", + "builtinTitle": "Built-in MCP Market", + "poweredBy": "Powered by MCPRouter", + "keyGuide": "Get API Key", + "keyHelpText": "Please go to", + "keyHelpEnd": "to apply for an API Key and fill it in the input box above", + "apiKeyPlaceholder": "Enter MCPRouter API Key", + "apiKeyRequiredTitle": "API Key Required", + "apiKeyRequiredDesc": "Please fill in the MCPRouter API Key before installation", + "install": "Install", + "installSuccess": "Installation successful", + "installFailed": "Installation failed", + "noMore": "No more", + "empty": "No services", + "loadMore": "Load More", + "pullDownToLoad": "Continue pulling down to load more" } } diff --git a/src/renderer/src/i18n/ja-JP/mcp.json b/src/renderer/src/i18n/ja-JP/mcp.json index 4fe0c8b63..fedcd06dc 100644 --- a/src/renderer/src/i18n/ja-JP/mcp.json +++ b/src/renderer/src/i18n/ja-JP/mcp.json @@ -246,5 +246,23 @@ "mcpDisabled": "MCPは無効になっています", "getPromptFailed": "プロンプトの取得に失敗しました", "readResourceFailed": "リソースの読み込みに失敗しました" + }, + "market": { + "browseBuiltin": "内蔵MCPマーケットを閲覧", + "builtinTitle": "内蔵MCPマーケット", + "poweredBy": "Powered by MCPRouter", + "keyGuide": "APIキーを取得", + "keyHelpText": "まず", + "keyHelpEnd": "でAPIキーを申請し、上記の入力ボックスに入力してください", + "apiKeyPlaceholder": "MCPRouter APIキーを入力", + "apiKeyRequiredTitle": "APIキーが必要", + "apiKeyRequiredDesc": "インストール前にMCPRouter APIキーを入力してください", + "install": "インストール", + "installSuccess": "インストール成功", + "installFailed": "インストール失敗", + "noMore": "これ以上ありません", + "empty": "サービスがありません", + "loadMore": "さらに読み込み", + "pullDownToLoad": "下にスクロールしてさらに読み込み" } } diff --git a/src/renderer/src/i18n/zh-CN/mcp.json b/src/renderer/src/i18n/zh-CN/mcp.json index 1d67da53a..59a9d6b52 100644 --- a/src/renderer/src/i18n/zh-CN/mcp.json +++ b/src/renderer/src/i18n/zh-CN/mcp.json @@ -246,5 +246,23 @@ "loadContent": "获取 Resource 内容", "pleaseSelect": "点击获取展示Resources详情", "dialogDescription": "浏览和查看MCP服务器提供的资源" + }, + "market": { + "browseBuiltin": "浏览内置 MCP 市场", + "builtinTitle": "内置 MCP 市场", + "poweredBy": "Powered by MCPRouter", + "keyGuide": "获取密钥", + "keyHelpText": "请先到", + "keyHelpEnd": "申请 API Key 后填写到上方输入框", + "apiKeyPlaceholder": "输入 MCPRouter API Key", + "apiKeyRequiredTitle": "需要 API Key", + "apiKeyRequiredDesc": "请先填写 MCPRouter API Key 后再安装", + "install": "安装", + "installSuccess": "安装成功", + "installFailed": "安装失败", + "noMore": "没有更多了", + "empty": "暂无服务", + "loadMore": "加载更多", + "pullDownToLoad": "继续下拉加载更多" } } diff --git a/src/renderer/src/i18n/zh-CN/routes.json b/src/renderer/src/i18n/zh-CN/routes.json index 5bf506b79..cf7c9c835 100644 --- a/src/renderer/src/i18n/zh-CN/routes.json +++ b/src/renderer/src/i18n/zh-CN/routes.json @@ -10,5 +10,6 @@ "settings-shortcut": "快捷键", "settings-display": "显示设置", "settings-knowledge-base": "知识库", - "settings-prompt": "Prompt管理" + "settings-prompt": "Prompt管理", + "settings-mcp-market": "内置MCP市场" } diff --git a/src/renderer/src/router/index.ts b/src/renderer/src/router/index.ts index 8a8ee75b7..b0d998f40 100644 --- a/src/renderer/src/router/index.ts +++ b/src/renderer/src/router/index.ts @@ -71,6 +71,15 @@ const router = createRouter({ icon: 'lucide:server' } }, + { + path: 'mcp-market', + name: 'settings-mcp-market', + component: () => import('@/components/settings/McpBuiltinMarket.vue'), + meta: { + titleKey: 'routes.settings-mcp-market', + icon: 'lucide:shopping-bag' + } + }, { path: 'prompt', name: 'settings-prompt', diff --git a/src/shared/presenter.d.ts b/src/shared/presenter.d.ts index b73a94f8d..cae876c30 100644 --- a/src/shared/presenter.d.ts +++ b/src/shared/presenter.d.ts @@ -1137,6 +1137,29 @@ export interface IMCPPresenter { setCustomNpmRegistry?(registry: string | undefined): Promise setAutoDetectNpmRegistry?(enabled: boolean): Promise clearNpmRegistryCache?(): Promise + + // McpRouter marketplace + listMcpRouterServers?( + page: number, + limit: number + ): Promise<{ + servers: Array<{ + uuid: string + 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 + }> + }> + installMcpRouterServer?(serverKey: string): Promise + getMcpRouterApiKey?(): Promise + setMcpRouterApiKey?(key: string): Promise } export interface IDeeplinkPresenter { From f8c87a669f9fb8d2527e6dc431d13500cc20def9 Mon Sep 17 00:00:00 2001 From: zerob13 Date: Tue, 12 Aug 2025 16:06:41 +0800 Subject: [PATCH 2/6] feat: mcp market install --- .../providers/modelscopeProvider.ts | 27 +++++--------- src/main/presenter/mcpPresenter/index.ts | 10 ++++++ .../mcpPresenter/mcprouterManager.ts | 4 ++- .../components/settings/McpBuiltinMarket.vue | 35 +++++++++++++++++-- src/renderer/src/i18n/en-US/common.json | 3 +- src/renderer/src/i18n/en-US/mcp.json | 3 +- src/renderer/src/i18n/en-US/routes.json | 3 +- src/renderer/src/i18n/fa-IR/common.json | 3 +- src/renderer/src/i18n/fa-IR/mcp.json | 19 ++++++++++ src/renderer/src/i18n/fa-IR/routes.json | 3 +- src/renderer/src/i18n/fr-FR/common.json | 3 +- src/renderer/src/i18n/fr-FR/mcp.json | 19 ++++++++++ src/renderer/src/i18n/fr-FR/routes.json | 3 +- src/renderer/src/i18n/ja-JP/common.json | 3 +- src/renderer/src/i18n/ja-JP/mcp.json | 3 +- src/renderer/src/i18n/ja-JP/routes.json | 3 +- src/renderer/src/i18n/ko-KR/common.json | 3 +- src/renderer/src/i18n/ko-KR/mcp.json | 19 ++++++++++ src/renderer/src/i18n/ko-KR/routes.json | 3 +- src/renderer/src/i18n/ru-RU/common.json | 3 +- src/renderer/src/i18n/ru-RU/mcp.json | 19 ++++++++++ src/renderer/src/i18n/ru-RU/routes.json | 3 +- src/renderer/src/i18n/zh-CN/common.json | 3 +- src/renderer/src/i18n/zh-CN/mcp.json | 3 +- src/renderer/src/i18n/zh-CN/routes.json | 2 +- src/renderer/src/i18n/zh-HK/common.json | 3 +- src/renderer/src/i18n/zh-HK/mcp.json | 19 ++++++++++ src/renderer/src/i18n/zh-HK/routes.json | 3 +- src/renderer/src/i18n/zh-TW/common.json | 3 +- src/renderer/src/i18n/zh-TW/mcp.json | 19 ++++++++++ src/renderer/src/i18n/zh-TW/routes.json | 3 +- src/shared/presenter.d.ts | 3 ++ 32 files changed, 211 insertions(+), 44 deletions(-) diff --git a/src/main/presenter/llmProviderPresenter/providers/modelscopeProvider.ts b/src/main/presenter/llmProviderPresenter/providers/modelscopeProvider.ts index e6b4df50b..eca4e601f 100644 --- a/src/main/presenter/llmProviderPresenter/providers/modelscopeProvider.ts +++ b/src/main/presenter/llmProviderPresenter/providers/modelscopeProvider.ts @@ -304,35 +304,24 @@ export class ModelscopeProvider extends OpenAICompatibleProvider { ] const randomEmoji = emojis[Math.floor(Math.random() * emojis.length)] - // Get display name: chinese_name first, then id - const displayName = mcpServer.chinese_name || mcpServer.id + // Get display name: chinese_name first, then name, then id + const displayName = mcpServer.chinese_name || mcpServer.name || mcpServer.id return { - name: `@modelscope/${mcpServer.id}`, // Use ModelScope ID format - description: - mcpServer.locales?.zh?.description || - mcpServer.description || - `ModelScope MCP Server: ${displayName}`, command: '', // Not needed for SSE type args: [], // Not needed for SSE type env: {}, - type: 'sse' as const, // SSE type for operational servers - baseUrl: baseUrl, // Use operational URL - enabled: false, // Default to disabled for safety - source: 'modelscope' as const, - // Additional metadata descriptions: mcpServer.locales?.zh?.description || mcpServer.description || `ModelScope MCP Server: ${displayName}`, icons: randomEmoji, // Random emoji instead of URL - provider: 'ModelScope', - providerUrl: `https://www.modelscope.cn/mcp/servers/@${mcpServer.id}`, - logoUrl: '', // No longer using logo URL - tags: mcpServer.tags || [], - // Store original data for reference - originalId: mcpServer.id, - displayName: displayName + autoApprove: ['all'], + disable: false, // Default to disabled for safety + type: 'sse' as const, // SSE type for operational servers + baseUrl: baseUrl, // Use operational URL + source: 'modelscope', + sourceId: mcpServer.id } } } diff --git a/src/main/presenter/mcpPresenter/index.ts b/src/main/presenter/mcpPresenter/index.ts index 66a57eddd..e4b5f9781 100644 --- a/src/main/presenter/mcpPresenter/index.ts +++ b/src/main/presenter/mcpPresenter/index.ts @@ -227,6 +227,16 @@ export class McpPresenter implements IMCPPresenter { this.configPresenter.setSetting('mcprouterApiKey', key) } + async isServerInstalled(source: string, sourceId: string): Promise { + const servers = await this.configPresenter.getMcpServers() + for (const config of Object.values(servers)) { + if (config.source === source && config.sourceId === sourceId) { + return true + } + } + return false + } + private scheduleBackgroundRegistryUpdate(): void { setTimeout(async () => { try { diff --git a/src/main/presenter/mcpPresenter/mcprouterManager.ts b/src/main/presenter/mcpPresenter/mcprouterManager.ts index e42deeb65..186682847 100644 --- a/src/main/presenter/mcpPresenter/mcprouterManager.ts +++ b/src/main/presenter/mcpPresenter/mcprouterManager.ts @@ -113,7 +113,9 @@ export class McpRouterManager { Authorization: `Bearer ${apiKey}`, 'HTTP-Referer': 'deepchatai.cn', 'X-Title': 'DeepChat' - } + }, + source: 'mcprouter', + sourceId: serverKey } const serverName = detail.config_name || detail.server_key || detail.name diff --git a/src/renderer/src/components/settings/McpBuiltinMarket.vue b/src/renderer/src/components/settings/McpBuiltinMarket.vue index 8c9c8fc65..e62004f43 100644 --- a/src/renderer/src/components/settings/McpBuiltinMarket.vue +++ b/src/renderer/src/components/settings/McpBuiltinMarket.vue @@ -57,9 +57,17 @@ {{ item.server_key }} - @@ -126,6 +134,7 @@ const hasMore = ref(true) const scrollContainer = ref(null) const showPullToLoad = ref(false) const canPullMore = ref(false) +const installedServers = ref>(new Set()) const apiKeyInput = ref('') @@ -149,6 +158,21 @@ const openHowToGetKey = () => { window.open('https://mcprouter.co/settings/keys', '_blank') } +const checkInstalledServers = async () => { + const installed = new Set() + for (const item of items.value) { + try { + const isInstalled = await mcpP.isServerInstalled?.('mcprouter', item.uuid) + if (isInstalled) { + installed.add(item.uuid) + } + } catch (e) { + console.error('Failed to check installation status:', e) + } + } + installedServers.value = installed +} + const fetchPage = async (forcePull = false) => { if (loading.value || (!hasMore.value && !forcePull)) return loading.value = true @@ -165,6 +189,9 @@ const fetchPage = async (forcePull = false) => { items.value.push(...list) page.value += 1 + // 检查安装状态 + await checkInstalledServers() + // 如果是强制拉取且成功获取到数据,重新启用拉取功能 if (forcePull) { hasMore.value = true @@ -233,6 +260,8 @@ const install = async (item: MarketItem) => { const ok = await mcpP.installMcpRouterServer?.(item.server_key) if (ok) { toast({ title: t('mcp.market.installSuccess') }) + // 更新安装状态 + installedServers.value.add(item.uuid) } else { toast({ title: t('mcp.market.installFailed'), variant: 'destructive' }) } diff --git a/src/renderer/src/i18n/en-US/common.json b/src/renderer/src/i18n/en-US/common.json index 0e4a53ed1..69f2e887a 100644 --- a/src/renderer/src/i18n/en-US/common.json +++ b/src/renderer/src/i18n/en-US/common.json @@ -72,5 +72,6 @@ "edit": "Edit", "delete": "Delete", "save": "Save", - "clear": "Clear" + "clear": "Clear", + "saved": "Saved" } diff --git a/src/renderer/src/i18n/en-US/mcp.json b/src/renderer/src/i18n/en-US/mcp.json index 1e31d1b8e..79f9021f5 100644 --- a/src/renderer/src/i18n/en-US/mcp.json +++ b/src/renderer/src/i18n/en-US/mcp.json @@ -249,7 +249,7 @@ }, "market": { "browseBuiltin": "Browse Built-in MCP Market", - "builtinTitle": "Built-in MCP Market", + "builtinTitle": "MCP Market", "poweredBy": "Powered by MCPRouter", "keyGuide": "Get API Key", "keyHelpText": "Please go to", @@ -258,6 +258,7 @@ "apiKeyRequiredTitle": "API Key Required", "apiKeyRequiredDesc": "Please fill in the MCPRouter API Key before installation", "install": "Install", + "installed": "Installed", "installSuccess": "Installation successful", "installFailed": "Installation failed", "noMore": "No more", diff --git a/src/renderer/src/i18n/en-US/routes.json b/src/renderer/src/i18n/en-US/routes.json index 1fbec3730..7448ac47d 100644 --- a/src/renderer/src/i18n/en-US/routes.json +++ b/src/renderer/src/i18n/en-US/routes.json @@ -10,5 +10,6 @@ "settings-shortcut": "Shortcuts", "settings-display": "Display", "settings-knowledge-base": "Knowledge Base", - "settings-prompt": "Prompts" + "settings-prompt": "Prompts", + "settings-mcp-market": "MCP Market" } diff --git a/src/renderer/src/i18n/fa-IR/common.json b/src/renderer/src/i18n/fa-IR/common.json index 1c587ab57..b492d0ff7 100644 --- a/src/renderer/src/i18n/fa-IR/common.json +++ b/src/renderer/src/i18n/fa-IR/common.json @@ -72,5 +72,6 @@ "edit": "ویرایش", "delete": "پاک کردن", "save": "ذخیره", - "clear": "پاک کردن" + "clear": "پاک کردن", + "saved": "ذخیره شده" } diff --git a/src/renderer/src/i18n/fa-IR/mcp.json b/src/renderer/src/i18n/fa-IR/mcp.json index b7bfab7a2..1be537173 100644 --- a/src/renderer/src/i18n/fa-IR/mcp.json +++ b/src/renderer/src/i18n/fa-IR/mcp.json @@ -246,5 +246,24 @@ "mcpDisabled": "MCP خاموش است", "getPromptFailed": "دریافت دستورکار ناموفق بود", "readResourceFailed": "خواندن منبع ناموفق بود" + }, + "market": { + "apiKeyPlaceholder": "کلید API MCPROUTER را وارد کنید", + "apiKeyRequiredDesc": "لطفاً قبل از نصب ، ابتدا کلید API McProuter را پر کنید", + "apiKeyRequiredTitle": "به کلید API نیاز دارد", + "browseBuiltin": "مرور بازار داخلی MCP", + "builtinTitle": "بازار MCP", + "empty": "هنوز هیچ خدمتی وجود ندارد", + "install": "نصب کردن", + "installFailed": "نصب انجام نشد", + "installSuccess": "نصب با موفقیت", + "installed": "نصب شده", + "keyGuide": "کلید را دریافت کنید", + "keyHelpEnd": "پس از درخواست کلید API ، کادر ورودی را در بالا پر کنید", + "keyHelpText": "لطفا ابتدا وارد شوید", + "loadMore": "بار بیشتر", + "noMore": "نه بیشتر", + "poweredBy": "نیرو توسط McProuter", + "pullDownToLoad": "برای بارگیری بیشتر به پایین بیاید" } } diff --git a/src/renderer/src/i18n/fa-IR/routes.json b/src/renderer/src/i18n/fa-IR/routes.json index 1754b6102..14e85d636 100644 --- a/src/renderer/src/i18n/fa-IR/routes.json +++ b/src/renderer/src/i18n/fa-IR/routes.json @@ -10,5 +10,6 @@ "settings-shortcut": "میان‌برها", "settings-display": "تنظیمات نمایش", "settings-knowledge-base": "پایگاه دانش", - "settings-prompt": "مدیریت پرامپت‌ها" + "settings-prompt": "مدیریت پرامپت‌ها", + "settings-mcp-market": "بازار MCP" } diff --git a/src/renderer/src/i18n/fr-FR/common.json b/src/renderer/src/i18n/fr-FR/common.json index 90a35bdee..3e7b21786 100644 --- a/src/renderer/src/i18n/fr-FR/common.json +++ b/src/renderer/src/i18n/fr-FR/common.json @@ -72,5 +72,6 @@ "edit": "Modifier", "delete": "Supprimer", "save": "Enregistrer", - "clear": "Effacer" + "clear": "Effacer", + "saved": "Sauvé" } diff --git a/src/renderer/src/i18n/fr-FR/mcp.json b/src/renderer/src/i18n/fr-FR/mcp.json index 09bbb0566..85708926c 100644 --- a/src/renderer/src/i18n/fr-FR/mcp.json +++ b/src/renderer/src/i18n/fr-FR/mcp.json @@ -246,5 +246,24 @@ "mcpDisabled": "MCP est désactivé", "getPromptFailed": "Échec de l'obtention du prompt", "readResourceFailed": "Échec de la lecture de la ressource" + }, + "market": { + "apiKeyPlaceholder": "Entrez la clé de l'API McProuter", + "apiKeyRequiredDesc": "Veuillez remplir la clé API McProuter avant d'installer", + "apiKeyRequiredTitle": "Nécessite la clé de l'API", + "browseBuiltin": "Parcourir le marché MCP intégré", + "builtinTitle": "Marché MCP", + "empty": "Pas encore de service", + "install": "Installer", + "installFailed": "L'installation a échoué", + "installSuccess": "Installation avec succès", + "installed": "Installé", + "keyGuide": "Obtenez la clé", + "keyHelpEnd": "Après avoir postulé pour la clé API, remplissez la zone d'entrée ci-dessus", + "keyHelpText": "Veuillez arriver en premier", + "loadMore": "Chargez plus", + "noMore": "Pas plus", + "poweredBy": "Propulsé par McProuter", + "pullDownToLoad": "Continuez à tomber pour charger plus" } } diff --git a/src/renderer/src/i18n/fr-FR/routes.json b/src/renderer/src/i18n/fr-FR/routes.json index b38a9f6fe..cf2c8f64a 100644 --- a/src/renderer/src/i18n/fr-FR/routes.json +++ b/src/renderer/src/i18n/fr-FR/routes.json @@ -10,5 +10,6 @@ "settings-shortcut": "Raccourcis", "settings-display": "Affichage", "settings-knowledge-base": "Base de connaissances", - "settings-prompt": "Gestion des Prompts" + "settings-prompt": "Gestion des Prompts", + "settings-mcp-market": "Marché MCP" } diff --git a/src/renderer/src/i18n/ja-JP/common.json b/src/renderer/src/i18n/ja-JP/common.json index 1f16d561c..316f31500 100644 --- a/src/renderer/src/i18n/ja-JP/common.json +++ b/src/renderer/src/i18n/ja-JP/common.json @@ -72,5 +72,6 @@ "edit": "編集", "delete": "削除", "save": "保存", - "clear": "クリア" + "clear": "クリア", + "saved": "保存" } diff --git a/src/renderer/src/i18n/ja-JP/mcp.json b/src/renderer/src/i18n/ja-JP/mcp.json index fedcd06dc..7c1abd480 100644 --- a/src/renderer/src/i18n/ja-JP/mcp.json +++ b/src/renderer/src/i18n/ja-JP/mcp.json @@ -249,7 +249,7 @@ }, "market": { "browseBuiltin": "内蔵MCPマーケットを閲覧", - "builtinTitle": "内蔵MCPマーケット", + "builtinTitle": "MCP市場", "poweredBy": "Powered by MCPRouter", "keyGuide": "APIキーを取得", "keyHelpText": "まず", @@ -258,6 +258,7 @@ "apiKeyRequiredTitle": "APIキーが必要", "apiKeyRequiredDesc": "インストール前にMCPRouter APIキーを入力してください", "install": "インストール", + "installed": "インストール済み", "installSuccess": "インストール成功", "installFailed": "インストール失敗", "noMore": "これ以上ありません", diff --git a/src/renderer/src/i18n/ja-JP/routes.json b/src/renderer/src/i18n/ja-JP/routes.json index a69506efc..28aaabb60 100644 --- a/src/renderer/src/i18n/ja-JP/routes.json +++ b/src/renderer/src/i18n/ja-JP/routes.json @@ -10,5 +10,6 @@ "settings-shortcut": "ショートカット", "settings-display": "表示設定", "settings-knowledge-base": "ナレッジベース", - "settings-prompt": "プロンプト管理" + "settings-prompt": "プロンプト管理", + "settings-mcp-market": "MCP市場" } diff --git a/src/renderer/src/i18n/ko-KR/common.json b/src/renderer/src/i18n/ko-KR/common.json index 14aef4e0e..a5fceba1f 100644 --- a/src/renderer/src/i18n/ko-KR/common.json +++ b/src/renderer/src/i18n/ko-KR/common.json @@ -72,5 +72,6 @@ "edit": "편집", "delete": "삭제", "save": "저장", - "clear": "지우기" + "clear": "지우기", + "saved": "저장" } diff --git a/src/renderer/src/i18n/ko-KR/mcp.json b/src/renderer/src/i18n/ko-KR/mcp.json index 5a46edaf8..62af1c203 100644 --- a/src/renderer/src/i18n/ko-KR/mcp.json +++ b/src/renderer/src/i18n/ko-KR/mcp.json @@ -246,5 +246,24 @@ "mcpDisabled": "MCP가 비활성화되어 있습니다", "getPromptFailed": "프롬프트 가져오기 실패", "readResourceFailed": "리소스 읽기 실패" + }, + "market": { + "apiKeyPlaceholder": "McProuter API 키를 입력하십시오", + "apiKeyRequiredDesc": "설치하기 전에 먼저 McProuter API 키를 작성하십시오.", + "apiKeyRequiredTitle": "API 키가 필요합니다", + "browseBuiltin": "내장 MCP 시장을 찾아보십시오", + "builtinTitle": "MCP 시장", + "empty": "아직 서비스가 없습니다", + "install": "설치하다", + "installFailed": "설치가 실패했습니다", + "installSuccess": "성공적으로 설치", + "installed": "설치", + "keyGuide": "열쇠를 얻으십시오", + "keyHelpEnd": "API 키를 신청 한 후 위의 입력 상자를 채우십시오.", + "keyHelpText": "먼저 도착하십시오", + "loadMore": "더로드하십시오", + "noMore": "더 이상", + "poweredBy": "McProuter에 의해 구동", + "pullDownToLoad": "더 많은로드하기 위해 계속 드롭 다운하십시오" } } diff --git a/src/renderer/src/i18n/ko-KR/routes.json b/src/renderer/src/i18n/ko-KR/routes.json index 787ba05ac..05895afe3 100644 --- a/src/renderer/src/i18n/ko-KR/routes.json +++ b/src/renderer/src/i18n/ko-KR/routes.json @@ -10,5 +10,6 @@ "settings-shortcut": "단축키", "settings-display": "디스플레이 설정", "settings-knowledge-base": "지식 기반", - "settings-prompt": "프롬프트 관리" + "settings-prompt": "프롬프트 관리", + "settings-mcp-market": "MCP 시장" } diff --git a/src/renderer/src/i18n/ru-RU/common.json b/src/renderer/src/i18n/ru-RU/common.json index bb5461a64..923dec577 100644 --- a/src/renderer/src/i18n/ru-RU/common.json +++ b/src/renderer/src/i18n/ru-RU/common.json @@ -72,5 +72,6 @@ "edit": "Редактировать", "delete": "Удалить", "save": "Сохранить", - "clear": "Очистить" + "clear": "Очистить", + "saved": "Сохранил" } diff --git a/src/renderer/src/i18n/ru-RU/mcp.json b/src/renderer/src/i18n/ru-RU/mcp.json index 719c17a49..3877d6a6a 100644 --- a/src/renderer/src/i18n/ru-RU/mcp.json +++ b/src/renderer/src/i18n/ru-RU/mcp.json @@ -246,5 +246,24 @@ "mcpDisabled": "MCP отключен", "getPromptFailed": "Не удалось получить промпт", "readResourceFailed": "Не удалось прочитать ресурс" + }, + "market": { + "apiKeyPlaceholder": "Введите ключ McProuter API", + "apiKeyRequiredDesc": "Пожалуйста, заполните клавишу McProuter API сначала перед установкой", + "apiKeyRequiredTitle": "Требует ключа API", + "browseBuiltin": "Просмотрите встроенный рынок MCP", + "builtinTitle": "MCP Market", + "empty": "Пока нет услуг", + "install": "Установить", + "installFailed": "Установка не удалась", + "installSuccess": "Установка успешно", + "installed": "Установлен", + "keyGuide": "Получите ключ", + "keyHelpEnd": "После подачи заявки на ключ API заполните поле ввода выше", + "keyHelpText": "Пожалуйста, приезжайте первым", + "loadMore": "Загружать больше", + "noMore": "Больше не надо", + "poweredBy": "Оборудован от McProuter", + "pullDownToLoad": "Продолжайте падать, чтобы загружать больше" } } diff --git a/src/renderer/src/i18n/ru-RU/routes.json b/src/renderer/src/i18n/ru-RU/routes.json index 9157b9943..01daba650 100644 --- a/src/renderer/src/i18n/ru-RU/routes.json +++ b/src/renderer/src/i18n/ru-RU/routes.json @@ -10,5 +10,6 @@ "settings-mcp": "Настройки MCP", "settings-display": "Настройки отображения", "settings-knowledge-base": "База знаний", - "settings-prompt": "Управление промптами" + "settings-prompt": "Управление промптами", + "settings-mcp-market": "MCP Market" } diff --git a/src/renderer/src/i18n/zh-CN/common.json b/src/renderer/src/i18n/zh-CN/common.json index 773524df3..27dc988d5 100644 --- a/src/renderer/src/i18n/zh-CN/common.json +++ b/src/renderer/src/i18n/zh-CN/common.json @@ -72,5 +72,6 @@ "edit": "编辑", "delete": "删除", "save": "保存", - "clear": "清除" + "clear": "清除", + "saved": "已保存" } diff --git a/src/renderer/src/i18n/zh-CN/mcp.json b/src/renderer/src/i18n/zh-CN/mcp.json index 59a9d6b52..b2e61e924 100644 --- a/src/renderer/src/i18n/zh-CN/mcp.json +++ b/src/renderer/src/i18n/zh-CN/mcp.json @@ -249,7 +249,7 @@ }, "market": { "browseBuiltin": "浏览内置 MCP 市场", - "builtinTitle": "内置 MCP 市场", + "builtinTitle": "MCP 市场", "poweredBy": "Powered by MCPRouter", "keyGuide": "获取密钥", "keyHelpText": "请先到", @@ -258,6 +258,7 @@ "apiKeyRequiredTitle": "需要 API Key", "apiKeyRequiredDesc": "请先填写 MCPRouter API Key 后再安装", "install": "安装", + "installed": "已安装", "installSuccess": "安装成功", "installFailed": "安装失败", "noMore": "没有更多了", diff --git a/src/renderer/src/i18n/zh-CN/routes.json b/src/renderer/src/i18n/zh-CN/routes.json index cf7c9c835..59c5fcd44 100644 --- a/src/renderer/src/i18n/zh-CN/routes.json +++ b/src/renderer/src/i18n/zh-CN/routes.json @@ -11,5 +11,5 @@ "settings-display": "显示设置", "settings-knowledge-base": "知识库", "settings-prompt": "Prompt管理", - "settings-mcp-market": "内置MCP市场" + "settings-mcp-market": "MCP市场" } diff --git a/src/renderer/src/i18n/zh-HK/common.json b/src/renderer/src/i18n/zh-HK/common.json index 9e488bd3b..035c6aa9c 100644 --- a/src/renderer/src/i18n/zh-HK/common.json +++ b/src/renderer/src/i18n/zh-HK/common.json @@ -72,5 +72,6 @@ "edit": "編輯", "delete": "刪除", "save": "儲存", - "clear": "清除" + "clear": "清除", + "saved": "已保存" } diff --git a/src/renderer/src/i18n/zh-HK/mcp.json b/src/renderer/src/i18n/zh-HK/mcp.json index 4754ae1dc..b415816ab 100644 --- a/src/renderer/src/i18n/zh-HK/mcp.json +++ b/src/renderer/src/i18n/zh-HK/mcp.json @@ -246,5 +246,24 @@ "mcpDisabled": "MCP功能已禁用", "getPromptFailed": "獲取提示模板失敗", "readResourceFailed": "讀取資源失敗" + }, + "market": { + "apiKeyPlaceholder": "輸入 MCPRouter API Key", + "apiKeyRequiredDesc": "請先填寫 MCPRouter API Key 後再安裝", + "apiKeyRequiredTitle": "需要 API Key", + "browseBuiltin": "瀏覽內置 MCP 市場", + "builtinTitle": "MCP 市場", + "empty": "暫無服務", + "install": "安裝", + "installFailed": "安裝失敗", + "installSuccess": "安裝成功", + "installed": "已安裝", + "keyGuide": "獲取密鑰", + "keyHelpEnd": "申請 API Key 後填寫到上方輸入框", + "keyHelpText": "請先到", + "loadMore": "加載更多", + "noMore": "沒有更多了", + "poweredBy": "Powered by MCPRouter", + "pullDownToLoad": "繼續下拉加載更多" } } diff --git a/src/renderer/src/i18n/zh-HK/routes.json b/src/renderer/src/i18n/zh-HK/routes.json index ee1c62a4e..7ffa7ffa9 100644 --- a/src/renderer/src/i18n/zh-HK/routes.json +++ b/src/renderer/src/i18n/zh-HK/routes.json @@ -10,5 +10,6 @@ "settings-mcp": "MCP設置", "settings-display": "顯示設置", "settings-knowledge-base": "知識庫", - "settings-prompt": "Prompt管理" + "settings-prompt": "Prompt管理", + "settings-mcp-market": "MCP市場" } diff --git a/src/renderer/src/i18n/zh-TW/common.json b/src/renderer/src/i18n/zh-TW/common.json index c8003853f..ec90d0089 100644 --- a/src/renderer/src/i18n/zh-TW/common.json +++ b/src/renderer/src/i18n/zh-TW/common.json @@ -72,5 +72,6 @@ "reset": "重置", "format": "格式", "save": "儲存", - "clear": "清除" + "clear": "清除", + "saved": "已保存" } diff --git a/src/renderer/src/i18n/zh-TW/mcp.json b/src/renderer/src/i18n/zh-TW/mcp.json index 36ff278b6..b5724f1a3 100644 --- a/src/renderer/src/i18n/zh-TW/mcp.json +++ b/src/renderer/src/i18n/zh-TW/mcp.json @@ -246,5 +246,24 @@ "loadContent": "獲取 Resource 內容", "pleaseSelect": "點擊獲取展示Resources詳情", "dialogDescription": "瀏覽和查看MCP伺服器提供的資源" + }, + "market": { + "apiKeyPlaceholder": "輸入 MCPRouter API Key", + "apiKeyRequiredDesc": "請先填寫 MCPRouter API Key 後再安裝", + "apiKeyRequiredTitle": "需要 API Key", + "browseBuiltin": "瀏覽內置 MCP 市場", + "builtinTitle": "MCP 市場", + "empty": "暫無服務", + "install": "安裝", + "installFailed": "安裝失敗", + "installSuccess": "安裝成功", + "installed": "已安裝", + "keyGuide": "獲取密鑰", + "keyHelpEnd": "申請 API Key 後填寫到上方輸入框", + "keyHelpText": "請先到", + "loadMore": "加載更多", + "noMore": "沒有更多了", + "poweredBy": "Powered by MCPRouter", + "pullDownToLoad": "繼續下拉加載更多" } } diff --git a/src/renderer/src/i18n/zh-TW/routes.json b/src/renderer/src/i18n/zh-TW/routes.json index 40f351121..0a1c366b0 100644 --- a/src/renderer/src/i18n/zh-TW/routes.json +++ b/src/renderer/src/i18n/zh-TW/routes.json @@ -10,5 +10,6 @@ "settings-shortcut": "快捷鍵", "settings-display": "顯示設置", "settings-knowledge-base": "知識庫", - "settings-prompt": "Prompt管理" + "settings-prompt": "Prompt管理", + "settings-mcp-market": "MCP市場" } diff --git a/src/shared/presenter.d.ts b/src/shared/presenter.d.ts index cae876c30..b0a4f38f0 100644 --- a/src/shared/presenter.d.ts +++ b/src/shared/presenter.d.ts @@ -988,6 +988,8 @@ export interface MCPServerConfig { customHeaders?: Record customNpmRegistry?: string type: 'sse' | 'stdio' | 'inmemory' | 'http' + source?: string // 来源标识: "mcprouter" | "modelscope" | undefined(for manual) + sourceId?: string // 来源ID: mcprouter的uuid 或 modelscope的mcpServer.id } export interface MCPConfig { @@ -1160,6 +1162,7 @@ export interface IMCPPresenter { installMcpRouterServer?(serverKey: string): Promise getMcpRouterApiKey?(): Promise setMcpRouterApiKey?(key: string): Promise + isServerInstalled?(source: string, sourceId: string): Promise } export interface IDeeplinkPresenter { From d135dcbf3e793da91391b4ecf8452e2bc73af32c Mon Sep 17 00:00:00 2001 From: zerob13 Date: Tue, 12 Aug 2025 16:23:30 +0800 Subject: [PATCH 3/6] wip: mcp install status sync --- src/main/presenter/mcpPresenter/index.ts | 25 +++++ .../components/mcp-config/mcpServerForm.vue | 98 +++++++++++++++++-- .../components/settings/McpBuiltinMarket.vue | 28 ++++-- src/renderer/src/i18n/en-US/settings.json | 1 + src/renderer/src/i18n/ja-JP/settings.json | 1 + src/renderer/src/i18n/zh-CN/settings.json | 1 + src/shared/presenter.d.ts | 1 + 7 files changed, 137 insertions(+), 18 deletions(-) diff --git a/src/main/presenter/mcpPresenter/index.ts b/src/main/presenter/mcpPresenter/index.ts index e4b5f9781..3d1b7715a 100644 --- a/src/main/presenter/mcpPresenter/index.ts +++ b/src/main/presenter/mcpPresenter/index.ts @@ -237,6 +237,31 @@ export class McpPresenter implements IMCPPresenter { return false } + async updateMcpRouterServersAuth(apiKey: string): Promise { + const servers = await this.configPresenter.getMcpServers() + const updates: Array<{ name: string; config: Partial }> = [] + + for (const [serverName, config] of Object.entries(servers)) { + if (config.source === 'mcprouter' && config.customHeaders) { + const updatedHeaders = { + ...config.customHeaders, + Authorization: `Bearer ${apiKey}` + } + updates.push({ + name: serverName, + config: { customHeaders: updatedHeaders } + }) + } + } + + // 批量更新所有服务器的 Authorization + for (const update of updates) { + await this.configPresenter.updateMcpServer(update.name, update.config) + } + + console.log(`Updated Authorization for ${updates.length} mcprouter servers`) + } + private scheduleBackgroundRegistryUpdate(): void { setTimeout(async () => { try { diff --git a/src/renderer/src/components/mcp-config/mcpServerForm.vue b/src/renderer/src/components/mcp-config/mcpServerForm.vue index c2c32f679..39db2e16d 100644 --- a/src/renderer/src/components/mcp-config/mcpServerForm.vue +++ b/src/renderer/src/components/mcp-config/mcpServerForm.vue @@ -56,6 +56,8 @@ const icons = ref(props.initialConfig?.icons || '📁') const type = ref<'sse' | 'stdio' | 'inmemory' | 'http'>(props.initialConfig?.type || 'stdio') const baseUrl = ref(props.initialConfig?.baseUrl || '') const customHeaders = ref('') +const customHeadersFocused = ref(false) +const customHeadersDisplayValue = ref('') const npmRegistry = ref(props.initialConfig?.customNpmRegistry || '') // 模型选择相关 @@ -551,6 +553,15 @@ watch( { immediate: true } ) +// 监听 customHeaders 变化以更新显示值 +watch( + customHeaders, + () => { + updateCustomHeadersDisplay() + }, + { immediate: true } +) + // 初始化时解析args中的provider和modelId(针对imageServer) watch( [() => name.value, () => args.value, () => type.value], @@ -655,6 +666,54 @@ const parseKeyValueHeaders = (text: string): Record => { return headers } +// 遮蔽敏感内容的函数 +const maskSensitiveValue = (value: string): string => { + // 只遮蔽等号后面的值,保留键名 + return value.replace(/=(.+)/g, (_, val) => { + if (val.trim().length <= 8) { + // 短值完全遮蔽 + return '=' + '*'.repeat(val.trim().length) + } else { + // 长值显示前2个和后2个字符,中间用*代替 + const trimmedVal = val.trim() + const start = trimmedVal.substring(0, 2) + const end = trimmedVal.substring(trimmedVal.length - 2) + const middle = '*'.repeat(Math.max(4, trimmedVal.length - 4)) + return '=' + start + middle + end + } + }) +} + +// 生成用于显示的 customHeaders 值 +const updateCustomHeadersDisplay = (): void => { + if (customHeadersFocused.value || !customHeaders.value.trim()) { + customHeadersDisplayValue.value = customHeaders.value + } else { + // 遮蔽敏感内容 + const lines = customHeaders.value.split('\n') + const maskedLines = lines.map(line => { + const trimmedLine = line.trim() + if (!trimmedLine || !trimmedLine.includes('=')) { + return line + } + return maskSensitiveValue(line) + }) + customHeadersDisplayValue.value = maskedLines.join('\n') + } +} + +// 处理 customHeaders 获得焦点 +const handleCustomHeadersFocus = (): void => { + customHeadersFocused.value = true + updateCustomHeadersDisplay() +} + +// 处理 customHeaders 失去焦点 +const handleCustomHeadersBlur = (): void => { + customHeadersFocused.value = false + updateCustomHeadersDisplay() +} + // --- 结束新增辅助函数 --- // 定义 customHeaders 的 placeholder @@ -1083,17 +1142,40 @@ HTTP-Referer=deepchatai.cn` -