fastmcp 3.19.3 → 3.20.1
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/.roo/mcp.json +11 -0
- package/README.md +83 -0
- package/dist/FastMCP.d.ts +16 -1
- package/dist/FastMCP.js +29 -5
- package/dist/FastMCP.js.map +1 -1
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/FastMCP.session-id.test.ts +359 -0
- package/src/FastMCP.test.ts +3 -1
- package/src/FastMCP.ts +58 -5
- package/src/examples/session-id-counter.ts +230 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
import { FastMCP } from "./FastMCP.js";
|
|
7
|
+
|
|
8
|
+
interface TestAuth {
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
userId: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe("FastMCP Session ID Support", () => {
|
|
14
|
+
describe("HTTP Stream transport", () => {
|
|
15
|
+
it("should expose sessionId to tool handlers from Mcp-Session-Id header", async () => {
|
|
16
|
+
const server = new FastMCP<TestAuth>({
|
|
17
|
+
authenticate: async () => ({
|
|
18
|
+
userId: "test-user",
|
|
19
|
+
}),
|
|
20
|
+
name: "test-server",
|
|
21
|
+
version: "1.0.0",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
let capturedSessionId: string | undefined;
|
|
25
|
+
let capturedRequestId: string | undefined;
|
|
26
|
+
|
|
27
|
+
server.addTool({
|
|
28
|
+
description: "Test tool that captures session and request IDs",
|
|
29
|
+
execute: async (_args, context) => {
|
|
30
|
+
capturedSessionId = context.sessionId;
|
|
31
|
+
capturedRequestId = context.requestId;
|
|
32
|
+
return `Session ID: ${context.sessionId || "none"}, Request ID: ${context.requestId || "none"}`;
|
|
33
|
+
},
|
|
34
|
+
name: "capture-ids",
|
|
35
|
+
parameters: z.object({}),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const port = 3000 + Math.floor(Math.random() * 1000);
|
|
39
|
+
|
|
40
|
+
await server.start({
|
|
41
|
+
httpStream: {
|
|
42
|
+
port,
|
|
43
|
+
},
|
|
44
|
+
transportType: "httpStream",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const transport = new StreamableHTTPClientTransport(
|
|
49
|
+
new URL(`http://localhost:${port}/mcp`),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const client = new Client(
|
|
53
|
+
{
|
|
54
|
+
name: "test-client",
|
|
55
|
+
version: "1.0.0",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
capabilities: {},
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
await client.connect(transport);
|
|
63
|
+
|
|
64
|
+
const result = await client.callTool({
|
|
65
|
+
arguments: {},
|
|
66
|
+
name: "capture-ids",
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(result).toBeDefined();
|
|
70
|
+
expect(capturedSessionId).toBeDefined();
|
|
71
|
+
expect(typeof capturedSessionId).toBe("string");
|
|
72
|
+
expect(capturedSessionId).toMatch(/^[0-9a-f-]+$/); // UUID format
|
|
73
|
+
|
|
74
|
+
// Request ID may or may not be provided by the client
|
|
75
|
+
// If provided, it should be a string
|
|
76
|
+
if (capturedRequestId !== undefined) {
|
|
77
|
+
expect(typeof capturedRequestId).toBe("string");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await client.close();
|
|
81
|
+
} finally {
|
|
82
|
+
await server.stop();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should maintain the same sessionId across multiple requests", async () => {
|
|
87
|
+
const server = new FastMCP<TestAuth>({
|
|
88
|
+
authenticate: async () => ({
|
|
89
|
+
userId: "test-user",
|
|
90
|
+
}),
|
|
91
|
+
name: "test-server",
|
|
92
|
+
version: "1.0.0",
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const capturedSessionIds: (string | undefined)[] = [];
|
|
96
|
+
|
|
97
|
+
server.addTool({
|
|
98
|
+
description: "Test tool that captures session ID",
|
|
99
|
+
execute: async (_args, context) => {
|
|
100
|
+
capturedSessionIds.push(context.sessionId);
|
|
101
|
+
return `Session ID: ${context.sessionId}`;
|
|
102
|
+
},
|
|
103
|
+
name: "capture-session",
|
|
104
|
+
parameters: z.object({}),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const port = 3000 + Math.floor(Math.random() * 1000);
|
|
108
|
+
|
|
109
|
+
await server.start({
|
|
110
|
+
httpStream: {
|
|
111
|
+
port,
|
|
112
|
+
},
|
|
113
|
+
transportType: "httpStream",
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const transport = new StreamableHTTPClientTransport(
|
|
118
|
+
new URL(`http://localhost:${port}/mcp`),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const client = new Client(
|
|
122
|
+
{
|
|
123
|
+
name: "test-client",
|
|
124
|
+
version: "1.0.0",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
capabilities: {},
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
await client.connect(transport);
|
|
132
|
+
|
|
133
|
+
// Make multiple requests
|
|
134
|
+
await client.callTool({
|
|
135
|
+
arguments: {},
|
|
136
|
+
name: "capture-session",
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await client.callTool({
|
|
140
|
+
arguments: {},
|
|
141
|
+
name: "capture-session",
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await client.callTool({
|
|
145
|
+
arguments: {},
|
|
146
|
+
name: "capture-session",
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// All requests should have the same session ID
|
|
150
|
+
expect(capturedSessionIds).toHaveLength(3);
|
|
151
|
+
expect(capturedSessionIds[0]).toBeDefined();
|
|
152
|
+
expect(capturedSessionIds[0]).toBe(capturedSessionIds[1]);
|
|
153
|
+
expect(capturedSessionIds[1]).toBe(capturedSessionIds[2]);
|
|
154
|
+
|
|
155
|
+
await client.close();
|
|
156
|
+
} finally {
|
|
157
|
+
await server.stop();
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should support per-session state management using sessionId", async () => {
|
|
162
|
+
const server = new FastMCP<TestAuth>({
|
|
163
|
+
authenticate: async () => ({
|
|
164
|
+
userId: "test-user",
|
|
165
|
+
}),
|
|
166
|
+
name: "test-server",
|
|
167
|
+
version: "1.0.0",
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Per-session counter storage
|
|
171
|
+
const sessionCounters = new Map<string, number>();
|
|
172
|
+
|
|
173
|
+
server.addTool({
|
|
174
|
+
description: "Increment a per-session counter",
|
|
175
|
+
execute: async (_args, context) => {
|
|
176
|
+
if (!context.sessionId) {
|
|
177
|
+
return "No session ID available";
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const currentCount = sessionCounters.get(context.sessionId) || 0;
|
|
181
|
+
const newCount = currentCount + 1;
|
|
182
|
+
sessionCounters.set(context.sessionId, newCount);
|
|
183
|
+
|
|
184
|
+
return `Counter for session ${context.sessionId}: ${newCount}`;
|
|
185
|
+
},
|
|
186
|
+
name: "increment-counter",
|
|
187
|
+
parameters: z.object({}),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const port = 3000 + Math.floor(Math.random() * 1000);
|
|
191
|
+
|
|
192
|
+
await server.start({
|
|
193
|
+
httpStream: {
|
|
194
|
+
port,
|
|
195
|
+
},
|
|
196
|
+
transportType: "httpStream",
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
// Create two separate clients with different sessions
|
|
201
|
+
const transport1 = new StreamableHTTPClientTransport(
|
|
202
|
+
new URL(`http://localhost:${port}/mcp`),
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const client1 = new Client(
|
|
206
|
+
{
|
|
207
|
+
name: "test-client-1",
|
|
208
|
+
version: "1.0.0",
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
capabilities: {},
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
await client1.connect(transport1);
|
|
216
|
+
|
|
217
|
+
const transport2 = new StreamableHTTPClientTransport(
|
|
218
|
+
new URL(`http://localhost:${port}/mcp`),
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const client2 = new Client(
|
|
222
|
+
{
|
|
223
|
+
name: "test-client-2",
|
|
224
|
+
version: "1.0.0",
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
capabilities: {},
|
|
228
|
+
},
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
await client2.connect(transport2);
|
|
232
|
+
|
|
233
|
+
// Increment counter for client 1 twice
|
|
234
|
+
const result1a = await client1.callTool({
|
|
235
|
+
arguments: {},
|
|
236
|
+
name: "increment-counter",
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const result1b = await client1.callTool({
|
|
240
|
+
arguments: {},
|
|
241
|
+
name: "increment-counter",
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Increment counter for client 2 once
|
|
245
|
+
const result2 = await client2.callTool({
|
|
246
|
+
arguments: {},
|
|
247
|
+
name: "increment-counter",
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Verify counters are independent per session
|
|
251
|
+
expect((result1a.content as Array<{ text: string }>)[0].text).toContain(
|
|
252
|
+
": 1",
|
|
253
|
+
);
|
|
254
|
+
expect((result1b.content as Array<{ text: string }>)[0].text).toContain(
|
|
255
|
+
": 2",
|
|
256
|
+
);
|
|
257
|
+
expect((result2.content as Array<{ text: string }>)[0].text).toContain(
|
|
258
|
+
": 1",
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
await client1.close();
|
|
262
|
+
await client2.close();
|
|
263
|
+
} finally {
|
|
264
|
+
await server.stop();
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("should work in stateless mode without persistent sessionId", async () => {
|
|
269
|
+
const server = new FastMCP<TestAuth>({
|
|
270
|
+
authenticate: async () => ({
|
|
271
|
+
userId: "test-user",
|
|
272
|
+
}),
|
|
273
|
+
name: "test-server",
|
|
274
|
+
version: "1.0.0",
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
let capturedSessionId: string | undefined;
|
|
278
|
+
|
|
279
|
+
server.addTool({
|
|
280
|
+
description: "Test tool in stateless mode",
|
|
281
|
+
execute: async (_args, context) => {
|
|
282
|
+
capturedSessionId = context.sessionId;
|
|
283
|
+
return `Session ID: ${context.sessionId || "none"}`;
|
|
284
|
+
},
|
|
285
|
+
name: "test-stateless",
|
|
286
|
+
parameters: z.object({}),
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const port = 3000 + Math.floor(Math.random() * 1000);
|
|
290
|
+
|
|
291
|
+
await server.start({
|
|
292
|
+
httpStream: {
|
|
293
|
+
port,
|
|
294
|
+
stateless: true,
|
|
295
|
+
},
|
|
296
|
+
transportType: "httpStream",
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
const transport = new StreamableHTTPClientTransport(
|
|
301
|
+
new URL(`http://localhost:${port}/mcp`),
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
const client = new Client(
|
|
305
|
+
{
|
|
306
|
+
name: "test-client",
|
|
307
|
+
version: "1.0.0",
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
capabilities: {},
|
|
311
|
+
},
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
await client.connect(transport);
|
|
315
|
+
|
|
316
|
+
await client.callTool({
|
|
317
|
+
arguments: {},
|
|
318
|
+
name: "test-stateless",
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// In stateless mode, sessionId should be undefined
|
|
322
|
+
expect(capturedSessionId).toBeUndefined();
|
|
323
|
+
|
|
324
|
+
await client.close();
|
|
325
|
+
} finally {
|
|
326
|
+
await server.stop();
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
describe("stdio transport", () => {
|
|
332
|
+
it("should not have sessionId in stdio transport", async () => {
|
|
333
|
+
const server = new FastMCP<TestAuth>({
|
|
334
|
+
authenticate: async () => ({
|
|
335
|
+
userId: "test-user",
|
|
336
|
+
}),
|
|
337
|
+
name: "test-server",
|
|
338
|
+
version: "1.0.0",
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
let capturedSessionId: string | undefined;
|
|
342
|
+
|
|
343
|
+
server.addTool({
|
|
344
|
+
description: "Test tool for stdio",
|
|
345
|
+
execute: async (_args, context) => {
|
|
346
|
+
capturedSessionId = context.sessionId;
|
|
347
|
+
return `Session ID: ${context.sessionId || "none"}`;
|
|
348
|
+
},
|
|
349
|
+
name: "test-stdio",
|
|
350
|
+
parameters: z.object({}),
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
await server.start({ transportType: "stdio" });
|
|
354
|
+
|
|
355
|
+
// In stdio transport, sessionId should be undefined
|
|
356
|
+
expect(capturedSessionId).toBeUndefined();
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
});
|
package/src/FastMCP.test.ts
CHANGED
|
@@ -148,7 +148,7 @@ test("adds tools with Zod v4 schema", async () => {
|
|
|
148
148
|
{
|
|
149
149
|
description: "Add two numbers (using Zod v4 schema)",
|
|
150
150
|
inputSchema: {
|
|
151
|
-
$schema: "
|
|
151
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
152
152
|
additionalProperties: false,
|
|
153
153
|
properties: {
|
|
154
154
|
a: { type: "number" },
|
|
@@ -2416,7 +2416,9 @@ test("provides auth to tools", async () => {
|
|
|
2416
2416
|
warn: expect.any(Function),
|
|
2417
2417
|
},
|
|
2418
2418
|
reportProgress: expect.any(Function),
|
|
2419
|
+
requestId: undefined,
|
|
2419
2420
|
session: { id: 1 },
|
|
2421
|
+
sessionId: expect.any(String),
|
|
2420
2422
|
streamContent: expect.any(Function),
|
|
2421
2423
|
},
|
|
2422
2424
|
);
|
package/src/FastMCP.ts
CHANGED
|
@@ -210,7 +210,19 @@ type Context<T extends FastMCPSessionAuth> = {
|
|
|
210
210
|
warn: (message: string, data?: SerializableValue) => void;
|
|
211
211
|
};
|
|
212
212
|
reportProgress: (progress: Progress) => Promise<void>;
|
|
213
|
+
/**
|
|
214
|
+
* Request ID from the current MCP request.
|
|
215
|
+
* Available for all transports when the client provides it.
|
|
216
|
+
*/
|
|
217
|
+
requestId?: string;
|
|
213
218
|
session: T | undefined;
|
|
219
|
+
/**
|
|
220
|
+
* Session ID from the Mcp-Session-Id header.
|
|
221
|
+
* Only available for HTTP-based transports (SSE, HTTP Stream).
|
|
222
|
+
* Can be used to track per-session state, implement session-specific
|
|
223
|
+
* counters, or maintain user-specific data across multiple requests.
|
|
224
|
+
*/
|
|
225
|
+
sessionId?: string;
|
|
214
226
|
streamContent: (content: Content | Content[]) => Promise<void>;
|
|
215
227
|
};
|
|
216
228
|
|
|
@@ -936,6 +948,12 @@ export class FastMCPSession<
|
|
|
936
948
|
public get server(): Server {
|
|
937
949
|
return this.#server;
|
|
938
950
|
}
|
|
951
|
+
public get sessionId(): string | undefined {
|
|
952
|
+
return this.#sessionId;
|
|
953
|
+
}
|
|
954
|
+
public set sessionId(value: string | undefined) {
|
|
955
|
+
this.#sessionId = value;
|
|
956
|
+
}
|
|
939
957
|
#auth: T | undefined;
|
|
940
958
|
#capabilities: ServerCapabilities = {};
|
|
941
959
|
#clientCapabilities?: ClientCapabilities;
|
|
@@ -959,6 +977,12 @@ export class FastMCPSession<
|
|
|
959
977
|
|
|
960
978
|
#server: Server;
|
|
961
979
|
|
|
980
|
+
/**
|
|
981
|
+
* Session ID from the Mcp-Session-Id header (HTTP transports only).
|
|
982
|
+
* Used to track per-session state across multiple requests.
|
|
983
|
+
*/
|
|
984
|
+
#sessionId?: string;
|
|
985
|
+
|
|
962
986
|
#utils?: ServerOptions<T>["utils"];
|
|
963
987
|
|
|
964
988
|
constructor({
|
|
@@ -971,6 +995,7 @@ export class FastMCPSession<
|
|
|
971
995
|
resources,
|
|
972
996
|
resourcesTemplates,
|
|
973
997
|
roots,
|
|
998
|
+
sessionId,
|
|
974
999
|
tools,
|
|
975
1000
|
transportType,
|
|
976
1001
|
utils,
|
|
@@ -985,6 +1010,7 @@ export class FastMCPSession<
|
|
|
985
1010
|
resources: Resource<T>[];
|
|
986
1011
|
resourcesTemplates: InputResourceTemplate<T>[];
|
|
987
1012
|
roots?: ServerOptions<T>["roots"];
|
|
1013
|
+
sessionId?: string;
|
|
988
1014
|
tools: Tool<T>[];
|
|
989
1015
|
transportType?: "httpStream" | "stdio";
|
|
990
1016
|
utils?: ServerOptions<T>["utils"];
|
|
@@ -996,6 +1022,7 @@ export class FastMCPSession<
|
|
|
996
1022
|
this.#logger = logger;
|
|
997
1023
|
this.#pingConfig = ping;
|
|
998
1024
|
this.#rootsConfig = roots;
|
|
1025
|
+
this.#sessionId = sessionId;
|
|
999
1026
|
this.#needsEventLoopFlush = transportType === "httpStream";
|
|
1000
1027
|
|
|
1001
1028
|
if (tools.length) {
|
|
@@ -1077,6 +1104,16 @@ export class FastMCPSession<
|
|
|
1077
1104
|
try {
|
|
1078
1105
|
await this.#server.connect(transport);
|
|
1079
1106
|
|
|
1107
|
+
// Extract session ID from transport if available (HTTP transports only)
|
|
1108
|
+
if ("sessionId" in transport) {
|
|
1109
|
+
const transportWithSessionId = transport as {
|
|
1110
|
+
sessionId?: string;
|
|
1111
|
+
} & Transport;
|
|
1112
|
+
if (typeof transportWithSessionId.sessionId === "string") {
|
|
1113
|
+
this.#sessionId = transportWithSessionId.sessionId;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1080
1117
|
let attempt = 0;
|
|
1081
1118
|
const maxAttempts = 10;
|
|
1082
1119
|
const retryDelay = 100;
|
|
@@ -1789,7 +1826,12 @@ export class FastMCPSession<
|
|
|
1789
1826
|
},
|
|
1790
1827
|
log,
|
|
1791
1828
|
reportProgress,
|
|
1829
|
+
requestId:
|
|
1830
|
+
typeof request.params?._meta?.requestId === "string"
|
|
1831
|
+
? request.params._meta.requestId
|
|
1832
|
+
: undefined,
|
|
1792
1833
|
session: this.#auth,
|
|
1834
|
+
sessionId: this.#sessionId,
|
|
1793
1835
|
streamContent,
|
|
1794
1836
|
});
|
|
1795
1837
|
|
|
@@ -2110,7 +2152,7 @@ export class FastMCP<
|
|
|
2110
2152
|
);
|
|
2111
2153
|
|
|
2112
2154
|
this.#httpStreamServer = await startHTTPServer<FastMCPSession<T>>({
|
|
2113
|
-
authenticate: this.#authenticate,
|
|
2155
|
+
...(this.#authenticate ? { authenticate: this.#authenticate } : {}),
|
|
2114
2156
|
createServer: async (request) => {
|
|
2115
2157
|
let auth: T | undefined;
|
|
2116
2158
|
|
|
@@ -2124,9 +2166,14 @@ export class FastMCP<
|
|
|
2124
2166
|
}
|
|
2125
2167
|
}
|
|
2126
2168
|
|
|
2169
|
+
// Extract session ID from headers
|
|
2170
|
+
const sessionId = Array.isArray(request.headers["mcp-session-id"])
|
|
2171
|
+
? request.headers["mcp-session-id"][0]
|
|
2172
|
+
: request.headers["mcp-session-id"];
|
|
2173
|
+
|
|
2127
2174
|
// In stateless mode, create a new session for each request
|
|
2128
2175
|
// without persisting it in the sessions array
|
|
2129
|
-
return this.#createSession(auth);
|
|
2176
|
+
return this.#createSession(auth, sessionId);
|
|
2130
2177
|
},
|
|
2131
2178
|
enableJsonResponse: httpConfig.enableJsonResponse,
|
|
2132
2179
|
eventStore: httpConfig.eventStore,
|
|
@@ -2151,7 +2198,7 @@ export class FastMCP<
|
|
|
2151
2198
|
} else {
|
|
2152
2199
|
// Regular mode with session management
|
|
2153
2200
|
this.#httpStreamServer = await startHTTPServer<FastMCPSession<T>>({
|
|
2154
|
-
authenticate: this.#authenticate,
|
|
2201
|
+
...(this.#authenticate ? { authenticate: this.#authenticate } : {}),
|
|
2155
2202
|
createServer: async (request) => {
|
|
2156
2203
|
let auth: T | undefined;
|
|
2157
2204
|
|
|
@@ -2159,7 +2206,12 @@ export class FastMCP<
|
|
|
2159
2206
|
auth = await this.#authenticate(request);
|
|
2160
2207
|
}
|
|
2161
2208
|
|
|
2162
|
-
|
|
2209
|
+
// Extract session ID from headers
|
|
2210
|
+
const sessionId = Array.isArray(request.headers["mcp-session-id"])
|
|
2211
|
+
? request.headers["mcp-session-id"][0]
|
|
2212
|
+
: request.headers["mcp-session-id"];
|
|
2213
|
+
|
|
2214
|
+
return this.#createSession(auth, sessionId);
|
|
2163
2215
|
},
|
|
2164
2216
|
enableJsonResponse: httpConfig.enableJsonResponse,
|
|
2165
2217
|
eventStore: httpConfig.eventStore,
|
|
@@ -2218,7 +2270,7 @@ export class FastMCP<
|
|
|
2218
2270
|
* Creates a new FastMCPSession instance with the current configuration.
|
|
2219
2271
|
* Used both for regular sessions and stateless requests.
|
|
2220
2272
|
*/
|
|
2221
|
-
#createSession(auth?: T): FastMCPSession<T> {
|
|
2273
|
+
#createSession(auth?: T, sessionId?: string): FastMCPSession<T> {
|
|
2222
2274
|
// Check if authentication failed
|
|
2223
2275
|
if (
|
|
2224
2276
|
auth &&
|
|
@@ -2249,6 +2301,7 @@ export class FastMCP<
|
|
|
2249
2301
|
resources: this.#resources,
|
|
2250
2302
|
resourcesTemplates: this.#resourcesTemplates,
|
|
2251
2303
|
roots: this.#options.roots,
|
|
2304
|
+
sessionId,
|
|
2252
2305
|
tools: allowedTools,
|
|
2253
2306
|
transportType: "httpStream",
|
|
2254
2307
|
utils: this.#options.utils,
|