feat(ai): 完善 MCP Docker 启动参数指引

- 新增 Docker MCP 启动模板和参数顺序提示

- 校验 docker run、-i 和镜像名等易漏参数

- 同步 MCP 设置页说明、空状态和单元测试
This commit is contained in:
Syngnat
2026-06-11 15:45:52 +08:00
parent e4672062f8
commit c9053bccc5
9 changed files with 196 additions and 19 deletions

View File

@@ -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 --stdionode 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 --stdionode 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 || []}

View File

@@ -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,
}));
});
});

View File

@@ -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) => (