fastmcp 3.19.2 → 3.20.0
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 +33 -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 +449 -1
- package/src/FastMCP.ts +73 -5
- package/src/examples/session-id-counter.ts +230 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example demonstrating session ID support in FastMCP
|
|
3
|
+
*
|
|
4
|
+
* This example shows how to use the sessionId from the Mcp-Session-Id header
|
|
5
|
+
* to implement per-session state management, such as maintaining counters
|
|
6
|
+
* or tracking user-specific data across multiple requests.
|
|
7
|
+
*
|
|
8
|
+
* To run this example:
|
|
9
|
+
* npx fastmcp dev src/examples/session-id-counter.ts --http-stream
|
|
10
|
+
*
|
|
11
|
+
* Then test with multiple clients to see how each session maintains its own state.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
|
|
16
|
+
import { FastMCP } from "../FastMCP.js";
|
|
17
|
+
|
|
18
|
+
interface UserSession {
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
role: "admin" | "user";
|
|
21
|
+
userId: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const server = new FastMCP<UserSession>({
|
|
25
|
+
authenticate: async (request) => {
|
|
26
|
+
if (!request) {
|
|
27
|
+
// stdio transport
|
|
28
|
+
return {
|
|
29
|
+
role: "user" as const,
|
|
30
|
+
userId: process.env.USER_ID || "default-user",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// HTTP transport - check authorization header
|
|
35
|
+
const authHeader = request.headers["authorization"] as string;
|
|
36
|
+
|
|
37
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
38
|
+
throw new Response("Missing or invalid authorization header", {
|
|
39
|
+
status: 401,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const token = authHeader.substring(7);
|
|
44
|
+
|
|
45
|
+
// Mock token validation
|
|
46
|
+
if (token === "admin-token") {
|
|
47
|
+
return {
|
|
48
|
+
role: "admin" as const,
|
|
49
|
+
userId: "admin-001",
|
|
50
|
+
};
|
|
51
|
+
} else if (token === "user-token") {
|
|
52
|
+
return {
|
|
53
|
+
role: "user" as const,
|
|
54
|
+
userId: "user-001",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
throw new Response("Invalid token", { status: 401 });
|
|
59
|
+
},
|
|
60
|
+
name: "Session ID Counter Demo",
|
|
61
|
+
version: "1.0.0",
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Per-session counter storage
|
|
65
|
+
// In a real application, this could be Redis, a database, or any other storage
|
|
66
|
+
const sessionCounters = new Map<string, number>();
|
|
67
|
+
const sessionData = new Map<
|
|
68
|
+
string,
|
|
69
|
+
{ createdAt: Date; lastAccessed: Date; requestCount: number }
|
|
70
|
+
>();
|
|
71
|
+
|
|
72
|
+
// Tool to increment a per-session counter
|
|
73
|
+
server.addTool({
|
|
74
|
+
description:
|
|
75
|
+
"Increment a counter that is unique to your session. Each client session maintains its own independent counter.",
|
|
76
|
+
execute: async (_args, context) => {
|
|
77
|
+
if (!context.sessionId) {
|
|
78
|
+
return "❌ No session ID available. This tool requires HTTP transport with session tracking.";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const currentCount = sessionCounters.get(context.sessionId) || 0;
|
|
82
|
+
const newCount = currentCount + 1;
|
|
83
|
+
sessionCounters.set(context.sessionId, newCount);
|
|
84
|
+
|
|
85
|
+
// Update session metadata
|
|
86
|
+
const metadata = sessionData.get(context.sessionId) || {
|
|
87
|
+
createdAt: new Date(),
|
|
88
|
+
lastAccessed: new Date(),
|
|
89
|
+
requestCount: 0,
|
|
90
|
+
};
|
|
91
|
+
metadata.lastAccessed = new Date();
|
|
92
|
+
metadata.requestCount += 1;
|
|
93
|
+
sessionData.set(context.sessionId, metadata);
|
|
94
|
+
|
|
95
|
+
return `✓ Counter incremented!
|
|
96
|
+
|
|
97
|
+
Session ID: ${context.sessionId}
|
|
98
|
+
Counter Value: ${newCount}
|
|
99
|
+
User: ${context.session?.userId}
|
|
100
|
+
Role: ${context.session?.role}
|
|
101
|
+
|
|
102
|
+
Session Info:
|
|
103
|
+
- Created: ${metadata.createdAt.toISOString()}
|
|
104
|
+
- Last Accessed: ${metadata.lastAccessed.toISOString()}
|
|
105
|
+
- Total Requests: ${metadata.requestCount}`;
|
|
106
|
+
},
|
|
107
|
+
name: "increment-counter",
|
|
108
|
+
parameters: z.object({}),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Tool to get the current counter value
|
|
112
|
+
server.addTool({
|
|
113
|
+
description: "Get the current value of your session's counter",
|
|
114
|
+
execute: async (_args, context) => {
|
|
115
|
+
if (!context.sessionId) {
|
|
116
|
+
return "❌ No session ID available. This tool requires HTTP transport with session tracking.";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const currentCount = sessionCounters.get(context.sessionId) || 0;
|
|
120
|
+
const metadata = sessionData.get(context.sessionId);
|
|
121
|
+
|
|
122
|
+
return `Session ID: ${context.sessionId}
|
|
123
|
+
Counter Value: ${currentCount}
|
|
124
|
+
User: ${context.session?.userId}
|
|
125
|
+
${metadata ? `\nSession created: ${metadata.createdAt.toISOString()}\nTotal requests: ${metadata.requestCount}` : ""}`;
|
|
126
|
+
},
|
|
127
|
+
name: "get-counter",
|
|
128
|
+
parameters: z.object({}),
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Tool to reset the counter
|
|
132
|
+
server.addTool({
|
|
133
|
+
description: "Reset your session's counter to zero",
|
|
134
|
+
execute: async (_args, context) => {
|
|
135
|
+
if (!context.sessionId) {
|
|
136
|
+
return "❌ No session ID available. This tool requires HTTP transport with session tracking.";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
sessionCounters.set(context.sessionId, 0);
|
|
140
|
+
|
|
141
|
+
return `✓ Counter reset to 0 for session ${context.sessionId}`;
|
|
142
|
+
},
|
|
143
|
+
name: "reset-counter",
|
|
144
|
+
parameters: z.object({}),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Tool to list all active sessions (admin only)
|
|
148
|
+
server.addTool({
|
|
149
|
+
description: "List all active sessions and their counter values (admin only)",
|
|
150
|
+
execute: async (_args, context) => {
|
|
151
|
+
if (context.session?.role !== "admin") {
|
|
152
|
+
return "❌ Access denied. This tool requires admin role.";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (sessionCounters.size === 0) {
|
|
156
|
+
return "No active sessions with counters.";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const sessions = Array.from(sessionCounters.entries())
|
|
160
|
+
.map(([sessionId, count]) => {
|
|
161
|
+
const metadata = sessionData.get(sessionId);
|
|
162
|
+
return `- Session: ${sessionId.substring(0, 8)}...
|
|
163
|
+
Counter: ${count}
|
|
164
|
+
Created: ${metadata?.createdAt.toISOString() || "unknown"}
|
|
165
|
+
Requests: ${metadata?.requestCount || 0}`;
|
|
166
|
+
})
|
|
167
|
+
.join("\n\n");
|
|
168
|
+
|
|
169
|
+
return `Active Sessions (${sessionCounters.size}):\n\n${sessions}`;
|
|
170
|
+
},
|
|
171
|
+
name: "list-sessions",
|
|
172
|
+
parameters: z.object({}),
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Tool to demonstrate request ID tracking
|
|
176
|
+
server.addTool({
|
|
177
|
+
description:
|
|
178
|
+
"Show both session ID and request ID to demonstrate per-request tracking",
|
|
179
|
+
execute: async (_args, context) => {
|
|
180
|
+
return `Session & Request Information:
|
|
181
|
+
|
|
182
|
+
Session ID: ${context.sessionId || "N/A"}
|
|
183
|
+
Request ID: ${context.requestId || "N/A"}
|
|
184
|
+
User ID: ${context.session?.userId || "N/A"}
|
|
185
|
+
Role: ${context.session?.role || "N/A"}
|
|
186
|
+
|
|
187
|
+
The session ID remains constant across multiple requests from the same client,
|
|
188
|
+
while the request ID is unique for each individual request.`;
|
|
189
|
+
},
|
|
190
|
+
name: "show-ids",
|
|
191
|
+
parameters: z.object({}),
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Start the server
|
|
195
|
+
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
|
|
196
|
+
|
|
197
|
+
server.start({
|
|
198
|
+
httpStream: { port: PORT },
|
|
199
|
+
transportType: "httpStream",
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
console.log(`
|
|
203
|
+
🚀 Session ID Counter Demo server running!
|
|
204
|
+
|
|
205
|
+
Server: http://localhost:${PORT}/mcp
|
|
206
|
+
Health: http://localhost:${PORT}/health
|
|
207
|
+
|
|
208
|
+
Test with curl:
|
|
209
|
+
# User token
|
|
210
|
+
curl -H "Authorization: Bearer user-token" \\
|
|
211
|
+
-H "Content-Type: application/json" \\
|
|
212
|
+
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}' \\
|
|
213
|
+
http://localhost:${PORT}/mcp
|
|
214
|
+
|
|
215
|
+
# Then call tools (use the Mcp-Session-Id from the initialize response)
|
|
216
|
+
curl -H "Authorization: Bearer user-token" \\
|
|
217
|
+
-H "Mcp-Session-Id: YOUR_SESSION_ID" \\
|
|
218
|
+
-H "Content-Type: application/json" \\
|
|
219
|
+
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"increment-counter","arguments":{}}}' \\
|
|
220
|
+
http://localhost:${PORT}/mcp
|
|
221
|
+
|
|
222
|
+
Available tools:
|
|
223
|
+
- increment-counter: Increment your session's counter
|
|
224
|
+
- get-counter: Get current counter value
|
|
225
|
+
- reset-counter: Reset counter to zero
|
|
226
|
+
- list-sessions: List all sessions (admin only)
|
|
227
|
+
- show-ids: Display session and request IDs
|
|
228
|
+
|
|
229
|
+
Try connecting with multiple clients to see how each maintains its own counter!
|
|
230
|
+
`);
|