From 5ad3c8659ac09a1a0c8f5ca9f11d4702e8548fca Mon Sep 17 00:00:00 2001 From: olaservo Date: Fri, 2 May 2025 06:36:33 -0700 Subject: [PATCH] Add failing tests and updated package-lock.json --- package-lock.json | 4 +- src/client/sse.test.ts | 153 +++++++++++++++++++++++++++++------------ 2 files changed, 111 insertions(+), 46 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1165b751..3c6e2d90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@modelcontextprotocol/sdk", - "version": "1.9.0", + "version": "1.10.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/sdk", - "version": "1.9.0", + "version": "1.10.2", "license": "MIT", "dependencies": { "content-type": "^1.0.5", diff --git a/src/client/sse.test.ts b/src/client/sse.test.ts index 77b28508..fc7a86c4 100644 --- a/src/client/sse.test.ts +++ b/src/client/sse.test.ts @@ -68,6 +68,71 @@ describe("SSEClientTransport", () => { }); describe("connection handling", () => { + it("maintains custom path when constructing endpoint URL", async () => { + // Create a URL with a custom path + const customPathUrl = new URL("/custom/path/sse", baseUrl); + transport = new SSEClientTransport(customPathUrl); + + // Start the transport + await transport.start(); + + // Send a test message to verify the endpoint URL + const message: JSONRPCMessage = { + jsonrpc: "2.0", + id: "test-1", + method: "test", + params: {} + }; + + await transport.send(message); + + // Verify the POST request maintains the custom path + expect(lastServerRequest.url).toBe("/custom/path/messages"); + }); + + it("handles multiple levels of custom paths", async () => { + // Test with a deeper nested path + const nestedPathUrl = new URL("/api/v1/custom/deep/path/sse", baseUrl); + transport = new SSEClientTransport(nestedPathUrl); + + await transport.start(); + + const message: JSONRPCMessage = { + jsonrpc: "2.0", + id: "test-1", + method: "test", + params: {} + }; + + await transport.send(message); + + // Verify the POST request maintains the full custom path + expect(lastServerRequest.url).toBe("/api/v1/custom/deep/path/messages"); + }); + + it("maintains custom path for SSE connection", async () => { + const customPathUrl = new URL("/custom/path/sse", baseUrl); + transport = new SSEClientTransport(customPathUrl); + await transport.start(); + expect(lastServerRequest.url).toBe("/custom/path/sse"); + }); + + it("handles URLs with query parameters", async () => { + const urlWithQuery = new URL("/custom/path/sse?param=value", baseUrl); + transport = new SSEClientTransport(urlWithQuery); + await transport.start(); + + const message: JSONRPCMessage = { + jsonrpc: "2.0", + id: "test-1", + method: "test", + params: {} + }; + + await transport.send(message); + expect(lastServerRequest.url).toBe("/custom/path/messages"); + }); + it("establishes SSE connection and receives endpoint", async () => { transport = new SSEClientTransport(baseUrl); await transport.start(); @@ -397,18 +462,18 @@ describe("SSEClientTransport", () => { return; } - res.writeHead(200, { - "Content-Type": "text/event-stream", - "Cache-Control": "no-cache, no-transform", - Connection: "keep-alive", - }); - res.write("event: endpoint\n"); - res.write(`data: ${baseUrl.href}\n\n`); + res.writeHead(200, { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache, no-transform", + Connection: "keep-alive", + }); + res.write("event: endpoint\n"); + res.write(`data: ${baseUrl.href}\n\n`); break; case "POST": - res.writeHead(401); - res.end(); + res.writeHead(401); + res.end(); break; } }); @@ -517,25 +582,25 @@ describe("SSEClientTransport", () => { return; } - const auth = req.headers.authorization; - if (auth === "Bearer expired-token") { - res.writeHead(401).end(); - return; - } + const auth = req.headers.authorization; + if (auth === "Bearer expired-token") { + res.writeHead(401).end(); + return; + } - if (auth === "Bearer new-token") { - res.writeHead(200, { - "Content-Type": "text/event-stream", - "Cache-Control": "no-cache, no-transform", - Connection: "keep-alive", - }); - res.write("event: endpoint\n"); - res.write(`data: ${baseUrl.href}\n\n`); - connectionAttempts++; - return; - } + if (auth === "Bearer new-token") { + res.writeHead(200, { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache, no-transform", + Connection: "keep-alive", + }); + res.write("event: endpoint\n"); + res.write(`data: ${baseUrl.href}\n\n`); + connectionAttempts++; + return; + } - res.writeHead(401).end(); + res.writeHead(401).end(); }); await new Promise(resolve => { @@ -610,13 +675,13 @@ describe("SSEClientTransport", () => { return; } - res.writeHead(200, { - "Content-Type": "text/event-stream", - "Cache-Control": "no-cache, no-transform", - Connection: "keep-alive", - }); - res.write("event: endpoint\n"); - res.write(`data: ${baseUrl.href}\n\n`); + res.writeHead(200, { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache, no-transform", + Connection: "keep-alive", + }); + res.write("event: endpoint\n"); + res.write(`data: ${baseUrl.href}\n\n`); break; case "POST": { @@ -625,19 +690,19 @@ describe("SSEClientTransport", () => { return; } - const auth = req.headers.authorization; - if (auth === "Bearer expired-token") { - res.writeHead(401).end(); - return; - } + const auth = req.headers.authorization; + if (auth === "Bearer expired-token") { + res.writeHead(401).end(); + return; + } - if (auth === "Bearer new-token") { - res.writeHead(200).end(); - postAttempts++; - return; - } + if (auth === "Bearer new-token") { + res.writeHead(200).end(); + postAttempts++; + return; + } - res.writeHead(401).end(); + res.writeHead(401).end(); break; } }