mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-09 17:19:02 +00:00
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:
parent
41c405b3bf
commit
bb96ac4fe5
2 changed files with 74 additions and 2 deletions
|
|
@ -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!'),
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue