Resources
A resource is a named piece of app state the agent can read - and optionally subscribe to for push updates. Resources complement actions: actions cause changes, resources expose what changed.
Declaration
Section titled “Declaration”tesseron.resource('currentRoute') .describe('The URL path the user is currently viewing') .read(() => window.location.pathname) .subscribe((emit) => { const onChange = () => emit(window.location.pathname); window.addEventListener('popstate', onChange); return () => window.removeEventListener('popstate', onChange); });.read()is a one-shot getter. Called on everyresources/readthe agent issues..subscribe()is optional. It registers an emitter; return an unsubscribe function so the SDK can clean up when the agent unsubscribes or the session closes.
URI convention
Section titled “URI convention”Resources are exposed to the agent with the URI tesseron://<app_id>/<resource_name>. For app.id = "shop" and resource = "currentRoute", the agent sees tesseron://shop/currentRoute.
Reading from clients that don't speak MCP resources
Section titled “Reading from clients that don't speak MCP resources”Some MCP clients don't surface resources/read to their model. The MCP gateway ships a meta-tool fallback:
tesseron__read_resource({ app_id, name }) - returns the resource's current value as a tool-call result. Prefer this over the genericReadMcpResourceToolbecause the agent doesn't have to know how the MCP server is namespaced on the client (e.g.plugin:tesseron:tesseronin Claude Code plugin installs vs.tesseronin a raw config).
tesseron__list_actions enumerates every claimed session's resources and includes both the preferred tesseron__read_resource args and the ReadMcpResourceTool fallback.
Wire format
Section titled “Wire format”Read (gateway → app, request)
Section titled “Read (gateway → app, request)”{ "jsonrpc": "2.0", "id": 14, "method": "resources/read", "params": { "name": "currentRoute" } }Response:
{ "jsonrpc": "2.0", "id": 14, "result": { "value": "/checkout" } }Subscribe (gateway → app, request)
Section titled “Subscribe (gateway → app, request)”{ "jsonrpc": "2.0", "id": 15, "method": "resources/subscribe", "params": { "name": "currentRoute", "subscriptionId": "sub_1" } }Response is empty - the SDK just acknowledges and now holds the emitter callback.
Update (app → gateway, notification)
Section titled “Update (app → gateway, notification)”Each time the emitter fires:
{ "jsonrpc": "2.0", "method": "resources/updated", "params": { "subscriptionId": "sub_1", "value": "/cart" }}The gateway forwards this as MCP notifications/resources/updated to the agent.
Unsubscribe (gateway → app, request)
Section titled “Unsubscribe (gateway → app, request)”{ "jsonrpc": "2.0", "id": 16, "method": "resources/unsubscribe", "params": { "subscriptionId": "sub_1" } }The SDK calls the unsubscribe function returned by your .subscribe() handler.
List changed (app → gateway, notification)
Section titled “List changed (app → gateway, notification)”If your app registers or removes resources after the initial tesseron/hello, the SDK emits resources/list_changed with the new manifest. The gateway forwards this as MCP notifications/resources/list_changed so agents can refetch the list. actions/list_changed follows the same pattern for dynamic action sets.
Patterns
Section titled “Patterns”Read-only projection
Section titled “Read-only projection”tesseron.resource('filterState').read(() => ({ search: state.search, onlyDone: state.onlyDone,}));Perfect for letting the agent reason about "what's the user currently looking at" before proposing actions.
Debounced subscription
Section titled “Debounced subscription”Don't emit on every keystroke - the agent can't meaningfully react at that rate.
tesseron.resource('search') .read(() => state.search) .subscribe((emit) => { let timer: ReturnType<typeof setTimeout> | null = null; const onChange = () => { if (timer) clearTimeout(timer); timer = setTimeout(() => emit(state.search), 250); }; state.on('change', onChange); return () => { if (timer) clearTimeout(timer); state.off('change', onChange); }; });Large or expensive resources
Section titled “Large or expensive resources”If the value is expensive to produce, remember that .read() runs every time the agent fetches. Cache inside the handler, or use .subscribe() as the source of truth and cache the latest emitted value in-memory.
Capability gate
Section titled “Capability gate”Subscriptions require agentCapabilities.subscriptions. Reads do not. If the agent can't subscribe, it will only call resources/read and your .subscribe() handler is never invoked.
Next: the full error catalog and capability negotiation.