modelcontextprotocol / modelcontextprotocol/ext-apps
Proposal: Allow Views to subscribe to server resource updates via resources/subscribe
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 2.9k
- Forks
- 387
- Avg merge
- 3h 21m
- Merged PRs (30d)
- 6
Description
Proposal: Allow Views to subscribe to server resource updates via resources/subscribe
Summary
Add resources/subscribe and resources/unsubscribe to the set of standard MCP messages that Views (UI iframes) can send through the host, and define notifications/resources/updated forwarding from host to View. This enables real-time, server-pushed data updates for interactive UIs without polling via repeated resources/read.
Motivation
The current MCP Apps spec (2026-01-26) allows Views to read server resources via resources/read but not subscribe to changes. For UIs that display live data — dashboards, monitors, collaborative editors, multiplayer games, streaming feeds — the only option today is polling via resources/read on a timer. This is:
- Wasteful — generates unnecessary server load and API traffic when nothing has changed
- Laggy — minimum latency is bounded by poll interval, not by actual data availability
- Inconsistent — the core MCP spec already supports
resources/subscribe+notifications/resources/updatedbetween client and server, but the MCP Apps extension blocks Views from using it
The gap is already visible in the ecosystem. UI libraries building on MCP Apps have independently implemented subscription mechanisms with automatic polling fallbacks for hosts that don't support subscriptions. The native subscription path cannot be exercised because no host currently advertises subscription support — only the polling fallback runs in practice.
Proposed Changes
1. Add to Standard MCP Messages (View → Host)
Add to the allowed message list in "Standard MCP Messages":
resources/subscribe— Subscribe to updates for a resource URIresources/unsubscribe— Unsubscribe from a previously subscribed resource URI
2. Add to Notifications (Host → View)
notifications/resources/updated— Host forwards this notification to the View when the server signals a subscribed resource has changed
// Host → View notification
{
jsonrpc: "2.0",
method: "notifications/resources/updated",
params: {
uri: string // URI of the updated resource
}
}
The View would then typically call resources/read to fetch the updated content — same pattern as the core MCP spec.
3. Host Capabilities
Add subscribe?: boolean to HostCapabilities.serverResources:
interface HostCapabilities {
// ...existing fields...
serverResources?: {
listChanged?: boolean;
subscribe?: boolean; // NEW: host supports proxying resource subscriptions
};
}
Views MUST check hostCapabilities.serverResources.subscribe before sending resources/subscribe. If absent or false, Views SHOULD fall back to polling via resources/read.
4. Subscription Limits
Add optional maxSubscriptions to host capabilities:
serverResources?: {
listChanged?: boolean;
subscribe?: boolean;
maxSubscriptions?: number; // Per-View subscription cap (e.g., 10)
};
Host MUST reject resources/subscribe with a JSON-RPC error when the limit is reached:
{
"jsonrpc": "2.0",
"id": 5,
"error": {
"code": -32001,
"message": "Subscription limit reached",
"data": {
"uri": "data://metrics/live",
"maxSubscriptions": 10
}
}
}
Note: -32002 is already used by the core MCP spec for "Resource not found." A new application-level error code should be defined for subscription limits. -32001 is used here as a placeholder — the final code should be coordinated with the core spec maintainers.
5. Lifecycle & Cleanup
Add to the existing Cleanup section:
When a View is torn down (via
ui/resource-teardownor iframe removal), the Host MUST sendresources/unsubscribeto the MCP server for every active subscription that was proxied on behalf of that View. The Host MUST track all subscriptions per View.
6. Security Requirements
| Requirement | Details |
|---|---|
| Per-View subscription cap | Host MUST enforce maxSubscriptions (default: 10 if unspecified) |
| Scoping | Subscriptions MUST be scoped to the originating server connection. A View MUST NOT receive notifications from a different server's resources |
| Rate limiting | Host SHOULD rate-limit forwarded notifications/resources/updated to the View (e.g., max 10/sec per URI) to prevent amplification |
| Cleanup on teardown | Host MUST unsubscribe all on View teardown — no orphaned server-side state |
| URI validation | Host SHOULD validate that subscribed URIs match resources previously returned by resources/list or resources/read from the same server |
Message Flow
sequenceDiagram
participant V as View (iframe)
participant H as Host
participant S as MCP Server
V->>H: resources/subscribe { uri: "data://metrics/live" }
H->>S: resources/subscribe { uri: "data://metrics/live" }
S-->>H: subscription confirmed
H-->>V: subscription confirmed
Note over S: Data changes
S--)H: notifications/resources/updated { uri: "data://metrics/live" }
H--)V: notifications/resources/updated { uri: "data://metrics/live" }
V->>H: resources/read { uri: "data://metrics/live" }
H->>S: resources/read { uri: "data://metrics/live" }
S-->>H: updated contents
H-->>V: updated contents
Note over V: View teardown
H->>S: resources/unsubscribe { uri: "data://metrics/live" }
Graceful Degradation
Views that want real-time updates SHOULD implement a dual-path pattern:
const hostCaps = await getHostCapabilities();
if (hostCaps.serverResources?.subscribe) {
// Real-time path
await client.request("resources/subscribe", { uri });
client.onNotification("notifications/resources/updated", async ({ uri }) => {
const data = await client.request("resources/read", { uri });
updateUI(data);
});
} else {
// Polling fallback
setInterval(async () => {
const data = await client.request("resources/read", { uri });
updateUI(data);
}, 5000);
}
Why Not Just Poll?
Polling (resources/read loop) |
Subscription | |
|---|---|---|
| Latency | Bounded by interval (seconds) | Near-instant |
| Server load | O(n) per interval regardless of changes | O(1) per actual change |
| Complexity for View developer | Timer management, dedup, cleanup | Subscribe once, handle events |
| Works today | Yes | Requires host support |
Scope
This proposal does NOT introduce:
- New subscription semantics beyond what core MCP already defines
- View-to-View communication
- Cross-server subscriptions
- Any change to the server-side
resources/subscribespec
It only allows Views to access an existing core MCP capability through the host proxy, with appropriate security controls.
Prior Art
- Core MCP spec (2025-03-26 and 2025-11-25):
resources/subscribe+notifications/resources/updatedfully specified for client↔server HostCapabilities.serverResources.listChangedalready exists in the MCP Apps spec —subscribeis the natural companion field@maxhealth.tech/prefab(Max-Health-Inc/prefab): Working reference implementation of this proposal's dual-path pattern. The bridge (src/renderer/bridge.ts) sendsresources/subscribevia JSON-RPC when the host supports it, listens for bothnotifications/resources/updatedandui/notifications/resource-updated, and returns a cleanup function that sendsresources/unsubscribe. Host capability is detected fromhostCapabilities.resources.subscribeduring theui/initializehandshake. When no host advertises support, theSubscribeaction falls back toSetInterval+CallToolpolling automatically. The native subscription path exists and is fully wired but has never been exercised on a real host — only the polling fallback runs today.- PrefectHQ/fastmcp (#3641): FastMCP doesn't yet support
resources/subscribe/resources/unsubscribe(both listed as expected failures in the MCP conformance suite). A design proposal for per-server subscription tracking with deterministic cleanup is under review. Once shipped, servers will handle subscriptions — but Views still can't use them without this spec change. CC @jlowin @syhstanley - PrefectHQ/prefab (repo): Prefect's own UI framework has no subscription support — it uses
SetInterval+CallToolpolling for live data, illustrating the gap even for the team building both the server framework and the UI framework.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the core MCP resource-subscription semantics and the spec's Standard MCP Messages, Notifications, HostCapabilities, and Cleanup sections; the issue does not identify repository files or tests. Compare the cited reference implementation in src/renderer/bridge.ts and src/actions/subscribe.ts, then establish the proposed protocol, lifecycle, capability, and security requirements as the definition of done.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100