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/README.md +39 -0
- package/dist/FastMCP.d.ts +15 -2
- package/dist/FastMCP.js +28 -21
- package/dist/FastMCP.js.map +1 -1
- package/jsr.json +1 -1
- package/package.json +17 -17
- package/src/FastMCP.ts +42 -21
- package/src/examples/custom-logger.ts +201 -0
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1107
|
+
this.#logger.debug(
|
|
1091
1108
|
"[FastMCP debug] listRoots method not supported by client",
|
|
1092
1109
|
);
|
|
1093
1110
|
} else {
|
|
1094
|
-
|
|
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
|
-
|
|
1134
|
+
this.#logger.debug("[FastMCP debug] server ping failed");
|
|
1118
1135
|
} else if (logLevel === "warning") {
|
|
1119
|
-
|
|
1136
|
+
this.#logger.warn(
|
|
1120
1137
|
"[FastMCP warning] server is not responding to ping",
|
|
1121
1138
|
);
|
|
1122
1139
|
} else if (logLevel === "error") {
|
|
1123
|
-
|
|
1140
|
+
this.#logger.error(
|
|
1124
1141
|
"[FastMCP error] server is not responding to ping",
|
|
1125
1142
|
);
|
|
1126
1143
|
} else {
|
|
1127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1609
|
+
this.#logger.debug(
|
|
1593
1610
|
"[FastMCP debug] listRoots method not supported by client",
|
|
1594
1611
|
);
|
|
1595
1612
|
} else {
|
|
1596
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2147
|
+
this.#logger.info(
|
|
2128
2148
|
`[FastMCP info] server is running on HTTP Stream at http://localhost:${httpConfig.port}${httpConfig.endpoint}`,
|
|
2129
2149
|
);
|
|
2130
|
-
|
|
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
|
-
|
|
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
|
+
});
|