fix: show custom error for modal state (#240)

Calling a tool that resolves modal state, when there's no such modal
state visible, currently shows this misleading message:

```md
Tool "browser_file_upload" does not handle the modal state.
### Modal state
```

Instead, we should show the error message from the tool implementation.
This commit is contained in:
Simon Knott 2025-04-29 18:48:52 +02:00 committed by GitHub
parent ad4147da54
commit 6efdc90078
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 25 additions and 21 deletions

View File

@ -58,6 +58,8 @@ export class Context {
modalStatesMarkdown(): string[] { modalStatesMarkdown(): string[] {
const result: string[] = ['### Modal state']; const result: string[] = ['### Modal state'];
if (this._modalStates.length === 0)
result.push('- There is no modal state present');
for (const state of this._modalStates) { for (const state of this._modalStates) {
const tool = this.tools.find(tool => tool.clearsModalState === state.type); const tool = this.tools.find(tool => tool.clearsModalState === state.type);
result.push(`- [${state.description}]: can be handled by the "${tool?.schema.name}" tool`); result.push(`- [${state.description}]: can be handled by the "${tool?.schema.name}" tool`);

View File

@ -49,34 +49,25 @@ export function createServerWithTools(serverOptions: MCPServerOptions, config: C
}); });
server.setRequestHandler(CallToolRequestSchema, async request => { server.setRequestHandler(CallToolRequestSchema, async request => {
const tool = tools.find(tool => tool.schema.name === request.params.name); const errorResult = (...messages: string[]) => ({
if (!tool) { content: [{ type: 'text', text: messages.join('\n') }],
return {
content: [{ type: 'text', text: `Tool "${request.params.name}" not found` }],
isError: true, isError: true,
}; });
} const tool = tools.find(tool => tool.schema.name === request.params.name);
if (!tool)
return errorResult(`Tool "${request.params.name}" not found`);
const modalStates = context.modalStates().map(state => state.type); const modalStates = context.modalStates().map(state => state.type);
if ((tool.clearsModalState && !modalStates.includes(tool.clearsModalState)) || if (tool.clearsModalState && !modalStates.includes(tool.clearsModalState))
(!tool.clearsModalState && modalStates.length)) { return errorResult(`The tool "${request.params.name}" can only be used when there is related modal state present.`, ...context.modalStatesMarkdown());
const text = [ if (!tool.clearsModalState && modalStates.length)
`Tool "${request.params.name}" does not handle the modal state.`, return errorResult(`Tool "${request.params.name}" does not handle the modal state.`, ...context.modalStatesMarkdown());
...context.modalStatesMarkdown(),
].join('\n');
return {
content: [{ type: 'text', text }],
isError: true,
};
}
try { try {
return await context.run(tool, request.params.arguments); return await context.run(tool, request.params.arguments);
} catch (error) { } catch (error) {
return { return errorResult(String(error));
content: [{ type: 'text', text: String(error) }],
isError: true,
};
} }
}); });

View File

@ -30,6 +30,17 @@ test('browser_file_upload', async ({ client }) => {
- button "Button" [ref=s1e4] - button "Button" [ref=s1e4]
\`\`\``); \`\`\``);
{
expect(await client.callTool({
name: 'browser_file_upload',
arguments: { paths: [] },
})).toHaveTextContent(`
The tool "browser_file_upload" can only be used when there is related modal state present.
### Modal state
- There is no modal state present
`.trim());
}
expect(await client.callTool({ expect(await client.callTool({
name: 'browser_click', name: 'browser_click',
arguments: { arguments: {