From 7965d333ac303bbaa761278dfa9db08e57dc2e8b Mon Sep 17 00:00:00 2001 From: RockChinQ Date: Sat, 13 Jun 2026 01:56:03 -0400 Subject: [PATCH] fix(mcp): read stdio args from form state in testMcp to avoid stale closure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MCP detail page invokes testMcp() through an imperative handle (formRef.current.testMcp()). The handle closure is only refreshed when [mcpTesting] changes, so testMcp read a stale snapshot of the stdioArgs/ extraArgs React state — on the detail page that snapshot is the empty initial [], so stdio 'args' were dropped entirely. The sandbox then launched 'uvx' with no package, which exits 2 and surfaces only an opaque 'Connection closed' with no detail. Read command/args/env via form.getValues() (kept in sync on every edit and on load) instead of the captured state, matching how 'command' was already read. Fixes stdio MCP test failing with empty args on the detail page. --- .../home/mcp/components/mcp-form/MCPForm.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/web/src/app/home/mcp/components/mcp-form/MCPForm.tsx b/web/src/app/home/mcp/components/mcp-form/MCPForm.tsx index d82d57ec..ae5f2ace 100644 --- a/web/src/app/home/mcp/components/mcp-form/MCPForm.tsx +++ b/web/src/app/home/mcp/components/mcp-form/MCPForm.tsx @@ -450,6 +450,9 @@ const MCPForm = forwardRef(function MCPForm( testMcp: () => testMcp(), isTesting: mcpTesting, }), + // testMcp now reads everything via form.getValues(), so it does not need + // the latest stdioArgs/extraArgs closure — but keep mcpTesting so the + // exposed isTesting flag stays accurate. [mcpTesting], ); @@ -680,6 +683,17 @@ const MCPForm = forwardRef(function MCPForm( try { const mode = form.getValues('mode'); + // Read every field via form.getValues() rather than the captured + // `stdioArgs` / `extraArgs` state. testMcp() is invoked through an + // imperative handle (formRef.current.testMcp()) whose closure is only + // refreshed when [mcpTesting] changes, so reading the React state here + // would use a stale snapshot — on the detail page that snapshot is the + // empty initial [], which dropped stdio args entirely and launched + // `uvx` with no package (exit 2 / "Connection closed", no detail). + // The form values are kept in sync on every edit and on load, so they + // are always current. + const formExtraArgs = form.getValues('extra_args') ?? []; + const formStdioArgs = form.getValues('args') ?? []; let extraArgsData: | MCPServerExtraArgsSSE | MCPServerExtraArgsHttp @@ -690,7 +704,7 @@ const MCPForm = forwardRef(function MCPForm( url: form.getValues('url')!, timeout: form.getValues('timeout'), headers: Object.fromEntries( - extraArgs.map((arg) => [arg.key, arg.value]), + formExtraArgs.map((arg) => [arg.key, arg.value]), ), ssereadtimeout: form.getValues('ssereadtimeout'), }; @@ -699,14 +713,16 @@ const MCPForm = forwardRef(function MCPForm( url: form.getValues('url')!, timeout: form.getValues('timeout'), headers: Object.fromEntries( - extraArgs.map((arg) => [arg.key, arg.value]), + formExtraArgs.map((arg) => [arg.key, arg.value]), ), }; } else { extraArgsData = { command: form.getValues('command')!, - args: stdioArgs.map((arg) => arg.value), - env: Object.fromEntries(extraArgs.map((arg) => [arg.key, arg.value])), + args: formStdioArgs.map((arg) => arg.value), + env: Object.fromEntries( + formExtraArgs.map((arg) => [arg.key, arg.value]), + ), }; }