fastmcp 3.13.0 → 3.14.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/src/FastMCP.ts CHANGED
@@ -38,6 +38,14 @@ import parseURITemplate from "uri-templates";
38
38
  import { toJsonSchema } from "xsschema";
39
39
  import { z } from "zod";
40
40
 
41
+ export interface Logger {
42
+ debug(...args: unknown[]): void;
43
+ error(...args: unknown[]): void;
44
+ info(...args: unknown[]): void;
45
+ log(...args: unknown[]): void;
46
+ warn(...args: unknown[]): void;
47
+ }
48
+
41
49
  export type SSEServer = {
42
50
  close: () => Promise<void>;
43
51
  };
@@ -570,6 +578,11 @@ type ServerOptions<T extends FastMCPSessionAuth> = {
570
578
  status?: number;
571
579
  };
572
580
  instructions?: string;
581
+ /**
582
+ * Custom logger instance. If not provided, defaults to console.
583
+ * Use this to integrate with your own logging system.
584
+ */
585
+ logger?: Logger;
573
586
  name: string;
574
587
 
575
588
  /**
@@ -924,6 +937,7 @@ export class FastMCPSession<
924
937
  #capabilities: ServerCapabilities = {};
925
938
  #clientCapabilities?: ClientCapabilities;
926
939
  #connectionState: "closed" | "connecting" | "error" | "ready" = "connecting";
940
+ #logger: Logger;
927
941
  #loggingLevel: LoggingLevel = "info";
928
942
  #needsEventLoopFlush: boolean = false;
929
943
  #pingConfig?: ServerOptions<T>["ping"];
@@ -947,6 +961,7 @@ export class FastMCPSession<
947
961
  constructor({
948
962
  auth,
949
963
  instructions,
964
+ logger,
950
965
  name,
951
966
  ping,
952
967
  prompts,
@@ -960,6 +975,7 @@ export class FastMCPSession<
960
975
  }: {
961
976
  auth?: T;
962
977
  instructions?: string;
978
+ logger: Logger;
963
979
  name: string;
964
980
  ping?: ServerOptions<T>["ping"];
965
981
  prompts: Prompt<T>[];
@@ -974,6 +990,7 @@ export class FastMCPSession<
974
990
  super();
975
991
 
976
992
  this.#auth = auth;
993
+ this.#logger = logger;
977
994
  this.#pingConfig = ping;
978
995
  this.#rootsConfig = roots;
979
996
  this.#needsEventLoopFlush = transportType === "httpStream";
@@ -1043,7 +1060,7 @@ export class FastMCPSession<
1043
1060
  try {
1044
1061
  await this.#server.close();
1045
1062
  } catch (error) {
1046
- console.error("[FastMCP error]", "could not close server", error);
1063
+ this.#logger.error("[FastMCP error]", "could not close server", error);
1047
1064
  }
1048
1065
  }
1049
1066
 
@@ -1073,7 +1090,7 @@ export class FastMCPSession<
1073
1090
  }
1074
1091
 
1075
1092
  if (!this.#clientCapabilities) {
1076
- console.warn(
1093
+ this.#logger.warn(
1077
1094
  `[FastMCP warning] could not infer client capabilities after ${maxAttempts} attempts. Connection may be unstable.`,
1078
1095
  );
1079
1096
  }
@@ -1087,11 +1104,11 @@ export class FastMCPSession<
1087
1104
  this.#roots = roots?.roots || [];
1088
1105
  } catch (e) {
1089
1106
  if (e instanceof McpError && e.code === ErrorCode.MethodNotFound) {
1090
- console.debug(
1107
+ this.#logger.debug(
1091
1108
  "[FastMCP debug] listRoots method not supported by client",
1092
1109
  );
1093
1110
  } else {
1094
- console.error(
1111
+ this.#logger.error(
1095
1112
  `[FastMCP error] received error listing roots.\n\n${
1096
1113
  e instanceof Error ? e.stack : JSON.stringify(e)
1097
1114
  }`,
@@ -1114,17 +1131,17 @@ export class FastMCPSession<
1114
1131
  const logLevel = pingConfig.logLevel;
1115
1132
 
1116
1133
  if (logLevel === "debug") {
1117
- console.debug("[FastMCP debug] server ping failed");
1134
+ this.#logger.debug("[FastMCP debug] server ping failed");
1118
1135
  } else if (logLevel === "warning") {
1119
- console.warn(
1136
+ this.#logger.warn(
1120
1137
  "[FastMCP warning] server is not responding to ping",
1121
1138
  );
1122
1139
  } else if (logLevel === "error") {
1123
- console.error(
1140
+ this.#logger.error(
1124
1141
  "[FastMCP error] server is not responding to ping",
1125
1142
  );
1126
1143
  } else {
1127
- console.info("[FastMCP info] server ping failed");
1144
+ this.#logger.info("[FastMCP info] server ping failed");
1128
1145
  }
1129
1146
  }
1130
1147
  }, pingConfig.intervalMs);
@@ -1360,7 +1377,7 @@ export class FastMCPSession<
1360
1377
 
1361
1378
  private setupErrorHandling() {
1362
1379
  this.#server.onerror = (error) => {
1363
- console.error("[FastMCP error]", error);
1380
+ this.#logger.error("[FastMCP error]", error);
1364
1381
  };
1365
1382
  }
1366
1383
 
@@ -1564,7 +1581,7 @@ export class FastMCPSession<
1564
1581
 
1565
1582
  private setupRootsHandlers() {
1566
1583
  if (this.#rootsConfig?.enabled === false) {
1567
- console.debug(
1584
+ this.#logger.debug(
1568
1585
  "[FastMCP debug] roots capability explicitly disabled via config",
1569
1586
  );
1570
1587
  return;
@@ -1589,11 +1606,11 @@ export class FastMCPSession<
1589
1606
  error instanceof McpError &&
1590
1607
  error.code === ErrorCode.MethodNotFound
1591
1608
  ) {
1592
- console.debug(
1609
+ this.#logger.debug(
1593
1610
  "[FastMCP debug] listRoots method not supported by client",
1594
1611
  );
1595
1612
  } else {
1596
- console.error(
1613
+ this.#logger.error(
1597
1614
  `[FastMCP error] received error listing roots.\n\n${
1598
1615
  error instanceof Error ? error.stack : JSON.stringify(error)
1599
1616
  }`,
@@ -1603,7 +1620,7 @@ export class FastMCPSession<
1603
1620
  },
1604
1621
  );
1605
1622
  } else {
1606
- console.debug(
1623
+ this.#logger.debug(
1607
1624
  "[FastMCP debug] roots capability not available, not setting up notification handler",
1608
1625
  );
1609
1626
  }
@@ -1686,7 +1703,7 @@ export class FastMCPSession<
1686
1703
  await new Promise((resolve) => setImmediate(resolve));
1687
1704
  }
1688
1705
  } catch (progressError) {
1689
- console.warn(
1706
+ this.#logger.warn(
1690
1707
  `[FastMCP warning] Failed to report progress for tool '${request.params.name}':`,
1691
1708
  progressError instanceof Error
1692
1709
  ? progressError.message
@@ -1753,7 +1770,7 @@ export class FastMCPSession<
1753
1770
  await new Promise((resolve) => setImmediate(resolve));
1754
1771
  }
1755
1772
  } catch (streamError) {
1756
- console.warn(
1773
+ this.#logger.warn(
1757
1774
  `[FastMCP warning] Failed to stream content for tool '${request.params.name}':`,
1758
1775
  streamError instanceof Error
1759
1776
  ? streamError.message
@@ -1879,6 +1896,7 @@ export class FastMCP<
1879
1896
  }
1880
1897
  #authenticate: Authenticate<T> | undefined;
1881
1898
  #httpStreamServer: null | SSEServer = null;
1899
+ #logger: Logger;
1882
1900
  #options: ServerOptions<T>;
1883
1901
  #prompts: InputPrompt<T>[] = [];
1884
1902
  #resources: Resource<T>[] = [];
@@ -1892,6 +1910,7 @@ export class FastMCP<
1892
1910
 
1893
1911
  this.#options = options;
1894
1912
  this.#authenticate = options.authenticate;
1913
+ this.#logger = options.logger || console;
1895
1914
  }
1896
1915
 
1897
1916
  /**
@@ -2029,6 +2048,7 @@ export class FastMCP<
2029
2048
  const transport = new StdioServerTransport();
2030
2049
  const session = new FastMCPSession<T>({
2031
2050
  instructions: this.#options.instructions,
2051
+ logger: this.#logger,
2032
2052
  name: this.#options.name,
2033
2053
  ping: this.#options.ping,
2034
2054
  prompts: this.#prompts,
@@ -2053,7 +2073,7 @@ export class FastMCP<
2053
2073
 
2054
2074
  if (httpConfig.stateless) {
2055
2075
  // Stateless mode - create new server instance for each request
2056
- console.info(
2076
+ this.#logger.info(
2057
2077
  `[FastMCP info] Starting server in stateless mode on HTTP Stream at http://localhost:${httpConfig.port}${httpConfig.endpoint}`,
2058
2078
  );
2059
2079
 
@@ -2077,7 +2097,7 @@ export class FastMCP<
2077
2097
  },
2078
2098
  onConnect: async () => {
2079
2099
  // No persistent session tracking in stateless mode
2080
- console.debug(
2100
+ this.#logger.debug(
2081
2101
  `[FastMCP debug] Stateless HTTP Stream request handled`,
2082
2102
  );
2083
2103
  },
@@ -2110,7 +2130,7 @@ export class FastMCP<
2110
2130
  onConnect: async (session) => {
2111
2131
  this.#sessions.push(session);
2112
2132
 
2113
- console.info(`[FastMCP info] HTTP Stream session established`);
2133
+ this.#logger.info(`[FastMCP info] HTTP Stream session established`);
2114
2134
 
2115
2135
  this.emit("connect", {
2116
2136
  session: session as FastMCPSession<FastMCPSessionAuth>,
@@ -2124,10 +2144,10 @@ export class FastMCP<
2124
2144
  streamEndpoint: httpConfig.endpoint,
2125
2145
  });
2126
2146
 
2127
- console.info(
2147
+ this.#logger.info(
2128
2148
  `[FastMCP info] server is running on HTTP Stream at http://localhost:${httpConfig.port}${httpConfig.endpoint}`,
2129
2149
  );
2130
- console.info(
2150
+ this.#logger.info(
2131
2151
  `[FastMCP info] Transport type: httpStream (Streamable HTTP, not SSE)`,
2132
2152
  );
2133
2153
  }
@@ -2157,6 +2177,7 @@ export class FastMCP<
2157
2177
  : this.#tools;
2158
2178
  return new FastMCPSession<T>({
2159
2179
  auth,
2180
+ logger: this.#logger,
2160
2181
  name: this.#options.name,
2161
2182
  ping: this.#options.ping,
2162
2183
  prompts: this.#prompts,
@@ -2242,7 +2263,7 @@ export class FastMCP<
2242
2263
  return;
2243
2264
  }
2244
2265
  } catch (error) {
2245
- console.error("[FastMCP error] health endpoint error", error);
2266
+ this.#logger.error("[FastMCP error] health endpoint error", error);
2246
2267
  }
2247
2268
  }
2248
2269
 
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Example FastMCP server demonstrating custom logger implementations.
3
+ *
4
+ * Features demonstrated:
5
+ * - Simple custom logger implementation
6
+ * - File-based logging example
7
+ * - Winston logger adapter
8
+ * - Pino logger adapter
9
+ *
10
+ */
11
+
12
+ import { z } from "zod";
13
+
14
+ import { FastMCP, Logger } from "../FastMCP.js";
15
+
16
+ // Example 1: Simple Custom Logger Implementation
17
+ class SimpleCustomLogger implements Logger {
18
+ debug(...args: unknown[]): void {
19
+ console.log("[CUSTOM DEBUG]", new Date().toISOString(), ...args);
20
+ }
21
+
22
+ error(...args: unknown[]): void {
23
+ console.error("[CUSTOM ERROR]", new Date().toISOString(), ...args);
24
+ }
25
+
26
+ info(...args: unknown[]): void {
27
+ console.info("[CUSTOM INFO]", new Date().toISOString(), ...args);
28
+ }
29
+
30
+ log(...args: unknown[]): void {
31
+ console.log("[CUSTOM LOG]", new Date().toISOString(), ...args);
32
+ }
33
+
34
+ warn(...args: unknown[]): void {
35
+ console.warn("[CUSTOM WARN]", new Date().toISOString(), ...args);
36
+ }
37
+ }
38
+
39
+ // Example 2: File-based Logger
40
+ // class FileLogger implements Logger {
41
+ // debug(...args: unknown[]): void {
42
+ // this.logToFile('DEBUG', ...args);
43
+ // }
44
+
45
+ // error(...args: unknown[]): void {
46
+ // this.logToFile('ERROR', ...args);
47
+ // }
48
+
49
+ // info(...args: unknown[]): void {
50
+ // this.logToFile('INFO', ...args);
51
+ // }
52
+
53
+ // log(...args: unknown[]): void {
54
+ // this.logToFile('LOG', ...args);
55
+ // }
56
+
57
+ // warn(...args: unknown[]): void {
58
+ // this.logToFile('WARN', ...args);
59
+ // }
60
+
61
+ // private logToFile(level: string, ...args: unknown[]): void {
62
+ // const timestamp = new Date().toISOString();
63
+ // const message = `[${timestamp}] [${level}] ${args.map(arg =>
64
+ // typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
65
+ // ).join(' ')}\n`;
66
+
67
+ // // In a real implementation, you might use fs.appendFile or a logging library
68
+ // console.log(message.trim());
69
+ // }
70
+ // }
71
+
72
+ // Example 3: Winston Logger Adapter
73
+ // To use this example, install winston: npm install winston
74
+ // import winston from 'winston';
75
+
76
+ // class WinstonLoggerAdapter implements Logger {
77
+ // private winston: winston.Logger;
78
+
79
+ // constructor() {
80
+ // this.winston = winston.createLogger({
81
+ // level: 'debug',
82
+ // format: winston.format.combine(
83
+ // winston.format.timestamp(),
84
+ // winston.format.errors({ stack: true }),
85
+ // winston.format.json()
86
+ // ),
87
+ // transports: [
88
+ // new winston.transports.Console({
89
+ // format: winston.format.combine(
90
+ // winston.format.colorize(),
91
+ // winston.format.simple()
92
+ // )
93
+ // }),
94
+ // new winston.transports.File({ filename: 'fastmcp.log' })
95
+ // ]
96
+ // });
97
+ // }
98
+
99
+ // debug(...args: unknown[]): void {
100
+ // this.winston.debug(this.formatArgs(args));
101
+ // }
102
+
103
+ // error(...args: unknown[]): void {
104
+ // this.winston.error(this.formatArgs(args));
105
+ // }
106
+
107
+ // info(...args: unknown[]): void {
108
+ // this.winston.info(this.formatArgs(args));
109
+ // }
110
+
111
+ // log(...args: unknown[]): void {
112
+ // this.winston.info(this.formatArgs(args));
113
+ // }
114
+
115
+ // warn(...args: unknown[]): void {
116
+ // this.winston.warn(this.formatArgs(args));
117
+ // }
118
+
119
+ // private formatArgs(args: unknown[]): string {
120
+ // return args.map(arg =>
121
+ // typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
122
+ // ).join(' ');
123
+ // }
124
+ // }
125
+
126
+ // Example 4: Pino Logger Adapter
127
+ // To use this example, install pino: npm install pino
128
+ // import pino from 'pino';
129
+ //
130
+ // class PinoLoggerAdapter implements Logger {
131
+ // private pino: pino.Logger;
132
+ //
133
+ // constructor() {
134
+ // this.pino = pino({
135
+ // level: 'debug',
136
+ // transport: {
137
+ // target: 'pino-pretty',
138
+ // options: {
139
+ // colorize: true,
140
+ // translateTime: 'SYS:standard',
141
+ // ignore: 'pid,hostname'
142
+ // }
143
+ // }
144
+ // });
145
+ // }
146
+ //
147
+ // debug(...args: unknown[]): void {
148
+ // this.pino.debug(this.formatMessage(args));
149
+ // }
150
+ //
151
+ // error(...args: unknown[]): void {
152
+ // this.pino.error(this.formatMessage(args));
153
+ // }
154
+ //
155
+ // info(...args: unknown[]): void {
156
+ // this.pino.info(this.formatMessage(args));
157
+ // }
158
+ //
159
+ // log(...args: unknown[]): void {
160
+ // this.pino.info(this.formatMessage(args));
161
+ // }
162
+ //
163
+ // warn(...args: unknown[]): void {
164
+ // this.pino.warn(this.formatMessage(args));
165
+ // }
166
+ //
167
+ // private formatMessage(args: unknown[]): string {
168
+ // return args.map(arg =>
169
+ // typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
170
+ // ).join(' ');
171
+ // }
172
+ // }
173
+
174
+ // Choose which logger to use (uncomment the one you want to use)
175
+ const logger = new SimpleCustomLogger();
176
+ // const logger = new FileLogger();
177
+ // const logger = new WinstonLoggerAdapter();
178
+ // const logger = new PinoLoggerAdapter();
179
+
180
+ const server = new FastMCP({
181
+ logger: logger,
182
+ name: "custom-logger-example",
183
+ version: "1.0.0",
184
+ });
185
+
186
+ server.addTool({
187
+ description: "A test tool that demonstrates custom logging",
188
+ execute: async (args) => {
189
+ return `Received: ${args.message}`;
190
+ },
191
+ name: "test_tool",
192
+ parameters: z.object({
193
+ message: z.string().describe("A message to log"),
194
+ }),
195
+ });
196
+
197
+ // Start the server with stdio transport
198
+ server.start({ transportType: "stdio" }).catch((error: unknown) => {
199
+ console.error("Failed to start server:", error);
200
+ process.exit(1);
201
+ });