Skip to content

fix(everything): scope session resources to their server (#4808) - #5032

Merged
cliffhall merged 2 commits into
v2/mainfrom
v2/fix/4808-everything-session-resources
Oct 5, 2026
Merged

cliffhall merged 2 commits into
v2/mainfrom
v2/fix/4808-everything-session-resources

Conversation

@cliffhall

Copy link
Copy Markdown
Member

Closes #4808

Part of Wave 1 (everything) of #5004.

Description

Session resources were tracked in one module-level Map keyed by URI only, shared by every session's McpServer. When a second session registered a session resource with the same name (for example gzip-file-as-resource with name: "shared.gz"), registerSessionResource called remove() on the first session's registration, so session A's resources/read started failing with -32602 ... not found and the resource disappeared from A's resources/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 behavior 3e1be88 added).

  • 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; only session.ts, since that PR's resources.test.ts additions targeted main's mock-based unit test file, which v2/main replaced 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's resources/list still has it), and its KNOWN BUG #4808 marker is removed. The same-session replacement test above it is unchanged and still passes.
  • .claude/skills/testing/SKILL.md: the marker example cited #4808 literally; it now reads #<N>, so git 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

  • Server: everything
  • Changes to: session resources (demo://resource/session/<name>, created by gzip-file-as-resource)

Motivation and Context

#4808. Docs (features.md, how-it-works.md, the session.ts docstring) 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's session.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.ts at 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/sdk 1.x Clients with StreamableHTTPClientTransport against node src/everything/dist/index.js streamableHttp. Each calls gzip-file-as-resource with name=shared.gz (A first, then B), then A reads and lists:

# v2/main (session.ts from origin/v2/main)
negotiated: 2025-11-25
A reads demo://resource/session/shared.gz -> MCP error -32602: MCP error -32602: Resource demo://resource/session/shared.gz not found
A resources/list has it: false
B reads demo://resource/session/shared.gz -> 1 content(s)

# this branch
negotiated: 2025-11-25
A reads demo://resource/session/shared.gz -> 1 content(s)
A resources/list has it: true
B reads demo://resource/session/shared.gz -> 1 content(s)

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:

$ npx -y @modelcontextprotocol/inspector@2.9.0 --cli http://localhost:<port>/mcp --protocol-era legacy \
    --method tools/call --tool-name gzip-file-as-resource \
    --tool-arg name=shared.gz --tool-arg data=data:text/plain,hello --tool-arg outputType=resourceLink --format json
# v2/main and this branch, identical, exit 0:
{"result":{"content":[{"name":"shared.gz","uri":"demo://resource/session/shared.gz","mimeType":"application/gzip","type":"resource_link"}]}}

--protocol-era modern fails, 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). everything does 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

  • Bug fix (non-breaking change which fixes an issue)
  • New feature
  • Breaking change
  • Documentation update

Checklist

  • I have read the MCP Protocol Documentation
  • My changes follow MCP security best practices
  • I have updated the server's README accordingly (not applicable: no documented behavior changes; the docs already describe session resources as per-session)
  • I have added a changeset (npm run changeset) if this changes what a TypeScript server publishes
  • I have tested this with an LLM client (single session only; see above)
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling (not applicable: no new error paths)
  • I have documented all environment variables and configuration options (not applicable: none added)

Additional context

WeakMap keyed by the McpServer lets 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

Tiancheng-Xu and others added 2 commits October 4, 2026 19:22
Ported from #4810 onto v2/main (session.ts only; the original
resources.test.ts additions targeted main's unit-test file, which v2/main
replaced with protocol-level suites).

(cherry picked from commit 1f2a610)
Signed-off-by: cliffhall <cliff@futurescale.com>
…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-bot

changeset-bot Bot commented Oct 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d4fb2f4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@modelcontextprotocol/server-everything Patch

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 1: no findings ("Approval recommended", 0 inline comments, no suppressed comments). Clean round, so the review loop stops here.

@cliffhall
cliffhall merged commit 8696907 into v2/main Oct 5, 2026
45 checks passed
@cliffhall
cliffhall deleted the v2/fix/4808-everything-session-resources branch October 5, 2026 03:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

everything: session resources evict another session's resource when two sessions use the same file name

3 participants