Skip to content

Fix stateless HTTP transport advertising listChanged capability (#1486)#1509

Merged
halter73 merged 1 commit intomodelcontextprotocol:mainfrom
jayaraman-venkatesan:csharpsdk/list_changed_fix
Apr 9, 2026
Merged

Fix stateless HTTP transport advertising listChanged capability (#1486)#1509
halter73 merged 1 commit intomodelcontextprotocol:mainfrom
jayaraman-venkatesan:csharpsdk/list_changed_fix

Conversation

@jayaraman-venkatesan
Copy link
Copy Markdown
Contributor

@jayaraman-venkatesan jayaraman-venkatesan commented Apr 9, 2026

Summary

Fixes #1486

When configuring an MCP server with Stateless = true on the HTTP transport, the initialize response incorrectly advertised listChanged: true for tools, resources, and prompts. In stateless mode there is no persistent session or SSE connection, so the server can never send unsolicited notifications — advertising this capability is misleading.

Before:

"capabilities": {
  "prompts":   { "listChanged": true },
  "resources": { "listChanged": true },
  "tools":     { "listChanged": true }
}

After:

"capabilities": {
  "prompts":   {},
  "resources": {},
  "tools":     {}
}

The capability objects remain — the server still supports tools/prompts/resources — only the listChanged key is suppressed.

Root Cause

In McpServerImpl.cs, the ConfigureTools, ConfigurePrompts, and ConfigureResources methods each set listChanged = true unconditionally whenever a primitive collection is registered, regardless of transport mode. The existing stateless guard at line ~102 already skips wiring up notification event handlers, but it never corrected the capability flag that had already been set.

Changes

src/ModelContextProtocol.Core/Server/McpServerImpl.cs

Added a block in the constructor immediately before the existing stateless notification-registration guard that nulls out ListChanged on each capability when the transport is stateless:

Setting to null (not false) omits the key entirely from the serialized JSON since the SDK uses DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, producing "tools": {} rather than "tools": { "listChanged": false }.

The fix is placed in the constructor rather than inside each individual Configure* method to keep all stateless-related behaviour co-located in one block, alongside the existing notification handler suppression, making it easy to see all stateless implications in one place.

tests/ModelContextProtocol.AspNetCore.Tests/StatelessServerTests.cs

Added StatelessMode_DoesNotAdvertise_ListChangedCapabilities — starts a stateless server configured with tools, prompts, and resources, connects a real in-process client via KestrelInMemoryTest, and asserts that all three ListChanged capability flags are null in the initialize response.

Verification

Verified with a AI generated sample app that sends a raw initialize request and performs a live tool-mutation test to confirm the end-to-end behaviour:
https://github.com/jayaraman-venkatesan/stateless-verify

Section 1 — initialize response:

Mode Before fix After fix
Stateless "tools": { "listChanged": true } "tools": {}
Stateful "tools": { "listChanged": true } "tools": { "listChanged": true }

Section 2 — live notification test:

Mode Add tool to collection
Stateful toolCollection.Add(...)
Stateless toolCollection.Add(...)

Section 2 confirms why the capability must not be advertised in stateless mode: even if the server attempted to send the notification, there is no channel to deliver it.

Checklist

  • Root cause fixed — listChanged = true was set unconditionally in ConfigureTools, ConfigurePrompts, and ConfigureResources regardless of transport mode
  • Stateful behaviour unchanged — listChanged: true is still advertised when Stateless = false
  • Serialization correct — null produces "tools": {}, not "tools": { "listChanged": false } and not a missing tools key
  • Test added in StatelessServerTests.cs — correct home alongside all other stateless-specific tests
  • Test covers all three capability types — tools, prompts, resources
  • No new warnings — repo treats warnings as errors
  • dotnet build ModelContextProtocol.slnx — clean
  • dotnet test tests/ModelContextProtocol.AspNetCore.Tests/ --framework net10.0309 passed, 0 failed

@jayaraman-venkatesan jayaraman-venkatesan marked this pull request as ready for review April 9, 2026 02:12
Copy link
Copy Markdown
Contributor

@mikekistler mikekistler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! 👍

Thanks for this contribution!

@halter73
Copy link
Copy Markdown
Contributor

halter73 commented Apr 9, 2026

Thank you!

@halter73 halter73 merged commit 07435dc into modelcontextprotocol:main Apr 9, 2026
10 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.

Stateless HTTP server should not advertise listChanged capabilities

3 participants