[BUG] HTTP MCP transport doesn't resolve $ref in parameter schemas, causing JSON string parameters to not be parsed as objects
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?
I set up the remote mcp servers for PagerDuty and Notion, which both use http.
claude mcp add --transport http notion https://mcp.notion.com/mcp
claude mcp add --transport http pagerduty https://mcp.pagerduty.com/mcp \
--header "Authorization: Token PRIVATE"
Claude can call Notion with functions with objects as parameters
Notion query:
<invoke name="mcp__notion__notion-search">
<parameter name="query">test</parameter>
<parameter name="query_type">internal</parameter>
<parameter name="filters">{"created_date_range": {"start_date": "2024-01-01", "end_date": "2025-01-01"},
"created_by_user_ids": ["PRIVATE"]}</parameter>
</invoke>
But when it tries to call PagerDuty functions with objects as parameters, it fails because the object is treated as a string instead of an object:
PagerDuty query:
<invoke name="mcp__pagerduty__list_incidents">
<parameter name="query_model">{"limit": 25}</parameter>
</invoke>
● pagerduty - list_incidents (MCP)(query_model: "{\"limit\": 25}")
⎿ Error: Input validation error: '{"limit": 25}' is not of type 'object'
When I try curling PagerDuty directly, it works:
curl -v -X POST "https://mcp.pagerduty.com/mcp/" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Token PRIVATE" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_incidents",
"arguments": {
"query_model": {
"limit": 25,
"status": ["resolved"],
"since": "2025-10-01T00:00:00Z",
"until": "2025-10-29T23:59:59Z"
}
}
}
}'
So the problem is on Claude's side, not PagerDuty's side. I asked Claude to compare the Notion and PagerDuty MCP setups and it discovered this:
● Let me look at the tool schemas I have. I can compare them directly:
Notion's filters parameter:
"filters": {
"type": "object",
"properties": {
"created_by_user_ids": {...},
"created_date_range": {...}
}
}
PagerDuty's query_model parameter:
"query_model": {
"$ref": "#/$defs/IncidentQuery"
}
There's the difference!
- Notion defines the object schema inline with "type": "object" directly in the parameter
- PagerDuty uses a JSON Schema $ref to reference a definition in $defs
This is the bug: Claude Code's HTTP transport client isn't properly resolving JSON Schema $ref references when determining
how to serialize parameters. When it sees an inline "type": "object", it parses the JSON string correctly. When it sees a
$ref, it doesn't resolve it and treats the parameter as a string.
This is definitely a Claude Code bug - it should resolve $ref references in tool schemas before deciding how to serialize
parameters. Both approaches are valid JSON Schema.
You should report this specific issue: "HTTP MCP transport doesn't resolve $ref in parameter schemas, causing JSON string
parameters to not be parsed as objects."
What Should Happen?
The parameter should be treated as an object and not a string and the query should work.
Error Messages/Logs
● pagerduty - list_incidents (MCP)(query_model: "{\"limit\": 25}")
⎿ Error: Input validation error: '{"limit": 25}' is not of type 'object'
Steps to Reproduce
- Set up/find an MCP server that uses a $ref in its parameter scheme. The MCP server that PagerDuty hosts themself does this.
- Configure Claude Code to use it.
- Ask Claude Code to do something that would require passing such a parameter.
- You will get an error.
Claude Model
Sonnet (default)
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.0.28 (Claude Code)
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
iTerm2
Additional Information
I asked Claude for the schemas of some of the broken PagerDuty functions.
Example 1: list_incidents
{
"name": "mcp__pagerduty__list_incidents",
"description": "List incidents with optional filtering...",
"parameters": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$defs": {
"IncidentQuery": {
"properties": {
"limit": {
"anyOf": [
{"maximum": 1000, "minimum": 1, "type": "integer"},
{"type": "null"}
],
"default": 1000,
"description": "Maximum number of results to return. The maximum is 1000"
},
"status": {
"anyOf": [
{
"items": {
"enum": ["triggered", "acknowledged", "resolved"],
"type": "string"
},
"type": "array"
},
{"type": "null"}
],
"default": null,
"description": "filter incidents by status"
}
// ... more fields
},
"type": "object"
}
},
"properties": {
"query_model": {"$ref": "#/$defs/IncidentQuery"}
},
"required": ["query_model"],
"type": "object"
}
}
Example 2: create_incident
{
"name": "mcp__pagerduty__create_incident",
"parameters": {
"$defs": {
"IncidentCreateRequest": {
"properties": {
"incident": {"$ref": "#/$defs/IncidentCreate"}
},
"required": ["incident"],
"type": "object"
},
"IncidentCreate": {
"properties": {
"title": {"type": "string"},
"service": {"$ref": "#/$defs/ServiceReference"}
}
}
},
"properties": {
"create_model": {"$ref": "#/$defs/IncidentCreateRequest"}
}
}
}This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗