mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-30 06:31:23 +08:00
✨ feat(ai): 完善 MCP Docker 启动参数指引
- 新增 Docker MCP 启动模板和参数顺序提示 - 校验 docker run、-i 和镜像名等易漏参数 - 同步 MCP 设置页说明、空状态和单元测试
This commit is contained in:
@@ -75,11 +75,11 @@ const AIMCPServerFormPanel: React.FC<AIMCPServerFormPanelProps> = ({
|
||||
options={[{ label: 'stdio', value: 'stdio' }]}
|
||||
/>
|
||||
</AIMCPHelpBlock>
|
||||
<AIMCPHelpBlock title="启动命令" description="这里只填命令本身;如果是 npx/node/uvx/python 这类启动器,把包名、脚本名或模块名放到下面的参数里。不要把 npx -y package --stdio 或 node server.js --stdio 整串都塞进这里。" overlayTheme={overlayTheme} darkMode={darkMode} fieldState="required" example="npx / node / uvx / python">
|
||||
<AIMCPHelpBlock title="启动命令" description="这里只填命令本身;如果是 npx/node/uvx/python/docker 这类启动器,把包名、脚本名、模块名或 docker run 参数放到下面的参数里。不要把 npx -y package --stdio、node server.js --stdio 或 docker run -i image 整串都塞进这里。" overlayTheme={overlayTheme} darkMode={darkMode} fieldState="required" example="npx / node / uvx / python / docker">
|
||||
<Input
|
||||
value={server.command}
|
||||
onChange={(event) => onChange({ command: event.target.value })}
|
||||
placeholder="启动命令,例如:npx / node / uvx / python"
|
||||
placeholder="启动命令,例如:npx / node / uvx / python / docker"
|
||||
style={{ borderRadius: 10, background: inputBg, border: `1px solid ${cardBorder}` }}
|
||||
/>
|
||||
</AIMCPHelpBlock>
|
||||
@@ -122,7 +122,7 @@ const AIMCPServerFormPanel: React.FC<AIMCPServerFormPanelProps> = ({
|
||||
</AIMCPHelpBlock>
|
||||
</div>
|
||||
|
||||
<AIMCPHelpBlock title="命令参数" description="每个参数单独录入一个标签;命令本体不要填在这里。比如 npx -y package --stdio,要把 -y、package 和 --stdio 分开填;node server.js --stdio 要把 server.js 和 --stdio 分开填。不确定怎么拆时,优先回到上面的“完整命令”框自动拆分。" overlayTheme={overlayTheme} darkMode={darkMode} fieldState="optional" example="-y、@modelcontextprotocol/server-filesystem、--stdio、server.js">
|
||||
<AIMCPHelpBlock title="命令参数" description="每个参数单独录入一个标签;命令本体不要填在这里。比如 npx -y package --stdio,要把 -y、package 和 --stdio 分开填;node server.js --stdio 要把 server.js 和 --stdio 分开填;docker run --rm -i image 要把 run、--rm、-i 和镜像名分开填。不确定怎么拆时,优先回到上面的“完整命令”框自动拆分。" overlayTheme={overlayTheme} darkMode={darkMode} fieldState="optional" example="-y、@modelcontextprotocol/server-filesystem、--stdio、server.js、run、--rm、-i、image">
|
||||
<Select
|
||||
mode="tags"
|
||||
value={server.args || []}
|
||||
|
||||
@@ -108,10 +108,10 @@ describe('AISettingsMCPSection', () => {
|
||||
expect(markup).toContain('timeout');
|
||||
expect(markup).toContain('只填程序名或启动器本身');
|
||||
expect(markup).toContain('应填:');
|
||||
expect(markup).toContain('填 npx、node、uvx、python,或某个 exe 的绝对路径');
|
||||
expect(markup).toContain('填 npx、node、uvx、python、docker,或某个 exe 的绝对路径');
|
||||
expect(markup).toContain('不要填整行命令,例如不要填 npx -y pkg --stdio');
|
||||
expect(markup).toContain('把脚本名、模块名、开关参数拆开逐项填写');
|
||||
expect(markup).toContain('不要再填 npx/node/uvx/python');
|
||||
expect(markup).toContain('不要再填 npx/node/uvx/python/docker');
|
||||
expect(markup).toContain('给 MCP Server 传入 KEY=VALUE 形式的配置');
|
||||
expect(markup).toContain('不要写 export、set 或 $env: 前缀');
|
||||
expect(markup).toContain('单次工具发现或调用最多等待多久');
|
||||
@@ -119,6 +119,8 @@ describe('AISettingsMCPSection', () => {
|
||||
expect(markup).toContain('npx 包');
|
||||
expect(markup).toContain('npx -y @modelcontextprotocol/server-filesystem --stdio');
|
||||
expect(markup).toContain('Node 脚本');
|
||||
expect(markup).toContain('Docker 镜像');
|
||||
expect(markup).toContain('docker run -i --rm image');
|
||||
expect(markup).toContain('新增 MCP 服务');
|
||||
expect(markup).toContain('还没有 MCP 服务');
|
||||
expect(markup).toContain('npx -y package --stdio');
|
||||
@@ -205,4 +207,26 @@ describe('AISettingsMCPSection', () => {
|
||||
args: ['-y', '@modelcontextprotocol/server-filesystem', '--stdio'],
|
||||
}));
|
||||
});
|
||||
|
||||
it('seeds a docker MCP draft with interactive stdio args', () => {
|
||||
const onAddServer = vi.fn();
|
||||
const tree = AISettingsMCPSection(buildMCPSectionProps({
|
||||
mcpClientStatuses: [],
|
||||
selectedMCPClientStatus: undefined,
|
||||
onAddServer,
|
||||
}));
|
||||
|
||||
const dockerTemplateButton = findElement(
|
||||
tree,
|
||||
(node) => node.type === 'button' && flattenElementText(node.props?.children).includes('Docker 镜像'),
|
||||
);
|
||||
expect(dockerTemplateButton).toBeTruthy();
|
||||
dockerTemplateButton.props.onClick();
|
||||
|
||||
expect(onAddServer).toHaveBeenCalledWith(expect.objectContaining({
|
||||
command: 'docker',
|
||||
args: ['run', '--rm', '-i', 'mcp/server-fetch:latest'],
|
||||
timeoutSeconds: 45,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -123,7 +123,7 @@ const AISettingsMCPSection: React.FC<AISettingsMCPSectionProps> = ({
|
||||
>
|
||||
<div style={{ fontWeight: 700, fontSize: 14, color: overlayTheme.titleText }}>常见启动方式模板</div>
|
||||
<div style={{ fontSize: 12, color: overlayTheme.mutedText, lineHeight: 1.7 }}>
|
||||
不确定命令和参数怎么拆时,先选一个最接近的启动方式。GoNavi 会自动带入示例值,你再改成自己的脚本名、模块名或 exe 路径即可。
|
||||
不确定命令和参数怎么拆时,先选一个最接近的启动方式。GoNavi 会自动带入示例值,你再改成自己的脚本名、模块名、Docker 镜像或 exe 路径即可。
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 10 }}>
|
||||
{MCP_SERVER_DRAFT_TEMPLATES.map((template) => (
|
||||
@@ -154,7 +154,7 @@ const AISettingsMCPSection: React.FC<AISettingsMCPSectionProps> = ({
|
||||
</div>
|
||||
{mcpServers.length === 0 && (
|
||||
<div style={{ padding: '18px 16px', borderRadius: 14, border: `1px dashed ${cardBorder}`, background: cardBg, color: overlayTheme.mutedText }}>
|
||||
还没有 MCP 服务。常见形式是 `npx -y package --stdio`、`node server.js`、`uvx some-mcp-server`、`python -m server`。
|
||||
还没有 MCP 服务。常见形式是 `npx -y package --stdio`、`node server.js`、`uvx some-mcp-server`、`python -m server`、`docker run --rm -i image`。
|
||||
</div>
|
||||
)}
|
||||
{mcpServers.map((server) => (
|
||||
|
||||
Reference in New Issue
Block a user