mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-21 14:04:01 +08:00
✨ feat(mcp): 强化远程 Agent 接入配置指引
This commit is contained in:
@@ -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 远程接入说明');
|
||||
});
|
||||
|
||||
@@ -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<AIMCPClientInstallPanelProps> = ({
|
||||
onInstall,
|
||||
}) => {
|
||||
const selectedIsRemoteClient = isRemoteMCPClientStatus(selectedStatus);
|
||||
const remoteQuickStart = selectedIsRemoteClient
|
||||
? buildRemoteMCPClientQuickStart(selectedStatus)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
@@ -290,6 +294,103 @@ const AIMCPClientInstallPanel: React.FC<AIMCPClientInstallPanelProps> = ({
|
||||
远程接入边界:数据库连接信息和密码仍保存在 Windows GoNavi;云端 Agent 只通过 MCP 工具读取连接摘要、库表和 DDL。跨机器接入请使用 GoNavi Streamable HTTP 模式,并配合 token、隧道或反向代理。
|
||||
</div>
|
||||
)}
|
||||
{remoteQuickStart && (
|
||||
<div
|
||||
style={{
|
||||
padding: '12px 14px',
|
||||
borderRadius: 12,
|
||||
border: `1px solid ${darkMode ? 'rgba(56,189,248,0.2)' : 'rgba(14,165,233,0.18)'}`,
|
||||
background: darkMode ? 'rgba(14,165,233,0.06)' : 'rgba(240,249,255,0.78)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 10,
|
||||
}}
|
||||
>
|
||||
<div style={{ fontWeight: 700, fontSize: 13, color: overlayTheme.titleText }}>
|
||||
{remoteQuickStart.displayName} 远程 MCP 快速配置
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: overlayTheme.mutedText, lineHeight: 1.7 }}>
|
||||
下面两段分别给云端 Agent 和 Windows GoNavi 使用。云端只保存 MCP URL 和 Bearer Token,不保存数据库账号密码。
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: 10 }}>
|
||||
<div
|
||||
style={{
|
||||
padding: '10px 12px',
|
||||
borderRadius: 10,
|
||||
border: `1px solid ${cardBorder}`,
|
||||
background: darkMode ? 'rgba(15,23,42,0.55)' : 'rgba(255,255,255,0.78)',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontWeight: 700, fontSize: 12, color: overlayTheme.titleText }}>
|
||||
配置到云端 Agent
|
||||
</div>
|
||||
<code
|
||||
style={{
|
||||
display: 'block',
|
||||
marginTop: 8,
|
||||
fontFamily: 'var(--gn-font-mono)',
|
||||
fontSize: 11,
|
||||
color: overlayTheme.titleText,
|
||||
whiteSpace: 'pre-wrap',
|
||||
overflowWrap: 'anywhere',
|
||||
}}
|
||||
>
|
||||
{remoteQuickStart.configJson}
|
||||
</code>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
padding: '10px 12px',
|
||||
borderRadius: 10,
|
||||
border: `1px solid ${cardBorder}`,
|
||||
background: darkMode ? 'rgba(15,23,42,0.55)' : 'rgba(255,255,255,0.78)',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontWeight: 700, fontSize: 12, color: overlayTheme.titleText }}>
|
||||
Windows 启动 GoNavi MCP HTTP
|
||||
</div>
|
||||
<code
|
||||
style={{
|
||||
display: 'block',
|
||||
marginTop: 8,
|
||||
fontFamily: 'var(--gn-font-mono)',
|
||||
fontSize: 11,
|
||||
color: overlayTheme.titleText,
|
||||
whiteSpace: 'pre-wrap',
|
||||
overflowWrap: 'anywhere',
|
||||
}}
|
||||
>
|
||||
{remoteQuickStart.launchCommand}
|
||||
</code>
|
||||
<div style={{ marginTop: 8, fontSize: 12, color: overlayTheme.mutedText, lineHeight: 1.6 }}>
|
||||
独立二进制:{remoteQuickStart.standaloneCommand}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10 }}>
|
||||
<div>
|
||||
<div style={{ fontWeight: 700, fontSize: 12, color: overlayTheme.titleText }}>验证顺序</div>
|
||||
<div style={{ marginTop: 5, display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
{remoteQuickStart.verificationSteps.map((item) => (
|
||||
<div key={item} style={{ fontSize: 12, color: overlayTheme.mutedText, lineHeight: 1.6 }}>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontWeight: 700, fontSize: 12, color: overlayTheme.titleText }}>安全边界</div>
|
||||
<div style={{ marginTop: 5, display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
{remoteQuickStart.securityNotes.map((item) => (
|
||||
<div key={item} style={{ fontSize: 12, color: overlayTheme.mutedText, lineHeight: 1.6 }}>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ fontSize: 12, color: overlayTheme.mutedText, lineHeight: 1.7 }}>
|
||||
CLI 检测:{selectedIsRemoteClient
|
||||
? `远程 Agent 不需要检测本机 ${resolveMCPClientCommandName(selectedStatus)} 命令`
|
||||
|
||||
@@ -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],
|
||||
);
|
||||
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,18 @@ export type MCPClientKey = 'claude-code' | 'codex' | 'openclaw' | 'hermans';
|
||||
|
||||
const AUTO_MCP_CLIENTS = new Set<MCPClientKey>(['claude-code', 'codex']);
|
||||
const REMOTE_MCP_CLIENTS = new Set<MCPClientKey>(['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<AIMCPClientInstallStatus, 'displayName' | 'message'> | 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<AIMCPClientInstallStatus, 'displayName'> | 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。',
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user