Skip to content

Modern era: subscriptions/listen can never be opened against a tools-only server (toolsListChanged unreachable from the UI) #1920

Description

@antoniocasagrande-airia

Summary

On a modern-era (2026-07-28) connection to a server that advertises only tools: { listChanged: true } — no resources — the Inspector can never open a subscriptions/listen stream, so notifications/tools/list_changed is unreachable from the UI. The filter side is already wired for this case; only the trigger that opens the stream is missing.

Repro

  1. Connect (modern era) to a server whose server/discover returns capabilities { "tools": { "listChanged": true } } and no resources capability. (Any tools-only server works — e.g. a gateway whose tool surface mutates via tool calls.)
  2. Call a tool that changes the server's tool list; the server publishes notifications/tools/list_changed to its subscriptions/listen subscribers.
  3. Expected: some way for the Inspector to receive the notification / refresh the tool list.
  4. Actual: no listen stream is ever opened, so the notification never arrives. The Resources screen (the only affordance that opens the stream) isn't available because the server has no resources.

Root cause

In core/mcp/inspectorClient.ts:

  • refreshModernSubscription() keeps the stream closed unless at least one resource URI is subscribed:

    // Nothing subscribed → keep the stream closed.
    if (this.subscribedResources.size === 0) {
      this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
      return;
    }
  • …while buildSubscriptionFilter() already handles the tools case whenever the stream does open:

    if (
      this.listChangedNotifications.tools &&
      this.capabilities?.tools?.listChanged
    ) {
      filter.toolsListChanged = true;
    }

So a tools-only server that correctly advertises tools.listChanged: true has no path to an open stream, and the toolsListChanged filter bit that #1630's design models never gets a chance to ride along. (The SDK itself is fine — Client.listen(filter) is caller-driven and works against such servers; this is purely about what the Inspector UI can trigger.)

Suggested fix

Open (or keep open) the listen stream whenever the built filter is non-empty, not only when resourceSubscriptions is non-empty — e.g. change the bail condition in refreshModernSubscription() from subscribedResources.size === 0 to "the filter from buildSubscriptionFilter() has no fields". That makes the enabled listChangedNotifications bits (gated on the corresponding server capability, as today) sufficient to open the stream on connect.

Happy to send a PR if this direction sounds right.

Environment

  • Inspector v2/main (verified present at current HEAD)
  • Server: modern era 2026-07-28, capabilities tools: { listChanged: true } only

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingv2Issues and PRs for v2

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions