[BUG] Claude Desktop - Code - MCP InputSchema with nullable arrays has type silently dropped

Resolved 💬 4 comments Opened May 13, 2026 by abirmingham Closed Jun 11, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

In Code mode (i.e. the Chat vs Code toggle in the left side pane), and MCP server with inputSchema property with type union of ["array", "null"] is received by Claude Desktop with no "type". Claude Desktop proceeds to send stringified arrays for this property. Does not reproduce in "Chat" mode.

What Should Happen?

The type field on inputSchema properties is preserved as-is.

Steps to Reproduce

1) Create file server.py containing the repro MCP server below.
2) Add to claude_desktop_config.json: "mcpServers": {"nullable_array_repro": {"command": "python", "args": ["/path/to/server.py"]}}
3) Start Claude Desktop, click on Code mode in left pane.
4) Tell Claude: "Use nullable-array-repro MCP to reproduce the reported issue. Subsequently, print the exact tool list for that MCP server."

Repro MCP Server:

import json
import sys
from typing import Any


def send_response(result: Any = None, error: Any = None, request_id: Any = None) -> None:
    """Send a JSON-RPC response."""
    response = {"jsonrpc": "2.0"}
    if request_id is not None:
        response["id"] = request_id
    if error is not None:
        response["error"] = error
    else:
        response["result"] = result
    json.dump(response, sys.stdout)
    sys.stdout.write("\n")
    sys.stdout.flush()


def handle_initialize(request_id: int) -> None:
    """Handle initialize request."""
    send_response({
        "protocolVersion": "2025-11-25",
        "capabilities": {},
        "serverInfo": {"name": "echo-stdio", "version": "1.0.0"}
    }, request_id=request_id)


def handle_tools_list(request_id: int) -> None:
    """Handle tools/list request."""
    send_response({
        "tools": [
            {
                "name": "echo_array",
                "description": "Echo an array parameter. Helps observe if arrays are stringified in transit.",
                "inputSchema": {
                    "type": "object",
                    "properties": {
                        "items": {
                            "type": ["array", "null"],
                            "items": {"type": "string"},
                            "description": "A list of strings"
                        }
                    },  
                    "required": ["items"]
                }
            }
        ]
    }, request_id=request_id)


def handle_call_tool(name: str, arguments: dict[str, Any], request_id: int) -> None:
    """Handle tool calls - echo back what was received."""
    if name == "echo_array":
        items = arguments.get('items')
        result = {
            "content": [{
                "type": "text",
                "text": (
                    f"RECEIVED:\n"
                    f"  Value: {json.dumps(items)}\n"
                    f"  Type: {type(items).__name__}\n\n"
                    + ("⚠️ BUG: Array was stringified! Expected type 'list' but got 'str'."
                       if isinstance(items, str) and items.startswith('[')
                       else "✓ OK: Received as proper list.")
                )
            }]
        }
    else:
        send_response(error={"code": -32601, "message": f"Unknown tool: {name}"}, request_id=request_id)
        return
    
    send_response(result, request_id=request_id)


def main() -> None:
    """Main loop - read JSON-RPC requests from stdin."""
    for line in sys.stdin:
        line = line.strip()
        if not line:
            continue
        
        try:
            request = json.loads(line)
        except json.JSONDecodeError:
            send_response(error={"code": -32700, "message": "Parse error"})
            continue
        
        method = request.get("method")
        params = request.get("params", {})
        request_id = request.get("id")
        
        try:
            if method == "initialize":
                handle_initialize(request_id)
            elif method.startswith("notifications/"):
                pass  # notifications must not receive responses
            elif method == "tools/list":
                handle_tools_list(request_id)
            elif method == "tools/call":
                handle_call_tool(params.get("name"), params.get("arguments", {}), request_id)
            elif request_id is not None:
                send_response(error={"code": -32601, "message": f"Method not found: {method}"}, request_id=request_id)
        except Exception as e:
            send_response(error={"code": -32603, "message": str(e)}, request_id=request_id)


if __name__ == "__main__":
    main()

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

Unknown

Claude Code Version

Claude 1.7196.0 (2dbd78) 2026-05-12T05:34:40.000Z

Platform

Anthropic API

Operating System

Windows

Additional Information

Code (reproduces)

<img width="1470" height="1249" alt="Image" src="https://github.com/user-attachments/assets/bd1c117b-72b5-46a0-85f1-079d1d6b0ee7" />

Chat (does not reproduce)

<img width="1653" height="1368" alt="Image" src="https://github.com/user-attachments/assets/5ccb5c4d-af7d-4ac4-a8fd-80caafd9ae2a" />

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗