Skip to content

Comments

feat(opencode): add MCP resource subscriptions#14569

Open
jtippett wants to merge 12 commits intoanomalyco:devfrom
jtippett:feat/mcp-resource-subscriptions
Open

feat(opencode): add MCP resource subscriptions#14569
jtippett wants to merge 12 commits intoanomalyco:devfrom
jtippett:feat/mcp-resource-subscriptions

Conversation

@jtippett
Copy link

Issue for this PR

Closes #12092

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

This adds MCP resource subscription support to opencode. It's experimental — my side project sends message updates over MCP and I want the client to react when resources (in this case the user's inbox) change.

What it does in practice: when an MCP server publishes a notifications/resources/updated or notifications/resources/list_changed, opencode intercepts and handles, showing a toast and if configured it kicks the AI with the updated resource. If there's no active session it creates one.

Where the changes live:

  • mcp/index.ts — the bulk of it. On connect, opencode auto-subscribes to every resource the server lists (plus any explicit URIs from config). Notification handlers publish bus events. On reconnect it merges previous subscription state + config + a fresh listResources() call so nothing gets lost. On disconnect it cleans up.
  • config/config.ts — two new optional fields on both local and remote MCP server configs: subscriptions:
    string[] (explicit URIs to subscribe to) and autoprompt: boolean (whether to trigger AI on updates).
  • app.tsx — TUI subscribes to the bus events. Shows toasts. If autoprompt is enabled for that server and the session is idle (or there's no session), it prompts the AI with context about what changed.
  • sdk/js/.../types.gen.ts — regenerated SDK types for the two new event types (mcp.resource.updated, mcp.resource.list.changed).

Key decisions and why:

  • Auto-subscribe to everything, not just config-specified URIs. In my project, the server exposes an inbox resource and we want to know the moment it changes. Requiring users to manually list every URI in config felt like unnecessary friction — if a server supports subscriptions, you probably want them. Config subscriptions still works for adding URIs that aren't in listResources().
  • autoprompt is opt-in and per-server.
  • MCP layer is event-only, TUI owns session logic. Followed the existing pattern — mcp/index.ts publishes bus events, app.tsx decides what to do with session context. Keeps the separation clean.
  • Fire-and-forget subscriptions with error logging. A failed subscription to one resource shouldn't block everything else. Errors get logged, the URI gets removed from tracking.
  • Three-way merge on reconnect. Previous in-memory state + config URIs + fresh listResources(). Handles the case where a server adds new resources while disconnected, or where config changed between connections.

context / why this matters beyond my project:

Resource subscriptions are part of the MCP spec but no-one seems to have implemented them yet. Any MCP server that exposes mutable resources (databases, message queues, config files, monitoring dashboards) benefits from this. The autoprompt pattern is more opinionated — it's basically "AI-on-interrupt" — but it's opt-in.

Mainly wanted to show one way it could work!

How did you verify your code works?

Tested locally with my project (llmsg.com) as a remote MCP server (autoprompt: true). Verified:

  • Subscriptions are established on connect
  • Toast notifications appear on resource updates
  • AI gets triggered when session is idle and autoprompt is enabled
  • Reconnection re-subscribes correctly
  • No subscriptions attempted for servers that don't advertise the capability

No automated tests — this is touching the MCP client integration layer and I wasn't sure what test patterns you prefer here.

I've built some test functions on my site to simulate resource updates, which might be helpful if you want to play around. On https://llmsg.com/agents you should see the ability to trigger synthetic updates to any agent with a live connection.

Screenshots / recordings

https://asciinema.org/a/qXsZ8NbiPJnyGznx

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

If you do not follow this template your PR will be automatically rejected.

- Fix race condition in subscription tracking: use optimistic set
  population (populate immediately, delete on failure) instead of
  populating in async .then() callbacks after set is already stored
- Add supportsSubscriptions() guard to unsubscribe() to avoid errors
  against servers that don't support subscriptions
- Fix misleading "prompt" log messages in readResource to say "resource"
- Remove unnecessary "as const" cast in TUI event handler
Convert try/catch to .catch(), remove destructuring, use functional
array methods over for loops, and inline single-use variables.
…config

- Auto-subscribe to all listed resources on connect for servers that
  support subscriptions, not just config-specified URIs
- Subscribe to newly listed resources when ResourceListChanged fires
- On reconnect, merge previous subs + config + fresh listResources()
- Add `autoprompt` config option (default false) to trigger AI when a
  subscribed resource updates — uses system prompt for instructions
  and creates a new session if not already in one
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]:Resource subscription

1 participant