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.
@@ -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
+ `);