+49
-7
@@ -225,2 +225,6 @@ import { createServer as createHttpServer, } from "node:http"; | ||
| // New session: spin up a fresh transport + its own server, sharing the store. | ||
| // Note we accept an initialize even when it still carries a stale `Mcp-Session-Id` | ||
| // header. The spec has the client re-initialize *without* one, but a client that | ||
| // forgets to strip it is trying to do exactly the right thing; answering 404 to its | ||
| // recovery attempt would strand it for good. It gets a brand-new session id back. | ||
| transport = new StreamableHTTPServerTransport({ | ||
@@ -240,4 +244,10 @@ sessionIdGenerator: () => randomUUID(), | ||
| } | ||
| else if (sessionId) { | ||
| sendSessionNotFound(res); | ||
| return; | ||
| } | ||
| else { | ||
| // Not an initialize and no known session → the client must initialize first. | ||
| // No session id at all and not an initialize → the client must initialize first. | ||
| // Streamable HTTP §Session Management point 2: a server that requires a session id | ||
| // SHOULD answer 400 Bad Request here. This is NOT the expired-session case. | ||
| sendJson(res, 400, { | ||
@@ -247,3 +257,3 @@ jsonrpc: "2.0", | ||
| code: -32000, | ||
| message: "Bad Request: no valid session id; send an initialize request first", | ||
| message: "Bad Request: no session id; send an initialize request first", | ||
| }, | ||
@@ -262,7 +272,12 @@ id: null, | ||
| if (!transport) { | ||
| sendJson(res, 400, { | ||
| jsonrpc: "2.0", | ||
| error: { code: -32000, message: "Bad Request: unknown or missing session id" }, | ||
| id: null, | ||
| }); | ||
| if (sessionId) { | ||
| sendSessionNotFound(res); | ||
| } | ||
| else { | ||
| sendJson(res, 400, { | ||
| jsonrpc: "2.0", | ||
| error: { code: -32000, message: "Bad Request: missing session id" }, | ||
| id: null, | ||
| }); | ||
| } | ||
| return; | ||
@@ -280,2 +295,29 @@ } | ||
| } | ||
| /** | ||
| * Answer a request that carries an `Mcp-Session-Id` we don't know (D-038). | ||
| * | ||
| * Sessions live in this process's memory, so every restart of the service — a deploy, a | ||
| * crash, an OOM kill — silently invalidates every session id its clients are still holding. | ||
| * The MCP Streamable HTTP spec has one recovery path for exactly this, and it is a status | ||
| * code: "The server MAY terminate the session at any time, after which it MUST respond to | ||
| * requests containing that session ID with HTTP 404 Not Found", and "When a client receives | ||
| * HTTP 404 in response to a request containing an Mcp-Session-Id, it MUST start a new session | ||
| * by sending a new InitializeRequest without a session ID attached." | ||
| * | ||
| * We used to answer 400 here. A conforming client (claude.ai) reads 400 as "that request was | ||
| * malformed", not "your session is gone", so it never re-handshakes — the conversation stays | ||
| * wedged on a dead session id until the user restarts the client. The 404 is the signal that | ||
| * makes recovery automatic and invisible; it is the entire fix. | ||
| */ | ||
| function sendSessionNotFound(res) { | ||
| sendJson(res, 404, { | ||
| jsonrpc: "2.0", | ||
| error: { | ||
| code: -32001, | ||
| message: "Session not found: this session id is unknown or expired (the server may have " + | ||
| "restarted). Send a new initialize request without a session id to start a new session.", | ||
| }, | ||
| id: null, | ||
| }); | ||
| } | ||
| function header(req, name) { | ||
@@ -282,0 +324,0 @@ const v = req.headers[name]; |
+1
-1
@@ -9,2 +9,2 @@ /** | ||
| */ | ||
| export const VERSION = "0.7.2"; | ||
| export const VERSION = "0.7.3"; |
+1
-1
| { | ||
| "name": "jamgate", | ||
| "version": "0.7.2", | ||
| "version": "0.7.3", | ||
| "mcpName": "io.github.amirj4m/jamgate", | ||
@@ -5,0 +5,0 @@ "description": "A neutral, cross-agent memory quality gate for AI agents, delivered as an MCP server — a gate, not a store.", |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
217619
1.06%3938
1.08%