From 1f2a610c2f59fb3e034c615d718b0aae39ff0d54 Mon Sep 17 00:00:00 2001 From: tiancheng-Xu <271251549@qq.com> Date: Tue, 15 Sep 2026 10:32:45 -0400 Subject: [PATCH] fix(everything): scope session resources to server --- src/everything/__tests__/resources.test.ts | 44 ++++++++++++++++++++++ src/everything/resources/session.ts | 18 ++++++--- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/everything/__tests__/resources.test.ts b/src/everything/__tests__/resources.test.ts index a22b904175..a1853a45b6 100644 --- a/src/everything/__tests__/resources.test.ts +++ b/src/everything/__tests__/resources.test.ts @@ -221,6 +221,50 @@ describe('Session Resources', () => { ); }); + it('should not evict a resource with the same URI from another server', () => { + const firstRegistration = { remove: vi.fn() }; + const secondRegistration = { remove: vi.fn() }; + const firstServer = { + registerResource: vi.fn(() => firstRegistration), + } as unknown as McpServer; + const secondServer = { + registerResource: vi.fn(() => secondRegistration), + } as unknown as McpServer; + const resource = { + uri: 'demo://resource/session/shared-file', + name: 'shared-file', + mimeType: 'text/plain', + }; + + registerSessionResource(firstServer, resource, 'text', 'first session'); + registerSessionResource(secondServer, resource, 'text', 'second session'); + + expect(firstRegistration.remove).not.toHaveBeenCalled(); + expect(secondRegistration.remove).not.toHaveBeenCalled(); + }); + + it('should replace a resource with the same URI on the same server', () => { + const firstRegistration = { remove: vi.fn() }; + const secondRegistration = { remove: vi.fn() }; + const server = { + registerResource: vi + .fn() + .mockReturnValueOnce(firstRegistration) + .mockReturnValueOnce(secondRegistration), + } as unknown as McpServer; + const resource = { + uri: 'demo://resource/session/replaced-file', + name: 'replaced-file', + mimeType: 'text/plain', + }; + + registerSessionResource(server, resource, 'text', 'first payload'); + registerSessionResource(server, resource, 'text', 'second payload'); + + expect(firstRegistration.remove).toHaveBeenCalledOnce(); + expect(secondRegistration.remove).not.toHaveBeenCalled(); + }); + it('should register blob resource correctly', () => { const mockServer = { registerResource: vi.fn(), diff --git a/src/everything/resources/session.ts b/src/everything/resources/session.ts index 10e0db33c1..d1df3cf50e 100644 --- a/src/everything/resources/session.ts +++ b/src/everything/resources/session.ts @@ -6,7 +6,10 @@ import { Resource, ResourceLink } from "@modelcontextprotocol/sdk/types.js"; * This prevents "Resource already registered" errors when a tool creates a resource * with the same URI multiple times during a session. */ -const registeredResources = new Map(); +const registeredResources = new WeakMap< + McpServer, + Map +>(); /** * Generates a session-scoped resource URI string based on the provided resource name. @@ -54,11 +57,16 @@ export const registerSessionResource = ( blob: payload, }; - // Check if a resource with this URI is already registered and remove it - const existingResource = registeredResources.get(uri); + const serverResources = + registeredResources.get(server) ?? new Map(); + registeredResources.set(server, serverResources); + + // Check if a resource with this URI is already registered on this server and remove it + const resourceKey = uri.toString(); + const existingResource = serverResources.get(resourceKey); if (existingResource) { existingResource.remove(); - registeredResources.delete(uri); + serverResources.delete(resourceKey); } // Register file resource @@ -74,7 +82,7 @@ export const registerSessionResource = ( ); // Track the registered resource for potential future removal - registeredResources.set(uri, registeredResource); + serverResources.set(resourceKey, registeredResource); return { type: "resource_link", ...resource }; };