modelcontextprotocol / modelcontextprotocol/typescript-sdk
Unordered and partial query parameter matching
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 13.4k
- Forks
- 2.2k
- Avg merge
- 3d 15h
- Merged PRs (30d)
- 4
Description
Describe the bug
MCP server does not match resource requests that do no have all query parameters specified in the order they were declared in the ResourceTemplate / UriTemplate. Instead, clients receive a not found error (MCP error -32602).
To Reproduce
Run the following reproducer (e.g. npx tsx repro.ts):
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import {
McpServer,
ResourceTemplate,
} from "@modelcontextprotocol/sdk/server/mcp.js";
import { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
const server = new McpServer({
name: "test-server",
version: "0.0.0",
});
server.resource(
"example",
new ResourceTemplate("acme://products{?page,limit}", { list: undefined }),
async (uri, vars): Promise<ReadResourceResult> => {
const page = vars.page ?? "1"
const limit = vars.limit ?? "20"
return {
contents: [
{
text: `Listing ${vars.limit} products on page ${vars.page}`,
mimeType: "text/plain",
uri: "uri",
},
],
};
}
);
const client = new Client({
name: "test-client",
version: "1.0.0",
});
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await Promise.all([
server.connect(serverTransport),
client.connect(clientTransport),
]);
const result = await client.readResource({
uri: "acme://products",
});
console.log(result.contents.map((c) => c.text).join("\n"));
Expected behavior
The script above should print Listing 20 products on page 1 but instead it errors with:
McpError: MCP error -32602: MCP error -32602: Resource acme://products not found
Additional context
I think the current handling of query parameters makes resource templates a little too rigid. My current workaround is to convert certain dynamic resources to tools but I'm not sure that's a good long term solution. I propose changing the behavior of UriTemplate such that the following test cases pass:
import { UriTemplate } from "@modelcontextprotocol/sdk/shared/uriTemplate.js";
import { expect, test } from "vitest";
test("UriTemplate::match treats query parameters as optional", () => {
const template = new UriTemplate("acme://products{?page,limit}");
expect(template.match("acme://products")).toEqual({});
expect(template.match("acme://products?page=2")).toEqual({ page: "2" });
});
test("UriTemplate::match accepts query parameters in arbitrary order", () => {
const template = new UriTemplate("acme://products{?page,limit,q*}");
expect(template.match("acme://products?q=cat,dog&limit=40&page=5")).toEqual({
page: "5",
limit: "40",
q: ["cat", "dog"],
});
});
// npx vitest uritemplate.test.ts
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with shared/uriTemplate.js and run the proposed uritemplate.test.ts cases with npx vitest. Verify that omitted query parameters match as optional and that parameters supplied in arbitrary order, including exploded values, produce the expected variables and make the resource reproducer succeed.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 70/100