From 4daaa22cba1c2ba5d368df7fb4d8fb4dd961e2a8 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Fri, 12 Jun 2026 02:16:26 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(mcp):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E4=B8=80=E9=94=AE=E8=A1=A5=E9=BD=90=E7=BC=BA=E5=A4=B1=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 MCP 参数提示中按当前 command 生成缺失必填参数 - 支持一键追加 docker、npx、python 等常见启动参数 - 增加组件交互测试覆盖参数补齐行为 --- .../components/ai/AIMCPArgumentHints.test.tsx | 52 +++++++++++++++++++ .../src/components/ai/AIMCPArgumentHints.tsx | 33 ++++++++++++ .../components/ai/AIMCPServerFormPanel.tsx | 1 + 3 files changed, 86 insertions(+) create mode 100644 frontend/src/components/ai/AIMCPArgumentHints.test.tsx diff --git a/frontend/src/components/ai/AIMCPArgumentHints.test.tsx b/frontend/src/components/ai/AIMCPArgumentHints.test.tsx new file mode 100644 index 0000000..253cfae --- /dev/null +++ b/frontend/src/components/ai/AIMCPArgumentHints.test.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { act, create, type ReactTestRenderer } from 'react-test-renderer'; +import { describe, expect, it, vi } from 'vitest'; + +import { buildOverlayWorkbenchTheme } from '../../utils/overlayWorkbenchTheme'; +import AIMCPArgumentHints from './AIMCPArgumentHints'; + +const flattenRendererText = (node: any): string => { + if (node == null || typeof node === 'boolean') { + return ''; + } + if (typeof node === 'string' || typeof node === 'number') { + return String(node); + } + if (Array.isArray(node)) { + return node.map((item) => flattenRendererText(item)).join(''); + } + return flattenRendererText(node.children ?? node.props?.children); +}; + +describe('AIMCPArgumentHints', () => { + it('can append missing required args from command-specific hints', async () => { + const onArgsChange = vi.fn(); + let renderer!: ReactTestRenderer; + + await act(async () => { + renderer = create( + , + ); + }); + + const buttons = renderer.root.findAll( + (node) => node.type === 'button' && flattenRendererText(node).includes('一键补齐缺失必填参数'), + ); + + expect(buttons.length).toBe(1); + expect(flattenRendererText(buttons[0])).toContain('-i / mcp/server-fetch:latest'); + + await act(async () => { + buttons[0].props.onClick(); + }); + + expect(onArgsChange).toHaveBeenCalledWith(['run', '--rm', '-i', 'mcp/server-fetch:latest']); + }); +}); diff --git a/frontend/src/components/ai/AIMCPArgumentHints.tsx b/frontend/src/components/ai/AIMCPArgumentHints.tsx index d89edf6..fbecbae 100644 --- a/frontend/src/components/ai/AIMCPArgumentHints.tsx +++ b/frontend/src/components/ai/AIMCPArgumentHints.tsx @@ -2,19 +2,31 @@ import React from 'react'; import type { OverlayWorkbenchTheme } from '../../utils/overlayWorkbenchTheme'; import { buildMCPArgumentHintProfile } from '../../utils/mcpArgumentHints'; +import { splitShellLikeCommand } from '../../utils/mcpCommandDraft'; import { buildMCPHintStyle, mcpLabelStyle } from './AIMCPHelpBlock'; interface AIMCPArgumentHintsProps { command: string; args?: string[]; + onArgsChange?: (args: string[]) => void; cardBorder: string; darkMode: boolean; overlayTheme: OverlayWorkbenchTheme; } +const buildMissingRequiredArgs = ( + profile: NonNullable>, +): string[] => + profile.steps + .filter((step) => step.required && !step.satisfied) + .flatMap((step) => splitShellLikeCommand(step.example).tokens) + .map((item) => item.trim()) + .filter(Boolean); + const AIMCPArgumentHints: React.FC = ({ command, args, + onArgsChange, cardBorder, darkMode, overlayTheme, @@ -23,6 +35,8 @@ const AIMCPArgumentHints: React.FC = ({ if (!profile) { return null; } + const missingRequiredArgs = buildMissingRequiredArgs(profile); + const canApplyMissingArgs = Boolean(onArgsChange && missingRequiredArgs.length > 0); return (
= ({ 必填参数看起来已经齐了,测试失败时再对照 README 检查业务参数和环境变量。
)} + {canApplyMissingArgs ? ( + + ) : null} ); }; diff --git a/frontend/src/components/ai/AIMCPServerFormPanel.tsx b/frontend/src/components/ai/AIMCPServerFormPanel.tsx index bc7265c..d45c19b 100644 --- a/frontend/src/components/ai/AIMCPServerFormPanel.tsx +++ b/frontend/src/components/ai/AIMCPServerFormPanel.tsx @@ -134,6 +134,7 @@ const AIMCPServerFormPanel: React.FC = ({ onChange({ args })} cardBorder={cardBorder} darkMode={darkMode} overlayTheme={overlayTheme}