anomalyco / anomalyco/opencode

MCP tool schemas are not sanitized for Anthropic: root-level anyOf/oneOf/allOf 400s, and tool.definition never sees MCP tools

Open
#46,628 6 comments 0 reactions 1 assignee View on GitHub

@kitlangton is already working on this.

Since Sep 1, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

Description

Any MCP server exposing a tool whose inputSchema has a root-level anyOf / oneOf / allOf makes every request to an Anthropic-backed model fail with a 400, before a single tool is called:

AI_APICallError: tools.5.custom.input_schema: input_schema does not support oneOf, allOf, or anyOf at the top level

ProviderTransform.schema already normalises tool schemas for OpenAI/Azure, Moonshot/Kimi and Gemini, but has no Anthropic branch, so the schema reaches @ai-sdk/anthropic / @ai-sdk/github-copilot verbatim. Anthropic accepts these combinators nested inside a property — only the root is rejected — so folding them into the root object schema is enough to fix it.

There is a second, separable half: MCP tools never reach the tool.definition hook, so a plugin cannot work around this either. With a plugin that only logs input.toolID, a run whose request carried 18 MCP tools plus the built-ins fired the hook 12 times, all built-in:

bash edit glob grep invalid question read skill task todowrite webfetch write

Built-in and plugin tools go through ToolRegistry.tools, which triggers tool.definition. MCP tools are converted separately in the session tool-assembly path and skip it. That leaves proxying the MCP server and rewriting tools/list as the only user-space fix, which is what I ended up doing.

Steps to reproduce
  1. repro-mcp.mjs — dependency-free stdio MCP server with one offending tool:
let buffer = "";
const send = (message) => process.stdout.write(JSON.stringify(message) + "\n");

process.stdin.on("data", (chunk) => {
  buffer += chunk;
  let newline;
  while ((newline = buffer.indexOf("\n")) >= 0) {
    const line = buffer.slice(0, newline).trim();
    buffer = buffer.slice(newline + 1);
    if (!line) continue;
    const message = JSON.parse(line);
    if (message.method === "initialize") {
      send({ jsonrpc: "2.0", id: message.id, result: {
        protocolVersion: "2024-11-05",
        capabilities: { tools: {} },
        serverInfo: { name: "repro", version: "1.0.0" },
      }});
    } else if (message.method === "tools/list") {
      send({ jsonrpc: "2.0", id: message.id, result: { tools: [{
        name: "pick_one",
        description: "Pass exactly one of a or b.",
        inputSchema: {
          type: "object",
          properties: { a: { type: "string" }, b: { type: "string" } },
          anyOf: [{ required: ["a"] }, { required: ["b"] }],
        },
      }]}});
    } else if (message.method === "tools/call") {
      send({ jsonrpc: "2.0", id: message.id, result: { content: [{ type: "text", text: "ok" }] } });
    } else if (message.id !== undefined) {
      send({ jsonrpc: "2.0", id: message.id, result: {} });
    }
  }
});
  1. opencode.json beside it:
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "repro": { "type": "local", "command": ["node", "repro-mcp.mjs"], "enabled": true }
  }
}
  1. opencode run --pure -m github-copilot/claude-haiku-4.5 "reply only: ok" → the 400 above.
    opencode run --pure -m github-copilot/gpt-5-mini "reply only: ok"ok. Same config, same server; it is specific to the Anthropic-family providers.

Real-world case that led me here: the ai-memory MCP server ships one such tool, which takes down every Claude session in OpenCode while GPT and Gemini sessions are fine.

What I would expect
  1. ProviderTransform.schema flattens root-level anyOf / oneOf / allOf into the root object schema for @ai-sdk/anthropic, @ai-sdk/google-vertex/anthropic and @ai-sdk/github-copilot, as it already does for the other providers.
  2. MCP tool definitions also run through tool.definition, so plugins can patch provider quirks without proxying the server.
Plugins

None — reproduced with --pure.

OpenCode version

1.18.25

Operating System

Arch Linux (CachyOS), x86_64

Terminal

N/A — reproduced with non-interactive opencode run.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.