{"record":{"id":"89e73bec2a3fba18","repo":"gethomepage/homepage","slug":"unknown-tool-name","errorCode":null,"errorMessage":"Unknown tool '${name}'","messagePattern":"Unknown tool '(.+?)'","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/utils/mcp/homepage-mcp.js","lineNumber":439,"sourceCode":"        JSON.stringify({ written: args.file, bytes: Buffer.byteLength(args.content, \"utf8\") }, null, 2),\n      );\n    }\n    case \"add_service\":\n      return addService(args);\n    case \"add_info_widget\":\n      return addInfoWidget(args);\n    case \"homepage_docs\": {\n      const topic = args.topic || \"overview\";\n      const links = {\n        overview: \"https://gethomepage.dev/configs/\",\n        troubleshooting: \"https://gethomepage.dev/troubleshooting/\",\n        widgets: \"https://gethomepage.dev/widgets/\",\n        ...DOC_LINKS,\n      };\n      return textContent(JSON.stringify({ topic, url: links[topic] || links.overview }, null, 2));\n    }\n    default:\n      throw new Error(`Unknown tool '${name}'`);\n  }\n}\n\nexport function mcpEnabled() {\n  return enabled();\n}\n\nexport function mcpTokenConfigError() {\n  if (!enabled()) return null;\n  const token = requiredToken();\n  if (token && token.length < MIN_TOKEN_LENGTH) {\n    return `HOMEPAGE_MCP_TOKEN must be at least ${MIN_TOKEN_LENGTH} characters. Generate one with: openssl rand -base64 32`;\n  }\n  return null;\n}\n\nexport function mcpTokenAuthorized(req) {\n  const token = requiredToken();","sourceCodeStart":421,"sourceCodeEnd":457,"githubUrl":"https://github.com/gethomepage/homepage/blob/b6dca1ae033e613d8e692f9a161a3cc53a5a2857/src/utils/mcp/homepage-mcp.js#L421-L457","documentation":"Thrown by the default branch of the switch in callTool() when the MCP tool dispatcher receives a name that does not match any of the registered tools (list_config_files, read_config_file, validate_config_file, write_config_file, add_service, add_info_widget, homepage_docs). Homepage's MCP server exposes a fixed tool list and rejects anything outside it, so this is an API contract violation by the MCP client, not a runtime failure of Homepage itself.","triggerScenarios":"An MCP client calls a tool name that is misspelled (e.g. 'add_widget' instead of 'add_info_widget'), uses a legacy/renamed tool name from an older Homepage version, sends a casing variant ('Homepage_Docs'), or calls a tool that exists in a different MCP server but not this one.","commonSituations":"Stale client cached against an older tool list after a Homepage upgrade that renamed/removed a tool; a client hard-coding tool names; LLM agent hallucinating a plausible-sounding tool name; copy/paste typos in a custom MCP client integration.","solutions":["Call listTools / read the MCP server's tools/list response and verify the exact name; correct the client to use one of the registered tool names.","If you expected a tool that no longer exists, check the Homepage changelog for a rename (e.g. an older 'add_widget' becoming 'add_info_widget').","Ensure the client sends the tool name verbatim in lowercase with underscores, matching the name field returned by the server's tool schema.","If you are extending Homepage, add a new case branch in callTool() (src/utils/mcp/homepage-mcp.js) and register the tool in getTools() so the name is both dispatched and advertised."],"exampleFix":"// before\nserver.callTool('add_widget', { type: 'search' });\n// after\nserver.callTool('add_info_widget', { type: 'search' });","handlingStrategy":"validation","validationCode":"// Before dispatching, validate the name against the advertised tool list.\nconst allowed = new Set((await server.listTools()).map((t) => t.name));\nif (!allowed.has(name)) {\n  return { isError: true, content: [{ type: \"text\", text: `Tool '${name}' not registered. Available: ${[...allowed].join(\", \")}` }] };\n}\nreturn server.callTool(name, args);","typeGuard":"// Narrow a tool name to the known Homepage tool union.\nconst HOMEPAGE_TOOLS = [\n  \"list_config_files\", \"read_config_file\", \"validate_config_file\",\n  \"write_config_file\", \"add_service\", \"add_info_widget\", \"homepage_docs\",\n];\nfunction isHomepageTool(name) {\n  return typeof name === \"string\" && HOMEPAGE_TOOLS.includes(name);\n}","tryCatchPattern":"try {\n  const result = await callTool(name, args);\n  res.json(result);\n} catch (err) {\n  if (/^Unknown tool/.test(err.message)) {\n    res.status(400).json({ error: err.message, available: HOMEPAGE_TOOLS });\n  } else {\n    res.status(500).json({ error: err.message });\n  }\n}","preventionTips":["Always drive tool selection from the live tools/list response, never from a hard-coded list in the client.","Regenerate any cached tool catalog after upgrading Homepage.","Write an integration test that asserts every tool your client calls appears in server.listTools()."],"tags":["mcp","api-contract","homepage","input-validation"],"backgroundTag":null,"analyzedSha":"b6dca1ae033e613d8e692f9a161a3cc53a5a2857","analyzedAt":"2026-08-13T04:48:44.121Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}