CherryHQ/cherry-studio · error · Error

Tool not found

Error message

Tool not found

What it means

Thrown by the fetch server's CallToolRequest handler when the incoming tool name is not one of fetch_html, fetch_json, fetch_txt, or fetch_markdown. Unlike the filesystem server, this handler has no surrounding try/catch, so the throw propagates out of the request handler to the MCP SDK transport, which converts it into a JSON-RPC error response rather than a tool result. It indicates a mismatch between the tools the server advertised in ListTools and what the client actually called.

Source

Thrown at src/main/ai/mcp/servers/fetch.ts:226

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { arguments: args } = request.params

  const validatedArgs = RequestPayloadSchema.parse(args)

  if (request.params.name === 'fetch_html') {
    return await Fetcher.html(validatedArgs)
  }
  if (request.params.name === 'fetch_json') {
    return await Fetcher.json(validatedArgs)
  }
  if (request.params.name === 'fetch_txt') {
    return await Fetcher.txt(validatedArgs)
  }
  if (request.params.name === 'fetch_markdown') {
    return await Fetcher.markdown(validatedArgs)
  }
  throw new Error('Tool not found')
})

class FetchServer {
  public server: Server
  constructor() {
    this.server = server
  }
}
export default FetchServer

View on GitHub (pinned to 726446b54c)

Solutions

  1. Compare the name sent by the client against the four registered names (fetch_html, fetch_json, fetch_txt, fetch_markdown) exactly — note underscores, not hyphens.
  2. Ensure the client refreshes its tool list via ListTools before calling; do not rely on a cached schema.
  3. If introducing or renaming a tool, update both ListToolsRequestSchema output and the dispatch chain in the same change, and bump the server version.
  4. Wrap the handler in try/catch to return an isError tool result instead of a transport-level JSON-RPC error, matching the filesystem server's pattern at server.ts:80-117.

Example fix

// before
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { arguments: args } = request.params
  const validatedArgs = RequestPayloadSchema.parse(args)
  if (request.params.name === 'fetch_html') return await Fetcher.html(validatedArgs)
  // ...
  throw new Error('Tool not found')
})

// after — normalize name, return a tool result on miss
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const name = request.params.name
  const handlers: Record<string, (a: RequestPayload) => Promise<unknown>> = {
    fetch_html: Fetcher.html, fetch_json: Fetcher.json,
    fetch_txt: Fetcher.txt, fetch_markdown: Fetcher.markdown
  }
  const handler = handlers[name]
  if (!handler) {
    return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true }
  }
  return await handler(RequestPayloadSchema.parse(request.params.arguments))
})
Defensive patterns

Strategy: validation

Validate before calling

// Validate the tool name against the registered list before dispatching.
const FETCH_TOOLS = new Set(['fetch_html', 'fetch_json', 'fetch_txt', 'fetch_markdown'])
function isValidFetchTool(name: unknown): name is string {
  return typeof name === 'string' && FETCH_TOOLS.has(name)
}
if (!isValidFetchTool(request.params.name)) {
  return { content: [{ type: 'text', text: `Unknown tool: ${request.params.name}` }], isError: true }
}

Type guard

function isKnownFetchTool(name: string): boolean {
  return ['fetch_html', 'fetch_json', 'fetch_txt', 'fetch_markdown'].includes(name)
}

Try / catch

// Wrap the dispatch so a miss returns a tool result, matching the filesystem server.
try {
  if (!isKnownFetchTool(name)) {
    return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true }
  }
  return await handlers[name](validatedArgs)
} catch (e) {
  return { content: [{ type: 'text', text: (e as Error).message }], isError: true }
}

Prevention

When it happens

Trigger: An MCP client dispatches a CallToolRequest with request.params.name set to a typo ('fetch-html', 'html', 'fetch_html '), an unregistered tool, or a tool name from a different server version. Also reachable if a client caches an old tool list and calls a since-removed tool.

Common situations: Client/server version skew after adding or renaming a fetch tool; a model hallucinating a tool name; a proxy or orchestrator routing the request to the wrong server instance; trailing whitespace or case differences in the name field.

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/93a9395c78d03238. Report an issue: GitHub.