mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-12 16:02:48 +08:00
✨ feat(ai): 完善远程 MCP 结构模式与面板稳定性
- MCP HTTP 支持 schema-only 模式,远程配置默认不暴露 execute_sql - OpenClaw/Hermans 向导补充安全边界与结构模式命令 - 拆分 AI 面板错误边界和 Linux CJK 字体提示组件
This commit is contained in:
@@ -6,11 +6,16 @@ const appSource = readFileSync(
|
||||
fileURLToPath(new globalThis.URL('./App.tsx', import.meta.url)),
|
||||
'utf8',
|
||||
);
|
||||
const aiPanelBoundarySource = readFileSync(
|
||||
fileURLToPath(new globalThis.URL('./components/ai/AIPanelErrorBoundary.tsx', import.meta.url)),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
describe('AI panel lazy-load guard', () => {
|
||||
it('keeps AI panel failures scoped to the panel area with retry support', () => {
|
||||
expect(appSource).toContain("import AIChatPanel from './components/AIChatPanel';");
|
||||
expect(appSource).toContain('class AIPanelErrorBoundary extends React.Component');
|
||||
expect(appSource).toContain("import AIPanelErrorBoundary from './components/ai/AIPanelErrorBoundary';");
|
||||
expect(aiPanelBoundarySource).toContain('class AIPanelErrorBoundary extends React.Component');
|
||||
expect(appSource).toContain('<AIPanelErrorBoundary');
|
||||
expect(appSource).toContain('key={aiPanelRenderNonce}');
|
||||
expect(appSource).toContain('AI 面板加载失败');
|
||||
|
||||
@@ -6,6 +6,10 @@ const appSource = readFileSync(
|
||||
fileURLToPath(new globalThis.URL('./App.tsx', import.meta.url)),
|
||||
'utf8',
|
||||
);
|
||||
const linuxCJKFontBannerSource = readFileSync(
|
||||
fileURLToPath(new globalThis.URL('./components/LinuxCJKFontBanner.tsx', import.meta.url)),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
const getGlobalShortcutCaseBlock = (action: string) => {
|
||||
const caseToken = `case '${action}':`;
|
||||
@@ -250,8 +254,10 @@ describe('global appearance tokens', () => {
|
||||
expect(appSource).toContain('buildFontFamilyOptions(runtimePlatform, \'mono\', installedFontFamilies)');
|
||||
expect(appSource).toContain('ListInstalledFontFamilies()');
|
||||
expect(appSource).toContain('const [installedFontFamilies, setInstalledFontFamilies] = useState<InstalledFontFamily[]>(EMPTY_INSTALLED_FONT_FAMILIES);');
|
||||
expect(appSource).toContain('data-gonavi-linux-cjk-font-banner="true"');
|
||||
expect(appSource).toContain('Linux CJK fonts missing / Ubuntu 中文字体缺失');
|
||||
expect(appSource).toContain("import LinuxCJKFontBanner from './components/LinuxCJKFontBanner';");
|
||||
expect(appSource).toContain('<LinuxCJKFontBanner');
|
||||
expect(linuxCJKFontBannerSource).toContain('data-gonavi-linux-cjk-font-banner="true"');
|
||||
expect(linuxCJKFontBannerSource).toContain('Linux CJK fonts missing / Ubuntu 中文字体缺失');
|
||||
expect(appSource).toContain('setIsLinuxCJKFontBannerDismissed(true)');
|
||||
expect(appSource).toContain('matchFontFamilyOption');
|
||||
expect(appSource).toContain('showSearch');
|
||||
|
||||
@@ -10,9 +10,11 @@ import SnippetSettingsModal from './components/SnippetSettingsModal';
|
||||
import ConnectionPackagePasswordModal from './components/ConnectionPackagePasswordModal';
|
||||
import DataSyncModal from './components/DataSyncModal';
|
||||
import DriverManagerModal from './components/DriverManagerModal';
|
||||
import LinuxCJKFontBanner from './components/LinuxCJKFontBanner';
|
||||
import LogPanel from './components/LogPanel';
|
||||
import AISettingsModal from './components/AISettingsModal';
|
||||
import AIChatPanel from './components/AIChatPanel';
|
||||
import AIPanelErrorBoundary from './components/ai/AIPanelErrorBoundary';
|
||||
import SecurityUpdateBanner from './components/SecurityUpdateBanner';
|
||||
import SecurityUpdateIntroModal from './components/SecurityUpdateIntroModal';
|
||||
import SecurityUpdateProgressModal from './components/SecurityUpdateProgressModal';
|
||||
@@ -201,43 +203,6 @@ const createClosedConnectionPackageDialogState = (): ConnectionPackageDialogStat
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
interface AIPanelErrorBoundaryProps {
|
||||
children: React.ReactNode;
|
||||
fallback: (error: Error | null) => React.ReactNode;
|
||||
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
|
||||
}
|
||||
|
||||
interface AIPanelErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
class AIPanelErrorBoundary extends React.Component<
|
||||
AIPanelErrorBoundaryProps,
|
||||
AIPanelErrorBoundaryState
|
||||
> {
|
||||
constructor(props: AIPanelErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): AIPanelErrorBoundaryState {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
this.props.onError?.(error, errorInfo);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return this.props.fallback(this.state.error);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isConnectionModalMounted, setIsConnectionModalMounted] = useState(false);
|
||||
@@ -3416,51 +3381,15 @@ function App() {
|
||||
</div>
|
||||
|
||||
{showLinuxCJKFontBanner && (
|
||||
<div
|
||||
data-gonavi-linux-cjk-font-banner="true"
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
padding: '8px 14px',
|
||||
borderBottom: darkMode ? '1px solid rgba(250,204,21,0.20)' : '1px solid rgba(217,119,6,0.18)',
|
||||
background: darkMode ? 'rgba(250,204,21,0.10)' : 'rgba(255,247,237,0.92)',
|
||||
color: darkMode ? 'rgba(254,249,195,0.96)' : '#7c2d12',
|
||||
fontSize: 12,
|
||||
lineHeight: 1.55,
|
||||
}}
|
||||
>
|
||||
<InfoCircleOutlined style={{ flexShrink: 0, color: darkMode ? '#facc15' : '#d97706' }} />
|
||||
<div style={{ minWidth: 0, flex: 1 }}>
|
||||
<div style={{ fontWeight: 700 }}>
|
||||
Linux CJK fonts missing / Ubuntu 中文字体缺失
|
||||
</div>
|
||||
<div>
|
||||
Chinese text may render as □□□. Install fonts, then restart GoNavi:
|
||||
<code style={{ marginLeft: 6, fontFamily: 'var(--gn-font-mono)', wordBreak: 'break-all' }}>
|
||||
{linuxCJKFontInstallHint}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => {
|
||||
<LinuxCJKFontBanner
|
||||
darkMode={darkMode}
|
||||
installHint={linuxCJKFontInstallHint || ''}
|
||||
onOpenFontSettings={() => {
|
||||
setThemeModalSection('appearance');
|
||||
setIsThemeModalOpen(true);
|
||||
}}
|
||||
>
|
||||
Font Settings
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
onClick={() => setIsLinuxCJKFontBannerDismissed(true)}
|
||||
style={{ color: 'inherit' }}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
}}
|
||||
onDismiss={() => setIsLinuxCJKFontBannerDismissed(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Layout style={{ flex: 1, minHeight: 0, minWidth: 0 }}>
|
||||
|
||||
62
frontend/src/components/LinuxCJKFontBanner.tsx
Normal file
62
frontend/src/components/LinuxCJKFontBanner.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import React from 'react';
|
||||
import { Button } from 'antd';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
|
||||
interface LinuxCJKFontBannerProps {
|
||||
darkMode: boolean;
|
||||
installHint: string;
|
||||
onOpenFontSettings: () => void;
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
const LinuxCJKFontBanner: React.FC<LinuxCJKFontBannerProps> = ({
|
||||
darkMode,
|
||||
installHint,
|
||||
onOpenFontSettings,
|
||||
onDismiss,
|
||||
}) => (
|
||||
<div
|
||||
data-gonavi-linux-cjk-font-banner="true"
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
padding: '8px 14px',
|
||||
borderBottom: darkMode ? '1px solid rgba(250,204,21,0.20)' : '1px solid rgba(217,119,6,0.18)',
|
||||
background: darkMode ? 'rgba(250,204,21,0.10)' : 'rgba(255,247,237,0.92)',
|
||||
color: darkMode ? 'rgba(254,249,195,0.96)' : '#7c2d12',
|
||||
fontSize: 12,
|
||||
lineHeight: 1.55,
|
||||
}}
|
||||
>
|
||||
<InfoCircleOutlined style={{ flexShrink: 0, color: darkMode ? '#facc15' : '#d97706' }} />
|
||||
<div style={{ minWidth: 0, flex: 1 }}>
|
||||
<div style={{ fontWeight: 700 }}>
|
||||
Linux CJK fonts missing / Ubuntu 中文字体缺失
|
||||
</div>
|
||||
<div>
|
||||
Chinese text may render as □□□. Install fonts, then restart GoNavi:
|
||||
<code style={{ marginLeft: 6, fontFamily: 'var(--gn-font-mono)', wordBreak: 'break-all' }}>
|
||||
{installHint}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={onOpenFontSettings}
|
||||
>
|
||||
Font Settings
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
onClick={onDismiss}
|
||||
style={{ color: 'inherit' }}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default LinuxCJKFontBanner;
|
||||
@@ -191,7 +191,8 @@ describe('AIMCPClientInstallPanel', () => {
|
||||
expect(markup).toContain('远程桥接');
|
||||
expect(markup).toContain('当前已选中,将复制远程接入说明');
|
||||
expect(markup).toContain('远程接入边界');
|
||||
expect(markup).toContain('云端 Agent 只通过 MCP 工具读取连接摘要、库表和 DDL');
|
||||
expect(markup).toContain('云端 Agent 默认通过 schema-only MCP 工具读取连接摘要、库表和 DDL');
|
||||
expect(markup).toContain('不注册 execute_sql');
|
||||
expect(markup).toContain('OpenClaw 远程 MCP 快速配置');
|
||||
expect(markup).toContain('公网/隧道 URL');
|
||||
expect(markup).toContain('云端 Agent 能访问到的 Streamable HTTP MCP 地址');
|
||||
@@ -206,13 +207,14 @@ describe('AIMCPClientInstallPanel', () => {
|
||||
expect(markup).toContain('"type": "streamable-http"');
|
||||
expect(markup).toContain('"url": "https://<你的域名或隧道地址>/mcp"');
|
||||
expect(markup).toContain('"Authorization": "Bearer <随机token>"');
|
||||
expect(markup).toContain('GoNavi.exe mcp-server remote-config --client openclaw --url https://<你的域名或隧道地址>/mcp --token <随机token>');
|
||||
expect(markup).toContain('GoNavi.exe mcp-server remote-config --client openclaw --url https://<你的域名或隧道地址>/mcp --token <随机token> --schema-only');
|
||||
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('GoNavi.exe mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <随机token> --schema-only');
|
||||
expect(markup).toContain('独立二进制:gonavi-mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <随机token> --schema-only');
|
||||
expect(markup).toContain('验证顺序');
|
||||
expect(markup).toContain('安全边界');
|
||||
expect(markup).toContain('数据库账号和密码仍保存在 Windows GoNavi');
|
||||
expect(markup).toContain('默认 --schema-only 不注册 execute_sql');
|
||||
expect(markup).toContain('CLI 检测:远程 Agent 不需要检测本机 openclaw 命令');
|
||||
expect(markup).toContain('复制 OpenClaw 远程接入说明');
|
||||
});
|
||||
|
||||
@@ -292,7 +292,7 @@ const AIMCPClientInstallPanel: React.FC<AIMCPClientInstallPanelProps> = ({
|
||||
lineHeight: 1.7,
|
||||
}}
|
||||
>
|
||||
远程接入边界:数据库连接信息和密码仍保存在 Windows GoNavi;云端 Agent 只通过 MCP 工具读取连接摘要、库表和 DDL。跨机器接入请使用 GoNavi Streamable HTTP 模式,并配合 token、隧道或反向代理。
|
||||
远程接入边界:数据库连接信息和密码仍保存在 Windows GoNavi;云端 Agent 默认通过 schema-only MCP 工具读取连接摘要、库表和 DDL,不注册 execute_sql。跨机器接入请使用 GoNavi Streamable HTTP 模式,并配合 token、隧道或反向代理。
|
||||
</div>
|
||||
)}
|
||||
{remoteQuickStart && (
|
||||
@@ -311,7 +311,7 @@ const AIMCPClientInstallPanel: React.FC<AIMCPClientInstallPanelProps> = ({
|
||||
{remoteQuickStart.displayName} 远程 MCP 快速配置
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: overlayTheme.mutedText, lineHeight: 1.7 }}>
|
||||
下面分别给云端 Agent、无 GUI/CLI 场景和 Windows GoNavi 使用。云端只保存 MCP URL 和 Bearer Token,不保存数据库账号密码。
|
||||
下面分别给云端 Agent、无 GUI/CLI 场景和 Windows GoNavi 使用。云端只保存 MCP URL 和 Bearer Token,不保存数据库账号密码;默认 schema-only 只暴露结构工具。
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(210px, 1fr))', gap: 10 }}>
|
||||
{REMOTE_MCP_PARAMETER_GUIDES.map((item) => (
|
||||
|
||||
40
frontend/src/components/ai/AIPanelErrorBoundary.tsx
Normal file
40
frontend/src/components/ai/AIPanelErrorBoundary.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
|
||||
interface AIPanelErrorBoundaryProps {
|
||||
children: React.ReactNode;
|
||||
fallback: (error: Error | null) => React.ReactNode;
|
||||
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
|
||||
}
|
||||
|
||||
interface AIPanelErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
class AIPanelErrorBoundary extends React.Component<
|
||||
AIPanelErrorBoundaryProps,
|
||||
AIPanelErrorBoundaryState
|
||||
> {
|
||||
constructor(props: AIPanelErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): AIPanelErrorBoundaryState {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
this.props.onError?.(error, errorInfo);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return this.props.fallback(this.state.error);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default AIPanelErrorBoundary;
|
||||
@@ -78,6 +78,7 @@ describe('mcpClientInstallPanelState', () => {
|
||||
|
||||
expect(getMCPClientStatusTone(status, false).label).toBe('远程桥接');
|
||||
expect(getMCPClientInstallStateLabel(status)).toBe('外部工具接入状态:需配置远程 MCP 桥接');
|
||||
expect(getMCPClientOptionSummary(status)).toContain('默认 schema-only');
|
||||
expect(getMCPClientOptionSummary(status)).toContain('不复制数据库密码');
|
||||
expect(getMCPClientDetectionSummary(status)).toContain('本机无需检测 openclaw 命令');
|
||||
expect(getSelectedMCPClientStateLine(status)).toContain('数据库密码仍留在 GoNavi 本机');
|
||||
|
||||
@@ -101,7 +101,7 @@ export const getMCPClientOptionSummary = (status: AIMCPClientInstallStatus | und
|
||||
return '接入状态读取异常,建议先刷新再处理。';
|
||||
}
|
||||
if (isRemoteMCPClientStatus(status)) {
|
||||
return '适合云端 Agent:通过远程 MCP 桥接读取 GoNavi 表结构,不复制数据库密码。';
|
||||
return '适合云端 Agent:默认 schema-only 读取 GoNavi 表结构,不复制数据库密码,不暴露 SQL 执行。';
|
||||
}
|
||||
return '尚未把当前 GoNavi MCP 接入到这里。';
|
||||
};
|
||||
|
||||
@@ -133,12 +133,13 @@ describe('mcpClientInstallStatus helpers', () => {
|
||||
const guide = buildRemoteMCPClientGuide(openClaw);
|
||||
expect(guide).toContain('GoNavi MCP 远程接入说明 - OpenClaw');
|
||||
expect(guide).toContain('云端 Agent 不需要保存数据库密码');
|
||||
expect(guide).toContain('默认使用 schema-only 模式,不注册 execute_sql');
|
||||
expect(guide).toContain('不能直接使用 Windows 本地 stdio 命令');
|
||||
expect(guide).toContain('allowMutating=true');
|
||||
expect(guide).toContain('"type": "streamable-http"');
|
||||
expect(guide).toContain('"Authorization": "Bearer <随机token>"');
|
||||
expect(guide).toContain('GoNavi.exe mcp-server remote-config --client openclaw --url https://<你的域名或隧道地址>/mcp --token <随机token>');
|
||||
expect(guide).toContain('GoNavi.exe mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <随机token>');
|
||||
expect(guide).toContain('GoNavi.exe mcp-server remote-config --client openclaw --url https://<你的域名或隧道地址>/mcp --token <随机token> --schema-only');
|
||||
expect(guide).toContain('GoNavi.exe mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <随机token> --schema-only');
|
||||
});
|
||||
|
||||
it('builds remote quick-start snippets for cloud agents without database secrets', () => {
|
||||
@@ -152,10 +153,11 @@ describe('mcpClientInstallStatus helpers', () => {
|
||||
expect(quickStart.configJson).toContain('"url": "https://<你的域名或隧道地址>/mcp"');
|
||||
expect(quickStart.configJson).toContain('"Authorization": "Bearer <随机token>"');
|
||||
expect(quickStart.configJson).not.toContain('password');
|
||||
expect(quickStart.configCommand).toBe('GoNavi.exe mcp-server remote-config --client hermans --url https://<你的域名或隧道地址>/mcp --token <随机token>');
|
||||
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.configCommand).toBe('GoNavi.exe mcp-server remote-config --client hermans --url https://<你的域名或隧道地址>/mcp --token <随机token> --schema-only');
|
||||
expect(quickStart.launchCommand).toBe('GoNavi.exe mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <随机token> --schema-only');
|
||||
expect(quickStart.standaloneCommand).toBe('gonavi-mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <随机token> --schema-only');
|
||||
expect(quickStart.verificationSteps.join('\n')).toContain('get_connections');
|
||||
expect(quickStart.securityNotes.join('\n')).toContain('默认 --schema-only 不注册 execute_sql');
|
||||
expect(quickStart.securityNotes.join('\n')).toContain('allowMutating=true');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -232,7 +232,7 @@ export const buildRemoteMCPClientGuide = (
|
||||
'目标:',
|
||||
'- 数据库连接、账号和密码继续保存在 Windows 上的 GoNavi。云端 Agent 不需要保存数据库密码。',
|
||||
'- 云端 Agent 只通过 MCP tools 读取 get_connections/get_databases/get_tables/get_columns/get_table_ddl 等结果。',
|
||||
'- execute_sql 仍受 GoNavi AI 安全控制约束;写操作必须显式传 allowMutating=true。',
|
||||
'- 远程接入默认使用 schema-only 模式,不注册 execute_sql,适合只给 OpenClaw/Hermans 读取库表结构。',
|
||||
'',
|
||||
'当前边界:',
|
||||
'- GoNavi 内置 MCP 本机入口是 stdio,适合 Claude Code / Codex 这类和 GoNavi 在同一台机器上的客户端。',
|
||||
@@ -253,6 +253,7 @@ export const buildRemoteMCPClientGuide = (
|
||||
'CLI / 服务启动命令:',
|
||||
quickStart.launchCommand,
|
||||
`或设置环境变量:GONAVI_MCP_HTTP_TOKEN=<随机token> 后运行 ${quickStart.standaloneCommand.replace(' --token <随机token>', '')}`,
|
||||
'如果明确需要远程执行 SQL,可去掉 --schema-only;此时 execute_sql 仍受 GoNavi AI 安全控制约束,写操作必须显式传 allowMutating=true。',
|
||||
'',
|
||||
status?.message ? `当前提示:${status.message}` : '',
|
||||
].filter((line, index, lines) => line || index < lines.length - 1).join('\n');
|
||||
@@ -263,9 +264,9 @@ export const buildRemoteMCPClientQuickStart = (
|
||||
): RemoteMCPClientQuickStart => {
|
||||
const displayName = String(status?.displayName || '远程 Agent').trim();
|
||||
const client = isMCPClientKey(String(status?.client || '')) ? String(status?.client || '').trim() : 'openclaw';
|
||||
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 configCommand = `GoNavi.exe mcp-server remote-config --client ${client} --url ${DEFAULT_REMOTE_MCP_PUBLIC_URL} --token <随机token>`;
|
||||
const launchCommand = `GoNavi.exe mcp-server http --addr ${DEFAULT_REMOTE_MCP_LOCAL_ADDR} --path ${DEFAULT_REMOTE_MCP_PATH} --token <随机token> --schema-only`;
|
||||
const standaloneCommand = `gonavi-mcp-server http --addr ${DEFAULT_REMOTE_MCP_LOCAL_ADDR} --path ${DEFAULT_REMOTE_MCP_PATH} --token <随机token> --schema-only`;
|
||||
const configCommand = `GoNavi.exe mcp-server remote-config --client ${client} --url ${DEFAULT_REMOTE_MCP_PUBLIC_URL} --token <随机token> --schema-only`;
|
||||
const configJson = JSON.stringify({
|
||||
mcpServers: {
|
||||
gonavi: {
|
||||
@@ -291,8 +292,9 @@ export const buildRemoteMCPClientQuickStart = (
|
||||
],
|
||||
securityNotes: [
|
||||
'数据库账号和密码仍保存在 Windows GoNavi,本段配置不要写数据库密码。',
|
||||
'默认 --schema-only 不注册 execute_sql,远程 Agent 只能走库表结构类工具。',
|
||||
'HTTP MCP 必须使用随机 Bearer Token,并放在 HTTPS、私有网络或受控隧道后面。',
|
||||
'execute_sql 仍受 GoNavi AI 安全控制约束,写操作仍必须显式传 allowMutating=true。',
|
||||
'如去掉 --schema-only 开放 execute_sql,仍受 GoNavi AI 安全控制约束,写操作仍必须显式传 allowMutating=true。',
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user