Skip to content

Ruby SDK returns result: null for an id-bearing notifications/cancelled message #434

Description

@cclabadmin

Describe the bug

When the Ruby SDK server receives a JSON-RPC message with an id and method: "notifications/cancelled", it returns a successful JSON-RPC response with result: null.

A valid MCP notification does not include an id and must not receive a response. If this id-bearing message is treated as a request, it should not be completed with a successful result: null response, because MCP result responses use an object-valued result.

  • Environment
    • Reproduced with stable release v0.16.0 (e35841afe89d86c7905bbab0c9fbe1d3f075e8d0)
    • Also reproduced on current main (5518e8db032517896e0025119a9073f4aa946ba4) using a 2026-07-28-style request envelope
    • Transport: stdio

To Reproduce

  1. Start a Ruby SDK MCP server over stdio.
  2. Send a notifications/cancelled message with a JSON-RPC id:
{"jsonrpc":"2.0","id":1001,"method":"notifications/cancelled"}
// or
{"jsonrpc":"2.0","id":1001,"method":"notifications/cancelled","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"repro","version":"0.1.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}
  1. Observe the response.

Expected behavior

The server should not emit a successful JSON-RPC result response for this message. A valid notifications/cancelled notification has no id and receives no response. If an id-bearing message for this notification-only method is treated as a request, it should be rejected with a JSON-RPC error rather than completed with result: null.

Logs

The server returns:

{"jsonrpc":"2.0","id":1001,"result":null}

The server remains healthy and continues to process subsequent requests.

Additional context

In lib/mcp/server.rb, notifications/cancelled is dispatched through a special-case handler:

if method == Methods::NOTIFICATIONS_CANCELLED
  return ->(params) { handle_cancelled_notification(params, session: session) }
end

handle_cancelled_notification can return nil, for example when there's no session:

def handle_cancelled_notification(params, session: nil)
  return unless session
  # ...
end

That nil value then flows through the normal success-response path in JsonRpcHandler#process_request:

result = method.call(params)
return if result.equal?(NO_RESPONSE)

success_response(id: id, result: result)  # result is nil → {"result": null}

JsonRpcHandler::NO_RESPONSE already exists for handlers that should not emit a JSON-RPC response, so this may be the appropriate sentinel for notification-style handling.

This appears related to #398 / #400, which preserved request ids for JSON-RPC envelope error responses in the same JsonRpcHandler area. This issue is different: the response id is preserved, but the message is completed as a successful response with result: null.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions