Skip to content

Refuse a _meta that is not an object as invalid params - #651

Merged
koic merged 1 commit into
modelcontextprotocol:mainfrom
koic:refuse_a_meta_that_is_not_an_object_as_invalid_params
Nov 3, 2026
Merged

koic merged 1 commit into
modelcontextprotocol:mainfrom
koic:refuse_a_meta_that_is_not_an_object_as_invalid_params

Conversation

@koic

@koic koic commented Nov 2, 2026

Copy link
Copy Markdown
Member

Motivation and Context

_meta is an object on every request (MetaObject in the schema). After #650, a String, Array, or number there no longer raises from dig, and the modern lifecycle answers -32602 because the envelope keys are missing; the legacy lifecycle, though, went on to run the handler. That left two gaps. The value still reached server_context[:_meta], where the server_context.dig(:_meta, ...) the server context page shows raises TypeError on a String or an Array, so a handler written as documented still turned the request into -32603. And the TypeScript and Python servers refuse such a request as invalid params on both lifecycles, through schema validation of params and their inbound checks, so the Ruby server answered differently from both.

The request is now refused with -32602 "Invalid params: _meta must be an object" before the envelope is read and before any handler runs, on both lifecycles. A notification handed to a handler is dropped the same way, without a response, as the TypeScript server drops a notification with invalid params; notifications/cancelled is served before the check and reads no _meta. A null _meta reads as absent, as it does for the Python server's request model. The server context page states the rule, since it tells handlers to read _meta from server_context.

How Has This Been Tested?

test/mcp/server_test.rb and test/mcp/server/transports/streamable_http_transport_test.rb. Before the fix, legacy tools/call with _meta: "not-an-object" ran the tool, and a tool reading server_context.dig(:_meta, :key) answered -32603 "Internal error" with "Internal error calling tool meta_reader" as the data.

Breaking Changes

A legacy-lifecycle request whose _meta is not an object now answers -32602. Released versions served such a request for methods that never read _meta, such as tools/list, and answered -32603 for tools/call and prompts/get.

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

## Motivation and Context

`_meta` is an object on every request (`MetaObject` in the schema). After modelcontextprotocol#650, a String, Array, or number there no
longer raises from `dig`, and the modern lifecycle answers -32602 because the envelope keys are missing; the legacy
lifecycle, though, went on to run the handler. That left two gaps. The value still reached `server_context[:_meta]`,
where the `server_context.dig(:_meta, ...)` the server context page shows raises `TypeError` on a String or an Array,
so a handler written as documented still turned the request into -32603. And the TypeScript and Python servers refuse
such a request as invalid params on both lifecycles, through schema validation of `params` and their inbound checks,
so the Ruby server answered differently from both.

The request is now refused with -32602 "Invalid params: _meta must be an object" before the envelope is read and
before any handler runs, on both lifecycles. A notification handed to a handler is dropped the same way, without a response,
as the TypeScript server drops a notification with invalid params; `notifications/cancelled` is served before
the check and reads no `_meta`. A `null` `_meta` reads as absent, as it does for the Python server's request model.
The server context page states the rule, since it tells handlers to read `_meta` from `server_context`.

## How Has This Been Tested?

test/mcp/server_test.rb and test/mcp/server/transports/streamable_http_transport_test.rb. Before the fix,
legacy `tools/call` with `_meta: "not-an-object"` ran the tool, and a tool reading `server_context.dig(:_meta, :key)`
answered -32603 "Internal error" with "Internal error calling tool meta_reader" as the data.

## Breaking Changes

A legacy-lifecycle request whose `_meta` is not an object now answers -32602. Released versions served such
a request for methods that never read `_meta`, such as `tools/list`, and answered -32603 for `tools/call` and `prompts/get`.
@koic
koic merged commit aeb871e into modelcontextprotocol:main Nov 3, 2026
13 checks passed
@koic
koic deleted the refuse_a_meta_that_is_not_an_object_as_invalid_params branch November 3, 2026 08:06
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