anomalyco / anomalyco/opencode

MCP: "Failed to get tools" when a tool inputSchema contains a boolean sub-schema in properties

Open
#49,464 0 comments 0 reactions 1 assignee View on GitHub

@neriousy is already working on this.

Since Sep 17, 2026.

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

Description

Summary

OpenCode marks an entire MCP server as failed with "Failed to get tools" when any tool's inputSchema contains a boolean sub-schema inside properties (e.g. "properties": { "raw_input": true }). Boolean sub-schemas are valid JSON Schema (2020-12 permits true/false where a schema is expected), and the same server works in clients that tolerate them. A single such tool takes down every tool on the server.

This is the same failure path as #21591 / #26529 (a schema-processing error in listTools() collapsing to "Failed to get tools"), but a different trigger: those were outputSchema $refs and were fixed by the tolerant retry keyed off isOutputSchemaValidationError(). Boolean inputSchema sub-schemas are not covered by that retry.

Related: #47644 (the underlying error is discarded), #48196 (silent failure), and the same class in the MCP Python SDK surface types — modelcontextprotocol/python-sdk#3353 / PR #3354.

Steps to reproduce

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: "read_everything",
        description: "Example tool.",
        inputSchema: {
          type: "object",
          additionalProperties: false,
          properties: {
            reason: { type: "string" },
            raw_input: true,               // <-- boolean sub-schema
            input: {
              type: "object",
              properties: { input: true }, // <-- boolean sub-schema (nested)
            },
          },
        },
      }]}});
    } else if (message.id !== undefined) {
      send({ jsonrpc: "2.0", id: message.id, result: {} });
    }
  }
});

opencode.json beside it:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "repro": { "type": "local", "command": ["node", "repro-mcp.mjs"], "enabled": true }
  }
}

Then:

opencode mcp list --print-logs --log-level DEBUG
Observed
  • The server process starts, the initialize handshake completes, and tools/list returns a valid response over stdio (verifiable with any raw MCP client).
  • OpenCode closes the connection and reports the whole server as failed:
●  ✗ repro  failed
│      Failed to get tools
│      node repro-mcp.mjs
  • No underlying schema error is surfaced, even with --print-logs --log-level DEBUG — only the constant "Failed to get tools" (#47644).
Expected

One of:

  1. Boolean sub-schemas in inputSchema/outputSchema properties are accepted (they are valid JSON Schema 2020-12), so the tools load; or
  2. A schema-processing failure on a single tool is skipped/downgraded and the remaining tools still load, with a visible warning; and
  3. The real underlying error is surfaced instead of the constant string.
Workaround

Because MCP tool definitions skip the tool.definition hook (#46628), a plugin cannot fix this. The only user-space workaround is to proxy the server and rewrite the tools/list response — rewriting boolean sub-schemas (true -> {}, false -> {"not": {}}) and dropping outputSchema. I verified this against a real server with 45 tools carrying "raw_input": true: after sanitizing, opencode mcp list reports connected and all 45 tools are preserved.

Environment
  • OpenCode version: 1.18.31
  • OS: macOS (darwin) x86_64
  • MCP transport: stdio (local), server protocol 2024-11-05
  • Also reproduced against a Go-based stdio MCP server; the same class is reported for third-party servers against Claude Code (anthropics/claude-code#25081) and the MCP Python SDK (modelcontextprotocol/python-sdk#3353).
Plugins

None — reproduced with the server alone; MCP tools do not pass through tool.definition.

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.