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

  1. Call `tools/list` first and use only `search_skills` / `install_skill`.
  2. Check the returned `isError: true` flag and the `Error: ...` text rather than expecting a thrown MethodNotFound.
  3. 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

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.