{"record":{"id":"69f75f3698262b28","repo":"zed-industries/zed","slug":"anthropic-does-not-support-custom-tools","errorCode":null,"errorMessage":"Anthropic does not support custom tools","messagePattern":"Anthropic does not support custom tools","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/anthropic/src/completion.rs","lineNumber":334,"sourceCode":"        Some(StringOrContents::String(system_message))\n    };\n\n    let mut tools: Vec<Tool> = request\n        .tools\n        .into_iter()\n        .map(|tool| match tool.input {\n            LanguageModelRequestToolInput::Function {\n                input_schema,\n                use_input_streaming,\n            } => Ok(Tool {\n                name: tool.name,\n                description: tool.description,\n                input_schema,\n                eager_input_streaming: use_input_streaming,\n                cache_control: None,\n            }),\n            LanguageModelRequestToolInput::Custom { .. } => {\n                Err(anyhow::anyhow!(\"Anthropic does not support custom tools\"))\n            }\n        })\n        .collect::<Result<_>>()?;\n    if let Some(cache_control) = long_lived_cache\n        && let Some(last_tool) = tools.last_mut()\n    {\n        last_tool.cache_control = Some(cache_control);\n    }\n\n    let thinking = if request.thinking_allowed {\n        match mode {\n            AnthropicModelMode::Thinking { budget_tokens } => {\n                Some(Thinking::Enabled { budget_tokens })\n            }\n            AnthropicModelMode::AdaptiveThinking => Some(Thinking::Adaptive {\n                display: Some(AdaptiveThinkingDisplay::Summarized),\n            }),\n            AnthropicModelMode::Default => None,","sourceCodeStart":316,"sourceCodeEnd":352,"githubUrl":"https://github.com/zed-industries/zed/blob/bc538def4545534201bbfcac4e95ac34ea6501b6/crates/anthropic/src/completion.rs#L316-L352","documentation":"Thrown by Zed's Anthropic provider while converting a LanguageModelRequest into a Messages API call. The adapter only maps LanguageModelRequestToolInput::Function tools (name, description, JSON Schema); the Custom variant used for MCP-style client-executed tools has no Anthropic equivalent, so the request fails during tool conversion before any network call is made.","triggerScenarios":"Sending a LanguageModelRequest through the Anthropic provider where request.tools contains a tool whose input is LanguageModelRequestToolInput::Custom; the collect::<Result<_>>() over the tool-mapping closure returns this error immediately.","commonSituations":"An assistant profile or MCP integration registers custom tools and the active model is a Claude model; switching a workspace from an OpenAI-compatible provider (which accepts custom tools) to Anthropic; a newer client emitting Custom tool input against this adapter.","solutions":["Route the request to a provider that supports custom tools (e.g. an OpenAI-compatible or Google model) instead of Anthropic.","Convert the custom tool into a function tool: give it a JSON Schema input_schema, let the model call it by name, and execute it client-side on receipt.","Upgrade Zed - later adapters may translate custom tools into Anthropic function-calling form instead of rejecting them.","Filter custom tools before dispatch and surface a targeted message so the user knows which tool is unsupported."],"exampleFix":"// before\nrequest.tools.push(LanguageModelRequestTool {\n    name: \"read_file\".into(),\n    description: None,\n    input: LanguageModelRequestToolInput::Custom { /* ... */ }, // rejected by Anthropic\n});\n\n// after: express it as a function tool the model calls by name\nrequest.tools.push(LanguageModelRequestTool {\n    name: \"read_file\".into(),\n    description: Some(\"Read a file from disk\".into()),\n    input: LanguageModelRequestToolInput::Function {\n        input_schema: json_schema!({\n            \"type\": \"object\",\n            \"properties\": { \"path\": { \"type\": \"string\" } },\n            \"required\": [\"path\"]\n        }),\n        use_input_streaming: false,\n    },\n});","handlingStrategy":"validation","validationCode":"let uses_custom_tools = request\n    .tools\n    .iter()\n    .any(|tool| matches!(tool.input, LanguageModelRequestToolInput::Custom { .. }));\nif uses_custom_tools && provider_id.is_anthropic() {\n    return Err(anyhow::anyhow!(\n        \"the Anthropic provider does not support custom tools; convert them to function tools or pick another provider\"\n    ));\n}","typeGuard":"fn has_only_function_tools(request: &LanguageModelRequest) -> bool {\n    request\n        .tools\n        .iter()\n        .all(|tool| matches!(tool.input, LanguageModelRequestToolInput::Function { .. }))\n}","tryCatchPattern":"match provider.complete(request).await {\n    Err(ref e) if e.to_string().contains(\"does not support custom tools\") => {\n        // degrade gracefully: strip custom tools and retry, or surface a targeted message\n    }\n    other => other,\n}","preventionTips":["Check provider tool capabilities before registering custom tools","Convert MCP-style custom tools to schema-backed function tools for Anthropic","Add a per-provider integration test that sends both tool variants"],"tags":["anthropic","tools","mcp","llm","unsupported-feature"],"backgroundTag":null,"analyzedSha":"bc538def4545534201bbfcac4e95ac34ea6501b6","analyzedAt":"2026-08-16T07:30:46.435Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}