{"record":{"id":"af9de53fd334509b","repo":"zylon-ai/private-gpt","slug":"mcp-tool-tool-name-r-is-no-longer-available-from","errorCode":null,"errorMessage":"MCP tool {tool_name!r} is no longer available from server {config.name!r}.","messagePattern":"MCP tool (.+?) is no longer available from server (.+?)\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"private_gpt/server/mcp/mcp_service.py","lineNumber":198,"sourceCode":"    tool_name: str,\n    name: str | None,\n    type: str | None,\n    description: str | None,\n    input_schema: dict[str, object] | None,\n) -> \"ToolSpec\":\n    \"\"\"Build an MCP tool whose client session is scoped to each invocation.\"\"\"\n    from private_gpt.components.chat.models.chat_config_models import (\n        ToolExecutionMetadata,\n        ToolSpec,\n    )\n\n    async def invoke_mcp_tool(**kwargs: object) -> object:\n        client = McpClient(config)\n        try:\n            tools = await client.list_tools()\n            available = {item.name for item in tools}\n            if tool_name not in available:\n                raise ValueError(\n                    f\"MCP tool {tool_name!r} is no longer available from \"\n                    f\"server {config.name!r}.\"\n                )\n            return await client.call_tool(tool_name, dict(kwargs))\n        finally:\n            await client.close()\n\n    rebuild_kwargs = {\n        \"config\": config,\n        \"tool_name\": tool_name,\n        \"name\": name,\n        \"type\": type,\n        \"description\": description,\n        \"input_schema\": input_schema,\n    }\n    return ToolSpec.from_defaults(\n        name=name or tool_name,\n        type=type,","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/server/mcp/mcp_service.py#L180-L216","documentation":"RuntimeError/ValueError raised inside the per-invocation MCP tool wrapper: each call opens a fresh McpClient, lists the server's current tools, and refuses to execute if tool_name is not among them. This guards against stale tool registrations — the tool existed when the chat session/config was built but the server no longer advertises it (renamed, removed, or upgraded schema).","triggerScenarios":"A saved chat config or tool spec referencing tool 'old_name' while the MCP server now exposes 'new_name'; the MCP server was restarted with tools disabled/removed; the server URL now points to a different deployment; intermittent server-side registration failures during list_tools.","commonSituations":"MCP server upgrades that rename tools; feature-flagged tools disabled in production but enabled where the config was authored; stale persisted chat configs replayed against a newer server; environment drift between staging and prod servers.","solutions":["Re-fetch the server's tool list (client.list_tools()) and update the stored ToolSpec/tool name to the current one.","Pin or align the MCP server version with the one the config was authored against.","Check the MCP server logs to confirm whether the tool was deliberately removed; if so migrate usage to its replacement.","If it should still exist, verify the server connection config (URL/auth) points at the right instance."],"exampleFix":"# before\ntool = ToolExecutionMetadata(tool_name='search_docs')  # renamed upstream\n# after\ntools = await McpClient(config).list_tools()\nassert 'docs_search' in {t.name for t in tools}\ntool = ToolExecutionMetadata(tool_name='docs_search')","handlingStrategy":"type-guard","validationCode":"async def tool_exists(config, tool_name: str) -> bool:\n    client = McpClient(config)\n    try:\n        tools = await client.list_tools()\n        return tool_name in {t.name for t in tools}\n    finally:\n        await client.close()","typeGuard":"const toolIsAvailable = async (config: McpConfig, name: string): Promise<boolean> => {\n  const client = new McpClient(config);\n  try {\n    const tools = await client.listTools();\n    return tools.some((t) => t.name === name);\n  } finally {\n    await client.close();\n  }\n};","tryCatchPattern":"try {\n  await invokeTool(toolName, args);\n} catch (e: any) {\n  if (/no longer available/.test(String(e?.message))) {\n    await refreshToolRegistry();  // re-list and re-map names, then retry once\n  } else throw e;\n}","preventionTips":["Refresh and persist tool registries after any MCP server upgrade.","Validate stored tool names against list_tools() on session load.","Subscribe to MCP servers/tools/list_changed notifications when the client supports it."],"tags":["mcp","tooling","stale-config","runtime","integration"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}