From bb96ac4fe5eeb86789dbd4f80fdcc942eac6c1e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=93=E8=89=AF?= <1204183885@qq.com> Date: Thu, 9 Jul 2026 20:49:01 +0800 Subject: [PATCH] fix(cli): forward user input to MCP prompts with no declared arguments (#6571) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(cli): forward user input to MCP prompts with no declared arguments When a prompt declares no arguments, parseArgs() silently discarded all user input. Forward named args as-is and positional input under the "input" key, matching Claude Code's behavior. Fixes #6563 * fix(cli): strip quotes and guard input key in MCP prompt arg forwarding - Use positionalArgs.join(' ') instead of positionalArgsString to properly strip quotes from positional input, consistent with the existing single-arg path. - Guard against overwriting a user-provided --input named arg with positional text. - Add comment explaining the input key convention. * fix(cli): update help text for no-argument MCP prompts The help text previously said the prompt 'has no arguments', which is now misleading — user input is forwarded as-is. Updated to explain that free-form text is accepted and how it maps to the input key. --- .../cli/src/services/McpPromptLoader.test.ts | 63 +++++++++++++++++++ packages/cli/src/services/McpPromptLoader.ts | 13 +++- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/services/McpPromptLoader.test.ts b/packages/cli/src/services/McpPromptLoader.test.ts index 832894e287..21ec28cd74 100644 --- a/packages/cli/src/services/McpPromptLoader.test.ts +++ b/packages/cli/src/services/McpPromptLoader.test.ts @@ -174,6 +174,51 @@ describe('McpPromptLoader', () => { pos3: 'p3 "with quotes"', }); }); + + it('should forward positional input as "input" when promptArgs is undefined', () => { + const loader = new McpPromptLoader(mockConfig); + const result = loader.parseArgs('hello world', undefined); + expect(result).toEqual({ input: 'hello world' }); + }); + + it('should forward positional input as "input" when promptArgs is empty', () => { + const loader = new McpPromptLoader(mockConfig); + const result = loader.parseArgs('hello world', []); + expect(result).toEqual({ input: 'hello world' }); + }); + + it('should return empty object when promptArgs is undefined and no input', () => { + const loader = new McpPromptLoader(mockConfig); + const result = loader.parseArgs('', undefined); + expect(result).toEqual({}); + }); + + it('should forward named args when promptArgs is undefined', () => { + const loader = new McpPromptLoader(mockConfig); + const result = loader.parseArgs('--key="value"', undefined); + expect(result).toEqual({ key: 'value' }); + }); + + it('should forward both named and positional args when promptArgs is undefined', () => { + const loader = new McpPromptLoader(mockConfig); + const result = loader.parseArgs('--key="value" some text', undefined); + expect(result).toEqual({ key: 'value', input: 'some text' }); + }); + + it('should strip quotes from positional input when promptArgs is undefined', () => { + const loader = new McpPromptLoader(mockConfig); + const result = loader.parseArgs('"hello world"', undefined); + expect(result).toEqual({ input: 'hello world' }); + }); + + it('should not overwrite a named "input" arg with positional text', () => { + const loader = new McpPromptLoader(mockConfig); + const result = loader.parseArgs( + '--input="named value" some text', + undefined, + ); + expect(result).toEqual({ input: 'named value' }); + }); }); describe('loadCommands', () => { @@ -222,6 +267,24 @@ describe('McpPromptLoader', () => { }); }); + it('should forward user input when prompt has no declared arguments', async () => { + vi.spyOn(cliCore, 'getMCPServerPrompts').mockReturnValue([ + { ...mockPrompt, arguments: undefined }, + ]); + const loader = new McpPromptLoader(mockConfigWithPrompts); + const commands = await loader.loadCommands(new AbortController().signal); + const action = commands[0].action!; + const context = {} as CommandContext; + const result = await action(context, 'some user input'); + expect(mockPrompt.invoke).toHaveBeenCalledWith({ + input: 'some user input', + }); + expect(result).toEqual({ + type: 'submit_prompt', + content: JSON.stringify('Hello, world!'), + }); + }); + it('should return an error message if prompt invocation fails', async () => { vi.spyOn(mockPrompt, 'invoke').mockRejectedValue( new Error('Invocation failed!'), diff --git a/packages/cli/src/services/McpPromptLoader.ts b/packages/cli/src/services/McpPromptLoader.ts index b25298de86..b6653382c5 100644 --- a/packages/cli/src/services/McpPromptLoader.ts +++ b/packages/cli/src/services/McpPromptLoader.ts @@ -63,7 +63,7 @@ export class McpPromptLoader implements ICommandLoader { return { type: 'message', messageType: 'info', - content: `Prompt "${prompt.name}" has no arguments.`, + content: `Prompt "${prompt.name}" has no declared arguments. Any text you provide will be forwarded as-is (e.g., /${prompt.name} some text sends { input: "some text" }).`, }; } @@ -273,7 +273,16 @@ export class McpPromptLoader implements ICommandLoader { positionalArgs.push((match[1] ?? match[2]).replace(/\\(.)/g, '$1')); } - if (!promptArgs) { + if (!promptArgs || promptArgs.length === 0) { + Object.assign(promptInputs, argValues); + // Forward positional text as a default "input" argument when the prompt + // declares no arguments, matching Claude Code's behavior. This key is a + // client-side convention, not part of the MCP spec. A user-provided + // --input named arg takes precedence over positional text. + const positionalInput = positionalArgs.join(' '); + if (positionalInput && !Object.hasOwn(argValues, 'input')) { + promptInputs['input'] = positionalInput; + } return promptInputs; } for (const arg of promptArgs) {