feat(ai): 完善 MCP 新增字段填写提示

- 增加 command、args、env、timeout 的应填与勿填对照

- 抽取 MCP 字段指南卡片复用速查与表单说明

- 补充 AI 设置 MCP 区域渲染测试
This commit is contained in:
Syngnat
2026-06-10 18:19:10 +08:00
parent d8da8d6abf
commit 156631c263
5 changed files with 111 additions and 61 deletions

View File

@@ -0,0 +1,71 @@
import React from 'react';
import type { OverlayWorkbenchTheme } from '../../utils/overlayWorkbenchTheme';
import type { MCPFieldGuide } from '../../utils/mcpServerGuidance';
import { buildMCPFieldTone, buildMCPHintStyle } from './AIMCPHelpBlock';
interface AIMCPFieldGuideCardProps {
item: MCPFieldGuide;
cardBorder: string;
darkMode: boolean;
overlayTheme: OverlayWorkbenchTheme;
compact?: boolean;
}
const AIMCPFieldGuideCard: React.FC<AIMCPFieldGuideCardProps> = ({
item,
cardBorder,
darkMode,
overlayTheme,
compact = false,
}) => {
const tone = buildMCPFieldTone(item.fieldState, darkMode);
return (
<div
style={{
padding: compact ? '10px 12px' : '10px 12px',
borderRadius: compact ? 12 : 10,
border: `1px solid ${cardBorder}`,
background: darkMode ? 'rgba(255,255,255,0.025)' : 'rgba(255,255,255,0.78)',
display: 'flex',
flexDirection: 'column',
gap: 6,
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
<div style={{ fontSize: 12, fontWeight: 700, color: overlayTheme.titleText }}>{item.title}</div>
<span
style={{
padding: '2px 8px',
borderRadius: 999,
fontSize: 11,
fontWeight: 700,
color: tone.color,
background: tone.bg,
}}
>
{tone.label}
</span>
</div>
<div style={{ fontSize: 12, lineHeight: 1.6, color: overlayTheme.titleText }}>{item.summary}</div>
{!compact && <div style={buildMCPHintStyle(overlayTheme.mutedText)}>{item.detail}</div>}
<div style={buildMCPHintStyle(overlayTheme.mutedText)}>
<strong></strong>
{item.fill}
</div>
<div style={buildMCPHintStyle(overlayTheme.mutedText)}>
<strong></strong>
{item.avoid}
</div>
{item.example ? (
<div style={buildMCPHintStyle(overlayTheme.mutedText)}>
{' '}
<code style={{ fontFamily: 'var(--gn-font-mono)' }}>{item.example}</code>
</div>
) : null}
</div>
);
};
export default AIMCPFieldGuideCard;

View File

@@ -11,7 +11,8 @@ import {
MCP_TROUBLESHOOTING_GUIDES,
} from '../../utils/mcpServerGuidance';
import AIMCPCommandDraftPreview from './AIMCPCommandDraftPreview';
import { buildMCPFieldTone, buildMCPHintStyle, mcpLabelStyle } from './AIMCPHelpBlock';
import AIMCPFieldGuideCard from './AIMCPFieldGuideCard';
import { buildMCPHintStyle, mcpLabelStyle } from './AIMCPHelpBlock';
interface AIMCPServerGuidePanelProps {
cardBorder: string;
@@ -73,48 +74,15 @@ const AIMCPServerGuidePanel: React.FC<AIMCPServerGuidePanelProps> = ({
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(210px, 1fr))', gap: 10 }}>
{MCP_FIELD_GUIDES.map((item) => {
const tone = buildMCPFieldTone(item.fieldState, darkMode);
return (
<div
key={item.key}
style={{
padding: '10px 12px',
borderRadius: 10,
border: `1px solid ${cardBorder}`,
background: darkMode ? 'rgba(255,255,255,0.025)' : 'rgba(255,255,255,0.78)',
display: 'flex',
flexDirection: 'column',
gap: 6,
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
<div style={{ fontSize: 12, fontWeight: 700, color: overlayTheme.titleText }}>{item.title}</div>
<span
style={{
padding: '2px 8px',
borderRadius: 999,
fontSize: 11,
fontWeight: 700,
color: tone.color,
background: tone.bg,
}}
>
{tone.label}
</span>
</div>
<div style={{ fontSize: 12, lineHeight: 1.6, color: overlayTheme.titleText }}>{item.summary}</div>
<div style={buildMCPHintStyle(overlayTheme.mutedText)}>{item.detail}</div>
{item.example ? (
<div style={buildMCPHintStyle(overlayTheme.mutedText)}>
{' '}
<code style={{ fontFamily: 'var(--gn-font-mono)' }}>{item.example}</code>
</div>
) : null}
</div>
);
})}
{MCP_FIELD_GUIDES.map((item) => (
<AIMCPFieldGuideCard
key={item.key}
item={item}
cardBorder={cardBorder}
darkMode={darkMode}
overlayTheme={overlayTheme}
/>
))}
</div>
</div>

View File

@@ -107,8 +107,13 @@ describe('AISettingsMCPSection', () => {
expect(markup).toContain('env');
expect(markup).toContain('timeout');
expect(markup).toContain('只填程序名或启动器本身');
expect(markup).toContain('应填:');
expect(markup).toContain('填 npx、node、uvx、python或某个 exe 的绝对路径');
expect(markup).toContain('不要填整行命令,例如不要填 npx -y pkg --stdio');
expect(markup).toContain('把脚本名、模块名、开关参数拆开逐项填写');
expect(markup).toContain('不要再填 npx/node/uvx/python');
expect(markup).toContain('给 MCP Server 传入 KEY=VALUE 形式的配置');
expect(markup).toContain('不要写 export、set 或 $env: 前缀');
expect(markup).toContain('单次工具发现或调用最多等待多久');
expect(markup).toContain('常见启动方式模板');
expect(markup).toContain('npx 包');

View File

@@ -8,6 +8,7 @@ import { MCP_FIELD_GUIDES } from '../../utils/mcpServerGuidance';
import { MCP_SERVER_DRAFT_TEMPLATES } from '../../utils/mcpServerTemplates';
import type { OverlayWorkbenchTheme } from '../../utils/overlayWorkbenchTheme';
import AIMCPClientInstallPanel from './AIMCPClientInstallPanel';
import AIMCPFieldGuideCard from './AIMCPFieldGuideCard';
import AIMCPServerCard from './AIMCPServerCard';
export type { MCPClientKey } from '../../utils/mcpClientInstallStatus';
@@ -98,25 +99,14 @@ const AISettingsMCPSection: React.FC<AISettingsMCPSectionProps> = ({
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(170px, 1fr))', gap: 10 }}>
{MCP_FIELD_GUIDES.filter((item) => ['command', 'args', 'env', 'timeout'].includes(item.key)).map((item) => (
<div
<AIMCPFieldGuideCard
key={item.key}
style={{
padding: '10px 12px',
borderRadius: 12,
border: `1px solid ${cardBorder}`,
background: darkMode ? 'rgba(255,255,255,0.03)' : 'rgba(255,255,255,0.72)',
}}
>
<div style={{ fontSize: 12, fontWeight: 700, color: overlayTheme.titleText }}>{item.title}</div>
<div style={{ marginTop: 4, fontSize: 12, color: overlayTheme.mutedText, lineHeight: 1.6 }}>{item.summary}</div>
{item.example ? (
<div style={{ marginTop: 6, fontSize: 12, color: overlayTheme.mutedText, lineHeight: 1.6 }}>
{' '}
<code style={{ fontFamily: 'var(--gn-font-mono)' }}>{item.example}</code>
</div>
) : null}
</div>
item={item}
cardBorder={cardBorder}
darkMode={darkMode}
overlayTheme={overlayTheme}
compact
/>
))}
</div>
</div>

View File

@@ -5,6 +5,8 @@ export interface MCPFieldGuide {
title: string;
summary: string;
detail: string;
fill: string;
avoid: string;
fieldState: MCPFieldState;
example?: string;
}
@@ -46,6 +48,8 @@ export const MCP_FIELD_GUIDES: MCPFieldGuide[] = [
title: '服务名称',
summary: '保存后显示给你和 AI 看的名字。',
detail: '按用途命名,建议写成 Browser、GitHub、Filesystem 这类一眼能认出的名字。',
fill: '这个 MCP 的用途名,例如 GitHub 或 Filesystem。',
avoid: '不要写 server、test、mcp1 这类看不出用途的名字。',
fieldState: 'required',
example: 'Filesystem / Browser / GitHub',
},
@@ -54,6 +58,8 @@ export const MCP_FIELD_GUIDES: MCPFieldGuide[] = [
title: '启用状态',
summary: '控制这条配置现在要不要参与工具发现和调用。',
detail: '禁用只是不使用,不会删除下面填好的配置。',
fill: '临时不用选已禁用;确认要给 AI 用时选已启用。',
avoid: '不要用删除代替临时停用,避免重新配置 command、args、env。',
fieldState: 'optional',
example: '已启用 / 已禁用',
},
@@ -62,6 +68,8 @@ export const MCP_FIELD_GUIDES: MCPFieldGuide[] = [
title: '传输方式',
summary: 'GoNavi 用什么方式和这个 MCP Server 通信。',
detail: '当前固定为 stdio表示本机直接启动进程并通过标准输入输出交互。',
fill: '保持 stdio。',
avoid: '不要填写 HTTP、SSE、URL 或端口;当前新增入口不是远程 MCP URL 配置。',
fieldState: 'fixed',
example: 'stdio',
},
@@ -70,6 +78,8 @@ export const MCP_FIELD_GUIDES: MCPFieldGuide[] = [
title: '启动命令',
summary: '只填程序名或启动器本身。',
detail: '常见是 npx、node、uvx、python包名、脚本名和 --stdio 这类内容放到参数里。',
fill: '填 npx、node、uvx、python或某个 exe 的绝对路径。',
avoid: '不要填整行命令,例如不要填 npx -y pkg --stdio。',
fieldState: 'required',
example: 'npx / node / uvx / python',
},
@@ -78,6 +88,8 @@ export const MCP_FIELD_GUIDES: MCPFieldGuide[] = [
title: '命令参数',
summary: '把脚本名、模块名、开关参数拆开逐项填写。',
detail: '例如 npx -y pkg --stdio要拆成 -y、pkg 和 --stdionode server.js --stdio 要拆成 server.js 和 --stdio。',
fill: '逐项填 -y、包名、脚本名、-m、--stdio 等参数。',
avoid: '不要再填 npx/node/uvx/python也不要把多个参数粘成一个长字符串。',
fieldState: 'optional',
example: '-y / @modelcontextprotocol/server-filesystem / --stdio / server.js',
},
@@ -86,6 +98,8 @@ export const MCP_FIELD_GUIDES: MCPFieldGuide[] = [
title: '环境变量',
summary: '给 MCP Server 传入 KEY=VALUE 形式的配置。',
detail: '通常用来放 API Key、服务地址、工作目录等每行一条不要写 export。',
fill: '每行一条 KEY=VALUE例如 GITHUB_TOKEN=...。',
avoid: '不要写 export、set 或 $env: 前缀;也不要把环境变量混进 command 或 args。',
fieldState: 'optional',
example: 'OPENAI_API_KEY=... / GITHUB_TOKEN=...',
},
@@ -94,6 +108,8 @@ export const MCP_FIELD_GUIDES: MCPFieldGuide[] = [
title: '超时(秒)',
summary: '单次工具发现或调用最多等待多久。',
detail: '本机常规工具一般 20 秒就够,启动慢或远端链路再适当调大。',
fill: '常规填 20启动慢时填 45 或 60。',
avoid: '不要随意填过小3 秒以下很容易让工具发现误判失败。',
fieldState: 'optional',
example: '20 / 45 / 60',
},