@ateam-ai/mcp 0.3.15 → 0.3.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/http.js +61 -15
package/package.json
CHANGED
package/src/http.js
CHANGED
|
@@ -205,11 +205,16 @@ export function startHttpServer(port = 3100) {
|
|
|
205
205
|
// Reuse existing session — seed credentials if Bearer token present
|
|
206
206
|
transport = transports[sessionId];
|
|
207
207
|
seedCredentials(req, sessionId);
|
|
208
|
-
} else if (isInitializeRequest(req.body)) {
|
|
209
|
-
// New session
|
|
210
|
-
//
|
|
211
|
-
|
|
212
|
-
|
|
208
|
+
} else if (isInitializeRequest(req.body) || (sessionId && !transports[sessionId])) {
|
|
209
|
+
// New session, OR stale session with any request type (server restart recovery).
|
|
210
|
+
// Many MCP clients (Claude mobile, Claude Code) cache the session ID and fail to
|
|
211
|
+
// re-initialize on 400. To survive container restarts transparently, we synthesize
|
|
212
|
+
// a fresh initialize under the hood whenever we see a stale session.
|
|
213
|
+
const isStaleRecovery = sessionId && !transports[sessionId] && !isInitializeRequest(req.body);
|
|
214
|
+
if (sessionId && isInitializeRequest(req.body)) {
|
|
215
|
+
console.log(`[HTTP] Stale session ${sessionId} — client re-initialized`);
|
|
216
|
+
} else if (isStaleRecovery) {
|
|
217
|
+
console.log(`[HTTP] Stale session ${sessionId} — auto-reinitializing transparently (${req.body?.method || "unknown"})`);
|
|
213
218
|
}
|
|
214
219
|
|
|
215
220
|
const newSessionId = randomUUID();
|
|
@@ -235,18 +240,59 @@ export function startHttpServer(port = 3100) {
|
|
|
235
240
|
|
|
236
241
|
const server = createServer(newSessionId);
|
|
237
242
|
await server.connect(transport);
|
|
243
|
+
|
|
244
|
+
if (isStaleRecovery) {
|
|
245
|
+
// Synthesize an initialize handshake so the SDK transport is in "ready" state
|
|
246
|
+
// before we dispatch the real request. We call handleRequest twice: once with a
|
|
247
|
+
// fake initialize (response is discarded), then with the real payload.
|
|
248
|
+
const fakeInit = {
|
|
249
|
+
jsonrpc: "2.0",
|
|
250
|
+
id: `__auto_init_${newSessionId}`,
|
|
251
|
+
method: "initialize",
|
|
252
|
+
params: {
|
|
253
|
+
protocolVersion: "2025-03-26",
|
|
254
|
+
capabilities: {},
|
|
255
|
+
clientInfo: { name: "auto-reinit", version: "1.0" },
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
// Mock response object that swallows the init response
|
|
259
|
+
const mockRes = {
|
|
260
|
+
_h: {},
|
|
261
|
+
statusCode: 200,
|
|
262
|
+
headersSent: false,
|
|
263
|
+
setHeader(k, v) { this._h[k.toLowerCase()] = v; },
|
|
264
|
+
getHeader(k) { return this._h[k.toLowerCase()]; },
|
|
265
|
+
removeHeader(k) { delete this._h[k.toLowerCase()]; },
|
|
266
|
+
writeHead() { return this; },
|
|
267
|
+
write() { return true; },
|
|
268
|
+
end() { this.headersSent = true; return this; },
|
|
269
|
+
json() { return this; },
|
|
270
|
+
status() { return this; },
|
|
271
|
+
on() {},
|
|
272
|
+
once() {},
|
|
273
|
+
emit() {},
|
|
274
|
+
};
|
|
275
|
+
// Strip the stale session-id header so the SDK treats this as a new init
|
|
276
|
+
const initReq = { ...req, headers: { ...req.headers } };
|
|
277
|
+
delete initReq.headers["mcp-session-id"];
|
|
278
|
+
try {
|
|
279
|
+
await transport.handleRequest(initReq, mockRes, fakeInit);
|
|
280
|
+
} catch (e) {
|
|
281
|
+
console.error("[HTTP] Auto-reinit synthetic initialize failed:", e.message);
|
|
282
|
+
}
|
|
283
|
+
// Ensure the transport is registered under the new id and update the real request
|
|
284
|
+
transports[newSessionId] = transport;
|
|
285
|
+
// Overwrite the client-supplied (stale) session-id with the new one so the SDK
|
|
286
|
+
// routes the real request to the freshly-initialized transport.
|
|
287
|
+
req.headers["mcp-session-id"] = newSessionId;
|
|
288
|
+
// Tell the client about the new session id so future requests use it.
|
|
289
|
+
res.setHeader("mcp-session-id", newSessionId);
|
|
290
|
+
await transport.handleRequest(req, res, req.body);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
238
294
|
await transport.handleRequest(req, res, req.body);
|
|
239
295
|
return;
|
|
240
|
-
} else if (sessionId && !transports[sessionId]) {
|
|
241
|
-
// Stale session (non-initialize request) — tell client to re-initialize.
|
|
242
|
-
// This happens after server restarts when the client still has the old session ID.
|
|
243
|
-
console.log(`[HTTP] Stale session ${sessionId} — returning 400 to trigger re-init`);
|
|
244
|
-
res.status(400).json({
|
|
245
|
-
jsonrpc: "2.0",
|
|
246
|
-
error: { code: -32600, message: "Session expired. Please re-initialize." },
|
|
247
|
-
id: req.body?.id || null,
|
|
248
|
-
});
|
|
249
|
-
return;
|
|
250
296
|
} else {
|
|
251
297
|
res.status(400).json({
|
|
252
298
|
jsonrpc: "2.0",
|