fix(cli): forward user input to MCP prompts with no declared arguments (#6571)

* 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.
This commit is contained in:
易良 2026-07-09 20:49:01 +08:00 committed by GitHub
parent 41c405b3bf
commit bb96ac4fe5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 74 additions and 2 deletions

View file

@ -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!'),

View file

@ -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) {