-
Notifications
You must be signed in to change notification settings - Fork 131
Description
Motivation
WebMCP currently exposes only tools. An agent that needs to track live application state - selections, filters, form state, document contents - has no mechanism other than calling get_* tools on every reasoning step. This polling approach has three concrete costs:
- Token overhead: every poll is a full tool-call round-trip, consuming input + output tokens even when state is unchanged.
- Temporal unsoundness: state read at step N may have changed by step N+1 when the agent acts on it. In WebMCP's primary use case - a human and agent working concurrently on the same page — the user is actively mutating state the agent depends on.
- Unnecessary latency: the agent's action loop stalls waiting for a poll result it could have received proactively.
The underlying issue is architectural: the page owns reactive state but has no way to publish it; the agent must pull what it should be able to subscribe to.
Proposal: Resource Registration and Subscription
Extend ModelContext with a resources surface that mirrors the MCP Resources and Subscriptions primitive. WebMCP already aligns its tools surface with MCP tools; this extends that alignment to resources. The browser acts as the subscription broker, routing notifications/resources/updated to subscribed agents when the page calls notifyResourceUpdated().
Proposed Web IDL
partial interface ModelContext {
undefined registerResource(ModelContextResource resource);
undefined unregisterResource(DOMString uri);
undefined notifyResourceUpdated(DOMString uri);
};
dictionary ModelContextResource {
required DOMString uri;
required DOMString name;
DOMString description;
DOMString mimeType;
ModelContextResourceAnnotations annotations;
required ResourceReadCallback read;
};
dictionary ModelContextResourceAnnotations {
boolean subscribeHint = false;
};
// Return value follows the same content shape as MCP resources/read:
// { text: DOMString } | { blob: DOMString } — mirrors ResourceContents in MCP schema
callback ResourceReadCallback = Promise<object> ();MCP Protocol Mapping
The browser bridge translates between the Web IDL surface and MCP protocol messages:
| Web API call | MCP message emitted by browser |
|---|---|
| Agent requests resource list | resources/list → built from registered resource map |
| Agent reads a resource | resources/read → invokes read callback, returns result |
| Agent subscribes | resources/subscribe → stored in browser subscription table |
notifyResourceUpdated(uri) |
notifications/resources/updated → fanned out to subscribers |
unregisterResource(uri) |
notifications/resources/list_changed |
Minimal Example
// Register a resource backed by existing page state
navigator.modelContext.registerResource({
uri: "https://example.com/app/resources/selection",
name: "Document Selection",
description: "The user's current text selection and cursor position.",
mimeType: "application/json",
annotations: { subscribeHint: true },
read: async () => ({ text: JSON.stringify(getSelectionState()) }),
});
// Page signals a state change — no polling, agent receives
// notifications/resources/updated and re-reads only if subscribed
document.addEventListener("selectionchange", () => {
navigator.modelContext.notifyResourceUpdated(
"https://example.com/app/resources/selection"
);
});Note the use of an https:// URI anchored to the page's own origin — see URI Scheme below.
Relationship to Existing Spec Work
- MCP Resources spec (modelcontextprotocol.io): This proposal is a direct Web IDL surface for the MCP resources + subscriptions primitive. The browser's role mirrors that of an MCP server implementing
resources/list,resources/read, andresources/subscribe. - Declarative WebMCP (PR Declarative API Explainer #76): Orthogonal and complementary. A declarative form could expose resources via a dedicated element or
<meta>binding in a follow-up. ModelContextClient.requestUserInteraction(): Complementary rather than overlapping -requestUserInteractionpauses the agent to collect user input; resource subscriptions push page-owned state to the agent proactively.
Design Questions
These are the open decisions that need group consensus before the shape of this API can be settled.
1. URI scheme for page-owned resources
app:// and similar custom schemes are not registered and create interoperability risk. Two options:
https://at the page's own origin (e.g.,https://example.com/app/resources/cart) - unambiguous origin binding, requires no new scheme, naturally enforces same-origin access.- A new
web+scheme (per WHATWG URL) - cleaner namespacing but requires registration and additional spec text.
Recommendation: https:// at the page origin, with the browser enforcing that registerResource URIs must be same-origin. Open to counter-arguments.
2. Read callback return type
Promise<any> is too loose. The return value should be constrained to match MCP ResourceContents - either { text: string } or { blob: string } (base64). Should the Web IDL define a ModelContextResourceContents dictionary, or rely on runtime validation with a thrown TypeError on malformed returns?
3. Subscription lifecycle and agent capability negotiation
MCP capability negotiation requires the server to declare resources: { subscribe: true } during initialization. In WebMCP, registerResource() is called at runtime, potentially after capability negotiation has already occurred. Two sub-questions:
- Should the browser declare
resourcescapability speculatively at init if the origin has previously calledregisterResource? Or lazily on first call? - When
unregisterResource()removes the last resource, should the browser withdraw the capability?
4. Security model for subscriptions
Agent-initiated subscriptions raise questions absent from the current tool invocation model:
- Does
resources/subscriberequire the same user-consent gate as tool invocations, or is it lower-friction given it is read-only? readOnlyHintalready exists on tools.subscribeHinton resources signals intent - but should the browser enforce read-only semantics on resources, or is this advisory only?- Cross-origin agents (e.g., a browser-embedded AI platform from a different origin than the page) - can they subscribe? The current spec has no cross-origin agent model; this proposal should not introduce one implicitly.
5. Notification debouncing and rate limiting
A page calling notifyResourceUpdated() in a tight loop (e.g., on mousemove) could flood an agent with notifications. Should the spec define any normative debouncing behavior on the browser side, or leave this entirely to the page author?
Prior Art and Alternatives Considered
Polling via tools - the status quo, which motivates this issue. See Problem above.
A generic event/push channel (e.g., a sendToAgent(event) API): more flexible but unstructured, no schema, no MCP alignment. Agents would need bespoke handling per page.
WebSockets or Server-Sent Events from a backend: moves state management off the page and requires a server round-trip for state that the page already owns. Contradicts WebMCP's premise of client-side MCP servers.
MCP Sampling (client/sampling): inverts the relationship - the server asks the model to reason, not the model subscribing to state. Different use case.
Resource Subscriptions are the minimal, MCP-aligned, additive primitive that addresses the problem without introducing a new communication model.