Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions packages/cli/src/services/McpPromptLoader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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!'),
Expand Down
13 changes: 11 additions & 2 deletions packages/cli/src/services/McpPromptLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" }).`,
};
}

Expand Down Expand Up @@ -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) {
Expand Down
Loading