claude-code - 💡(How to fix) Fix [BUG] Claude Desktop - Code - MCP InputSchema with nullable arrays has type silently dropped [3 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#58747Fetched 2026-05-14 03:40:32
View on GitHub
Comments
3
Participants
2
Timeline
8
Reactions
0
Timeline (top)
labeled ×4commented ×3cross-referenced ×1

Error Message

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()

Code Example

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()
RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • 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" />

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [BUG] Claude Desktop - Code - MCP InputSchema with nullable arrays has type silently dropped [3 comments, 2 participants]