fastmcp 3.25.3 → 3.26.2
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/dist/FastMCP.cjs +1931 -0
- package/dist/FastMCP.cjs.map +1 -0
- package/dist/FastMCP.d.cts +754 -0
- package/dist/FastMCP.d.ts +3 -1
- package/dist/FastMCP.js +25 -4
- package/dist/FastMCP.js.map +1 -1
- package/dist/{OAuthProxy-BOCkkAhO.d.ts → OAuthProxy-jvsyum7s.d.cts} +5 -0
- package/dist/OAuthProxy-jvsyum7s.d.ts +524 -0
- package/dist/auth/index.cjs +1875 -0
- package/dist/auth/index.cjs.map +1 -0
- package/dist/auth/index.d.cts +445 -0
- package/dist/auth/index.d.ts +2 -2
- package/dist/auth/index.js +34 -3
- package/dist/auth/index.js.map +1 -1
- package/dist/bin/fastmcp.cjs +152 -0
- package/dist/bin/fastmcp.cjs.map +1 -0
- package/dist/bin/fastmcp.d.cts +1 -0
- package/package.json +7 -4
package/dist/FastMCP.cjs
ADDED
|
@@ -0,0 +1,1931 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/FastMCP.ts
|
|
2
|
+
var _indexjs = require('@modelcontextprotocol/sdk/server/index.js');
|
|
3
|
+
var _stdiojs = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
var _typesjs = require('@modelcontextprotocol/sdk/types.js');
|
|
18
|
+
var _events = require('events');
|
|
19
|
+
var _promises = require('fs/promises');
|
|
20
|
+
var _fusejs = require('fuse.js'); var _fusejs2 = _interopRequireDefault(_fusejs);
|
|
21
|
+
var _mcpproxy = require('mcp-proxy');
|
|
22
|
+
var _promises3 = require('timers/promises');
|
|
23
|
+
var _undici = require('undici');
|
|
24
|
+
var _uritemplates = require('uri-templates'); var _uritemplates2 = _interopRequireDefault(_uritemplates);
|
|
25
|
+
var _xsschema = require('xsschema');
|
|
26
|
+
var _zod = require('zod');
|
|
27
|
+
|
|
28
|
+
// src/DiscoveryDocumentCache.ts
|
|
29
|
+
var DiscoveryDocumentCache = class {
|
|
30
|
+
get size() {
|
|
31
|
+
return this.#cache.size;
|
|
32
|
+
}
|
|
33
|
+
#cache = /* @__PURE__ */ new Map();
|
|
34
|
+
#inFlight = /* @__PURE__ */ new Map();
|
|
35
|
+
#ttl;
|
|
36
|
+
/**
|
|
37
|
+
* @param options - configuration options
|
|
38
|
+
* @param options.ttl - time-to-live in miliseconds
|
|
39
|
+
*/
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
this.#ttl = _nullishCoalesce(options.ttl, () => ( 36e5));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* @param url - optional URL to clear. if omitted, clears all cached documents.
|
|
45
|
+
*/
|
|
46
|
+
clear(url) {
|
|
47
|
+
if (url) {
|
|
48
|
+
this.#cache.delete(url);
|
|
49
|
+
} else {
|
|
50
|
+
this.#cache.clear();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* fetches a discovery document from the given URL.
|
|
55
|
+
* uses cached value if available and not expired.
|
|
56
|
+
* coalesces concurrent requests for the same URL to prevent duplicate fetches.
|
|
57
|
+
*
|
|
58
|
+
* @param url - the discovery document URL (e.g., /.well-known/openid-configuration)
|
|
59
|
+
* @returns the discovery document as a JSON object
|
|
60
|
+
* @throws Error if the fetch fails or returns non-OK status
|
|
61
|
+
*/
|
|
62
|
+
async get(url) {
|
|
63
|
+
const now = Date.now();
|
|
64
|
+
const cached = this.#cache.get(url);
|
|
65
|
+
if (cached && cached.expiresAt > now) {
|
|
66
|
+
return cached.data;
|
|
67
|
+
}
|
|
68
|
+
const inFlight = this.#inFlight.get(url);
|
|
69
|
+
if (inFlight) {
|
|
70
|
+
return inFlight;
|
|
71
|
+
}
|
|
72
|
+
const fetchPromise = this.#fetchAndCache(url);
|
|
73
|
+
this.#inFlight.set(url, fetchPromise);
|
|
74
|
+
try {
|
|
75
|
+
const data = await fetchPromise;
|
|
76
|
+
return data;
|
|
77
|
+
} finally {
|
|
78
|
+
this.#inFlight.delete(url);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* @param url - the URL to check
|
|
83
|
+
* @returns true if the URL is cached and nott expired
|
|
84
|
+
*/
|
|
85
|
+
has(url) {
|
|
86
|
+
const cached = this.#cache.get(url);
|
|
87
|
+
if (!cached) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
if (cached.expiresAt <= now) {
|
|
92
|
+
this.#cache.delete(url);
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
async #fetchAndCache(url) {
|
|
98
|
+
const res = await fetch(url);
|
|
99
|
+
if (!res.ok) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Failed to fetch discovery document from ${url}: ${res.status} ${res.statusText}`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
const data = await res.json();
|
|
105
|
+
const expiresAt = Date.now() + this.#ttl;
|
|
106
|
+
this.#cache.set(url, {
|
|
107
|
+
data,
|
|
108
|
+
expiresAt
|
|
109
|
+
});
|
|
110
|
+
return data;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// src/FastMCP.ts
|
|
115
|
+
var imageContent = async (input) => {
|
|
116
|
+
let rawData;
|
|
117
|
+
try {
|
|
118
|
+
if ("url" in input) {
|
|
119
|
+
try {
|
|
120
|
+
const response = await _undici.fetch.call(void 0, input.url);
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Server responded with status: ${response.status} - ${response.statusText}`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
rawData = Buffer.from(await response.arrayBuffer());
|
|
127
|
+
} catch (error) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Failed to fetch image from URL (${input.url}): ${error instanceof Error ? error.message : String(error)}`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
} else if ("path" in input) {
|
|
133
|
+
try {
|
|
134
|
+
rawData = await _promises.readFile.call(void 0, input.path);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Failed to read image from path (${input.path}): ${error instanceof Error ? error.message : String(error)}`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
} else if ("buffer" in input) {
|
|
141
|
+
rawData = input.buffer;
|
|
142
|
+
} else {
|
|
143
|
+
throw new Error(
|
|
144
|
+
"Invalid input: Provide a valid 'url', 'path', or 'buffer'"
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
const { fileTypeFromBuffer } = await Promise.resolve().then(() => _interopRequireWildcard(require("file-type")));
|
|
148
|
+
const mimeType = await fileTypeFromBuffer(rawData);
|
|
149
|
+
if (!mimeType || !mimeType.mime.startsWith("image/")) {
|
|
150
|
+
console.warn(
|
|
151
|
+
`Warning: Content may not be a valid image. Detected MIME: ${_optionalChain([mimeType, 'optionalAccess', _2 => _2.mime]) || "unknown"}`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
const base64Data = rawData.toString("base64");
|
|
155
|
+
return {
|
|
156
|
+
data: base64Data,
|
|
157
|
+
mimeType: _nullishCoalesce(_optionalChain([mimeType, 'optionalAccess', _3 => _3.mime]), () => ( "image/png")),
|
|
158
|
+
type: "image"
|
|
159
|
+
};
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (error instanceof Error) {
|
|
162
|
+
throw error;
|
|
163
|
+
} else {
|
|
164
|
+
throw new Error(`Unexpected error processing image: ${String(error)}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
var audioContent = async (input) => {
|
|
169
|
+
let rawData;
|
|
170
|
+
try {
|
|
171
|
+
if ("url" in input) {
|
|
172
|
+
try {
|
|
173
|
+
const response = await _undici.fetch.call(void 0, input.url);
|
|
174
|
+
if (!response.ok) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`Server responded with status: ${response.status} - ${response.statusText}`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
rawData = Buffer.from(await response.arrayBuffer());
|
|
180
|
+
} catch (error) {
|
|
181
|
+
throw new Error(
|
|
182
|
+
`Failed to fetch audio from URL (${input.url}): ${error instanceof Error ? error.message : String(error)}`
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
} else if ("path" in input) {
|
|
186
|
+
try {
|
|
187
|
+
rawData = await _promises.readFile.call(void 0, input.path);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`Failed to read audio from path (${input.path}): ${error instanceof Error ? error.message : String(error)}`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
} else if ("buffer" in input) {
|
|
194
|
+
rawData = input.buffer;
|
|
195
|
+
} else {
|
|
196
|
+
throw new Error(
|
|
197
|
+
"Invalid input: Provide a valid 'url', 'path', or 'buffer'"
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
const { fileTypeFromBuffer } = await Promise.resolve().then(() => _interopRequireWildcard(require("file-type")));
|
|
201
|
+
const mimeType = await fileTypeFromBuffer(rawData);
|
|
202
|
+
if (!mimeType || !mimeType.mime.startsWith("audio/")) {
|
|
203
|
+
console.warn(
|
|
204
|
+
`Warning: Content may not be a valid audio file. Detected MIME: ${_optionalChain([mimeType, 'optionalAccess', _4 => _4.mime]) || "unknown"}`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
const base64Data = rawData.toString("base64");
|
|
208
|
+
return {
|
|
209
|
+
data: base64Data,
|
|
210
|
+
mimeType: _nullishCoalesce(_optionalChain([mimeType, 'optionalAccess', _5 => _5.mime]), () => ( "audio/mpeg")),
|
|
211
|
+
type: "audio"
|
|
212
|
+
};
|
|
213
|
+
} catch (error) {
|
|
214
|
+
if (error instanceof Error) {
|
|
215
|
+
throw error;
|
|
216
|
+
} else {
|
|
217
|
+
throw new Error(`Unexpected error processing audio: ${String(error)}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
var FastMCPError = class extends Error {
|
|
222
|
+
constructor(message) {
|
|
223
|
+
super(message);
|
|
224
|
+
this.name = new.target.name;
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
var UnexpectedStateError = class extends FastMCPError {
|
|
228
|
+
|
|
229
|
+
constructor(message, extras) {
|
|
230
|
+
super(message);
|
|
231
|
+
this.name = new.target.name;
|
|
232
|
+
this.extras = extras;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
var UserError = class extends UnexpectedStateError {
|
|
236
|
+
};
|
|
237
|
+
var TextContentZodSchema = _zod.z.object({
|
|
238
|
+
/**
|
|
239
|
+
* The text content of the message.
|
|
240
|
+
*/
|
|
241
|
+
text: _zod.z.string(),
|
|
242
|
+
type: _zod.z.literal("text")
|
|
243
|
+
}).strict();
|
|
244
|
+
var ImageContentZodSchema = _zod.z.object({
|
|
245
|
+
/**
|
|
246
|
+
* The base64-encoded image data.
|
|
247
|
+
*/
|
|
248
|
+
data: _zod.z.string().base64(),
|
|
249
|
+
/**
|
|
250
|
+
* The MIME type of the image. Different providers may support different image types.
|
|
251
|
+
*/
|
|
252
|
+
mimeType: _zod.z.string(),
|
|
253
|
+
type: _zod.z.literal("image")
|
|
254
|
+
}).strict();
|
|
255
|
+
var AudioContentZodSchema = _zod.z.object({
|
|
256
|
+
/**
|
|
257
|
+
* The base64-encoded audio data.
|
|
258
|
+
*/
|
|
259
|
+
data: _zod.z.string().base64(),
|
|
260
|
+
mimeType: _zod.z.string(),
|
|
261
|
+
type: _zod.z.literal("audio")
|
|
262
|
+
}).strict();
|
|
263
|
+
var ResourceContentZodSchema = _zod.z.object({
|
|
264
|
+
resource: _zod.z.object({
|
|
265
|
+
blob: _zod.z.string().optional(),
|
|
266
|
+
mimeType: _zod.z.string().optional(),
|
|
267
|
+
text: _zod.z.string().optional(),
|
|
268
|
+
uri: _zod.z.string()
|
|
269
|
+
}),
|
|
270
|
+
type: _zod.z.literal("resource")
|
|
271
|
+
}).strict();
|
|
272
|
+
var ResourceLinkZodSchema = _zod.z.object({
|
|
273
|
+
description: _zod.z.string().optional(),
|
|
274
|
+
mimeType: _zod.z.string().optional(),
|
|
275
|
+
name: _zod.z.string(),
|
|
276
|
+
title: _zod.z.string().optional(),
|
|
277
|
+
type: _zod.z.literal("resource_link"),
|
|
278
|
+
uri: _zod.z.string()
|
|
279
|
+
});
|
|
280
|
+
var ContentZodSchema = _zod.z.discriminatedUnion("type", [
|
|
281
|
+
TextContentZodSchema,
|
|
282
|
+
ImageContentZodSchema,
|
|
283
|
+
AudioContentZodSchema,
|
|
284
|
+
ResourceContentZodSchema,
|
|
285
|
+
ResourceLinkZodSchema
|
|
286
|
+
]);
|
|
287
|
+
var ContentResultZodSchema = _zod.z.object({
|
|
288
|
+
content: ContentZodSchema.array(),
|
|
289
|
+
isError: _zod.z.boolean().optional()
|
|
290
|
+
}).strict();
|
|
291
|
+
var CompletionZodSchema = _zod.z.object({
|
|
292
|
+
/**
|
|
293
|
+
* Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown.
|
|
294
|
+
*/
|
|
295
|
+
hasMore: _zod.z.optional(_zod.z.boolean()),
|
|
296
|
+
/**
|
|
297
|
+
* The total number of completion options available. This can exceed the number of values actually sent in the response.
|
|
298
|
+
*/
|
|
299
|
+
total: _zod.z.optional(_zod.z.number().int()),
|
|
300
|
+
/**
|
|
301
|
+
* An array of completion values. Must not exceed 100 items.
|
|
302
|
+
*/
|
|
303
|
+
values: _zod.z.array(_zod.z.string()).max(100)
|
|
304
|
+
});
|
|
305
|
+
var FastMCPSessionEventEmitterBase = _events.EventEmitter;
|
|
306
|
+
var ServerState = /* @__PURE__ */ ((ServerState2) => {
|
|
307
|
+
ServerState2["Error"] = "error";
|
|
308
|
+
ServerState2["Running"] = "running";
|
|
309
|
+
ServerState2["Stopped"] = "stopped";
|
|
310
|
+
return ServerState2;
|
|
311
|
+
})(ServerState || {});
|
|
312
|
+
var FastMCPSessionEventEmitter = class extends FastMCPSessionEventEmitterBase {
|
|
313
|
+
};
|
|
314
|
+
var FastMCPSession = class extends FastMCPSessionEventEmitter {
|
|
315
|
+
get clientCapabilities() {
|
|
316
|
+
return _nullishCoalesce(this.#clientCapabilities, () => ( null));
|
|
317
|
+
}
|
|
318
|
+
get isReady() {
|
|
319
|
+
return this.#connectionState === "ready";
|
|
320
|
+
}
|
|
321
|
+
get loggingLevel() {
|
|
322
|
+
return this.#loggingLevel;
|
|
323
|
+
}
|
|
324
|
+
get roots() {
|
|
325
|
+
return this.#roots;
|
|
326
|
+
}
|
|
327
|
+
get server() {
|
|
328
|
+
return this.#server;
|
|
329
|
+
}
|
|
330
|
+
get sessionId() {
|
|
331
|
+
return this.#sessionId;
|
|
332
|
+
}
|
|
333
|
+
set sessionId(value) {
|
|
334
|
+
this.#sessionId = value;
|
|
335
|
+
}
|
|
336
|
+
#auth;
|
|
337
|
+
#capabilities = {};
|
|
338
|
+
#clientCapabilities;
|
|
339
|
+
#connectionState = "connecting";
|
|
340
|
+
#logger;
|
|
341
|
+
#loggingLevel = "info";
|
|
342
|
+
#needsEventLoopFlush = false;
|
|
343
|
+
#pingConfig;
|
|
344
|
+
#pingInterval = null;
|
|
345
|
+
#prompts = [];
|
|
346
|
+
#resources = [];
|
|
347
|
+
#resourceTemplates = [];
|
|
348
|
+
#roots = [];
|
|
349
|
+
#rootsConfig;
|
|
350
|
+
#server;
|
|
351
|
+
/**
|
|
352
|
+
* Session ID from the Mcp-Session-Id header (HTTP transports only).
|
|
353
|
+
* Used to track per-session state across multiple requests.
|
|
354
|
+
*/
|
|
355
|
+
#sessionId;
|
|
356
|
+
#utils;
|
|
357
|
+
constructor({
|
|
358
|
+
auth,
|
|
359
|
+
instructions,
|
|
360
|
+
logger,
|
|
361
|
+
name,
|
|
362
|
+
ping,
|
|
363
|
+
prompts,
|
|
364
|
+
resources,
|
|
365
|
+
resourcesTemplates,
|
|
366
|
+
roots,
|
|
367
|
+
sessionId,
|
|
368
|
+
tools,
|
|
369
|
+
transportType,
|
|
370
|
+
utils,
|
|
371
|
+
version
|
|
372
|
+
}) {
|
|
373
|
+
super();
|
|
374
|
+
this.#auth = auth;
|
|
375
|
+
this.#logger = logger;
|
|
376
|
+
this.#pingConfig = ping;
|
|
377
|
+
this.#rootsConfig = roots;
|
|
378
|
+
this.#sessionId = sessionId;
|
|
379
|
+
this.#needsEventLoopFlush = transportType === "httpStream";
|
|
380
|
+
if (tools.length) {
|
|
381
|
+
this.#capabilities.tools = {};
|
|
382
|
+
}
|
|
383
|
+
if (resources.length || resourcesTemplates.length) {
|
|
384
|
+
this.#capabilities.resources = {};
|
|
385
|
+
}
|
|
386
|
+
if (prompts.length) {
|
|
387
|
+
for (const prompt of prompts) {
|
|
388
|
+
this.addPrompt(prompt);
|
|
389
|
+
}
|
|
390
|
+
this.#capabilities.prompts = {};
|
|
391
|
+
}
|
|
392
|
+
this.#capabilities.logging = {};
|
|
393
|
+
this.#capabilities.completions = {};
|
|
394
|
+
this.#server = new (0, _indexjs.Server)(
|
|
395
|
+
{ name, version },
|
|
396
|
+
{ capabilities: this.#capabilities, instructions }
|
|
397
|
+
);
|
|
398
|
+
this.#utils = utils;
|
|
399
|
+
this.setupErrorHandling();
|
|
400
|
+
this.setupLoggingHandlers();
|
|
401
|
+
this.setupRootsHandlers();
|
|
402
|
+
this.setupCompleteHandlers();
|
|
403
|
+
if (tools.length) {
|
|
404
|
+
this.setupToolHandlers(tools);
|
|
405
|
+
}
|
|
406
|
+
if (resources.length || resourcesTemplates.length) {
|
|
407
|
+
for (const resource of resources) {
|
|
408
|
+
this.addResource(resource);
|
|
409
|
+
}
|
|
410
|
+
this.setupResourceHandlers(resources);
|
|
411
|
+
if (resourcesTemplates.length) {
|
|
412
|
+
for (const resourceTemplate of resourcesTemplates) {
|
|
413
|
+
this.addResourceTemplate(resourceTemplate);
|
|
414
|
+
}
|
|
415
|
+
this.setupResourceTemplateHandlers(resourcesTemplates);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (prompts.length) {
|
|
419
|
+
this.setupPromptHandlers(prompts);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
async close() {
|
|
423
|
+
this.#connectionState = "closed";
|
|
424
|
+
if (this.#pingInterval) {
|
|
425
|
+
clearInterval(this.#pingInterval);
|
|
426
|
+
}
|
|
427
|
+
try {
|
|
428
|
+
await this.#server.close();
|
|
429
|
+
} catch (error) {
|
|
430
|
+
this.#logger.error("[FastMCP error]", "could not close server", error);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
async connect(transport) {
|
|
434
|
+
if (this.#server.transport) {
|
|
435
|
+
throw new UnexpectedStateError("Server is already connected");
|
|
436
|
+
}
|
|
437
|
+
this.#connectionState = "connecting";
|
|
438
|
+
try {
|
|
439
|
+
await this.#server.connect(transport);
|
|
440
|
+
if ("sessionId" in transport) {
|
|
441
|
+
const transportWithSessionId = transport;
|
|
442
|
+
if (typeof transportWithSessionId.sessionId === "string") {
|
|
443
|
+
this.#sessionId = transportWithSessionId.sessionId;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
let attempt = 0;
|
|
447
|
+
const maxAttempts = 10;
|
|
448
|
+
const retryDelay = 100;
|
|
449
|
+
while (attempt++ < maxAttempts) {
|
|
450
|
+
const capabilities = this.#server.getClientCapabilities();
|
|
451
|
+
if (capabilities) {
|
|
452
|
+
this.#clientCapabilities = capabilities;
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
await _promises3.setTimeout.call(void 0, retryDelay);
|
|
456
|
+
}
|
|
457
|
+
if (!this.#clientCapabilities) {
|
|
458
|
+
this.#logger.warn(
|
|
459
|
+
`[FastMCP warning] could not infer client capabilities after ${maxAttempts} attempts. Connection may be unstable.`
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
if (_optionalChain([this, 'access', _6 => _6.#rootsConfig, 'optionalAccess', _7 => _7.enabled]) !== false && _optionalChain([this, 'access', _8 => _8.#clientCapabilities, 'optionalAccess', _9 => _9.roots, 'optionalAccess', _10 => _10.listChanged]) && typeof this.#server.listRoots === "function") {
|
|
463
|
+
try {
|
|
464
|
+
const roots = await this.#server.listRoots();
|
|
465
|
+
this.#roots = _optionalChain([roots, 'optionalAccess', _11 => _11.roots]) || [];
|
|
466
|
+
} catch (e) {
|
|
467
|
+
if (e instanceof _typesjs.McpError && e.code === _typesjs.ErrorCode.MethodNotFound) {
|
|
468
|
+
this.#logger.debug(
|
|
469
|
+
"[FastMCP debug] listRoots method not supported by client"
|
|
470
|
+
);
|
|
471
|
+
} else {
|
|
472
|
+
this.#logger.error(
|
|
473
|
+
`[FastMCP error] received error listing roots.
|
|
474
|
+
|
|
475
|
+
${e instanceof Error ? e.stack : JSON.stringify(e)}`
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (this.#clientCapabilities) {
|
|
481
|
+
const pingConfig = this.#getPingConfig(transport);
|
|
482
|
+
if (pingConfig.enabled) {
|
|
483
|
+
this.#pingInterval = setInterval(async () => {
|
|
484
|
+
try {
|
|
485
|
+
await this.#server.ping();
|
|
486
|
+
} catch (e2) {
|
|
487
|
+
const logLevel = pingConfig.logLevel;
|
|
488
|
+
if (logLevel === "debug") {
|
|
489
|
+
this.#logger.debug("[FastMCP debug] server ping failed");
|
|
490
|
+
} else if (logLevel === "warning") {
|
|
491
|
+
this.#logger.warn(
|
|
492
|
+
"[FastMCP warning] server is not responding to ping"
|
|
493
|
+
);
|
|
494
|
+
} else if (logLevel === "error") {
|
|
495
|
+
this.#logger.error(
|
|
496
|
+
"[FastMCP error] server is not responding to ping"
|
|
497
|
+
);
|
|
498
|
+
} else {
|
|
499
|
+
this.#logger.info("[FastMCP info] server ping failed");
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}, pingConfig.intervalMs);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
this.#connectionState = "ready";
|
|
506
|
+
this.emit("ready");
|
|
507
|
+
} catch (error) {
|
|
508
|
+
this.#connectionState = "error";
|
|
509
|
+
const errorEvent = {
|
|
510
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
511
|
+
};
|
|
512
|
+
this.emit("error", errorEvent);
|
|
513
|
+
throw error;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
promptsListChanged(prompts) {
|
|
517
|
+
this.#prompts = [];
|
|
518
|
+
for (const prompt of prompts) {
|
|
519
|
+
this.addPrompt(prompt);
|
|
520
|
+
}
|
|
521
|
+
this.setupPromptHandlers(prompts);
|
|
522
|
+
this.triggerListChangedNotification("notifications/prompts/list_changed");
|
|
523
|
+
}
|
|
524
|
+
async requestSampling(message, options) {
|
|
525
|
+
return this.#server.createMessage(message, options);
|
|
526
|
+
}
|
|
527
|
+
resourcesListChanged(resources) {
|
|
528
|
+
this.#resources = [];
|
|
529
|
+
for (const resource of resources) {
|
|
530
|
+
this.addResource(resource);
|
|
531
|
+
}
|
|
532
|
+
this.setupResourceHandlers(resources);
|
|
533
|
+
this.triggerListChangedNotification("notifications/resources/list_changed");
|
|
534
|
+
}
|
|
535
|
+
resourceTemplatesListChanged(resourceTemplates) {
|
|
536
|
+
this.#resourceTemplates = [];
|
|
537
|
+
for (const resourceTemplate of resourceTemplates) {
|
|
538
|
+
this.addResourceTemplate(resourceTemplate);
|
|
539
|
+
}
|
|
540
|
+
this.setupResourceTemplateHandlers(resourceTemplates);
|
|
541
|
+
this.triggerListChangedNotification("notifications/resources/list_changed");
|
|
542
|
+
}
|
|
543
|
+
toolsListChanged(tools) {
|
|
544
|
+
const allowedTools = tools.filter(
|
|
545
|
+
(tool) => tool.canAccess ? tool.canAccess(this.#auth) : true
|
|
546
|
+
);
|
|
547
|
+
this.setupToolHandlers(allowedTools);
|
|
548
|
+
this.triggerListChangedNotification("notifications/tools/list_changed");
|
|
549
|
+
}
|
|
550
|
+
async triggerListChangedNotification(method) {
|
|
551
|
+
try {
|
|
552
|
+
await this.#server.notification({
|
|
553
|
+
method
|
|
554
|
+
});
|
|
555
|
+
} catch (error) {
|
|
556
|
+
this.#logger.error(
|
|
557
|
+
`[FastMCP error] failed to send ${method} notification.
|
|
558
|
+
|
|
559
|
+
${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
waitForReady() {
|
|
564
|
+
if (this.isReady) {
|
|
565
|
+
return Promise.resolve();
|
|
566
|
+
}
|
|
567
|
+
if (this.#connectionState === "error" || this.#connectionState === "closed") {
|
|
568
|
+
return Promise.reject(
|
|
569
|
+
new Error(`Connection is in ${this.#connectionState} state`)
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
return new Promise((resolve, reject) => {
|
|
573
|
+
const timeout = setTimeout(() => {
|
|
574
|
+
reject(
|
|
575
|
+
new Error(
|
|
576
|
+
"Connection timeout: Session failed to become ready within 5 seconds"
|
|
577
|
+
)
|
|
578
|
+
);
|
|
579
|
+
}, 5e3);
|
|
580
|
+
this.once("ready", () => {
|
|
581
|
+
clearTimeout(timeout);
|
|
582
|
+
resolve();
|
|
583
|
+
});
|
|
584
|
+
this.once("error", (event) => {
|
|
585
|
+
clearTimeout(timeout);
|
|
586
|
+
reject(event.error);
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
#getPingConfig(transport) {
|
|
591
|
+
const pingConfig = this.#pingConfig || {};
|
|
592
|
+
let defaultEnabled = false;
|
|
593
|
+
if ("type" in transport) {
|
|
594
|
+
if (transport.type === "httpStream") {
|
|
595
|
+
defaultEnabled = true;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return {
|
|
599
|
+
enabled: pingConfig.enabled !== void 0 ? pingConfig.enabled : defaultEnabled,
|
|
600
|
+
intervalMs: pingConfig.intervalMs || 5e3,
|
|
601
|
+
logLevel: pingConfig.logLevel || "debug"
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
addPrompt(inputPrompt) {
|
|
605
|
+
const completers = {};
|
|
606
|
+
const enums = {};
|
|
607
|
+
const fuseInstances = {};
|
|
608
|
+
for (const argument of _nullishCoalesce(inputPrompt.arguments, () => ( []))) {
|
|
609
|
+
if (argument.complete) {
|
|
610
|
+
completers[argument.name] = argument.complete;
|
|
611
|
+
}
|
|
612
|
+
if (argument.enum) {
|
|
613
|
+
enums[argument.name] = argument.enum;
|
|
614
|
+
fuseInstances[argument.name] = new (0, _fusejs2.default)(argument.enum, {
|
|
615
|
+
includeScore: true,
|
|
616
|
+
threshold: 0.3
|
|
617
|
+
// More flexible matching!
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
const prompt = {
|
|
622
|
+
...inputPrompt,
|
|
623
|
+
complete: async (name, value, auth) => {
|
|
624
|
+
if (completers[name]) {
|
|
625
|
+
return await completers[name](value, auth);
|
|
626
|
+
}
|
|
627
|
+
if (inputPrompt.complete) {
|
|
628
|
+
return await inputPrompt.complete(name, value, auth);
|
|
629
|
+
}
|
|
630
|
+
if (fuseInstances[name]) {
|
|
631
|
+
const result = fuseInstances[name].search(value);
|
|
632
|
+
return {
|
|
633
|
+
total: result.length,
|
|
634
|
+
values: result.map((item) => item.item)
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
638
|
+
values: []
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
this.#prompts.push(prompt);
|
|
643
|
+
}
|
|
644
|
+
addResource(inputResource) {
|
|
645
|
+
this.#resources.push(inputResource);
|
|
646
|
+
}
|
|
647
|
+
addResourceTemplate(inputResourceTemplate) {
|
|
648
|
+
const completers = {};
|
|
649
|
+
for (const argument of _nullishCoalesce(inputResourceTemplate.arguments, () => ( []))) {
|
|
650
|
+
if (argument.complete) {
|
|
651
|
+
completers[argument.name] = argument.complete;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
const resourceTemplate = {
|
|
655
|
+
...inputResourceTemplate,
|
|
656
|
+
complete: async (name, value, auth) => {
|
|
657
|
+
if (completers[name]) {
|
|
658
|
+
return await completers[name](value, auth);
|
|
659
|
+
}
|
|
660
|
+
if (inputResourceTemplate.complete) {
|
|
661
|
+
return await inputResourceTemplate.complete(name, value, auth);
|
|
662
|
+
}
|
|
663
|
+
return {
|
|
664
|
+
values: []
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
this.#resourceTemplates.push(resourceTemplate);
|
|
669
|
+
}
|
|
670
|
+
setupCompleteHandlers() {
|
|
671
|
+
this.#server.setRequestHandler(_typesjs.CompleteRequestSchema, async (request) => {
|
|
672
|
+
if (request.params.ref.type === "ref/prompt") {
|
|
673
|
+
const ref = request.params.ref;
|
|
674
|
+
const prompt = "name" in ref && this.#prompts.find((prompt2) => prompt2.name === ref.name);
|
|
675
|
+
if (!prompt) {
|
|
676
|
+
throw new UnexpectedStateError("Unknown prompt", {
|
|
677
|
+
request
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
if (!prompt.complete) {
|
|
681
|
+
throw new UnexpectedStateError("Prompt does not support completion", {
|
|
682
|
+
request
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
const completion = CompletionZodSchema.parse(
|
|
686
|
+
await prompt.complete(
|
|
687
|
+
request.params.argument.name,
|
|
688
|
+
request.params.argument.value,
|
|
689
|
+
this.#auth
|
|
690
|
+
)
|
|
691
|
+
);
|
|
692
|
+
return {
|
|
693
|
+
completion
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
if (request.params.ref.type === "ref/resource") {
|
|
697
|
+
const ref = request.params.ref;
|
|
698
|
+
const resource = "uri" in ref && this.#resourceTemplates.find(
|
|
699
|
+
(resource2) => resource2.uriTemplate === ref.uri
|
|
700
|
+
);
|
|
701
|
+
if (!resource) {
|
|
702
|
+
throw new UnexpectedStateError("Unknown resource", {
|
|
703
|
+
request
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
if (!("uriTemplate" in resource)) {
|
|
707
|
+
throw new UnexpectedStateError("Unexpected resource");
|
|
708
|
+
}
|
|
709
|
+
if (!resource.complete) {
|
|
710
|
+
throw new UnexpectedStateError(
|
|
711
|
+
"Resource does not support completion",
|
|
712
|
+
{
|
|
713
|
+
request
|
|
714
|
+
}
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
const completion = CompletionZodSchema.parse(
|
|
718
|
+
await resource.complete(
|
|
719
|
+
request.params.argument.name,
|
|
720
|
+
request.params.argument.value,
|
|
721
|
+
this.#auth
|
|
722
|
+
)
|
|
723
|
+
);
|
|
724
|
+
return {
|
|
725
|
+
completion
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
throw new UnexpectedStateError("Unexpected completion request", {
|
|
729
|
+
request
|
|
730
|
+
});
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
setupErrorHandling() {
|
|
734
|
+
this.#server.onerror = (error) => {
|
|
735
|
+
this.#logger.error("[FastMCP error]", error);
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
setupLoggingHandlers() {
|
|
739
|
+
this.#server.setRequestHandler(_typesjs.SetLevelRequestSchema, (request) => {
|
|
740
|
+
this.#loggingLevel = request.params.level;
|
|
741
|
+
return {};
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
setupPromptHandlers(prompts) {
|
|
745
|
+
this.#server.setRequestHandler(_typesjs.ListPromptsRequestSchema, async () => {
|
|
746
|
+
return {
|
|
747
|
+
prompts: prompts.map((prompt) => {
|
|
748
|
+
return {
|
|
749
|
+
arguments: prompt.arguments,
|
|
750
|
+
complete: prompt.complete,
|
|
751
|
+
description: prompt.description,
|
|
752
|
+
name: prompt.name
|
|
753
|
+
};
|
|
754
|
+
})
|
|
755
|
+
};
|
|
756
|
+
});
|
|
757
|
+
this.#server.setRequestHandler(_typesjs.GetPromptRequestSchema, async (request) => {
|
|
758
|
+
const prompt = prompts.find(
|
|
759
|
+
(prompt2) => prompt2.name === request.params.name
|
|
760
|
+
);
|
|
761
|
+
if (!prompt) {
|
|
762
|
+
throw new (0, _typesjs.McpError)(
|
|
763
|
+
_typesjs.ErrorCode.MethodNotFound,
|
|
764
|
+
`Unknown prompt: ${request.params.name}`
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
const args = request.params.arguments;
|
|
768
|
+
for (const arg of _nullishCoalesce(prompt.arguments, () => ( []))) {
|
|
769
|
+
if (arg.required && !(args && arg.name in args)) {
|
|
770
|
+
throw new (0, _typesjs.McpError)(
|
|
771
|
+
_typesjs.ErrorCode.InvalidRequest,
|
|
772
|
+
`Prompt '${request.params.name}' requires argument '${arg.name}': ${arg.description || "No description provided"}`
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
let result;
|
|
777
|
+
try {
|
|
778
|
+
result = await prompt.load(
|
|
779
|
+
args,
|
|
780
|
+
this.#auth
|
|
781
|
+
);
|
|
782
|
+
} catch (error) {
|
|
783
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
784
|
+
throw new (0, _typesjs.McpError)(
|
|
785
|
+
_typesjs.ErrorCode.InternalError,
|
|
786
|
+
`Failed to load prompt '${request.params.name}': ${errorMessage}`
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
if (typeof result === "string") {
|
|
790
|
+
return {
|
|
791
|
+
description: prompt.description,
|
|
792
|
+
messages: [
|
|
793
|
+
{
|
|
794
|
+
content: { text: result, type: "text" },
|
|
795
|
+
role: "user"
|
|
796
|
+
}
|
|
797
|
+
]
|
|
798
|
+
};
|
|
799
|
+
} else {
|
|
800
|
+
return {
|
|
801
|
+
description: prompt.description,
|
|
802
|
+
messages: result.messages
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
setupResourceHandlers(resources) {
|
|
808
|
+
this.#server.setRequestHandler(_typesjs.ListResourcesRequestSchema, async () => {
|
|
809
|
+
return {
|
|
810
|
+
resources: resources.map((resource) => ({
|
|
811
|
+
description: resource.description,
|
|
812
|
+
mimeType: resource.mimeType,
|
|
813
|
+
name: resource.name,
|
|
814
|
+
uri: resource.uri
|
|
815
|
+
}))
|
|
816
|
+
};
|
|
817
|
+
});
|
|
818
|
+
this.#server.setRequestHandler(
|
|
819
|
+
_typesjs.ReadResourceRequestSchema,
|
|
820
|
+
async (request) => {
|
|
821
|
+
if ("uri" in request.params) {
|
|
822
|
+
const resource = resources.find(
|
|
823
|
+
(resource2) => "uri" in resource2 && resource2.uri === request.params.uri
|
|
824
|
+
);
|
|
825
|
+
if (!resource) {
|
|
826
|
+
for (const resourceTemplate of this.#resourceTemplates) {
|
|
827
|
+
const uriTemplate = _uritemplates2.default.call(void 0,
|
|
828
|
+
resourceTemplate.uriTemplate
|
|
829
|
+
);
|
|
830
|
+
const match = uriTemplate.fromUri(request.params.uri);
|
|
831
|
+
if (!match) {
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
const uri = uriTemplate.fill(match);
|
|
835
|
+
const result = await resourceTemplate.load(match, this.#auth);
|
|
836
|
+
const resources2 = Array.isArray(result) ? result : [result];
|
|
837
|
+
return {
|
|
838
|
+
contents: resources2.map((resource2) => ({
|
|
839
|
+
...resource2,
|
|
840
|
+
description: resourceTemplate.description,
|
|
841
|
+
mimeType: _nullishCoalesce(resource2.mimeType, () => ( resourceTemplate.mimeType)),
|
|
842
|
+
name: resourceTemplate.name,
|
|
843
|
+
uri: _nullishCoalesce(resource2.uri, () => ( uri))
|
|
844
|
+
}))
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
throw new (0, _typesjs.McpError)(
|
|
848
|
+
_typesjs.ErrorCode.MethodNotFound,
|
|
849
|
+
`Resource not found: '${request.params.uri}'. Available resources: ${resources.map((r) => r.uri).join(", ") || "none"}`
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
if (!("uri" in resource)) {
|
|
853
|
+
throw new UnexpectedStateError("Resource does not support reading");
|
|
854
|
+
}
|
|
855
|
+
let maybeArrayResult;
|
|
856
|
+
try {
|
|
857
|
+
maybeArrayResult = await resource.load(this.#auth);
|
|
858
|
+
} catch (error) {
|
|
859
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
860
|
+
throw new (0, _typesjs.McpError)(
|
|
861
|
+
_typesjs.ErrorCode.InternalError,
|
|
862
|
+
`Failed to load resource '${resource.name}' (${resource.uri}): ${errorMessage}`,
|
|
863
|
+
{
|
|
864
|
+
uri: resource.uri
|
|
865
|
+
}
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
const resourceResults = Array.isArray(maybeArrayResult) ? maybeArrayResult : [maybeArrayResult];
|
|
869
|
+
return {
|
|
870
|
+
contents: resourceResults.map((result) => ({
|
|
871
|
+
...result,
|
|
872
|
+
mimeType: _nullishCoalesce(result.mimeType, () => ( resource.mimeType)),
|
|
873
|
+
name: resource.name,
|
|
874
|
+
uri: _nullishCoalesce(result.uri, () => ( resource.uri))
|
|
875
|
+
}))
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
throw new UnexpectedStateError("Unknown resource request", {
|
|
879
|
+
request
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
setupResourceTemplateHandlers(resourceTemplates) {
|
|
885
|
+
this.#server.setRequestHandler(
|
|
886
|
+
_typesjs.ListResourceTemplatesRequestSchema,
|
|
887
|
+
async () => {
|
|
888
|
+
return {
|
|
889
|
+
resourceTemplates: resourceTemplates.map((resourceTemplate) => ({
|
|
890
|
+
description: resourceTemplate.description,
|
|
891
|
+
mimeType: resourceTemplate.mimeType,
|
|
892
|
+
name: resourceTemplate.name,
|
|
893
|
+
uriTemplate: resourceTemplate.uriTemplate
|
|
894
|
+
}))
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
setupRootsHandlers() {
|
|
900
|
+
if (_optionalChain([this, 'access', _12 => _12.#rootsConfig, 'optionalAccess', _13 => _13.enabled]) === false) {
|
|
901
|
+
this.#logger.debug(
|
|
902
|
+
"[FastMCP debug] roots capability explicitly disabled via config"
|
|
903
|
+
);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
if (typeof this.#server.listRoots === "function") {
|
|
907
|
+
this.#server.setNotificationHandler(
|
|
908
|
+
_typesjs.RootsListChangedNotificationSchema,
|
|
909
|
+
() => {
|
|
910
|
+
this.#server.listRoots().then((roots) => {
|
|
911
|
+
this.#roots = roots.roots;
|
|
912
|
+
this.emit("rootsChanged", {
|
|
913
|
+
roots: roots.roots
|
|
914
|
+
});
|
|
915
|
+
}).catch((error) => {
|
|
916
|
+
if (error instanceof _typesjs.McpError && error.code === _typesjs.ErrorCode.MethodNotFound) {
|
|
917
|
+
this.#logger.debug(
|
|
918
|
+
"[FastMCP debug] listRoots method not supported by client"
|
|
919
|
+
);
|
|
920
|
+
} else {
|
|
921
|
+
this.#logger.error(
|
|
922
|
+
`[FastMCP error] received error listing roots.
|
|
923
|
+
|
|
924
|
+
${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
);
|
|
930
|
+
} else {
|
|
931
|
+
this.#logger.debug(
|
|
932
|
+
"[FastMCP debug] roots capability not available, not setting up notification handler"
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
setupToolHandlers(tools) {
|
|
937
|
+
this.#server.setRequestHandler(_typesjs.ListToolsRequestSchema, async () => {
|
|
938
|
+
return {
|
|
939
|
+
tools: await Promise.all(
|
|
940
|
+
tools.map(async (tool) => {
|
|
941
|
+
return {
|
|
942
|
+
annotations: tool.annotations,
|
|
943
|
+
description: tool.description,
|
|
944
|
+
inputSchema: tool.parameters ? await _xsschema.toJsonSchema.call(void 0, tool.parameters) : {
|
|
945
|
+
additionalProperties: false,
|
|
946
|
+
properties: {},
|
|
947
|
+
type: "object"
|
|
948
|
+
},
|
|
949
|
+
// More complete schema for Cursor compatibility
|
|
950
|
+
name: tool.name
|
|
951
|
+
};
|
|
952
|
+
})
|
|
953
|
+
)
|
|
954
|
+
};
|
|
955
|
+
});
|
|
956
|
+
this.#server.setRequestHandler(_typesjs.CallToolRequestSchema, async (request) => {
|
|
957
|
+
const tool = tools.find((tool2) => tool2.name === request.params.name);
|
|
958
|
+
if (!tool) {
|
|
959
|
+
throw new (0, _typesjs.McpError)(
|
|
960
|
+
_typesjs.ErrorCode.MethodNotFound,
|
|
961
|
+
`Unknown tool: ${request.params.name}`
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
let args = void 0;
|
|
965
|
+
if (tool.parameters) {
|
|
966
|
+
const parsed = await tool.parameters["~standard"].validate(
|
|
967
|
+
request.params.arguments
|
|
968
|
+
);
|
|
969
|
+
if (parsed.issues) {
|
|
970
|
+
const friendlyErrors = _optionalChain([this, 'access', _14 => _14.#utils, 'optionalAccess', _15 => _15.formatInvalidParamsErrorMessage]) ? this.#utils.formatInvalidParamsErrorMessage(parsed.issues) : parsed.issues.map((issue) => {
|
|
971
|
+
const path = _optionalChain([issue, 'access', _16 => _16.path, 'optionalAccess', _17 => _17.join, 'call', _18 => _18(".")]) || "root";
|
|
972
|
+
return `${path}: ${issue.message}`;
|
|
973
|
+
}).join(", ");
|
|
974
|
+
throw new (0, _typesjs.McpError)(
|
|
975
|
+
_typesjs.ErrorCode.InvalidParams,
|
|
976
|
+
`Tool '${request.params.name}' parameter validation failed: ${friendlyErrors}. Please check the parameter types and values according to the tool's schema.`
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
args = parsed.value;
|
|
980
|
+
}
|
|
981
|
+
const progressToken = _optionalChain([request, 'access', _19 => _19.params, 'optionalAccess', _20 => _20._meta, 'optionalAccess', _21 => _21.progressToken]);
|
|
982
|
+
let result;
|
|
983
|
+
try {
|
|
984
|
+
const reportProgress = async (progress) => {
|
|
985
|
+
try {
|
|
986
|
+
await this.#server.notification({
|
|
987
|
+
method: "notifications/progress",
|
|
988
|
+
params: {
|
|
989
|
+
...progress,
|
|
990
|
+
progressToken
|
|
991
|
+
}
|
|
992
|
+
});
|
|
993
|
+
if (this.#needsEventLoopFlush) {
|
|
994
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
995
|
+
}
|
|
996
|
+
} catch (progressError) {
|
|
997
|
+
this.#logger.warn(
|
|
998
|
+
`[FastMCP warning] Failed to report progress for tool '${request.params.name}':`,
|
|
999
|
+
progressError instanceof Error ? progressError.message : String(progressError)
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
const log = {
|
|
1004
|
+
debug: (message, context) => {
|
|
1005
|
+
this.#server.sendLoggingMessage({
|
|
1006
|
+
data: {
|
|
1007
|
+
context,
|
|
1008
|
+
message
|
|
1009
|
+
},
|
|
1010
|
+
level: "debug"
|
|
1011
|
+
});
|
|
1012
|
+
},
|
|
1013
|
+
error: (message, context) => {
|
|
1014
|
+
this.#server.sendLoggingMessage({
|
|
1015
|
+
data: {
|
|
1016
|
+
context,
|
|
1017
|
+
message
|
|
1018
|
+
},
|
|
1019
|
+
level: "error"
|
|
1020
|
+
});
|
|
1021
|
+
},
|
|
1022
|
+
info: (message, context) => {
|
|
1023
|
+
this.#server.sendLoggingMessage({
|
|
1024
|
+
data: {
|
|
1025
|
+
context,
|
|
1026
|
+
message
|
|
1027
|
+
},
|
|
1028
|
+
level: "info"
|
|
1029
|
+
});
|
|
1030
|
+
},
|
|
1031
|
+
warn: (message, context) => {
|
|
1032
|
+
this.#server.sendLoggingMessage({
|
|
1033
|
+
data: {
|
|
1034
|
+
context,
|
|
1035
|
+
message
|
|
1036
|
+
},
|
|
1037
|
+
level: "warning"
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
const streamContent = async (content) => {
|
|
1042
|
+
const contentArray = Array.isArray(content) ? content : [content];
|
|
1043
|
+
try {
|
|
1044
|
+
await this.#server.notification({
|
|
1045
|
+
method: "notifications/tool/streamContent",
|
|
1046
|
+
params: {
|
|
1047
|
+
content: contentArray,
|
|
1048
|
+
toolName: request.params.name
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
if (this.#needsEventLoopFlush) {
|
|
1052
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
1053
|
+
}
|
|
1054
|
+
} catch (streamError) {
|
|
1055
|
+
this.#logger.warn(
|
|
1056
|
+
`[FastMCP warning] Failed to stream content for tool '${request.params.name}':`,
|
|
1057
|
+
streamError instanceof Error ? streamError.message : String(streamError)
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
const executeToolPromise = tool.execute(args, {
|
|
1062
|
+
client: {
|
|
1063
|
+
version: this.#server.getClientVersion()
|
|
1064
|
+
},
|
|
1065
|
+
log,
|
|
1066
|
+
reportProgress,
|
|
1067
|
+
requestId: typeof _optionalChain([request, 'access', _22 => _22.params, 'optionalAccess', _23 => _23._meta, 'optionalAccess', _24 => _24.requestId]) === "string" ? request.params._meta.requestId : void 0,
|
|
1068
|
+
session: this.#auth,
|
|
1069
|
+
sessionId: this.#sessionId,
|
|
1070
|
+
streamContent
|
|
1071
|
+
});
|
|
1072
|
+
const maybeStringResult = await (tool.timeoutMs ? Promise.race([
|
|
1073
|
+
executeToolPromise,
|
|
1074
|
+
new Promise((_, reject) => {
|
|
1075
|
+
const timeoutId = setTimeout(() => {
|
|
1076
|
+
reject(
|
|
1077
|
+
new UserError(
|
|
1078
|
+
`Tool '${request.params.name}' timed out after ${tool.timeoutMs}ms. Consider increasing timeoutMs or optimizing the tool implementation.`
|
|
1079
|
+
)
|
|
1080
|
+
);
|
|
1081
|
+
}, tool.timeoutMs);
|
|
1082
|
+
executeToolPromise.finally(() => clearTimeout(timeoutId));
|
|
1083
|
+
})
|
|
1084
|
+
]) : executeToolPromise);
|
|
1085
|
+
await _promises3.setTimeout.call(void 0, 1);
|
|
1086
|
+
if (maybeStringResult === void 0 || maybeStringResult === null) {
|
|
1087
|
+
result = ContentResultZodSchema.parse({
|
|
1088
|
+
content: []
|
|
1089
|
+
});
|
|
1090
|
+
} else if (typeof maybeStringResult === "string") {
|
|
1091
|
+
result = ContentResultZodSchema.parse({
|
|
1092
|
+
content: [{ text: maybeStringResult, type: "text" }]
|
|
1093
|
+
});
|
|
1094
|
+
} else if ("type" in maybeStringResult) {
|
|
1095
|
+
result = ContentResultZodSchema.parse({
|
|
1096
|
+
content: [maybeStringResult]
|
|
1097
|
+
});
|
|
1098
|
+
} else {
|
|
1099
|
+
result = ContentResultZodSchema.parse(maybeStringResult);
|
|
1100
|
+
}
|
|
1101
|
+
} catch (error) {
|
|
1102
|
+
if (error instanceof UserError) {
|
|
1103
|
+
return {
|
|
1104
|
+
content: [{ text: error.message, type: "text" }],
|
|
1105
|
+
isError: true,
|
|
1106
|
+
...error.extras ? { structuredContent: error.extras } : {}
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1110
|
+
return {
|
|
1111
|
+
content: [
|
|
1112
|
+
{
|
|
1113
|
+
text: `Tool '${request.params.name}' execution failed: ${errorMessage}`,
|
|
1114
|
+
type: "text"
|
|
1115
|
+
}
|
|
1116
|
+
],
|
|
1117
|
+
isError: true
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
return result;
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
function camelToSnakeCase(str) {
|
|
1125
|
+
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
1126
|
+
}
|
|
1127
|
+
function convertObjectToSnakeCase(obj) {
|
|
1128
|
+
const result = {};
|
|
1129
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1130
|
+
const snakeKey = camelToSnakeCase(key);
|
|
1131
|
+
result[snakeKey] = value;
|
|
1132
|
+
}
|
|
1133
|
+
return result;
|
|
1134
|
+
}
|
|
1135
|
+
function parseBasicAuthHeader(authHeader) {
|
|
1136
|
+
const basicMatch = _optionalChain([authHeader, 'optionalAccess', _25 => _25.match, 'call', _26 => _26(/^Basic\s+(.+)$/)]);
|
|
1137
|
+
if (!basicMatch) return null;
|
|
1138
|
+
try {
|
|
1139
|
+
const credentials = Buffer.from(basicMatch[1], "base64").toString("utf-8");
|
|
1140
|
+
const credMatch = credentials.match(/^([^:]+):(.*)$/);
|
|
1141
|
+
if (!credMatch) return null;
|
|
1142
|
+
return { clientId: credMatch[1], clientSecret: credMatch[2] };
|
|
1143
|
+
} catch (e3) {
|
|
1144
|
+
return null;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
var FastMCPEventEmitterBase = _events.EventEmitter;
|
|
1148
|
+
var FastMCPEventEmitter = class extends FastMCPEventEmitterBase {
|
|
1149
|
+
};
|
|
1150
|
+
var FastMCP = class extends FastMCPEventEmitter {
|
|
1151
|
+
constructor(options) {
|
|
1152
|
+
super();
|
|
1153
|
+
this.options = options;
|
|
1154
|
+
this.#options = options;
|
|
1155
|
+
this.#authenticate = options.authenticate;
|
|
1156
|
+
this.#logger = options.logger || console;
|
|
1157
|
+
}
|
|
1158
|
+
get serverState() {
|
|
1159
|
+
return this.#serverState;
|
|
1160
|
+
}
|
|
1161
|
+
get sessions() {
|
|
1162
|
+
return this.#sessions;
|
|
1163
|
+
}
|
|
1164
|
+
#authenticate;
|
|
1165
|
+
#httpStreamServer = null;
|
|
1166
|
+
#logger;
|
|
1167
|
+
#options;
|
|
1168
|
+
#prompts = [];
|
|
1169
|
+
#resources = [];
|
|
1170
|
+
#resourcesTemplates = [];
|
|
1171
|
+
#serverState = "stopped" /* Stopped */;
|
|
1172
|
+
#sessions = [];
|
|
1173
|
+
#tools = [];
|
|
1174
|
+
/**
|
|
1175
|
+
* Adds a prompt to the server.
|
|
1176
|
+
*/
|
|
1177
|
+
addPrompt(prompt) {
|
|
1178
|
+
this.#prompts = this.#prompts.filter((p) => p.name !== prompt.name);
|
|
1179
|
+
this.#prompts.push(prompt);
|
|
1180
|
+
if (this.#serverState === "running" /* Running */) {
|
|
1181
|
+
this.#promptsListChanged(this.#prompts);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Adds prompts to the server.
|
|
1186
|
+
*/
|
|
1187
|
+
addPrompts(prompts) {
|
|
1188
|
+
const newPromptNames = new Set(prompts.map((prompt) => prompt.name));
|
|
1189
|
+
this.#prompts = this.#prompts.filter((p) => !newPromptNames.has(p.name));
|
|
1190
|
+
this.#prompts.push(...prompts);
|
|
1191
|
+
if (this.#serverState === "running" /* Running */) {
|
|
1192
|
+
this.#promptsListChanged(this.#prompts);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Adds a resource to the server.
|
|
1197
|
+
*/
|
|
1198
|
+
addResource(resource) {
|
|
1199
|
+
this.#resources = this.#resources.filter((r) => r.name !== resource.name);
|
|
1200
|
+
this.#resources.push(resource);
|
|
1201
|
+
if (this.#serverState === "running" /* Running */) {
|
|
1202
|
+
this.#resourcesListChanged(this.#resources);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Adds resources to the server.
|
|
1207
|
+
*/
|
|
1208
|
+
addResources(resources) {
|
|
1209
|
+
const newResourceNames = new Set(
|
|
1210
|
+
resources.map((resource) => resource.name)
|
|
1211
|
+
);
|
|
1212
|
+
this.#resources = this.#resources.filter(
|
|
1213
|
+
(r) => !newResourceNames.has(r.name)
|
|
1214
|
+
);
|
|
1215
|
+
this.#resources.push(...resources);
|
|
1216
|
+
if (this.#serverState === "running" /* Running */) {
|
|
1217
|
+
this.#resourcesListChanged(this.#resources);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Adds a resource template to the server.
|
|
1222
|
+
*/
|
|
1223
|
+
addResourceTemplate(resource) {
|
|
1224
|
+
this.#resourcesTemplates = this.#resourcesTemplates.filter(
|
|
1225
|
+
(t) => t.name !== resource.name
|
|
1226
|
+
);
|
|
1227
|
+
this.#resourcesTemplates.push(resource);
|
|
1228
|
+
if (this.#serverState === "running" /* Running */) {
|
|
1229
|
+
this.#resourceTemplatesListChanged(this.#resourcesTemplates);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Adds resource templates to the server.
|
|
1234
|
+
*/
|
|
1235
|
+
addResourceTemplates(resources) {
|
|
1236
|
+
const newResourceTemplateNames = new Set(
|
|
1237
|
+
resources.map((resource) => resource.name)
|
|
1238
|
+
);
|
|
1239
|
+
this.#resourcesTemplates = this.#resourcesTemplates.filter(
|
|
1240
|
+
(t) => !newResourceTemplateNames.has(t.name)
|
|
1241
|
+
);
|
|
1242
|
+
this.#resourcesTemplates.push(...resources);
|
|
1243
|
+
if (this.#serverState === "running" /* Running */) {
|
|
1244
|
+
this.#resourceTemplatesListChanged(this.#resourcesTemplates);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Adds a tool to the server.
|
|
1249
|
+
*/
|
|
1250
|
+
addTool(tool) {
|
|
1251
|
+
this.#tools = this.#tools.filter((t) => t.name !== tool.name);
|
|
1252
|
+
this.#tools.push(tool);
|
|
1253
|
+
if (this.#serverState === "running" /* Running */) {
|
|
1254
|
+
this.#toolsListChanged(this.#tools);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Adds tools to the server.
|
|
1259
|
+
*/
|
|
1260
|
+
addTools(tools) {
|
|
1261
|
+
const newToolNames = new Set(tools.map((tool) => tool.name));
|
|
1262
|
+
this.#tools = this.#tools.filter((t) => !newToolNames.has(t.name));
|
|
1263
|
+
this.#tools.push(...tools);
|
|
1264
|
+
if (this.#serverState === "running" /* Running */) {
|
|
1265
|
+
this.#toolsListChanged(this.#tools);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Embeds a resource by URI, making it easy to include resources in tool responses.
|
|
1270
|
+
*
|
|
1271
|
+
* @param uri - The URI of the resource to embed
|
|
1272
|
+
* @returns Promise<ResourceContent> - The embedded resource content
|
|
1273
|
+
*/
|
|
1274
|
+
async embedded(uri) {
|
|
1275
|
+
const directResource = this.#resources.find(
|
|
1276
|
+
(resource) => resource.uri === uri
|
|
1277
|
+
);
|
|
1278
|
+
if (directResource) {
|
|
1279
|
+
const result = await directResource.load();
|
|
1280
|
+
const results = Array.isArray(result) ? result : [result];
|
|
1281
|
+
const firstResult = results[0];
|
|
1282
|
+
const resourceData = {
|
|
1283
|
+
mimeType: directResource.mimeType,
|
|
1284
|
+
uri
|
|
1285
|
+
};
|
|
1286
|
+
if ("text" in firstResult) {
|
|
1287
|
+
resourceData.text = firstResult.text;
|
|
1288
|
+
}
|
|
1289
|
+
if ("blob" in firstResult) {
|
|
1290
|
+
resourceData.blob = firstResult.blob;
|
|
1291
|
+
}
|
|
1292
|
+
return resourceData;
|
|
1293
|
+
}
|
|
1294
|
+
for (const template of this.#resourcesTemplates) {
|
|
1295
|
+
const parsedTemplate = _uritemplates2.default.call(void 0, template.uriTemplate);
|
|
1296
|
+
const params = parsedTemplate.fromUri(uri);
|
|
1297
|
+
if (!params) {
|
|
1298
|
+
continue;
|
|
1299
|
+
}
|
|
1300
|
+
const result = await template.load(
|
|
1301
|
+
params
|
|
1302
|
+
);
|
|
1303
|
+
const resourceData = {
|
|
1304
|
+
mimeType: template.mimeType,
|
|
1305
|
+
uri
|
|
1306
|
+
};
|
|
1307
|
+
if ("text" in result) {
|
|
1308
|
+
resourceData.text = result.text;
|
|
1309
|
+
}
|
|
1310
|
+
if ("blob" in result) {
|
|
1311
|
+
resourceData.blob = result.blob;
|
|
1312
|
+
}
|
|
1313
|
+
return resourceData;
|
|
1314
|
+
}
|
|
1315
|
+
throw new UnexpectedStateError(`Resource not found: ${uri}`, { uri });
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Removes a prompt from the server.
|
|
1319
|
+
*/
|
|
1320
|
+
removePrompt(name) {
|
|
1321
|
+
this.#prompts = this.#prompts.filter((p) => p.name !== name);
|
|
1322
|
+
if (this.#serverState === "running" /* Running */) {
|
|
1323
|
+
this.#promptsListChanged(this.#prompts);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Removes prompts from the server.
|
|
1328
|
+
*/
|
|
1329
|
+
removePrompts(names) {
|
|
1330
|
+
for (const name of names) {
|
|
1331
|
+
this.#prompts = this.#prompts.filter((p) => p.name !== name);
|
|
1332
|
+
}
|
|
1333
|
+
if (this.#serverState === "running" /* Running */) {
|
|
1334
|
+
this.#promptsListChanged(this.#prompts);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Removes a resource from the server.
|
|
1339
|
+
*/
|
|
1340
|
+
removeResource(name) {
|
|
1341
|
+
this.#resources = this.#resources.filter((r) => r.name !== name);
|
|
1342
|
+
if (this.#serverState === "running" /* Running */) {
|
|
1343
|
+
this.#resourcesListChanged(this.#resources);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Removes resources from the server.
|
|
1348
|
+
*/
|
|
1349
|
+
removeResources(names) {
|
|
1350
|
+
for (const name of names) {
|
|
1351
|
+
this.#resources = this.#resources.filter((r) => r.name !== name);
|
|
1352
|
+
}
|
|
1353
|
+
if (this.#serverState === "running" /* Running */) {
|
|
1354
|
+
this.#resourcesListChanged(this.#resources);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Removes a resource template from the server.
|
|
1359
|
+
*/
|
|
1360
|
+
removeResourceTemplate(name) {
|
|
1361
|
+
this.#resourcesTemplates = this.#resourcesTemplates.filter(
|
|
1362
|
+
(t) => t.name !== name
|
|
1363
|
+
);
|
|
1364
|
+
if (this.#serverState === "running" /* Running */) {
|
|
1365
|
+
this.#resourceTemplatesListChanged(this.#resourcesTemplates);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Removes resource templates from the server.
|
|
1370
|
+
*/
|
|
1371
|
+
removeResourceTemplates(names) {
|
|
1372
|
+
for (const name of names) {
|
|
1373
|
+
this.#resourcesTemplates = this.#resourcesTemplates.filter(
|
|
1374
|
+
(t) => t.name !== name
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
if (this.#serverState === "running" /* Running */) {
|
|
1378
|
+
this.#resourceTemplatesListChanged(this.#resourcesTemplates);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
* Removes a tool from the server.
|
|
1383
|
+
*/
|
|
1384
|
+
removeTool(name) {
|
|
1385
|
+
this.#tools = this.#tools.filter((t) => t.name !== name);
|
|
1386
|
+
if (this.#serverState === "running" /* Running */) {
|
|
1387
|
+
this.#toolsListChanged(this.#tools);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* Removes tools from the server.
|
|
1392
|
+
*/
|
|
1393
|
+
removeTools(names) {
|
|
1394
|
+
for (const name of names) {
|
|
1395
|
+
this.#tools = this.#tools.filter((t) => t.name !== name);
|
|
1396
|
+
}
|
|
1397
|
+
if (this.#serverState === "running" /* Running */) {
|
|
1398
|
+
this.#toolsListChanged(this.#tools);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Starts the server.
|
|
1403
|
+
*/
|
|
1404
|
+
async start(options) {
|
|
1405
|
+
const config = this.#parseRuntimeConfig(options);
|
|
1406
|
+
if (config.transportType === "stdio") {
|
|
1407
|
+
const transport = new (0, _stdiojs.StdioServerTransport)();
|
|
1408
|
+
let auth;
|
|
1409
|
+
if (this.#authenticate) {
|
|
1410
|
+
try {
|
|
1411
|
+
auth = await this.#authenticate(
|
|
1412
|
+
void 0
|
|
1413
|
+
);
|
|
1414
|
+
} catch (error) {
|
|
1415
|
+
this.#logger.error(
|
|
1416
|
+
"[FastMCP error] Authentication failed for stdio transport:",
|
|
1417
|
+
error instanceof Error ? error.message : String(error)
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
const session = new FastMCPSession({
|
|
1422
|
+
auth,
|
|
1423
|
+
instructions: this.#options.instructions,
|
|
1424
|
+
logger: this.#logger,
|
|
1425
|
+
name: this.#options.name,
|
|
1426
|
+
ping: this.#options.ping,
|
|
1427
|
+
prompts: this.#prompts,
|
|
1428
|
+
resources: this.#resources,
|
|
1429
|
+
resourcesTemplates: this.#resourcesTemplates,
|
|
1430
|
+
roots: this.#options.roots,
|
|
1431
|
+
tools: this.#tools,
|
|
1432
|
+
transportType: "stdio",
|
|
1433
|
+
utils: this.#options.utils,
|
|
1434
|
+
version: this.#options.version
|
|
1435
|
+
});
|
|
1436
|
+
await session.connect(transport);
|
|
1437
|
+
this.#sessions.push(session);
|
|
1438
|
+
session.once("error", () => {
|
|
1439
|
+
this.#removeSession(session);
|
|
1440
|
+
});
|
|
1441
|
+
if (transport.onclose) {
|
|
1442
|
+
const originalOnClose = transport.onclose;
|
|
1443
|
+
transport.onclose = () => {
|
|
1444
|
+
this.#removeSession(session);
|
|
1445
|
+
if (originalOnClose) {
|
|
1446
|
+
originalOnClose();
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
} else {
|
|
1450
|
+
transport.onclose = () => {
|
|
1451
|
+
this.#removeSession(session);
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
this.emit("connect", {
|
|
1455
|
+
session
|
|
1456
|
+
});
|
|
1457
|
+
this.#serverState = "running" /* Running */;
|
|
1458
|
+
} else if (config.transportType === "httpStream") {
|
|
1459
|
+
const httpConfig = config.httpStream;
|
|
1460
|
+
if (httpConfig.stateless) {
|
|
1461
|
+
this.#logger.info(
|
|
1462
|
+
`[FastMCP info] Starting server in stateless mode on HTTP Stream at http://${httpConfig.host}:${httpConfig.port}${httpConfig.endpoint}`
|
|
1463
|
+
);
|
|
1464
|
+
this.#httpStreamServer = await _mcpproxy.startHTTPServer.call(void 0, {
|
|
1465
|
+
...this.#authenticate ? { authenticate: this.#authenticate } : {},
|
|
1466
|
+
createServer: async (request) => {
|
|
1467
|
+
let auth;
|
|
1468
|
+
if (this.#authenticate) {
|
|
1469
|
+
auth = await this.#authenticate(request);
|
|
1470
|
+
if (auth === void 0 || auth === null) {
|
|
1471
|
+
throw new Error("Authentication required");
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
const sessionId = Array.isArray(request.headers["mcp-session-id"]) ? request.headers["mcp-session-id"][0] : request.headers["mcp-session-id"];
|
|
1475
|
+
return this.#createSession(auth, sessionId);
|
|
1476
|
+
},
|
|
1477
|
+
enableJsonResponse: httpConfig.enableJsonResponse,
|
|
1478
|
+
eventStore: httpConfig.eventStore,
|
|
1479
|
+
host: httpConfig.host,
|
|
1480
|
+
..._optionalChain([this, 'access', _27 => _27.#options, 'access', _28 => _28.oauth, 'optionalAccess', _29 => _29.enabled]) && _optionalChain([this, 'access', _30 => _30.#options, 'access', _31 => _31.oauth, 'access', _32 => _32.protectedResource, 'optionalAccess', _33 => _33.resource]) ? {
|
|
1481
|
+
oauth: {
|
|
1482
|
+
protectedResource: {
|
|
1483
|
+
resource: this.#options.oauth.protectedResource.resource
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
} : {},
|
|
1487
|
+
// In stateless mode, we don't track sessions
|
|
1488
|
+
onClose: async () => {
|
|
1489
|
+
},
|
|
1490
|
+
onConnect: async () => {
|
|
1491
|
+
this.#logger.debug(
|
|
1492
|
+
`[FastMCP debug] Stateless HTTP Stream request handled`
|
|
1493
|
+
);
|
|
1494
|
+
},
|
|
1495
|
+
onUnhandledRequest: async (req, res) => {
|
|
1496
|
+
await this.#handleUnhandledRequest(
|
|
1497
|
+
req,
|
|
1498
|
+
res,
|
|
1499
|
+
true,
|
|
1500
|
+
httpConfig.host,
|
|
1501
|
+
httpConfig.endpoint
|
|
1502
|
+
);
|
|
1503
|
+
},
|
|
1504
|
+
port: httpConfig.port,
|
|
1505
|
+
stateless: true,
|
|
1506
|
+
streamEndpoint: httpConfig.endpoint
|
|
1507
|
+
});
|
|
1508
|
+
} else {
|
|
1509
|
+
this.#httpStreamServer = await _mcpproxy.startHTTPServer.call(void 0, {
|
|
1510
|
+
...this.#authenticate ? { authenticate: this.#authenticate } : {},
|
|
1511
|
+
createServer: async (request) => {
|
|
1512
|
+
let auth;
|
|
1513
|
+
if (this.#authenticate) {
|
|
1514
|
+
auth = await this.#authenticate(request);
|
|
1515
|
+
}
|
|
1516
|
+
const sessionId = Array.isArray(request.headers["mcp-session-id"]) ? request.headers["mcp-session-id"][0] : request.headers["mcp-session-id"];
|
|
1517
|
+
return this.#createSession(auth, sessionId);
|
|
1518
|
+
},
|
|
1519
|
+
enableJsonResponse: httpConfig.enableJsonResponse,
|
|
1520
|
+
eventStore: httpConfig.eventStore,
|
|
1521
|
+
host: httpConfig.host,
|
|
1522
|
+
..._optionalChain([this, 'access', _34 => _34.#options, 'access', _35 => _35.oauth, 'optionalAccess', _36 => _36.enabled]) && _optionalChain([this, 'access', _37 => _37.#options, 'access', _38 => _38.oauth, 'access', _39 => _39.protectedResource, 'optionalAccess', _40 => _40.resource]) ? {
|
|
1523
|
+
oauth: {
|
|
1524
|
+
protectedResource: {
|
|
1525
|
+
resource: this.#options.oauth.protectedResource.resource
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
} : {},
|
|
1529
|
+
onClose: async (session) => {
|
|
1530
|
+
const sessionIndex = this.#sessions.indexOf(session);
|
|
1531
|
+
if (sessionIndex !== -1) this.#sessions.splice(sessionIndex, 1);
|
|
1532
|
+
this.emit("disconnect", {
|
|
1533
|
+
session
|
|
1534
|
+
});
|
|
1535
|
+
},
|
|
1536
|
+
onConnect: async (session) => {
|
|
1537
|
+
this.#sessions.push(session);
|
|
1538
|
+
this.#logger.info(`[FastMCP info] HTTP Stream session established`);
|
|
1539
|
+
this.emit("connect", {
|
|
1540
|
+
session
|
|
1541
|
+
});
|
|
1542
|
+
},
|
|
1543
|
+
onUnhandledRequest: async (req, res) => {
|
|
1544
|
+
await this.#handleUnhandledRequest(
|
|
1545
|
+
req,
|
|
1546
|
+
res,
|
|
1547
|
+
false,
|
|
1548
|
+
httpConfig.host,
|
|
1549
|
+
httpConfig.endpoint
|
|
1550
|
+
);
|
|
1551
|
+
},
|
|
1552
|
+
port: httpConfig.port,
|
|
1553
|
+
stateless: httpConfig.stateless,
|
|
1554
|
+
streamEndpoint: httpConfig.endpoint
|
|
1555
|
+
});
|
|
1556
|
+
this.#logger.info(
|
|
1557
|
+
`[FastMCP info] server is running on HTTP Stream at http://${httpConfig.host}:${httpConfig.port}${httpConfig.endpoint}`
|
|
1558
|
+
);
|
|
1559
|
+
}
|
|
1560
|
+
this.#serverState = "running" /* Running */;
|
|
1561
|
+
} else {
|
|
1562
|
+
throw new Error("Invalid transport type");
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
/**
|
|
1566
|
+
* Stops the server.
|
|
1567
|
+
*/
|
|
1568
|
+
async stop() {
|
|
1569
|
+
if (this.#httpStreamServer) {
|
|
1570
|
+
await this.#httpStreamServer.close();
|
|
1571
|
+
}
|
|
1572
|
+
this.#serverState = "stopped" /* Stopped */;
|
|
1573
|
+
}
|
|
1574
|
+
/**
|
|
1575
|
+
* Creates a new FastMCPSession instance with the current configuration.
|
|
1576
|
+
* Used both for regular sessions and stateless requests.
|
|
1577
|
+
*/
|
|
1578
|
+
#createSession(auth, sessionId) {
|
|
1579
|
+
if (auth && typeof auth === "object" && "authenticated" in auth && !auth.authenticated) {
|
|
1580
|
+
const errorMessage = "error" in auth && typeof auth.error === "string" ? auth.error : "Authentication failed";
|
|
1581
|
+
throw new Error(errorMessage);
|
|
1582
|
+
}
|
|
1583
|
+
const allowedTools = auth ? this.#tools.filter(
|
|
1584
|
+
(tool) => tool.canAccess ? tool.canAccess(auth) : true
|
|
1585
|
+
) : this.#tools;
|
|
1586
|
+
return new FastMCPSession({
|
|
1587
|
+
auth,
|
|
1588
|
+
instructions: this.#options.instructions,
|
|
1589
|
+
logger: this.#logger,
|
|
1590
|
+
name: this.#options.name,
|
|
1591
|
+
ping: this.#options.ping,
|
|
1592
|
+
prompts: this.#prompts,
|
|
1593
|
+
resources: this.#resources,
|
|
1594
|
+
resourcesTemplates: this.#resourcesTemplates,
|
|
1595
|
+
roots: this.#options.roots,
|
|
1596
|
+
sessionId,
|
|
1597
|
+
tools: allowedTools,
|
|
1598
|
+
transportType: "httpStream",
|
|
1599
|
+
utils: this.#options.utils,
|
|
1600
|
+
version: this.#options.version
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Handles unhandled HTTP requests with health, readiness, and OAuth endpoints
|
|
1605
|
+
*/
|
|
1606
|
+
#handleUnhandledRequest = async (req, res, isStateless = false, host, streamEndpoint) => {
|
|
1607
|
+
const healthConfig = _nullishCoalesce(this.#options.health, () => ( {}));
|
|
1608
|
+
const enabled = healthConfig.enabled === void 0 ? true : healthConfig.enabled;
|
|
1609
|
+
if (enabled) {
|
|
1610
|
+
const path = _nullishCoalesce(healthConfig.path, () => ( "/health"));
|
|
1611
|
+
const url = new URL(req.url || "", `http://${host}`);
|
|
1612
|
+
try {
|
|
1613
|
+
if (req.method === "GET" && url.pathname === path) {
|
|
1614
|
+
res.writeHead(_nullishCoalesce(healthConfig.status, () => ( 200)), {
|
|
1615
|
+
"Content-Type": "text/plain"
|
|
1616
|
+
}).end(_nullishCoalesce(healthConfig.message, () => ( "\u2713 Ok")));
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
if (req.method === "GET" && url.pathname === "/ready") {
|
|
1620
|
+
if (isStateless) {
|
|
1621
|
+
const response = {
|
|
1622
|
+
mode: "stateless",
|
|
1623
|
+
ready: 1,
|
|
1624
|
+
status: "ready",
|
|
1625
|
+
total: 1
|
|
1626
|
+
};
|
|
1627
|
+
res.writeHead(200, {
|
|
1628
|
+
"Content-Type": "application/json"
|
|
1629
|
+
}).end(JSON.stringify(response));
|
|
1630
|
+
} else {
|
|
1631
|
+
const readySessions = this.#sessions.filter(
|
|
1632
|
+
(s) => s.isReady
|
|
1633
|
+
).length;
|
|
1634
|
+
const totalSessions = this.#sessions.length;
|
|
1635
|
+
const allReady = readySessions === totalSessions && totalSessions > 0;
|
|
1636
|
+
const response = {
|
|
1637
|
+
ready: readySessions,
|
|
1638
|
+
status: allReady ? "ready" : totalSessions === 0 ? "no_sessions" : "initializing",
|
|
1639
|
+
total: totalSessions
|
|
1640
|
+
};
|
|
1641
|
+
res.writeHead(allReady ? 200 : 503, {
|
|
1642
|
+
"Content-Type": "application/json"
|
|
1643
|
+
}).end(JSON.stringify(response));
|
|
1644
|
+
}
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
} catch (error) {
|
|
1648
|
+
this.#logger.error("[FastMCP error] health endpoint error", error);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
const oauthConfig = this.#options.oauth;
|
|
1652
|
+
if (_optionalChain([oauthConfig, 'optionalAccess', _41 => _41.enabled]) && req.method === "GET") {
|
|
1653
|
+
const url = new URL(req.url || "", `http://${host}`);
|
|
1654
|
+
if (url.pathname === "/.well-known/oauth-authorization-server" && oauthConfig.authorizationServer) {
|
|
1655
|
+
const metadata = convertObjectToSnakeCase(
|
|
1656
|
+
oauthConfig.authorizationServer
|
|
1657
|
+
);
|
|
1658
|
+
res.writeHead(200, {
|
|
1659
|
+
"Content-Type": "application/json"
|
|
1660
|
+
}).end(JSON.stringify(metadata));
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
if (oauthConfig.protectedResource) {
|
|
1664
|
+
const wellKnownBase = "/.well-known/oauth-protected-resource";
|
|
1665
|
+
let shouldServeMetadata = false;
|
|
1666
|
+
if (streamEndpoint && url.pathname === `${wellKnownBase}${streamEndpoint}`) {
|
|
1667
|
+
shouldServeMetadata = true;
|
|
1668
|
+
} else if (url.pathname === wellKnownBase) {
|
|
1669
|
+
shouldServeMetadata = true;
|
|
1670
|
+
}
|
|
1671
|
+
if (shouldServeMetadata) {
|
|
1672
|
+
const metadata = convertObjectToSnakeCase(
|
|
1673
|
+
oauthConfig.protectedResource
|
|
1674
|
+
);
|
|
1675
|
+
res.writeHead(200, {
|
|
1676
|
+
"Content-Type": "application/json"
|
|
1677
|
+
}).end(JSON.stringify(metadata));
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
const oauthProxy = _optionalChain([oauthConfig, 'optionalAccess', _42 => _42.proxy]);
|
|
1683
|
+
if (oauthProxy && _optionalChain([oauthConfig, 'optionalAccess', _43 => _43.enabled])) {
|
|
1684
|
+
const url = new URL(req.url || "", `http://${host}`);
|
|
1685
|
+
try {
|
|
1686
|
+
if (req.method === "POST" && url.pathname === "/oauth/register") {
|
|
1687
|
+
let body = "";
|
|
1688
|
+
req.on("data", (chunk) => body += chunk);
|
|
1689
|
+
req.on("end", async () => {
|
|
1690
|
+
try {
|
|
1691
|
+
const request = JSON.parse(body);
|
|
1692
|
+
const response = await oauthProxy.registerClient(request);
|
|
1693
|
+
res.writeHead(201, { "Content-Type": "application/json" }).end(JSON.stringify(response));
|
|
1694
|
+
} catch (error) {
|
|
1695
|
+
const statusCode = error.statusCode || 400;
|
|
1696
|
+
res.writeHead(statusCode, { "Content-Type": "application/json" }).end(
|
|
1697
|
+
JSON.stringify(
|
|
1698
|
+
_optionalChain([error, 'access', _44 => _44.toJSON, 'optionalCall', _45 => _45()]) || {
|
|
1699
|
+
error: "invalid_request"
|
|
1700
|
+
}
|
|
1701
|
+
)
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
});
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
if (req.method === "GET" && url.pathname === "/oauth/authorize") {
|
|
1708
|
+
try {
|
|
1709
|
+
const params = Object.fromEntries(url.searchParams.entries());
|
|
1710
|
+
const response = await oauthProxy.authorize(
|
|
1711
|
+
params
|
|
1712
|
+
);
|
|
1713
|
+
const location = response.headers.get("Location");
|
|
1714
|
+
if (location) {
|
|
1715
|
+
res.writeHead(response.status, { Location: location }).end();
|
|
1716
|
+
} else {
|
|
1717
|
+
const html = await response.text();
|
|
1718
|
+
res.writeHead(response.status, { "Content-Type": "text/html" }).end(html);
|
|
1719
|
+
}
|
|
1720
|
+
} catch (error) {
|
|
1721
|
+
res.writeHead(400, { "Content-Type": "application/json" }).end(
|
|
1722
|
+
JSON.stringify(
|
|
1723
|
+
_optionalChain([error, 'access', _46 => _46.toJSON, 'optionalCall', _47 => _47()]) || {
|
|
1724
|
+
error: "invalid_request"
|
|
1725
|
+
}
|
|
1726
|
+
)
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
if (req.method === "GET" && url.pathname === "/oauth/callback") {
|
|
1732
|
+
try {
|
|
1733
|
+
const mockRequest = new Request(`http://${host}${req.url}`);
|
|
1734
|
+
const response = await oauthProxy.handleCallback(mockRequest);
|
|
1735
|
+
const location = response.headers.get("Location");
|
|
1736
|
+
if (location) {
|
|
1737
|
+
res.writeHead(response.status, { Location: location }).end();
|
|
1738
|
+
} else {
|
|
1739
|
+
const text = await response.text();
|
|
1740
|
+
res.writeHead(response.status).end(text);
|
|
1741
|
+
}
|
|
1742
|
+
} catch (error) {
|
|
1743
|
+
res.writeHead(400, { "Content-Type": "application/json" }).end(
|
|
1744
|
+
JSON.stringify(
|
|
1745
|
+
_optionalChain([error, 'access', _48 => _48.toJSON, 'optionalCall', _49 => _49()]) || {
|
|
1746
|
+
error: "server_error"
|
|
1747
|
+
}
|
|
1748
|
+
)
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
1751
|
+
return;
|
|
1752
|
+
}
|
|
1753
|
+
if (req.method === "POST" && url.pathname === "/oauth/consent") {
|
|
1754
|
+
let body = "";
|
|
1755
|
+
req.on("data", (chunk) => body += chunk);
|
|
1756
|
+
req.on("end", async () => {
|
|
1757
|
+
try {
|
|
1758
|
+
const mockRequest = new Request(`http://${host}/oauth/consent`, {
|
|
1759
|
+
body,
|
|
1760
|
+
headers: {
|
|
1761
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1762
|
+
},
|
|
1763
|
+
method: "POST"
|
|
1764
|
+
});
|
|
1765
|
+
const response = await oauthProxy.handleConsent(mockRequest);
|
|
1766
|
+
const location = response.headers.get("Location");
|
|
1767
|
+
if (location) {
|
|
1768
|
+
res.writeHead(response.status, { Location: location }).end();
|
|
1769
|
+
} else {
|
|
1770
|
+
const text = await response.text();
|
|
1771
|
+
res.writeHead(response.status).end(text);
|
|
1772
|
+
}
|
|
1773
|
+
} catch (error) {
|
|
1774
|
+
res.writeHead(400, { "Content-Type": "application/json" }).end(
|
|
1775
|
+
JSON.stringify(
|
|
1776
|
+
_optionalChain([error, 'access', _50 => _50.toJSON, 'optionalCall', _51 => _51()]) || {
|
|
1777
|
+
error: "server_error"
|
|
1778
|
+
}
|
|
1779
|
+
)
|
|
1780
|
+
);
|
|
1781
|
+
}
|
|
1782
|
+
});
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
if (req.method === "POST" && url.pathname === "/oauth/token") {
|
|
1786
|
+
let body = "";
|
|
1787
|
+
req.on("data", (chunk) => body += chunk);
|
|
1788
|
+
req.on("end", async () => {
|
|
1789
|
+
try {
|
|
1790
|
+
const params = new URLSearchParams(body);
|
|
1791
|
+
const grantType = params.get("grant_type");
|
|
1792
|
+
const basicAuth = parseBasicAuthHeader(req.headers.authorization);
|
|
1793
|
+
const clientId = _optionalChain([basicAuth, 'optionalAccess', _52 => _52.clientId]) || params.get("client_id") || "";
|
|
1794
|
+
const clientSecret = _nullishCoalesce(_nullishCoalesce(_optionalChain([basicAuth, 'optionalAccess', _53 => _53.clientSecret]), () => ( params.get("client_secret"))), () => ( void 0));
|
|
1795
|
+
let response;
|
|
1796
|
+
if (grantType === "authorization_code") {
|
|
1797
|
+
response = await oauthProxy.exchangeAuthorizationCode({
|
|
1798
|
+
client_id: clientId,
|
|
1799
|
+
client_secret: clientSecret,
|
|
1800
|
+
code: params.get("code") || "",
|
|
1801
|
+
code_verifier: params.get("code_verifier") || void 0,
|
|
1802
|
+
grant_type: "authorization_code",
|
|
1803
|
+
redirect_uri: params.get("redirect_uri") || ""
|
|
1804
|
+
});
|
|
1805
|
+
} else if (grantType === "refresh_token") {
|
|
1806
|
+
response = await oauthProxy.exchangeRefreshToken({
|
|
1807
|
+
client_id: clientId,
|
|
1808
|
+
client_secret: clientSecret,
|
|
1809
|
+
grant_type: "refresh_token",
|
|
1810
|
+
refresh_token: params.get("refresh_token") || "",
|
|
1811
|
+
scope: params.get("scope") || void 0
|
|
1812
|
+
});
|
|
1813
|
+
} else {
|
|
1814
|
+
throw {
|
|
1815
|
+
statusCode: 400,
|
|
1816
|
+
toJSON: () => ({ error: "unsupported_grant_type" })
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(response));
|
|
1820
|
+
} catch (error) {
|
|
1821
|
+
const statusCode = error.statusCode || 400;
|
|
1822
|
+
res.writeHead(statusCode, { "Content-Type": "application/json" }).end(
|
|
1823
|
+
JSON.stringify(
|
|
1824
|
+
_optionalChain([error, 'access', _54 => _54.toJSON, 'optionalCall', _55 => _55()]) || {
|
|
1825
|
+
error: "invalid_request"
|
|
1826
|
+
}
|
|
1827
|
+
)
|
|
1828
|
+
);
|
|
1829
|
+
}
|
|
1830
|
+
});
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
} catch (error) {
|
|
1834
|
+
this.#logger.error("[FastMCP error] OAuth Proxy endpoint error", error);
|
|
1835
|
+
res.writeHead(500).end();
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
res.writeHead(404).end();
|
|
1840
|
+
};
|
|
1841
|
+
#parseRuntimeConfig(overrides) {
|
|
1842
|
+
const args = process.argv.slice(2);
|
|
1843
|
+
const getArg = (name) => {
|
|
1844
|
+
const index = args.findIndex((arg) => arg === `--${name}`);
|
|
1845
|
+
return index !== -1 && index + 1 < args.length ? args[index + 1] : void 0;
|
|
1846
|
+
};
|
|
1847
|
+
const transportArg = getArg("transport");
|
|
1848
|
+
const portArg = getArg("port");
|
|
1849
|
+
const endpointArg = getArg("endpoint");
|
|
1850
|
+
const statelessArg = getArg("stateless");
|
|
1851
|
+
const hostArg = getArg("host");
|
|
1852
|
+
const envTransport = process.env.FASTMCP_TRANSPORT;
|
|
1853
|
+
const envPort = process.env.FASTMCP_PORT;
|
|
1854
|
+
const envEndpoint = process.env.FASTMCP_ENDPOINT;
|
|
1855
|
+
const envStateless = process.env.FASTMCP_STATELESS;
|
|
1856
|
+
const envHost = process.env.FASTMCP_HOST;
|
|
1857
|
+
const transportType = _optionalChain([overrides, 'optionalAccess', _56 => _56.transportType]) || (transportArg === "http-stream" ? "httpStream" : transportArg) || envTransport || "stdio";
|
|
1858
|
+
if (transportType === "httpStream") {
|
|
1859
|
+
const port = parseInt(
|
|
1860
|
+
_optionalChain([overrides, 'optionalAccess', _57 => _57.httpStream, 'optionalAccess', _58 => _58.port, 'optionalAccess', _59 => _59.toString, 'call', _60 => _60()]) || portArg || envPort || "8080"
|
|
1861
|
+
);
|
|
1862
|
+
const host = _optionalChain([overrides, 'optionalAccess', _61 => _61.httpStream, 'optionalAccess', _62 => _62.host]) || hostArg || envHost || "localhost";
|
|
1863
|
+
const endpoint = _optionalChain([overrides, 'optionalAccess', _63 => _63.httpStream, 'optionalAccess', _64 => _64.endpoint]) || endpointArg || envEndpoint || "/mcp";
|
|
1864
|
+
const enableJsonResponse = _optionalChain([overrides, 'optionalAccess', _65 => _65.httpStream, 'optionalAccess', _66 => _66.enableJsonResponse]) || false;
|
|
1865
|
+
const stateless = _optionalChain([overrides, 'optionalAccess', _67 => _67.httpStream, 'optionalAccess', _68 => _68.stateless]) || statelessArg === "true" || envStateless === "true" || false;
|
|
1866
|
+
return {
|
|
1867
|
+
httpStream: {
|
|
1868
|
+
enableJsonResponse,
|
|
1869
|
+
endpoint,
|
|
1870
|
+
host,
|
|
1871
|
+
port,
|
|
1872
|
+
stateless
|
|
1873
|
+
},
|
|
1874
|
+
transportType: "httpStream"
|
|
1875
|
+
};
|
|
1876
|
+
}
|
|
1877
|
+
return { transportType: "stdio" };
|
|
1878
|
+
}
|
|
1879
|
+
/**
|
|
1880
|
+
* Notifies all sessions that the prompts list has changed.
|
|
1881
|
+
*/
|
|
1882
|
+
#promptsListChanged(prompts) {
|
|
1883
|
+
for (const session of this.#sessions) {
|
|
1884
|
+
session.promptsListChanged(prompts);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
#removeSession(session) {
|
|
1888
|
+
const sessionIndex = this.#sessions.indexOf(session);
|
|
1889
|
+
if (sessionIndex !== -1) {
|
|
1890
|
+
this.#sessions.splice(sessionIndex, 1);
|
|
1891
|
+
this.emit("disconnect", {
|
|
1892
|
+
session
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
/**
|
|
1897
|
+
* Notifies all sessions that the resources list has changed.
|
|
1898
|
+
*/
|
|
1899
|
+
#resourcesListChanged(resources) {
|
|
1900
|
+
for (const session of this.#sessions) {
|
|
1901
|
+
session.resourcesListChanged(resources);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
/**
|
|
1905
|
+
* Notifies all sessions that the resource templates list has changed.
|
|
1906
|
+
*/
|
|
1907
|
+
#resourceTemplatesListChanged(templates) {
|
|
1908
|
+
for (const session of this.#sessions) {
|
|
1909
|
+
session.resourceTemplatesListChanged(templates);
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* Notifies all sessions that the tools list has changed.
|
|
1914
|
+
*/
|
|
1915
|
+
#toolsListChanged(tools) {
|
|
1916
|
+
for (const session of this.#sessions) {
|
|
1917
|
+
session.toolsListChanged(tools);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
};
|
|
1921
|
+
|
|
1922
|
+
|
|
1923
|
+
|
|
1924
|
+
|
|
1925
|
+
|
|
1926
|
+
|
|
1927
|
+
|
|
1928
|
+
|
|
1929
|
+
|
|
1930
|
+
exports.DiscoveryDocumentCache = DiscoveryDocumentCache; exports.FastMCP = FastMCP; exports.FastMCPSession = FastMCPSession; exports.ServerState = ServerState; exports.UnexpectedStateError = UnexpectedStateError; exports.UserError = UserError; exports.audioContent = audioContent; exports.imageContent = imageContent;
|
|
1931
|
+
//# sourceMappingURL=FastMCP.cjs.map
|