Skip to content

A reused BuildMcpServerOptions() server can never emit */list_changed #94

Description

@carldebilly

BuildMcpServerOptions() returns one options instance a host may reuse across connections — the pattern docs/mcp-transports.md prescribes. That server never emits tools/list_changed, resources/list_changed or prompts/list_changed, while the SDK advertises ListChanged = true for it. A client that would refresh on the notification never does, and nothing says why.

PR #71 documented the behaviour truthfully (docs/mcp-transports.md, known-limitation callout) rather than fixing it. This issue is the fix.

Why it cannot be fixed cheaply

Repl cannot simply stop advertising the capability. McpServerImpl.ConfigureTools / ConfigureResources / ConfigurePrompts seed listChanged from options.Capabilities?.X?.ListChanged and then force it to true whenever the matching primitive collection is non-null. The static path always supplies collections, so the flag is the SDK's to set, not BuildCapabilities's. (The SDK does strip it where it genuinely cannot deliver — GetAdvertisedCapabilities nulls all three when !HasStatefulTransport() && !listenStreamCanDeliverListChanged — so the false advertisement survives only on stateful transports.)

EnsureRoutingSubscription would be inert even if called. The static options carry the snapshot collections, not the _toolListChanged / _resourceListChanged / _promptListChanged signal collections, so SignalDiscoveryChanged() clears collections no server on that path subscribes to.

What the real fix looks like

Make the static collections live: rebuild from the catalog context on routing invalidation and reconcile in place under McpServerPrimitiveCollection<T>.DeferChangedEvents(), which coalesces into one Changed event the SDK fans out to every connected server.

Two concrete obstacles, both verified against the SDK 2.2.0 assembly:

  1. Lifetime. Nothing in the returned options references McpServerHandler — the primitives hold McpToolAdapter, which holds the app, options and services. EnsureRoutingSubscription uses a WeakReference (McpServerHandler.cs, EnsureRoutingSubscription), so notifications would die silently at the first GC. Making this work means deliberately rooting the handler from the options, which is a design decision rather than a patch.
  2. McpServerResourceCollection is sealed. The clean shape — a Repl subclass swapping an immutable snapshot atomically and raising Changed — is available for tools and prompts but not resources, leaving a window where a concurrent resources/list observes a half-rebuilt list. Given_McpServerOptionsBuilder.cs:24-26,54-57 also pins the "pre-populated collection" contract, so moving to handlers is not a sidestep.

Scope

Not issue #70: this is catalog freshness, not DI scoping. Not blocked by anything — but it needs a decision on rooting the handler, so it deserves its own design pass rather than riding on the next PR.

Origin: PR #71 review panel (issue #51), operability and architect lenses.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions