Skip to content

Commit 7e69735

Browse files
authored
fix(server): return Invalid Params for malformed resource URIs (#2451)
1 parent e2aeac2 commit 7e69735

3 files changed

Lines changed: 88 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@modelcontextprotocol/server': patch
3+
---
4+
5+
Return JSON-RPC Invalid Params with the original URI and an `invalid_uri` reason when `resources/read` receives a syntactically malformed URI.

packages/server/src/server/mcp.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,15 @@ export class McpServer {
476476
});
477477

478478
this.server.setRequestHandler('resources/read', async (request, ctx) => {
479-
const uri = new URL(request.params.uri);
479+
let uri: URL;
480+
try {
481+
uri = new URL(request.params.uri);
482+
} catch {
483+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Resource URI ${request.params.uri} is invalid`, {
484+
uri: request.params.uri,
485+
reason: 'invalid_uri'
486+
});
487+
}
480488

481489
// First check for exact resource match
482490
const resource = this._registeredResources[uri.toString()];

test/integration/test/server/mcp.test.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2743,6 +2743,80 @@ describe('Zod v4', () => {
27432743
});
27442744
});
27452745

2746+
test('should echo the exact requested URI for nonexistent resources', async () => {
2747+
const mcpServer = new McpServer({
2748+
name: 'test server',
2749+
version: '1.0'
2750+
});
2751+
const client = new Client({
2752+
name: 'test client',
2753+
version: '1.0'
2754+
});
2755+
const requestedUri = 'HTTP://example.com:80/docs/../missing file.txt';
2756+
mcpServer.registerResource('test', 'test://resource', {}, async () => ({
2757+
contents: [
2758+
{
2759+
uri: 'test://resource',
2760+
text: 'Test content'
2761+
}
2762+
]
2763+
}));
2764+
2765+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
2766+
2767+
await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
2768+
2769+
await expect(
2770+
client.request({
2771+
method: 'resources/read',
2772+
params: {
2773+
uri: requestedUri
2774+
}
2775+
})
2776+
).rejects.toMatchObject({
2777+
code: ProtocolErrorCode.InvalidParams,
2778+
message: expect.stringContaining('not found'),
2779+
data: { uri: requestedUri }
2780+
});
2781+
});
2782+
2783+
test('should return invalid params for syntactically invalid resource URIs', async () => {
2784+
const mcpServer = new McpServer({
2785+
name: 'test server',
2786+
version: '1.0'
2787+
});
2788+
const client = new Client({
2789+
name: 'test client',
2790+
version: '1.0'
2791+
});
2792+
const requestedUri = 'not a valid URI';
2793+
mcpServer.registerResource('test', 'test://resource', {}, async () => ({
2794+
contents: [
2795+
{
2796+
uri: 'test://resource',
2797+
text: 'Test content'
2798+
}
2799+
]
2800+
}));
2801+
2802+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
2803+
2804+
await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
2805+
2806+
await expect(
2807+
client.request({
2808+
method: 'resources/read',
2809+
params: {
2810+
uri: requestedUri
2811+
}
2812+
})
2813+
).rejects.toMatchObject({
2814+
code: ProtocolErrorCode.InvalidParams,
2815+
message: expect.stringContaining('invalid'),
2816+
data: { uri: requestedUri, reason: 'invalid_uri' }
2817+
});
2818+
});
2819+
27462820
/***
27472821
* Test: ProtocolError for Disabled Resource
27482822
*/

0 commit comments

Comments
 (0)