Skip to content

Leave listChanged out of initialize when notifications cannot be delivered - #620

Open
koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:leave_list_changed_out_of_initialize_on_a_stateless_transport
Open

koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:leave_list_changed_out_of_initialize_on_a_stateless_transport

Conversation

@koic

@koic koic commented Oct 16, 2026

Copy link
Copy Markdown
Member

Motivation and Context

initialize answers with the server's capabilities as configured, by default listChanged: true on tools, prompts and resources, whatever the transport can deliver. Stateless Streamable HTTP has no session streams, so send_notification reports non-delivery there and a notifications/*/list_changed never reaches a handshake client, yet initialize promised it (issue #619). The same server already leaves the flags out of server/discover when subscriptions/listen is not served, so the two lifecycles described it differently. The specification defines listChanged as whether the server will emit the notification when the list changes.

Transport#delivers_notifications? now tells the server whether notifications reach a client outside a response (true by default, false for stateless Streamable HTTP), and initialize leaves listChanged and subscribe out when they do not, with the stripping server/discover already applies. The capability objects themselves stay: prompts and resources with nothing registered still answer their list methods, as the Python SDK advertises them too, and logging stays on both lifecycles, since logging/setLevel is served, although stateless Streamable HTTP drops a handshake client's log messages, as its documentation says.

Each lifecycle keeps the flags its own delivery path can honor, so the two still differ where those paths do: a stateless transport serving subscriptions/listen keeps them in server/discover, since listen streams deliver the notifications there, but not in initialize, and a stateful one without it does the reverse.

Fixes #619.

How Has This Been Tested?

test/mcp/server_test.rb (the flags are stripped when the transport delivers no notifications, and kept when it does or does not say) and test/mcp/server/transports/streamable_http_transport_test.rb (a stateless transport's initialize advertises no flags and, without subscriptions/listen, the same capabilities as its server/discover; a stateful one keeps them). Against the previous library the stripping tests fail on the present flags.
The same files also cover a Capabilities builder, string keys, the configured capabilities staying unchanged, and the two configurations where only one lifecycle keeps the flags.

Breaking Changes

The initialize result of a stateless Streamable HTTP server carries no listChanged or subscribe flags.

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

…vered

## Motivation and Context

`initialize` answers with the server's capabilities as configured, by default `listChanged: true` on tools,
prompts and resources, whatever the transport can deliver. Stateless Streamable HTTP has no session streams,
so `send_notification` reports non-delivery there and a `notifications/*/list_changed` never reaches
a handshake client, yet `initialize` promised it (issue modelcontextprotocol#619). The same server already leaves the flags out of
`server/discover` when `subscriptions/listen` is not served, so the two lifecycles described it differently.
The specification defines `listChanged` as whether the server will emit the notification when the list changes.

`Transport#delivers_notifications?` now tells the server whether notifications reach a client outside
a response (`true` by default, `false` for stateless Streamable HTTP), and `initialize` leaves `listChanged`
and `subscribe` out when they do not, with the stripping `server/discover` already applies. The capability
objects themselves stay: `prompts` and `resources` with nothing registered still answer their list methods,
as the Python SDK advertises them too, and `logging` stays on both lifecycles, since `logging/setLevel` is
served, although stateless Streamable HTTP drops a handshake client's log messages, as its documentation says.

Each lifecycle keeps the flags its own delivery path can honor, so the two still differ where those paths
do: a stateless transport serving `subscriptions/listen` keeps them in `server/discover`, since listen
streams deliver the notifications there, but not in `initialize`, and a stateful one without it does the reverse.

Fixes modelcontextprotocol#619.

## How Has This Been Tested?

`test/mcp/server_test.rb` (the flags are stripped when the transport delivers no notifications, and kept when
it does or does not say) and `test/mcp/server/transports/streamable_http_transport_test.rb` (a stateless
transport's `initialize` advertises no flags and, without `subscriptions/listen`, the same capabilities as its
`server/discover`; a stateful one keeps them). Against the previous library the stripping tests fail on
the present flags.
The same files also cover a `Capabilities` builder, string keys, the configured capabilities staying unchanged,
and the two configurations where only one lifecycle keeps the flags.

## Breaking Changes

The `initialize` result of a stateless Streamable HTTP server carries no `listChanged` or `subscribe` flags.

This branch has not been deployed

No deployments
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.

Default capabilities advertise listChanged, prompts, resources and logging on a tools-only stateless server

2 participants