Set Mcp-Protocol-Version in client requests after init, and warn when it doesn't match negotiated version (it SHOULD match)
This commit is contained in:
parent
1878143f1a
commit
84971c83ea
1 changed files with 243 additions and 19 deletions
|
|
@ -185,6 +185,8 @@ async function sendPostRequest(baseUrl: URL, message: JSONRPCMessage | JSONRPCMe
|
|||
|
||||
if (sessionId) {
|
||||
headers["mcp-session-id"] = sessionId;
|
||||
// After initialization, include the protocol version header
|
||||
headers["mcp-protocol-version"] = "2025-03-26";
|
||||
}
|
||||
|
||||
return fetch(baseUrl, {
|
||||
|
|
@ -277,7 +279,7 @@ describe("StreamableHTTPServerTransport", () => {
|
|||
expectErrorResponse(errorData, -32600, /Only one initialization request is allowed/);
|
||||
});
|
||||
|
||||
it("should pandle post requests via sse response correctly", async () => {
|
||||
it("should handle post requests via sse response correctly", async () => {
|
||||
sessionId = await initializeServer();
|
||||
|
||||
const response = await sendPostRequest(baseUrl, TEST_MESSAGES.toolsList, sessionId);
|
||||
|
|
@ -376,6 +378,7 @@ describe("StreamableHTTPServerTransport", () => {
|
|||
headers: {
|
||||
Accept: "text/event-stream",
|
||||
"mcp-session-id": sessionId,
|
||||
"mcp-protocol-version": "2025-03-26",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -417,6 +420,7 @@ describe("StreamableHTTPServerTransport", () => {
|
|||
headers: {
|
||||
Accept: "text/event-stream",
|
||||
"mcp-session-id": sessionId,
|
||||
"mcp-protocol-version": "2025-03-26",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -448,6 +452,7 @@ describe("StreamableHTTPServerTransport", () => {
|
|||
headers: {
|
||||
Accept: "text/event-stream",
|
||||
"mcp-session-id": sessionId,
|
||||
"mcp-protocol-version": "2025-03-26",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -459,6 +464,7 @@ describe("StreamableHTTPServerTransport", () => {
|
|||
headers: {
|
||||
Accept: "text/event-stream",
|
||||
"mcp-session-id": sessionId,
|
||||
"mcp-protocol-version": "2025-03-26",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -477,6 +483,7 @@ describe("StreamableHTTPServerTransport", () => {
|
|||
headers: {
|
||||
Accept: "application/json",
|
||||
"mcp-session-id": sessionId,
|
||||
"mcp-protocol-version": "2025-03-26",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -670,6 +677,7 @@ describe("StreamableHTTPServerTransport", () => {
|
|||
headers: {
|
||||
Accept: "text/event-stream",
|
||||
"mcp-session-id": sessionId,
|
||||
"mcp-protocol-version": "2025-03-26",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -705,7 +713,10 @@ describe("StreamableHTTPServerTransport", () => {
|
|||
// Now DELETE the session
|
||||
const deleteResponse = await fetch(tempUrl, {
|
||||
method: "DELETE",
|
||||
headers: { "mcp-session-id": tempSessionId || "" },
|
||||
headers: {
|
||||
"mcp-session-id": tempSessionId || "",
|
||||
"mcp-protocol-version": "2025-03-26",
|
||||
},
|
||||
});
|
||||
|
||||
expect(deleteResponse.status).toBe(200);
|
||||
|
|
@ -721,13 +732,129 @@ describe("StreamableHTTPServerTransport", () => {
|
|||
// Try to delete with invalid session ID
|
||||
const response = await fetch(baseUrl, {
|
||||
method: "DELETE",
|
||||
headers: { "mcp-session-id": "invalid-session-id" },
|
||||
headers: {
|
||||
"mcp-session-id": "invalid-session-id",
|
||||
"mcp-protocol-version": "2025-03-26",
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
const errorData = await response.json();
|
||||
expectErrorResponse(errorData, -32001, /Session not found/);
|
||||
});
|
||||
|
||||
describe("protocol version header validation", () => {
|
||||
it("should accept requests with matching protocol version", async () => {
|
||||
sessionId = await initializeServer();
|
||||
|
||||
// Send request with matching protocol version
|
||||
const response = await sendPostRequest(baseUrl, TEST_MESSAGES.toolsList, sessionId);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
|
||||
it("should accept requests without protocol version header", async () => {
|
||||
sessionId = await initializeServer();
|
||||
|
||||
// Send request without protocol version header
|
||||
const response = await fetch(baseUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json, text/event-stream",
|
||||
"mcp-session-id": sessionId,
|
||||
// No mcp-protocol-version header
|
||||
},
|
||||
body: JSON.stringify(TEST_MESSAGES.toolsList),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
|
||||
it("should reject requests with unsupported protocol version", async () => {
|
||||
sessionId = await initializeServer();
|
||||
|
||||
// Send request with unsupported protocol version
|
||||
const response = await fetch(baseUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json, text/event-stream",
|
||||
"mcp-session-id": sessionId,
|
||||
"mcp-protocol-version": "1999-01-01", // Unsupported version
|
||||
},
|
||||
body: JSON.stringify(TEST_MESSAGES.toolsList),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
const errorData = await response.json();
|
||||
expectErrorResponse(errorData, -32000, /Bad Request: Unsupported protocol version/);
|
||||
});
|
||||
|
||||
it("should accept but warn when protocol version differs from negotiated version", async () => {
|
||||
sessionId = await initializeServer();
|
||||
|
||||
// Spy on console.warn to verify warning is logged
|
||||
const warnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
||||
|
||||
// Send request with different but supported protocol version
|
||||
const response = await fetch(baseUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json, text/event-stream",
|
||||
"mcp-session-id": sessionId,
|
||||
"mcp-protocol-version": "2024-11-05", // Different but supported version
|
||||
},
|
||||
body: JSON.stringify(TEST_MESSAGES.toolsList),
|
||||
});
|
||||
|
||||
// Request should still succeed
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
// But warning should have been logged
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Request has header with protocol version 2024-11-05, but version previously negotiated is 2025-03-26")
|
||||
);
|
||||
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("should handle protocol version validation for GET requests", async () => {
|
||||
sessionId = await initializeServer();
|
||||
|
||||
// GET request with unsupported protocol version
|
||||
const response = await fetch(baseUrl, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "text/event-stream",
|
||||
"mcp-session-id": sessionId,
|
||||
"mcp-protocol-version": "invalid-version",
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
const errorData = await response.json();
|
||||
expectErrorResponse(errorData, -32000, /Bad Request: Unsupported protocol version/);
|
||||
});
|
||||
|
||||
it("should handle protocol version validation for DELETE requests", async () => {
|
||||
sessionId = await initializeServer();
|
||||
|
||||
// DELETE request with unsupported protocol version
|
||||
const response = await fetch(baseUrl, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"mcp-session-id": sessionId,
|
||||
"mcp-protocol-version": "invalid-version",
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
const errorData = await response.json();
|
||||
expectErrorResponse(errorData, -32000, /Bad Request: Unsupported protocol version/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("StreamableHTTPServerTransport with AuthInfo", () => {
|
||||
|
|
@ -1120,6 +1247,7 @@ describe("StreamableHTTPServerTransport with resumability", () => {
|
|||
headers: {
|
||||
Accept: "text/event-stream",
|
||||
"mcp-session-id": sessionId,
|
||||
"mcp-protocol-version": "2025-03-26",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -1196,6 +1324,7 @@ describe("StreamableHTTPServerTransport with resumability", () => {
|
|||
headers: {
|
||||
Accept: "text/event-stream",
|
||||
"mcp-session-id": sessionId,
|
||||
"mcp-protocol-version": "2025-03-26",
|
||||
"last-event-id": firstEventId
|
||||
},
|
||||
});
|
||||
|
|
@ -1282,14 +1411,20 @@ describe("StreamableHTTPServerTransport in stateless mode", () => {
|
|||
// Open first SSE stream
|
||||
const stream1 = await fetch(baseUrl, {
|
||||
method: "GET",
|
||||
headers: { Accept: "text/event-stream" },
|
||||
headers: {
|
||||
Accept: "text/event-stream",
|
||||
"mcp-protocol-version": "2025-03-26"
|
||||
},
|
||||
});
|
||||
expect(stream1.status).toBe(200);
|
||||
|
||||
// Open second SSE stream - should still be rejected, stateless mode still only allows one
|
||||
const stream2 = await fetch(baseUrl, {
|
||||
method: "GET",
|
||||
headers: { Accept: "text/event-stream" },
|
||||
headers: {
|
||||
Accept: "text/event-stream",
|
||||
"mcp-protocol-version": "2025-03-26"
|
||||
},
|
||||
});
|
||||
expect(stream2.status).toBe(409); // Conflict - only one stream allowed
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue