Skip to content

Handle a _meta that is not an object without raising - #650

Merged
koic merged 1 commit into
modelcontextprotocol:mainfrom
emirn:fix-non-object-meta
Nov 2, 2026
Merged

koic merged 1 commit into
modelcontextprotocol:mainfrom
emirn:fix-non-object-meta

Conversation

@emirn

@emirn emirn commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Motivation and Context

MCP requires _meta to be an object, but a request whose _meta is a string, an array or a number
makes the server raise TypeError from dig instead of answering a client error:

  • Streamable HTTP, MCP-Protocol-Version: 2026-07-28: validate_modern_headers calls
    params.dig(:_meta, ...), and the catch-all in handle_modern answers HTTP 500 -32603 for every method.
    RequestEnvelope.parse! already treats a non-object _meta as empty to answer -32602, but the header check runs first.
  • Legacy requests: call_tool and build_server_context call dig(:_meta, :progressToken),
    so tools/call and prompts/get answer -32603 Internal error. tools/list already ignores the value.

A non-object _meta carries no protocol version and no progress token, so these reads now skip it:

  • the 2026-07-28 path answers HTTP 400 -32602 from envelope validation;
  • legacy tools/call and prompts/get ignore it, as tools/list does and as RequestEnvelope.modern? does.

Similar to #609 / #616 (a malformed Accept member answered 500).

How Has This Been Tested?

  • test/mcp/server/transports/streamable_http_transport_test.rb: a modern POST with a string or array _meta answers 400 with -32602.
  • test/mcp/server_test.rb: tools/call and prompts/get with a string or array _meta run normally.
  • Both tests fail without the change (500 instead of 400; -32603 "Internal error calling tool"). rake test and rake rubocop pass.

Breaking Changes

None. Requests with an object _meta are handled as before. Only requests that raised now get a client error (2026-07-28) or are served (legacy).

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

MCP requires `_meta` to be an object. When a request's `_meta` is a
string, an array or a number, three `dig(:_meta, ...)` calls raised
TypeError instead of answering a client error:

- `validate_modern_headers` (Streamable HTTP, 2026-07-28): every method
  answered HTTP 500 -32603. `RequestEnvelope.parse!` already treats a
  non-object `_meta` as empty to answer -32602, but the header check
  runs first.
- `call_tool`: legacy `tools/call` answered -32603 Internal error.
- `build_server_context`: legacy `prompts/get` answered -32603.

A non-object `_meta` carries no protocol version and no progress token,
so these reads now skip it. The 2026-07-28 path answers HTTP 400 -32602,
and legacy `tools/call` and `prompts/get` ignore the value, as legacy
`tools/list` and `RequestEnvelope.modern?` already do.
@koic
koic merged commit 51db52a into modelcontextprotocol:main Nov 2, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants