Skip to content

Claude Code silently drops all tools due to MCP SDK 1.26.0 fields (instructions, annotations, execution) #220

@greenstevester

Description

@greenstevester

Description

XcodeBuildMCP v2.0.7 tools are completely invisible in Claude Code 2.1.39 despite the server showing as "Connected" in claude mcp list. Zero tools appear — no error, no warning.

This is caused by fields added by @modelcontextprotocol/sdk v1.26.0 that Claude Code's MCP client doesn't handle gracefully.

Affected Fields

In initialize response:

  • instructions — large documentation string (~1500 chars)
  • capabilities.tools.listChanged: true
  • capabilities.resources and capabilities.logging

In tools/list response (per-tool):

  • execution: { taskSupport: "forbidden" }
  • annotations: { title: "...", readOnlyHint: true/false }
  • inputSchema.$schema: "http://json-schema.org/draft-07/schema#"

How We Diagnosed

  1. Created a minimal canary MCP server with bare {name, description, inputSchema} and capabilities: {tools: {}}tools loaded
  2. XcodeBuildMCP proxy stripping only tool-level fields — still dropped
  3. Proxy also normalizing the initialize response (removing instructions, simplifying capabilities) — tools loaded

The instructions field and/or extra capabilities in the initialize response appear to be the primary trigger. Tool-level annotations and execution may also contribute.

Workaround

Node.js proxy that normalizes MCP responses:

#!/usr/bin/env node
const { spawn } = require('child_process');

const proc = spawn('xcodebuildmcp', ['mcp'], {
  stdio: ['pipe', 'pipe', process.stderr],
  cwd: process.env.XCODEBUILDMCP_CWD || process.cwd(),
});

let buffer = '';
proc.stdout.on('data', (data) => {
  buffer += data.toString();
  const lines = buffer.split('\n');
  buffer = lines.pop();
  for (const line of lines) {
    if (!line.trim()) { process.stdout.write('\n'); continue; }
    try {
      const msg = JSON.parse(line);
      // Clean initialize response
      if (msg.result && msg.result.protocolVersion) {
        delete msg.result.instructions;
        if (msg.result.capabilities) {
          msg.result.capabilities = { tools: {} };
        }
      }
      // Clean tools/list response
      if (msg.result && msg.result.tools && Array.isArray(msg.result.tools)) {
        msg.result.tools = msg.result.tools.map(tool => {
          const { execution, annotations, ...rest } = tool;
          if (rest.inputSchema) delete rest.inputSchema['$schema'];
          return rest;
        });
      }
      process.stdout.write(JSON.stringify(msg) + '\n');
    } catch (e) {
      process.stdout.write(line + '\n');
    }
  }
});

process.stdin.pipe(proc.stdin);
proc.on('exit', (code) => process.exit(code || 0));
process.on('SIGTERM', () => proc.kill('SIGTERM'));
process.on('SIGINT', () => proc.kill('SIGINT'));

Save as a proxy script, then in .mcp.json:

{
  "XcodeBuildMCP": {
    "type": "stdio",
    "command": "node",
    "args": ["/path/to/xcodebuildmcp-proxy.js"],
    "env": { "XCODEBUILDMCP_CWD": "/path/to/your/project" }
  }
}

Suggested Fix

Consider one or more of:

  1. Make instructions opt-in via config (e.g. enableInstructions: false in .xcodebuildmcp/config.yaml)
  2. Strip instructions from initialize when the client doesn't advertise support
  3. Make annotations and execution optional per MCP spec — only include them if the client negotiates support during initialize

Related

Environment

  • XcodeBuildMCP: 2.0.7
  • @modelcontextprotocol/sdk: 1.26.0
  • Claude Code: 2.1.39
  • macOS: Darwin 24.6.0 (Apple Silicon)

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions