Resources: subscriptions/listen model on modern servers (era fork) (#1630) (#1730)

Fork resource subscriptions by protocol era: legacy keeps resources/subscribe; modern (2026-07-28) models subscriptions as a filter over one long-lived subscriptions/listen stream with reconnect-by-re-listen (capped backoff), an optimistic Connecting→Listening header badge, and a fix so the browser proxy no longer blocks listen() ~60s. Closes #1630.
This commit is contained in:
Cliff Hall 2026-07-20 19:33:40 -04:00 • committed by GitHub
commit 320daf2f67
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 1656 additions and 90 deletions

View file

@ -239,6 +239,27 @@ export const SubscriptionsUnsupported: Story = {
},
};
// Modern (2026-07-28) era: subscriptions ride one `subscriptions/listen`
// stream, so the Subscriptions section shows a stream-status badge in its panel
// and a status dot in its header (#1630).
export const ModernSubscriptionStream: Story = {
args: {
resources: sampleResources,
templates: sampleTemplates,
subscriptions: sampleSubscriptions,
protocolEra: "modern",
subscriptionStreamState: {
active: true,
status: "acknowledged",
honoredUris: sampleSubscriptions.map((s) => s.resource.uri),
},
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(await canvas.findByText("Listening")).toBeInTheDocument();
},
};
const manyResources: Resource[] = Array.from({ length: 40 }, (_, i) => ({
name: `resource-${String(i + 1).padStart(2, "0")}.wav`,
uri: `file:///kit/resource-${i + 1}.wav`,

View file

@ -9,11 +9,15 @@ import {
Text,
} from "@mantine/core";
import type {
ProtocolEra,
ReadResourceResult,
Resource,
ResourceTemplateType as ResourceTemplate,
} from "@modelcontextprotocol/client";
import type { InspectorResourceSubscription } from "../../../../../../core/mcp/types.js";
import type {
InspectorResourceSubscription,
ResourceSubscriptionStreamState,
} from "../../../../../../core/mcp/types.js";
import { ResourceControls } from "../../groups/ResourceControls/ResourceControls";
import type { ListPaginationControlsProps } from "../../elements/ListPaginationControls/ListPaginationControls";
import { ResourcePreviewPanel } from "../../groups/ResourcePreviewPanel/ResourcePreviewPanel";
@ -32,6 +36,14 @@ export interface ResourcesScreenProps {
resources: Resource[];
templates: ResourceTemplate[];
subscriptions: InspectorResourceSubscription[];
/**
* Modern-era `subscriptions/listen` stream state (#1630). On the modern era
* the Subscriptions section shows a stream-status badge and a header dot;
* `active: false` (the legacy default) renders neither.
*/
subscriptionStreamState?: ResourceSubscriptionStreamState;
/** Negotiated protocol era; gates the modern subscription stream chrome. */
protocolEra?: ProtocolEra;
readState?: ReadResourceState;
ui: ResourcesUiState;
listChanged: boolean;
@ -137,6 +149,8 @@ export function ResourcesScreen({
resources,
templates,
subscriptions,
subscriptionStreamState,
protocolEra,
readState,
ui,
listChanged,
@ -291,6 +305,8 @@ export function ResourcesScreen({
templates={templates}
subscriptions={subscriptions}
subscriptionsSupported={subscriptionsSupported}
subscriptionStreamState={subscriptionStreamState}
protocolEra={protocolEra}
selectedUri={selectedResourceUri}
selectedTemplateUri={selectedTemplateUri}
searchText={search}