Skip to content

Commit f417691

Browse files
authored
Merge pull request #358 from lforst/lforst-pass-request-id-to-extra
Pass `requestId` to handlers via metadata
2 parents 4d6197a + ec23b71 commit f417691

File tree

2 files changed

+170
-0
lines changed

2 files changed

+170
-0
lines changed

src/server/mcp.test.ts

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ describe("ResourceTemplate", () => {
101101
const abortController = new AbortController();
102102
const result = await template.listCallback?.({
103103
signal: abortController.signal,
104+
requestId: 'not-implemented',
104105
sendRequest: () => { throw new Error("Not implemented") },
105106
sendNotification: () => { throw new Error("Not implemented") }
106107
});
@@ -646,6 +647,59 @@ describe("tool()", () => {
646647
expect(receivedSessionId).toBe("test-session-123");
647648
});
648649

650+
test("should pass requestId to tool callback via RequestHandlerExtra", async () => {
651+
const mcpServer = new McpServer({
652+
name: "test server",
653+
version: "1.0",
654+
});
655+
656+
const client = new Client(
657+
{
658+
name: "test client",
659+
version: "1.0",
660+
},
661+
{
662+
capabilities: {
663+
tools: {},
664+
},
665+
},
666+
);
667+
668+
let receivedRequestId: string | number | undefined;
669+
mcpServer.tool("request-id-test", async (extra) => {
670+
receivedRequestId = extra.requestId;
671+
return {
672+
content: [
673+
{
674+
type: "text",
675+
text: `Received request ID: ${extra.requestId}`,
676+
},
677+
],
678+
};
679+
});
680+
681+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
682+
683+
await Promise.all([
684+
client.connect(clientTransport),
685+
mcpServer.server.connect(serverTransport),
686+
]);
687+
688+
const result = await client.request(
689+
{
690+
method: "tools/call",
691+
params: {
692+
name: "request-id-test",
693+
},
694+
},
695+
CallToolResultSchema,
696+
);
697+
698+
expect(receivedRequestId).toBeDefined();
699+
expect(typeof receivedRequestId === 'string' || typeof receivedRequestId === 'number').toBe(true);
700+
expect(result.content[0].text).toContain("Received request ID:");
701+
});
702+
649703
test("should provide sendNotification within tool call", async () => {
650704
const mcpServer = new McpServer(
651705
{
@@ -1702,6 +1756,59 @@ describe("resource()", () => {
17021756
expect(result.completion.values).toEqual(["movies", "music"]);
17031757
expect(result.completion.total).toBe(2);
17041758
});
1759+
1760+
test("should pass requestId to resource callback via RequestHandlerExtra", async () => {
1761+
const mcpServer = new McpServer({
1762+
name: "test server",
1763+
version: "1.0",
1764+
});
1765+
1766+
const client = new Client(
1767+
{
1768+
name: "test client",
1769+
version: "1.0",
1770+
},
1771+
{
1772+
capabilities: {
1773+
resources: {},
1774+
},
1775+
},
1776+
);
1777+
1778+
let receivedRequestId: string | number | undefined;
1779+
mcpServer.resource("request-id-test", "test://resource", async (_uri, extra) => {
1780+
receivedRequestId = extra.requestId;
1781+
return {
1782+
contents: [
1783+
{
1784+
uri: "test://resource",
1785+
text: `Received request ID: ${extra.requestId}`,
1786+
},
1787+
],
1788+
};
1789+
});
1790+
1791+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
1792+
1793+
await Promise.all([
1794+
client.connect(clientTransport),
1795+
mcpServer.server.connect(serverTransport),
1796+
]);
1797+
1798+
const result = await client.request(
1799+
{
1800+
method: "resources/read",
1801+
params: {
1802+
uri: "test://resource",
1803+
},
1804+
},
1805+
ReadResourceResultSchema,
1806+
);
1807+
1808+
expect(receivedRequestId).toBeDefined();
1809+
expect(typeof receivedRequestId === 'string' || typeof receivedRequestId === 'number').toBe(true);
1810+
expect(result.contents[0].text).toContain("Received request ID:");
1811+
});
17051812
});
17061813

17071814
describe("prompt()", () => {
@@ -2511,4 +2618,60 @@ describe("prompt()", () => {
25112618
expect(result.completion.values).toEqual(["Alice"]);
25122619
expect(result.completion.total).toBe(1);
25132620
});
2621+
2622+
test("should pass requestId to prompt callback via RequestHandlerExtra", async () => {
2623+
const mcpServer = new McpServer({
2624+
name: "test server",
2625+
version: "1.0",
2626+
});
2627+
2628+
const client = new Client(
2629+
{
2630+
name: "test client",
2631+
version: "1.0",
2632+
},
2633+
{
2634+
capabilities: {
2635+
prompts: {},
2636+
},
2637+
},
2638+
);
2639+
2640+
let receivedRequestId: string | number | undefined;
2641+
mcpServer.prompt("request-id-test", async (extra) => {
2642+
receivedRequestId = extra.requestId;
2643+
return {
2644+
messages: [
2645+
{
2646+
role: "assistant",
2647+
content: {
2648+
type: "text",
2649+
text: `Received request ID: ${extra.requestId}`,
2650+
},
2651+
},
2652+
],
2653+
};
2654+
});
2655+
2656+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
2657+
2658+
await Promise.all([
2659+
client.connect(clientTransport),
2660+
mcpServer.server.connect(serverTransport),
2661+
]);
2662+
2663+
const result = await client.request(
2664+
{
2665+
method: "prompts/get",
2666+
params: {
2667+
name: "request-id-test",
2668+
},
2669+
},
2670+
GetPromptResultSchema,
2671+
);
2672+
2673+
expect(receivedRequestId).toBeDefined();
2674+
expect(typeof receivedRequestId === 'string' || typeof receivedRequestId === 'number').toBe(true);
2675+
expect(result.messages[0].content.text).toContain("Received request ID:");
2676+
});
25142677
});

src/shared/protocol.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,12 @@ export type RequestHandlerExtra<SendRequestT extends Request,
115115
*/
116116
sessionId?: string;
117117

118+
/**
119+
* The JSON-RPC ID of the request being handled.
120+
* This can be useful for tracking or logging purposes.
121+
*/
122+
requestId: RequestId;
123+
118124
/**
119125
* Sends a notification that relates to the current request being handled.
120126
*
@@ -361,6 +367,7 @@ export abstract class Protocol<
361367
sendRequest: (r, resultSchema, options?) =>
362368
this.request(r, resultSchema, { ...options, relatedRequestId: request.id }),
363369
authInfo: extra?.authInfo,
370+
requestId: request.id,
364371
};
365372

366373
// Starting with Promise.resolve() puts any synchronous errors into the monad as well.

0 commit comments

Comments
 (0)