From 53b4fcb8425d70e9b436cde8c3de9ea6857311de Mon Sep 17 00:00:00 2001 From: Syngnat Date: Mon, 8 Jun 2026 17:06:58 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(ai-mcp):=20=E8=A1=A5=E5=BC=BAM?= =?UTF-8?q?CP=E5=8F=82=E6=95=B0=E5=A1=AB=E5=86=99=E5=BC=95=E5=AF=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ai/AIMCPServerCard.test.tsx | 9 +- .../src/components/ai/AIMCPServerCard.tsx | 91 +++++++++++++++++-- 2 files changed, 89 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/ai/AIMCPServerCard.test.tsx b/frontend/src/components/ai/AIMCPServerCard.test.tsx index 9cf344a..ef97c58 100644 --- a/frontend/src/components/ai/AIMCPServerCard.test.tsx +++ b/frontend/src/components/ai/AIMCPServerCard.test.tsx @@ -6,7 +6,7 @@ import AIMCPServerCard from './AIMCPServerCard'; import { buildOverlayWorkbenchTheme } from '../../utils/overlayWorkbenchTheme'; describe('AIMCPServerCard', () => { - it('renders explicit MCP parameter hints and the actual launch preview for command, args, and env', () => { + it('renders explicit MCP parameter hints, required badges, and the actual launch preview for command, args, and env', () => { const markup = renderToStaticMarkup( { ); expect(markup).toContain('启动命令只填可执行程序本身'); + expect(markup).toContain('推荐填写顺序'); + expect(markup).toContain('小白用户可以按这个顺序填'); + expect(markup).toContain('必填'); + expect(markup).toContain('可选'); + expect(markup).toContain('固定'); expect(markup).toContain('直接粘贴完整命令'); expect(markup).toContain('自动拆分到下方字段'); expect(markup).toContain('每个参数单独录入一个标签'); expect(markup).toContain('每行一个 KEY=VALUE'); expect(markup).toContain('没有等号或 key 含空格的行不会保存'); + expect(markup).toContain('不要把 node server.js --stdio 整串都塞进这里'); + expect(markup).toContain('不要写 export'); expect(markup).toContain('当前阶段只支持 stdio'); expect(markup).toContain('实际启动命令预览'); expect(markup).toContain('node server.js --stdio'); diff --git a/frontend/src/components/ai/AIMCPServerCard.tsx b/frontend/src/components/ai/AIMCPServerCard.tsx index 55ccf89..574440d 100644 --- a/frontend/src/components/ai/AIMCPServerCard.tsx +++ b/frontend/src/components/ai/AIMCPServerCard.tsx @@ -33,6 +33,29 @@ const hintStyle = (mutedText: string): React.CSSProperties => ({ lineHeight: 1.6, }); +const buildFieldTone = (kind: 'required' | 'optional' | 'fixed', darkMode: boolean) => { + switch (kind) { + case 'required': + return { + label: '必填', + color: '#b45309', + bg: darkMode ? 'rgba(245,158,11,0.18)' : 'rgba(245,158,11,0.12)', + }; + case 'fixed': + return { + label: '固定', + color: '#2563eb', + bg: darkMode ? 'rgba(59,130,246,0.18)' : 'rgba(59,130,246,0.12)', + }; + default: + return { + label: '可选', + color: '#475569', + bg: darkMode ? 'rgba(148,163,184,0.18)' : 'rgba(148,163,184,0.12)', + }; + } +}; + const MCP_COMMAND_EXAMPLES = [ 'uvx mcp-server-fetch', 'node server.js --stdio', @@ -57,11 +80,29 @@ const MCPHelpBlock: React.FC<{ title: string; description: string; overlayTheme: OverlayWorkbenchTheme; + darkMode: boolean; + fieldState: 'required' | 'optional' | 'fixed'; example?: string; children: React.ReactNode; -}> = ({ title, description, overlayTheme, example, children }) => ( +}> = ({ title, description, overlayTheme, darkMode, fieldState, example, children }) => { + const tone = buildFieldTone(fieldState, darkMode); + return (
-
{title}
+
+
{title}
+ + {tone.label} + +
{description} {example ? ( @@ -72,7 +113,8 @@ const MCPHelpBlock: React.FC<{
{children}
-); + ); +}; export const AIMCPServerCard: React.FC = ({ server, @@ -121,6 +163,35 @@ export const AIMCPServerCard: React.FC = ({ +
+
推荐填写顺序
+
+ 小白用户可以按这个顺序填:先选上面的模板或粘整行命令,再确认下面的必填项,最后只在需要时补参数、环境变量和超时。 +
+
+ {[ + '1. 模板 / 完整命令', + '2. 服务名称', + '3. 启动命令', + '4. 命令参数(可选)', + '5. 环境变量 / 超时(按需)', + ].map((item) => ( + + {item} + + ))} +
+
+
只有一条完整命令?
@@ -148,7 +219,7 @@ export const AIMCPServerCard: React.FC = ({
- + onChange({ name: event.target.value })} @@ -156,7 +227,7 @@ export const AIMCPServerCard: React.FC = ({ style={{ borderRadius: 10, background: inputBg, border: `1px solid ${cardBorder}` }} /> - + onChange({ transport: value as AIMCPServerConfig['transport'] })} options={[{ label: 'stdio', value: 'stdio' }]} /> - + onChange({ command: event.target.value })} @@ -181,7 +252,7 @@ export const AIMCPServerCard: React.FC = ({ style={{ borderRadius: 10, background: inputBg, border: `1px solid ${cardBorder}` }} /> - + = ({
- +