Repository navigation
fix(everything): scope session resources to their server (#4808) - #5032
Conversation
…BUG marker The gzip-file-as-resource suite pinned #4808 (a second session's registration evicting the first session's resource of the same name). With session resources now tracked per server, the test asserts the correct behavior: both sessions read their own content and session A still lists the resource. The KNOWN BUG marker is removed, the testing skill's marker example no longer cites #4808, and a changeset records the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
🦋 Changeset detectedLatest commit: d4fb2f4 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The fix is correctly scoped, preserves same-session replacement behavior, and includes appropriate regression coverage.
Review effort: Balanced
Findings: None
What changed in this PR
Scopes dynamic session-resource registrations to each McpServer, preventing cross-session eviction.
Changes:
- Replaces the global URI map with a per-server
WeakMap. - Adds protocol-level regression coverage for identical URIs across sessions.
- Updates testing guidance and adds a patch changeset.
| File | Description |
|---|---|
src/everything/resources/session.ts |
Scopes resource tracking by server. |
src/everything/__tests__/gzip-file-as-resource.test.ts |
Verifies independent same-name resources. |
.claude/skills/testing/SKILL.md |
Generalizes the known-bug example. |
.changeset/everything-session-resources-per-server.md |
Records the patch release. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
|
Copilot round 1: no findings ("Approval recommended", 0 inline comments, no suppressed comments). Clean round, so the review loop stops here. |
Closes #4808
Part of Wave 1 (
everything) of #5004.Description
Session resources were tracked in one module-level
Mapkeyed by URI only, shared by every session'sMcpServer. When a second session registered a session resource with the same name (for examplegzip-file-as-resourcewithname: "shared.gz"),registerSessionResourcecalledremove()on the first session's registration, so session A'sresources/readstarted failing with-32602 ... not foundand the resource disappeared from A'sresources/list.The registry is now a
WeakMap<McpServer, Map<string, RegisteredResource>>: lookups, removals and inserts only touch the server that is registering. Re-registering the same URI on the same server still replaces the earlier registration (the retry behavior3e1be88added).src/everything/resources/session.ts: the fix, ported from fix(everything): scope session resources to their server #4810 by @Tiancheng-Xu (cherry-picked with authorship kept; onlysession.ts, since that PR'sresources.test.tsadditions targetedmain's mock-based unit test file, whichv2/mainreplaced with protocol-level suites). One follow-up edit updates the registry's doc comment.src/everything/__tests__/gzip-file-as-resource.test.ts: the everything: session resources evict another session's resource when two sessions use the same file name #4808 pin now asserts the correct behavior (A and B each read back their own content under the same URI, and A'sresources/liststill has it), and itsKNOWN BUG #4808marker is removed. The same-session replacement test above it is unchanged and still passes..claude/skills/testing/SKILL.md: the marker example cited#4808literally; it now reads#<N>, sogit grep "KNOWN BUG #4808"is empty. Body text only, no description change..changeset/everything-session-resources-per-server.md: patch changeset for@modelcontextprotocol/server-everything.Server Details
demo://resource/session/<name>, created bygzip-file-as-resource)Motivation and Context
#4808. Docs (
features.md,how-it-works.md, thesession.tsdocstring) describe session resources as per-session; the cross-session eviction was a side effect of the module-level map. Fixed in the legacy server on SDK 1.x per #5004's rules.How Has This Been Tested?
Unit/protocol suite. The rewritten #4808 test fails against
v2/main'ssession.ts(McpError: MCP error -32602: ... Resource demo://resource/session/shared.gz not found) and passes with the fix.npm test -w src/everything: 238 passed.npm run coverage -w src/everything:session.tsat 100%, all files over the per-file 90% gate.npm run local:gate: passed (exit 0).Two clients, one server (Streamable HTTP), before/after. The bug only exists when two sessions share one server process, which a one-shot client cannot show, so this used two
@modelcontextprotocol/sdk1.xClients withStreamableHTTPClientTransportagainstnode src/everything/dist/index.js streamableHttp. Each callsgzip-file-as-resourcewithname=shared.gz(A first, then B), then A reads and lists:Inspector CLI 2.9.0, Streamable HTTP. The CLI makes one request per connection, so it cannot hold session A open while B registers; it shows the tool's single-session result is unchanged:
--protocol-era modernfails, as expected for an unmigrated server:Version negotiation failed: the server did not offer pinned protocol version 2026-07-28 via server/discover (no fallback in pin mode).everythingdoes not yet speak 2026-07-28, so only the legacy era (2025-11-25) is exercised.LLM client: Claude Code 2.1.289 (
claude -p --strict-mcp-config, local build over stdio). Prompt: "Call the gzip-file-as-resource tool of the everything server with name 'shared.gz', data 'data:text/plain,hello-llm' and outputType 'resourceLink'. Then read the resource it links to from the everything server and reply with the resource URI and its mimeType only." Reply:URI: demo://resource/session/shared.gz/mimeType: application/gzip. This is a single session: stdio gives each client its own server process, so the cross-session case cannot be reproduced through an LLM client. Claude Code offers no era switch; against this v1-SDK server the connection is legacy.Breaking Changes
None. No client configuration changes.
Types of changes
Checklist
npm run changeset) if this changes what a TypeScript server publishesAdditional context
WeakMapkeyed by theMcpServerlets a closed session's server and its registrations be garbage-collected with no cleanup hook.Co-authored-by: tiancheng-Xu 271251549@qq.com
🤖 Generated with Claude Code