Skip to content

Propagate backend resources/prompts list_changed in vMCP - #5971

Merged
JAORMX merged 1 commit into
mainfrom
resources-prompts-list-changed-5969
Jul 24, 2026
Merged

Propagate backend resources/prompts list_changed in vMCP#5971
JAORMX merged 1 commit into
mainfrom
resources-prompts-list-changed-5969

Conversation

@JAORMX

@JAORMX JAORMX commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

#5965 propagated backend list_changed to vMCP clients for tools only. Resources and prompts were explicitly deferred: the backend connector dropped their list_changed notifications, vMCP advertised resources.listChanged=false, and it never advertised the prompts capability at all — a spec-conformance defect, since MCP 2025-11-25 says "Servers that support prompts MUST declare the prompts capability during initialization" and vMCP already serves prompts per session.

This extends the existing #5965 machinery (no new coordinator) to resources and prompts:

  • Dispatch backend notifications/resources/list_changed and notifications/prompts/list_changed (add KindResources/KindPrompts); resource-template changes ride the resources method (there is no separate templates notification in 2025-11-25).
  • Run one coalescing resync worker per (session, kind) — reusing Consume backend list_changed in vMCP and propagate to clients #5965's listChangedResyncWorker verbatim — so a tools change never triggers a resources/prompts re-apply (and vice-versa).
  • On a change: invalidate the capability cache, re-derive the session's advertised resources, resource templates, and prompts under the session's own identity, and apply them with the REPLACE setters (SetSessionResources/SetSessionResourceTemplates/SetSessionPrompts). The per-session go-sdk server then auto-emits the downstream list_changed.
  • Flip WithResourceCapabilities(true, true) and add WithPromptCapabilities(true).

Closes #5969

Type of change

  • New feature

Test plan

  • Unit tests (task test)
  • Linting (task lint-fix)

task lint-fix 0 issues; go build/go vet clean; go test -race across ./pkg/vmcp/... all green. Coverage added:

  • Unit: connector method→kind dispatch (all three methods + negatives); per-kind resync isolation (a resources notification touches only ListResources+ListResourceTemplates, never tools/prompts); REPLACE-not-merge (a pre-seeded removed entry is dropped from the overlay); fail-loudly type-assert branches for resources/templates/prompts; auth-context reconstruction (identity + forwarded header) asserted for all three kinds.
  • Integration (real go-sdk backend over continuous-listening): resources and prompts list_changed fire the sink with the right kind; end-to-end resync so a client resources/list / resources/templates/list / prompts/list reflects added items, with explicit before-snapshots.
  • Regression guards: capability-regression asserts resources.listChanged/prompts.listChanged/prompts present + resources.subscribe retained; an add-only guard pins that a removed resource/prompt currently still appears (fails loudly when toolhive-core#184 lands).

Does this introduce a user-facing change?

Yes: vMCP now propagates backend resources and prompts list_changed to clients (added items appear on the next */list without reconnecting), advertises resources.listChanged=true, and — newly — advertises the prompts capability at initialize (fixing a spec MUST that previously hid vMCP's prompts from clients gating on capabilities.prompts).

Special notes for reviewers

Reviewed by a 4-axis Opus panel (MCP-correctness, concurrency+quality, security, tests): security APPROVE, the rest APPROVE-WITH-NITS (all nits addressed).

Known limitation (tracked): stacklok/toolhive-core#184. mcpcompat's per-session sync for resources/resource templates/prompts is add-only (unlike tools, which reconciles removals via RemoveTools). So additions propagate but removals do not — a backend-removed resource/prompt lingers in the advertised list until the session re-initializes. Advertising listChanged: true remains honest (a notification is emitted on change; listChanged promises notification, not list minimization). Read-time access is unaffected — the read path re-derives admission per call, so a removed/deadmitted resource is denied at read time regardless of the stale list. Documented at the resyncSessionResources/resyncSessionPrompts apply paths; the add-only guard test will flip to assert removal once #184 lands.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MRh394VZgnqbYjJwjrcJWm

@github-actions github-actions Bot added the size/XL Extra large PR: 1000+ lines changed label Jul 24, 2026
#5965 propagated backend list_changed to clients for tools only.
Resources and prompts were deferred: the backend connector dropped
their list_changed notifications and vMCP advertised
resources.listChanged=false (and never advertised the prompts
capability at all, a spec-conformance defect since it served prompts
per session).

Extend the existing #5965 machinery — no new coordinator — to
resources and prompts: dispatch their list_changed notifications, add
KindResources/KindPrompts, run one coalescing resync worker per
(session, kind), re-derive and REPLACE the session's advertised
resources, resource templates, and prompts, and flip
WithResourceCapabilities(true, true) + WithPromptCapabilities(true).
The per-session go-sdk server then auto-emits list_changed downstream.

Additions propagate; removals do not, because mcpcompat's per-session
resource/template/prompt sync is add-only (tracked in
stacklok/toolhive-core#184). Advertising listChanged stays honest —
notifications are still emitted on change.

Closes #5969

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRh394VZgnqbYjJwjrcJWm
@JAORMX
JAORMX force-pushed the resources-prompts-list-changed-5969 branch from 3209eb3 to 5264c7e Compare July 24, 2026 14:09
@github-actions github-actions Bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Jul 24, 2026
@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.21053% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.94%. Comparing base (5f6076e) to head (5264c7e).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
pkg/vmcp/server/serve_list_changed.go 82.85% 8 Missing and 4 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5971      +/-   ##
==========================================
+ Coverage   71.88%   71.94%   +0.06%     
==========================================
  Files         715      715              
  Lines       73501    73571      +70     
==========================================
+ Hits        52835    52933      +98     
+ Misses      16883    16839      -44     
- Partials     3783     3799      +16     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@JAORMX
JAORMX merged commit d77dafc into main Jul 24, 2026
48 checks passed
@JAORMX
JAORMX deleted the resources-prompts-list-changed-5969 branch July 24, 2026 15:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XL Extra large PR: 1000+ lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Propagate backend resources/prompts list_changed in vMCP

2 participants