From 6ae0e986afbfb2b00de4f39f06fe673ed1c444a9 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Tue, 7 Jul 2026 12:08:54 +0100 Subject: [PATCH] fix(server): reject malformed resource URIs --- .../malformed-resource-uri-invalid-params.md | 5 ++ packages/server/src/server/mcp.ts | 10 ++- test/integration/test/server/mcp.test.ts | 74 +++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 .changeset/malformed-resource-uri-invalid-params.md diff --git a/.changeset/malformed-resource-uri-invalid-params.md b/.changeset/malformed-resource-uri-invalid-params.md new file mode 100644 index 0000000000..565de8bad2 --- /dev/null +++ b/.changeset/malformed-resource-uri-invalid-params.md @@ -0,0 +1,5 @@ +--- +'@modelcontextprotocol/server': patch +--- + +Return JSON-RPC Invalid Params with the original URI and an `invalid_uri` reason when `resources/read` receives a syntactically malformed URI. diff --git a/packages/server/src/server/mcp.ts b/packages/server/src/server/mcp.ts index e0d10b5a8e..d2e40181e4 100644 --- a/packages/server/src/server/mcp.ts +++ b/packages/server/src/server/mcp.ts @@ -476,7 +476,15 @@ export class McpServer { }); this.server.setRequestHandler('resources/read', async (request, ctx) => { - const uri = new URL(request.params.uri); + let uri: URL; + try { + uri = new URL(request.params.uri); + } catch { + throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Resource URI ${request.params.uri} is invalid`, { + uri: request.params.uri, + reason: 'invalid_uri' + }); + } // First check for exact resource match const resource = this._registeredResources[uri.toString()]; diff --git a/test/integration/test/server/mcp.test.ts b/test/integration/test/server/mcp.test.ts index 2597c14f84..4b9a3865f0 100644 --- a/test/integration/test/server/mcp.test.ts +++ b/test/integration/test/server/mcp.test.ts @@ -2743,6 +2743,80 @@ describe('Zod v4', () => { }); }); + test('should echo the exact requested URI for nonexistent resources', async () => { + const mcpServer = new McpServer({ + name: 'test server', + version: '1.0' + }); + const client = new Client({ + name: 'test client', + version: '1.0' + }); + const requestedUri = 'HTTP://example.com:80/docs/../missing file.txt'; + mcpServer.registerResource('test', 'test://resource', {}, async () => ({ + contents: [ + { + uri: 'test://resource', + text: 'Test content' + } + ] + })); + + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + + await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]); + + await expect( + client.request({ + method: 'resources/read', + params: { + uri: requestedUri + } + }) + ).rejects.toMatchObject({ + code: ProtocolErrorCode.InvalidParams, + message: expect.stringContaining('not found'), + data: { uri: requestedUri } + }); + }); + + test('should return invalid params for syntactically invalid resource URIs', async () => { + const mcpServer = new McpServer({ + name: 'test server', + version: '1.0' + }); + const client = new Client({ + name: 'test client', + version: '1.0' + }); + const requestedUri = 'not a valid URI'; + mcpServer.registerResource('test', 'test://resource', {}, async () => ({ + contents: [ + { + uri: 'test://resource', + text: 'Test content' + } + ] + })); + + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + + await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]); + + await expect( + client.request({ + method: 'resources/read', + params: { + uri: requestedUri + } + }) + ).rejects.toMatchObject({ + code: ProtocolErrorCode.InvalidParams, + message: expect.stringContaining('invalid'), + data: { uri: requestedUri, reason: 'invalid_uri' } + }); + }); + /*** * Test: ProtocolError for Disabled Resource */