fastmcp 1.6.1 → 1.8.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # FastMCP
2
2
 
3
- A TypeScript framework for building [MCP](https://modelcontextprotocol.io/) servers.
3
+ A TypeScript framework for building [MCP](https://modelcontextprotocol.io/) servers capable of handling client sessions.
4
4
 
5
5
  > [!NOTE]
6
6
  > For a Python implementation, see [FastMCP](https://github.com/jlowin/fastmcp).
@@ -8,13 +8,15 @@ A TypeScript framework for building [MCP](https://modelcontextprotocol.io/) serv
8
8
  ## Features
9
9
 
10
10
  - Simple Tool, Resource, Prompt definition
11
- - Full TypeScript support
12
- - Built-in support for [image content](#returning-an-image)
13
- - Built-in [logging](#logging)
14
- - Built-in [error handling](#errors)
15
- - Built-in CLI for [testing](#test-with-mcp-cli) and [debugging](#inspect-with-mcp-inspector)
16
- - Built-in support for [SSE](#sse)
17
- - Built-in [progress notifications](#progress)
11
+ - [Sessions](#sessions)
12
+ - [Image content](#returning-an-image)
13
+ - [Logging](#logging)
14
+ - [Error handling](#errors)
15
+ - [SSE](#sse)
16
+ - [Progress notifications](#progress)
17
+ - [Typed events](#typed-events)
18
+ - Roots
19
+ - CLI for [testing](#test-with-mcp-cli) and [debugging](#inspect-with-mcp-inspector)
18
20
 
19
21
  ## Installation
20
22
 
@@ -50,6 +52,8 @@ server.start({
50
52
  });
51
53
  ```
52
54
 
55
+ _That's it!_ You have a working MCP server.
56
+
53
57
  You can test the server in terminal with:
54
58
 
55
59
  ```bash
@@ -371,7 +375,7 @@ async load() {
371
375
 
372
376
  [Prompts](https://modelcontextprotocol.io/docs/concepts/prompts) enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs. They provide a powerful way to standardize and share common LLM interactions.
373
377
 
374
- ```js
378
+ ```ts
375
379
  server.addPrompt({
376
380
  name: "git-commit",
377
381
  description: "Generate a Git commit message",
@@ -388,6 +392,68 @@ server.addPrompt({
388
392
  });
389
393
  ```
390
394
 
395
+ ### Sessions
396
+
397
+ The `session` object is an instance of `FastMCPSession` and it describes active client sessions.
398
+
399
+ ```ts
400
+ server.sessions;
401
+ ```
402
+
403
+ We allocate a new server instance for each client connection to enable 1:1 communication between a client and the server.
404
+
405
+ ### Typed events
406
+
407
+ You can listen to events emitted by the server using the `on` method:
408
+
409
+ ```ts
410
+ server.on("connect", (event) => {
411
+ console.log("Client connected:", event.session);
412
+ });
413
+
414
+ server.on("disconnect", (event) => {
415
+ console.log("Client disconnected:", event.session);
416
+ });
417
+ ```
418
+
419
+ ## `FastMCPSession`
420
+
421
+ `FastMCPSession` represents a client session and provides methods to interact with the client.
422
+
423
+ Refer to [Sessions](#sessions) for examples of how to obtain a `FastMCPSession` instance.
424
+
425
+ ### `clientCapabilities`
426
+
427
+ The `clientCapabilities` property contains the client capabilities.
428
+
429
+ ```ts
430
+ session.clientCapabilities;
431
+ ```
432
+
433
+ ### `loggingLevel`
434
+
435
+ The `loggingLevel` property describes the logging level as set by the client.
436
+
437
+ ```ts
438
+ session.loggingLevel;
439
+ ```
440
+
441
+ ### `roots`
442
+
443
+ The `roots` property contains the roots as set by the client.
444
+
445
+ ```ts
446
+ session.roots;
447
+ ```
448
+
449
+ ### `server`
450
+
451
+ The `server` property contains an instance of MCP server that is associated with the session.
452
+
453
+ ```ts
454
+ session.server;
455
+ ```
456
+
391
457
  ## Running Your Server
392
458
 
393
459
  ### Test with `mcp-cli`
@@ -414,7 +480,7 @@ npx fastmcp inspect server.ts
414
480
  > [!NOTE]
415
481
  > If you've developed a server using FastMCP, please [submit a PR](https://github.com/punkpeye/fastmcp) to showcase it here!
416
482
 
417
- * https://github.com/apinetwork/piapi-mcp-server
483
+ - https://github.com/apinetwork/piapi-mcp-server
418
484
 
419
485
  ## Acknowledgements
420
486
 
package/dist/FastMCP.d.ts CHANGED
@@ -1,5 +1,21 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { ClientCapabilities, Root } from '@modelcontextprotocol/sdk/types.js';
1
3
  import { z } from 'zod';
4
+ import { StrictEventEmitter } from 'strict-event-emitter-types';
5
+ import { EventEmitter } from 'events';
6
+ import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
2
7
 
8
+ type SSEServer = {
9
+ close: () => Promise<void>;
10
+ };
11
+ type FastMCPEvents = {
12
+ connect: (event: {
13
+ session: FastMCPSession;
14
+ }) => void;
15
+ disconnect: (event: {
16
+ session: FastMCPSession;
17
+ }) => void;
18
+ };
3
19
  /**
4
20
  * Generates an image content object from a URL, file path, or buffer.
5
21
  */
@@ -100,19 +116,37 @@ type ServerOptions = {
100
116
  version: `${number}.${number}.${number}`;
101
117
  };
102
118
  type LoggingLevel = "debug" | "info" | "notice" | "warning" | "error" | "critical" | "alert" | "emergency";
103
- declare class FastMCP {
119
+ declare class FastMCPSession {
104
120
  #private;
105
- options: ServerOptions;
106
- constructor(options: ServerOptions);
107
- private setupHandlers;
121
+ constructor({ name, version, tools, resources, prompts, }: {
122
+ name: string;
123
+ version: string;
124
+ tools: Tool[];
125
+ resources: Resource[];
126
+ prompts: Prompt[];
127
+ });
128
+ get clientCapabilities(): ClientCapabilities | null;
129
+ get server(): Server;
130
+ connect(transport: Transport): Promise<void>;
131
+ get roots(): Root[];
132
+ close(): Promise<void>;
108
133
  private setupErrorHandling;
109
- /**
110
- * Returns the current logging level.
111
- */
112
134
  get loggingLevel(): LoggingLevel;
135
+ private setupLoggingHandlers;
113
136
  private setupToolHandlers;
114
137
  private setupResourceHandlers;
115
138
  private setupPromptHandlers;
139
+ }
140
+ declare const FastMCPEventEmitterBase: {
141
+ new (): StrictEventEmitter<EventEmitter, FastMCPEvents>;
142
+ };
143
+ declare class FastMCPEventEmitter extends FastMCPEventEmitterBase {
144
+ }
145
+ declare class FastMCP extends FastMCPEventEmitter {
146
+ #private;
147
+ options: ServerOptions;
148
+ constructor(options: ServerOptions);
149
+ get sessions(): FastMCPSession[];
116
150
  /**
117
151
  * Adds a tool to the server.
118
152
  */
@@ -143,4 +177,4 @@ declare class FastMCP {
143
177
  stop(): Promise<void>;
144
178
  }
145
179
 
146
- export { FastMCP, UserError, imageContent };
180
+ export { FastMCP, FastMCPSession, type SSEServer, UserError, imageContent };
package/dist/FastMCP.js CHANGED
@@ -14,8 +14,10 @@ import {
14
14
  } from "@modelcontextprotocol/sdk/types.js";
15
15
  import { zodToJsonSchema } from "zod-to-json-schema";
16
16
  import { z } from "zod";
17
+ import { setTimeout as delay } from "timers/promises";
17
18
  import { readFile } from "fs/promises";
18
19
  import { fileTypeFromBuffer } from "file-type";
20
+ import { EventEmitter } from "events";
19
21
  import { startSSEServer } from "mcp-proxy";
20
22
  var imageContent = async (input) => {
21
23
  let rawData;
@@ -84,55 +86,97 @@ var ContentResultZodSchema = z.object({
84
86
  content: ContentZodSchema.array(),
85
87
  isError: z.boolean().optional()
86
88
  }).strict();
87
- var FastMCP = class {
88
- constructor(options) {
89
- this.options = options;
90
- this.#options = options;
91
- this.#tools = [];
92
- this.#resources = [];
93
- this.#prompts = [];
94
- }
95
- #tools;
96
- #resources;
97
- #prompts;
98
- #server = null;
99
- #options;
89
+ var FastMCPSession = class {
90
+ #capabilities = {};
100
91
  #loggingLevel = "info";
101
- setupHandlers(server) {
102
- this.setupErrorHandling(server);
103
- if (this.#tools.length) {
104
- this.setupToolHandlers(server);
92
+ #server;
93
+ #clientCapabilities;
94
+ #roots = [];
95
+ constructor({
96
+ name,
97
+ version,
98
+ tools,
99
+ resources,
100
+ prompts
101
+ }) {
102
+ if (tools.length) {
103
+ this.#capabilities.tools = {};
105
104
  }
106
- if (this.#resources.length) {
107
- this.setupResourceHandlers(server);
105
+ if (resources.length) {
106
+ this.#capabilities.resources = {};
108
107
  }
109
- if (this.#prompts.length) {
110
- this.setupPromptHandlers(server);
108
+ if (prompts.length) {
109
+ this.#capabilities.prompts = {};
111
110
  }
112
- server.setRequestHandler(SetLevelRequestSchema, (request) => {
113
- this.#loggingLevel = request.params.level;
114
- return {};
115
- });
111
+ this.#capabilities.logging = {};
112
+ this.#server = new Server(
113
+ { name, version },
114
+ { capabilities: this.#capabilities }
115
+ );
116
+ this.setupErrorHandling();
117
+ this.setupLoggingHandlers();
118
+ if (tools.length) {
119
+ this.setupToolHandlers(tools);
120
+ }
121
+ if (resources.length) {
122
+ this.setupResourceHandlers(resources);
123
+ }
124
+ if (prompts.length) {
125
+ this.setupPromptHandlers(prompts);
126
+ }
127
+ }
128
+ get clientCapabilities() {
129
+ return this.#clientCapabilities ?? null;
130
+ }
131
+ get server() {
132
+ return this.#server;
133
+ }
134
+ async connect(transport) {
135
+ if (this.#server.transport) {
136
+ throw new UnexpectedStateError("Server is already connected");
137
+ }
138
+ await this.#server.connect(transport);
139
+ let attempt = 0;
140
+ while (attempt++ < 10) {
141
+ const capabilities = await this.#server.getClientCapabilities();
142
+ if (capabilities) {
143
+ this.#clientCapabilities = capabilities;
144
+ break;
145
+ }
146
+ await delay(100);
147
+ }
148
+ if (this.#clientCapabilities?.roots) {
149
+ const roots = await this.#server.listRoots();
150
+ this.#roots = roots.roots;
151
+ }
152
+ if (!this.#clientCapabilities) {
153
+ throw new UnexpectedStateError("Server did not connect");
154
+ }
155
+ }
156
+ get roots() {
157
+ return this.#roots;
158
+ }
159
+ async close() {
160
+ await this.#server.close();
116
161
  }
117
- setupErrorHandling(server) {
118
- server.onerror = (error) => {
162
+ setupErrorHandling() {
163
+ this.#server.onerror = (error) => {
119
164
  console.error("[MCP Error]", error);
120
165
  };
121
- process.on("SIGINT", async () => {
122
- await server.close();
123
- process.exit(0);
124
- });
125
166
  }
126
- /**
127
- * Returns the current logging level.
128
- */
129
167
  get loggingLevel() {
130
168
  return this.#loggingLevel;
131
169
  }
132
- setupToolHandlers(server) {
133
- server.setRequestHandler(ListToolsRequestSchema, async () => {
170
+ setupLoggingHandlers() {
171
+ this.#server.setRequestHandler(SetLevelRequestSchema, (request) => {
172
+ this.#loggingLevel = request.params.level;
173
+ return {};
174
+ });
175
+ }
176
+ setupToolHandlers(tools) {
177
+ this.#server.setRequestHandler(ListToolsRequestSchema, async () => {
134
178
  return {
135
- tools: this.#tools.map((tool) => {
179
+ tools: tools.map((tool) => {
136
180
  return {
137
181
  name: tool.name,
138
182
  description: tool.description,
@@ -141,10 +185,8 @@ var FastMCP = class {
141
185
  })
142
186
  };
143
187
  });
144
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
145
- const tool = this.#tools.find(
146
- (tool2) => tool2.name === request.params.name
147
- );
188
+ this.#server.setRequestHandler(CallToolRequestSchema, async (request) => {
189
+ const tool = tools.find((tool2) => tool2.name === request.params.name);
148
190
  if (!tool) {
149
191
  throw new McpError(
150
192
  ErrorCode.MethodNotFound,
@@ -166,7 +208,7 @@ var FastMCP = class {
166
208
  let result;
167
209
  try {
168
210
  const reportProgress = async (progress) => {
169
- await server.notification({
211
+ await this.#server.notification({
170
212
  method: "notifications/progress",
171
213
  params: {
172
214
  ...progress,
@@ -176,7 +218,7 @@ var FastMCP = class {
176
218
  };
177
219
  const log = {
178
220
  debug: (message, context) => {
179
- server.sendLoggingMessage({
221
+ this.#server.sendLoggingMessage({
180
222
  level: "debug",
181
223
  data: {
182
224
  message,
@@ -185,7 +227,7 @@ var FastMCP = class {
185
227
  });
186
228
  },
187
229
  error: (message, context) => {
188
- server.sendLoggingMessage({
230
+ this.#server.sendLoggingMessage({
189
231
  level: "error",
190
232
  data: {
191
233
  message,
@@ -194,7 +236,7 @@ var FastMCP = class {
194
236
  });
195
237
  },
196
238
  info: (message, context) => {
197
- server.sendLoggingMessage({
239
+ this.#server.sendLoggingMessage({
198
240
  level: "info",
199
241
  data: {
200
242
  message,
@@ -203,7 +245,7 @@ var FastMCP = class {
203
245
  });
204
246
  },
205
247
  warn: (message, context) => {
206
- server.sendLoggingMessage({
248
+ this.#server.sendLoggingMessage({
207
249
  level: "warning",
208
250
  data: {
209
251
  message,
@@ -242,10 +284,10 @@ var FastMCP = class {
242
284
  return result;
243
285
  });
244
286
  }
245
- setupResourceHandlers(server) {
246
- server.setRequestHandler(ListResourcesRequestSchema, async () => {
287
+ setupResourceHandlers(resources) {
288
+ this.#server.setRequestHandler(ListResourcesRequestSchema, async () => {
247
289
  return {
248
- resources: this.#resources.map((resource) => {
290
+ resources: resources.map((resource) => {
249
291
  return {
250
292
  uri: resource.uri,
251
293
  name: resource.name,
@@ -254,43 +296,46 @@ var FastMCP = class {
254
296
  })
255
297
  };
256
298
  });
257
- server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
258
- const resource = this.#resources.find(
259
- (resource2) => resource2.uri === request.params.uri
260
- );
261
- if (!resource) {
262
- throw new McpError(
263
- ErrorCode.MethodNotFound,
264
- `Unknown resource: ${request.params.uri}`
265
- );
266
- }
267
- let result;
268
- try {
269
- result = await resource.load();
270
- } catch (error) {
271
- throw new McpError(
272
- ErrorCode.InternalError,
273
- `Error reading resource: ${error}`,
274
- {
275
- uri: resource.uri
276
- }
299
+ this.#server.setRequestHandler(
300
+ ReadResourceRequestSchema,
301
+ async (request) => {
302
+ const resource = resources.find(
303
+ (resource2) => resource2.uri === request.params.uri
277
304
  );
305
+ if (!resource) {
306
+ throw new McpError(
307
+ ErrorCode.MethodNotFound,
308
+ `Unknown resource: ${request.params.uri}`
309
+ );
310
+ }
311
+ let result;
312
+ try {
313
+ result = await resource.load();
314
+ } catch (error) {
315
+ throw new McpError(
316
+ ErrorCode.InternalError,
317
+ `Error reading resource: ${error}`,
318
+ {
319
+ uri: resource.uri
320
+ }
321
+ );
322
+ }
323
+ return {
324
+ contents: [
325
+ {
326
+ uri: resource.uri,
327
+ mimeType: resource.mimeType,
328
+ ...result
329
+ }
330
+ ]
331
+ };
278
332
  }
279
- return {
280
- contents: [
281
- {
282
- uri: resource.uri,
283
- mimeType: resource.mimeType,
284
- ...result
285
- }
286
- ]
287
- };
288
- });
333
+ );
289
334
  }
290
- setupPromptHandlers(server) {
291
- server.setRequestHandler(ListPromptsRequestSchema, async () => {
335
+ setupPromptHandlers(prompts) {
336
+ this.#server.setRequestHandler(ListPromptsRequestSchema, async () => {
292
337
  return {
293
- prompts: this.#prompts.map((prompt) => {
338
+ prompts: prompts.map((prompt) => {
294
339
  return {
295
340
  name: prompt.name,
296
341
  description: prompt.description,
@@ -299,8 +344,8 @@ var FastMCP = class {
299
344
  })
300
345
  };
301
346
  });
302
- server.setRequestHandler(GetPromptRequestSchema, async (request) => {
303
- const prompt = this.#prompts.find(
347
+ this.#server.setRequestHandler(GetPromptRequestSchema, async (request) => {
348
+ const prompt = prompts.find(
304
349
  (prompt2) => prompt2.name === request.params.name
305
350
  );
306
351
  if (!prompt) {
@@ -340,6 +385,25 @@ var FastMCP = class {
340
385
  };
341
386
  });
342
387
  }
388
+ };
389
+ var FastMCPEventEmitterBase = EventEmitter;
390
+ var FastMCPEventEmitter = class extends FastMCPEventEmitterBase {
391
+ };
392
+ var FastMCP = class extends FastMCPEventEmitter {
393
+ constructor(options) {
394
+ super();
395
+ this.options = options;
396
+ this.#options = options;
397
+ }
398
+ #options;
399
+ #prompts = [];
400
+ #resources = [];
401
+ #sessions = [];
402
+ #sseServer = null;
403
+ #tools = [];
404
+ get sessions() {
405
+ return this.#sessions;
406
+ }
343
407
  /**
344
408
  * Adds a tool to the server.
345
409
  */
@@ -358,39 +422,55 @@ var FastMCP = class {
358
422
  addPrompt(prompt) {
359
423
  this.#prompts.push(prompt);
360
424
  }
361
- #sseServer = null;
362
425
  /**
363
426
  * Starts the server.
364
427
  */
365
428
  async start(options = {
366
429
  transportType: "stdio"
367
430
  }) {
368
- const capabilities = {};
369
- if (this.#tools.length) {
370
- capabilities.tools = {};
371
- }
372
- if (this.#resources.length) {
373
- capabilities.resources = {};
374
- }
375
- if (this.#prompts.length) {
376
- capabilities.prompts = {};
377
- }
378
- capabilities.logging = {};
379
- this.#server = new Server(
380
- { name: this.#options.name, version: this.#options.version },
381
- { capabilities }
382
- );
383
- this.setupHandlers(this.#server);
384
431
  if (options.transportType === "stdio") {
385
432
  const transport = new StdioServerTransport();
386
- await this.#server.connect(transport);
433
+ const session = new FastMCPSession({
434
+ name: this.#options.name,
435
+ version: this.#options.version,
436
+ tools: this.#tools,
437
+ resources: this.#resources,
438
+ prompts: this.#prompts
439
+ });
440
+ await session.connect(transport);
441
+ this.#sessions.push(session);
442
+ this.emit("connect", {
443
+ session
444
+ });
387
445
  console.error(`server is running on stdio`);
388
446
  } else if (options.transportType === "sse") {
389
447
  this.#sseServer = await startSSEServer({
390
448
  endpoint: options.sse.endpoint,
391
449
  port: options.sse.port,
392
- server: this.#server
450
+ createServer: async () => {
451
+ return new FastMCPSession({
452
+ name: this.#options.name,
453
+ version: this.#options.version,
454
+ tools: this.#tools,
455
+ resources: this.#resources,
456
+ prompts: this.#prompts
457
+ });
458
+ },
459
+ onClose: (session) => {
460
+ this.emit("disconnect", {
461
+ session
462
+ });
463
+ },
464
+ onConnect: async (session) => {
465
+ this.#sessions.push(session);
466
+ this.emit("connect", {
467
+ session
468
+ });
469
+ }
393
470
  });
471
+ console.error(
472
+ `server is running on SSE at http://localhost:${options.sse.port}${options.sse.endpoint}`
473
+ );
394
474
  } else {
395
475
  throw new Error("Invalid transport type");
396
476
  }
@@ -406,6 +486,7 @@ var FastMCP = class {
406
486
  };
407
487
  export {
408
488
  FastMCP,
489
+ FastMCPSession,
409
490
  UserError,
410
491
  imageContent
411
492
  };