CherryHQ/cherry-studio · error · McpError
MethodNotFound
MethodNotFound
Error message
Unknown tool: ${toolName} What it means
The skills MCP server's CallTool handler only recognizes `search_skills` and `install_skill`. The `default` branch throws McpError MethodNotFound with the offending name. NOTE: unlike the memory server, this throw is INSIDE a try/catch (line 96) that converts every error — including this McpError — into a tool result `{ content: [{ text: "Error: ..." }], isError: true }`. So the client sees an error result, not a protocol-level MethodNotFound response.
Source
Thrown at src/main/ai/mcp/servers/skills.ts:94
}
private setupHandlers() {
this.mcpServer.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [SEARCH_TOOL, INSTALL_TOOL]
}))
this.mcpServer.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const toolName = request.params.name
const args = (request.params.arguments ?? {}) as Record<string, string | undefined>
try {
switch (toolName) {
case 'search_skills':
return await this.searchSkills(args)
case 'install_skill':
return await this.installSkill(args)
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${toolName}`)
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
logger.error(`Tool error: ${toolName}`, { agentId: this.agentId, error: message })
return {
content: [{ type: 'text' as const, text: `Error: ${message}` }],
isError: true
}
}
})
}
private async searchSkills(args: Record<string, string | undefined>) {
const query = args.query
if (!query) throw new McpError(ErrorCode.InvalidParams, "'query' is required for search_skills")
const results = await searchSkillMarketplaces(
query.replace(/[-_]+/g, ' ').trim(),View on GitHub (pinned to 726446b54c)
Solutions
- Call `tools/list` first and use only `search_skills` / `install_skill`.
- Check the returned `isError: true` flag and the `Error: ...` text rather than expecting a thrown MethodNotFound.
- Fix typos and reconcile client/server versions.
Example fix
// before
{ name: "list_skills" }
// after
{ name: "search_skills", arguments: { query: "pdf" } } Defensive patterns
Strategy: validation
Validate before calling
const SKILLS_TOOLS = new Set(['search_skills', 'install_skill'])
function assertSkillsTool(name: string) {
if (!SKILLS_TOOLS.has(name)) throw new Error(`Unknown skills tool: ${name}. Only search_skills/install_skill exist.`)
} Type guard
const isSkillsTool = (name: string): boolean => SKILLS_TOOLS.has(name)
Try / catch
// This server converts errors to isError results — check the result, not a throw.
const result = await client.callTool({ name, arguments })
if (result.isError) {
const text = (result.content[0] as any).text
if (/Unknown tool/.test(text)) {/* refresh tools/list, fix the name */}
} Prevention
- Discover tools via `tools/list`; only `search_skills` and `install_skill` exist.
- Check `isError` on every skills-server result — its handler swallows throws.
- Treat an unknown-tool result as version skew and refresh capabilities.
When it happens
Trigger: Calling any tool name other than `search_skills` or `install_skill` against this server: typos, deprecated names, or a call routed here by mistake.
Common situations: Version skew (a tool was renamed/removed); the model hallucinates a tool name (e.g. `list_skills`, `uninstall_skill`); routing mismatch.
Related errors
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/20028a7023b9d401.
Report an issue: GitHub.