diff --git a/frontend/src/components/ai/AIMCPClientInstallPanel.test.tsx b/frontend/src/components/ai/AIMCPClientInstallPanel.test.tsx index 6bca8b8..7960c2f 100644 --- a/frontend/src/components/ai/AIMCPClientInstallPanel.test.tsx +++ b/frontend/src/components/ai/AIMCPClientInstallPanel.test.tsx @@ -192,6 +192,17 @@ describe('AIMCPClientInstallPanel', () => { expect(markup).toContain('当前已选中,将复制远程接入说明'); expect(markup).toContain('远程接入边界'); expect(markup).toContain('云端 Agent 只通过 MCP 工具读取连接摘要、库表和 DDL'); + expect(markup).toContain('OpenClaw 远程 MCP 快速配置'); + expect(markup).toContain('配置到云端 Agent'); + expect(markup).toContain('"type": "streamable-http"'); + expect(markup).toContain('"url": "https://<你的域名或隧道地址>/mcp"'); + expect(markup).toContain('"Authorization": "Bearer <随机token>"'); + expect(markup).toContain('Windows 启动 GoNavi MCP HTTP'); + expect(markup).toContain('GoNavi.exe mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <随机token>'); + expect(markup).toContain('独立二进制:gonavi-mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <随机token>'); + expect(markup).toContain('验证顺序'); + expect(markup).toContain('安全边界'); + expect(markup).toContain('数据库账号和密码仍保存在 Windows GoNavi'); expect(markup).toContain('CLI 检测:远程 Agent 不需要检测本机 openclaw 命令'); expect(markup).toContain('复制 OpenClaw 远程接入说明'); }); diff --git a/frontend/src/components/ai/AIMCPClientInstallPanel.tsx b/frontend/src/components/ai/AIMCPClientInstallPanel.tsx index 009e058..f997465 100644 --- a/frontend/src/components/ai/AIMCPClientInstallPanel.tsx +++ b/frontend/src/components/ai/AIMCPClientInstallPanel.tsx @@ -4,6 +4,7 @@ import { CheckCircleFilled, CopyOutlined, ReloadOutlined } from '@ant-design/ico import type { AIMCPClientInstallStatus } from '../../types'; import { + buildRemoteMCPClientQuickStart, isMCPClientKey, isRemoteMCPClientStatus, type MCPClientKey, @@ -56,6 +57,9 @@ const AIMCPClientInstallPanel: React.FC = ({ onInstall, }) => { const selectedIsRemoteClient = isRemoteMCPClientStatus(selectedStatus); + const remoteQuickStart = selectedIsRemoteClient + ? buildRemoteMCPClientQuickStart(selectedStatus) + : null; return (
@@ -290,6 +294,103 @@ const AIMCPClientInstallPanel: React.FC = ({ 远程接入边界:数据库连接信息和密码仍保存在 Windows GoNavi;云端 Agent 只通过 MCP 工具读取连接摘要、库表和 DDL。跨机器接入请使用 GoNavi Streamable HTTP 模式,并配合 token、隧道或反向代理。
)} + {remoteQuickStart && ( +
+
+ {remoteQuickStart.displayName} 远程 MCP 快速配置 +
+
+ 下面两段分别给云端 Agent 和 Windows GoNavi 使用。云端只保存 MCP URL 和 Bearer Token,不保存数据库账号密码。 +
+
+
+
+ 配置到云端 Agent +
+ + {remoteQuickStart.configJson} + +
+
+
+ Windows 启动 GoNavi MCP HTTP +
+ + {remoteQuickStart.launchCommand} + +
+ 独立二进制:{remoteQuickStart.standaloneCommand} +
+
+
+
+
+
验证顺序
+
+ {remoteQuickStart.verificationSteps.map((item) => ( +
+ {item} +
+ ))} +
+
+
+
安全边界
+
+ {remoteQuickStart.securityNotes.map((item) => ( +
+ {item} +
+ ))} +
+
+
+
+ )}
CLI 检测:{selectedIsRemoteClient ? `远程 Agent 不需要检测本机 ${resolveMCPClientCommandName(selectedStatus)} 命令` diff --git a/frontend/src/components/ai/useAIMCPClientInstaller.ts b/frontend/src/components/ai/useAIMCPClientInstaller.ts index 614e7cd..1a7b057 100644 --- a/frontend/src/components/ai/useAIMCPClientInstaller.ts +++ b/frontend/src/components/ai/useAIMCPClientInstaller.ts @@ -3,6 +3,7 @@ import { useCallback, useMemo, useState } from 'react'; import type { AIMCPClientInstallStatus } from '../../types'; import { buildRemoteMCPClientGuide, + buildRemoteMCPClientQuickStart, EMPTY_MCP_CLIENT_STATUSES, formatMCPLaunchCommand, isRemoteMCPClientStatus, @@ -59,7 +60,9 @@ export const useAIMCPClientInstaller = ({ [mcpClientStatuses, selectedMCPClient], ); const selectedMCPClientCommandText = useMemo( - () => formatMCPLaunchCommand(selectedMCPClientStatus), + () => isRemoteMCPClientStatus(selectedMCPClientStatus) + ? buildRemoteMCPClientQuickStart(selectedMCPClientStatus).launchCommand + : formatMCPLaunchCommand(selectedMCPClientStatus), [selectedMCPClientStatus], ); diff --git a/frontend/src/utils/mcpClientInstallStatus.test.ts b/frontend/src/utils/mcpClientInstallStatus.test.ts index d3a5fdc..6468e83 100644 --- a/frontend/src/utils/mcpClientInstallStatus.test.ts +++ b/frontend/src/utils/mcpClientInstallStatus.test.ts @@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest'; import type { AIMCPClientInstallStatus } from '../types'; import { buildRemoteMCPClientGuide, + buildRemoteMCPClientQuickStart, EMPTY_MCP_CLIENT_STATUSES, formatMCPLaunchCommand, isRemoteMCPClientStatus, @@ -138,4 +139,20 @@ describe('mcpClientInstallStatus helpers', () => { expect(guide).toContain('"Authorization": "Bearer <随机token>"'); expect(guide).toContain('GoNavi.exe mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <随机token>'); }); + + it('builds remote quick-start snippets for cloud agents without database secrets', () => { + const quickStart = buildRemoteMCPClientQuickStart({ + displayName: 'OpenClaw', + }); + + expect(quickStart.displayName).toBe('OpenClaw'); + expect(quickStart.configJson).toContain('"type": "streamable-http"'); + expect(quickStart.configJson).toContain('"url": "https://<你的域名或隧道地址>/mcp"'); + expect(quickStart.configJson).toContain('"Authorization": "Bearer <随机token>"'); + expect(quickStart.configJson).not.toContain('password'); + expect(quickStart.launchCommand).toBe('GoNavi.exe mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <随机token>'); + expect(quickStart.standaloneCommand).toBe('gonavi-mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <随机token>'); + expect(quickStart.verificationSteps.join('\n')).toContain('get_connections'); + expect(quickStart.securityNotes.join('\n')).toContain('allowMutating=true'); + }); }); diff --git a/frontend/src/utils/mcpClientInstallStatus.ts b/frontend/src/utils/mcpClientInstallStatus.ts index 39f5b4e..a256fc8 100644 --- a/frontend/src/utils/mcpClientInstallStatus.ts +++ b/frontend/src/utils/mcpClientInstallStatus.ts @@ -4,6 +4,18 @@ export type MCPClientKey = 'claude-code' | 'codex' | 'openclaw' | 'hermans'; const AUTO_MCP_CLIENTS = new Set(['claude-code', 'codex']); const REMOTE_MCP_CLIENTS = new Set(['openclaw', 'hermans']); +const DEFAULT_REMOTE_MCP_PUBLIC_URL = 'https://<你的域名或隧道地址>/mcp'; +const DEFAULT_REMOTE_MCP_LOCAL_ADDR = '127.0.0.1:8765'; +const DEFAULT_REMOTE_MCP_PATH = '/mcp'; + +export interface RemoteMCPClientQuickStart { + displayName: string; + configJson: string; + launchCommand: string; + standaloneCommand: string; + verificationSteps: string[]; + securityNotes: string[]; +} export const EMPTY_MCP_CLIENT_STATUSES: AIMCPClientInstallStatus[] = [ { @@ -160,22 +172,9 @@ export const formatMCPLaunchCommand = ( export const buildRemoteMCPClientGuide = ( status?: Pick | null, ): string => { - const displayName = String(status?.displayName || '远程 Agent').trim(); - const streamableHTTPConfig = [ - '{', - ' "mcpServers": {', - ' "gonavi": {', - ' "type": "streamable-http",', - ' "url": "https://<你的域名或隧道地址>/mcp",', - ' "headers": {', - ' "Authorization": "Bearer <随机token>"', - ' }', - ' }', - ' }', - '}', - ]; + const quickStart = buildRemoteMCPClientQuickStart(status); return [ - `GoNavi MCP 远程接入说明 - ${displayName}`, + `GoNavi MCP 远程接入说明 - ${quickStart.displayName}`, '', '目标:', '- 数据库连接、账号和密码继续保存在 Windows 上的 GoNavi。云端 Agent 不需要保存数据库密码。', @@ -188,17 +187,53 @@ export const buildRemoteMCPClientGuide = ( '', '建议接入方式:', '1. Windows 本机保持 GoNavi 可访问,由 GoNavi 读取保存连接和系统凭据。', - '2. 在 Windows 或可信内网侧运行:GoNavi.exe mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <随机token>。', - `3. 在 ${displayName} 中添加远程 MCP Server,transport 选择 Streamable HTTP,URL 填隧道/反向代理后的 /mcp 地址,并设置 Authorization: Bearer <随机token>。`, + `2. 在 Windows 或可信内网侧运行:${quickStart.launchCommand}。`, + `3. 在 ${quickStart.displayName} 中添加远程 MCP Server,transport 选择 Streamable HTTP,URL 填隧道/反向代理后的 /mcp 地址,并设置 Authorization: Bearer <随机token>。`, '4. 先调用 get_connections 获取 connectionId,再调用表结构工具;不要把数据库 host/user/password 写进云端 Agent 配置。', '', '可复制配置片段(适用于支持 mcpServers JSON 的 Agent):', - ...streamableHTTPConfig, + ...quickStart.configJson.split('\n'), '', 'CLI / 服务启动命令:', - 'GoNavi.exe mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <随机token>', - '或设置环境变量:GONAVI_MCP_HTTP_TOKEN=<随机token> 后运行 gonavi-mcp-server http --addr 127.0.0.1:8765 --path /mcp', + quickStart.launchCommand, + `或设置环境变量:GONAVI_MCP_HTTP_TOKEN=<随机token> 后运行 ${quickStart.standaloneCommand.replace(' --token <随机token>', '')}`, '', status?.message ? `当前提示:${status.message}` : '', ].filter((line, index, lines) => line || index < lines.length - 1).join('\n'); }; + +export const buildRemoteMCPClientQuickStart = ( + status?: Pick | null, +): RemoteMCPClientQuickStart => { + const displayName = String(status?.displayName || '远程 Agent').trim(); + const launchCommand = `GoNavi.exe mcp-server http --addr ${DEFAULT_REMOTE_MCP_LOCAL_ADDR} --path ${DEFAULT_REMOTE_MCP_PATH} --token <随机token>`; + const standaloneCommand = `gonavi-mcp-server http --addr ${DEFAULT_REMOTE_MCP_LOCAL_ADDR} --path ${DEFAULT_REMOTE_MCP_PATH} --token <随机token>`; + const configJson = JSON.stringify({ + mcpServers: { + gonavi: { + type: 'streamable-http', + url: DEFAULT_REMOTE_MCP_PUBLIC_URL, + headers: { + Authorization: 'Bearer <随机token>', + }, + }, + }, + }, null, 2); + + return { + displayName, + configJson, + launchCommand, + standaloneCommand, + verificationSteps: [ + 'Windows 本机先访问 http://127.0.0.1:8765/healthz,确认 GoNavi MCP HTTP 服务已启动。', + `${displayName} 里配置 Streamable HTTP MCP,URL 指向隧道或反向代理后的 /mcp 地址。`, + '先调用 get_connections 获取 connectionId,再读取 get_databases / get_tables / get_columns。', + ], + securityNotes: [ + '数据库账号和密码仍保存在 Windows GoNavi,本段配置不要写数据库密码。', + 'HTTP MCP 必须使用随机 Bearer Token,并放在 HTTPS、私有网络或受控隧道后面。', + 'execute_sql 仍受 GoNavi AI 安全控制约束,写操作仍必须显式传 allowMutating=true。', + ], + }; +};