@prefecthq/fastmcp-ts 1.5.2 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/entrypoint-runtime.cjs +0 -5
- package/dist/cli/entrypoint-runtime.cjs.map +1 -1
- package/dist/cli/index.cjs +5171 -4927
- package/dist/cli/index.cjs.map +1 -1
- package/dist/client.d.ts +15 -1
- package/dist/client.js +163 -2
- package/dist/client.js.map +1 -1
- package/dist/server.d.ts +135 -6
- package/dist/server.js +578 -101
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -74,7 +74,7 @@ function resolveSensitiveHeaders(options) {
|
|
|
74
74
|
for (const h of options?.exposeHeaders ?? []) set.delete(h.toLowerCase());
|
|
75
75
|
return set;
|
|
76
76
|
}
|
|
77
|
-
function buildHttpRequestContext(req, sensitiveHeaders) {
|
|
77
|
+
function buildHttpRequestContext(req, sensitiveHeaders, logger) {
|
|
78
78
|
const headers = new Headers();
|
|
79
79
|
const redacted = [];
|
|
80
80
|
req.headers.forEach((value, name) => {
|
|
@@ -89,7 +89,8 @@ function buildHttpRequestContext(req, sensitiveHeaders) {
|
|
|
89
89
|
try {
|
|
90
90
|
const u = new URL(req.url);
|
|
91
91
|
url = u.pathname + u.search;
|
|
92
|
-
} catch {
|
|
92
|
+
} catch (err) {
|
|
93
|
+
logger?.debug("request URL parse failed; using origin-form as-is", { component: "http", error: err });
|
|
93
94
|
url = req.url;
|
|
94
95
|
}
|
|
95
96
|
return { headers, redactedHeaderNames: redacted, method: req.method, url };
|
|
@@ -147,7 +148,7 @@ var SESSION_CLOSE_CALLBACKS_KEY = "__fastmcp_session_close_callbacks";
|
|
|
147
148
|
var SESSION_STATE_MODERN_HTTP_ERROR = "[fastmcp] Session state is not available on modern HTTP requests (protocol revision 2026-07-28). Each request runs statelessly with no shared session store. Use ctx.requestState() to read per-request state. Use ctx.mintRequestState() to carry state across a multi-round-trip flow.";
|
|
148
149
|
var SESSION_STATE_STATELESS_HTTP_ERROR = "[fastmcp] Session state is not available on a stateless HTTP server (RunOptions.stateless or FASTMCP_STATELESS_HTTP). Each request runs against a fresh store that is discarded when the request ends. Use ctx.requestState() to read per-request state. Use ctx.mintRequestState() to carry state across a multi-round-trip flow. Turn stateless off to use session state, or keep state in your own external store.";
|
|
149
150
|
var SERVER_INITIATED_STATELESS_HTTP_ERROR = "[fastmcp] Server-initiated requests (ctx.elicit, ctx.sample, ctx.listRoots) are not available on a stateless HTTP server (RunOptions.stateless or FASTMCP_STATELESS_HTTP). They need a session: client capabilities are negotiated once at initialize, and the client replies on a separate request. Return inputRequired({ requestState }) to carry a multi-round-trip flow without a session; the embedded-request form of inputRequired needs a session too. Turn stateless off to use server-initiated requests.";
|
|
150
|
-
function createContext(server, sdkCtx, auth, sessionState, requestStateCodec, stateless, sensitiveHeaders) {
|
|
151
|
+
function createContext(server, sdkCtx, auth, sessionState, requestStateCodec, stateless, sensitiveHeaders, logger) {
|
|
151
152
|
const requestId = String(sdkCtx.mcpReq.id);
|
|
152
153
|
const progressToken = sdkCtx.mcpReq._meta?.progressToken;
|
|
153
154
|
const isModernEra = sdkCtx.mcpReq.envelope !== void 0;
|
|
@@ -165,16 +166,16 @@ function createContext(server, sdkCtx, auth, sessionState, requestStateCodec, st
|
|
|
165
166
|
return {
|
|
166
167
|
auth,
|
|
167
168
|
requestId,
|
|
168
|
-
http: httpReq ? buildHttpRequestContext(httpReq, sensitive) : void 0,
|
|
169
|
+
http: httpReq ? buildHttpRequestContext(httpReq, sensitive, logger) : void 0,
|
|
169
170
|
log,
|
|
170
|
-
debug: (msg,
|
|
171
|
-
info: (msg,
|
|
172
|
-
notice: (msg,
|
|
173
|
-
warning: (msg,
|
|
174
|
-
error: (msg,
|
|
175
|
-
critical: (msg,
|
|
176
|
-
alert: (msg,
|
|
177
|
-
emergency: (msg,
|
|
171
|
+
debug: (msg, logger2) => log("debug", msg, logger2),
|
|
172
|
+
info: (msg, logger2) => log("info", msg, logger2),
|
|
173
|
+
notice: (msg, logger2) => log("notice", msg, logger2),
|
|
174
|
+
warning: (msg, logger2) => log("warning", msg, logger2),
|
|
175
|
+
error: (msg, logger2) => log("error", msg, logger2),
|
|
176
|
+
critical: (msg, logger2) => log("critical", msg, logger2),
|
|
177
|
+
alert: (msg, logger2) => log("alert", msg, logger2),
|
|
178
|
+
emergency: (msg, logger2) => log("emergency", msg, logger2),
|
|
178
179
|
async reportProgress(progress, total, message) {
|
|
179
180
|
if (progressToken === void 0) return;
|
|
180
181
|
await sdkCtx.mcpReq.notify({
|
|
@@ -256,9 +257,16 @@ function createContext(server, sdkCtx, auth, sessionState, requestStateCodec, st
|
|
|
256
257
|
requestState: () => sdkCtx.mcpReq.requestState(),
|
|
257
258
|
async mintRequestState(payload) {
|
|
258
259
|
if (requestStateCodec) return requestStateCodec.mint(payload, sdkCtx);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
260
|
+
if (logger) {
|
|
261
|
+
logger.warn(
|
|
262
|
+
"ctx.mintRequestState() called without FastMCPOptions.requestState configured; the resulting requestState is unsigned. Configure FastMCPOptions.requestState for any state that influences authorization, resource access, or business logic.",
|
|
263
|
+
{ component: "context" }
|
|
264
|
+
);
|
|
265
|
+
} else {
|
|
266
|
+
console.warn(
|
|
267
|
+
"[fastmcp] ctx.mintRequestState() called without FastMCPOptions.requestState configured \u2014 the resulting requestState is unsigned. Configure FastMCPOptions.requestState for any state that influences authorization, resource access, or business logic."
|
|
268
|
+
);
|
|
269
|
+
}
|
|
262
270
|
return JSON.stringify(payload);
|
|
263
271
|
},
|
|
264
272
|
getState: (key) => {
|
|
@@ -296,7 +304,7 @@ var CustomRouteRegistry = class {
|
|
|
296
304
|
}
|
|
297
305
|
if (methods.includes("OPTIONS")) {
|
|
298
306
|
throw new Error(
|
|
299
|
-
`customRoute ${path}: OPTIONS cannot be routed \u2014 the CORS preflight
|
|
307
|
+
`customRoute ${path}: OPTIONS cannot be routed \u2014 it is reserved for the CORS preflight and never dispatches to routes`
|
|
300
308
|
);
|
|
301
309
|
}
|
|
302
310
|
if (this._routes.has(path)) {
|
|
@@ -355,13 +363,14 @@ function resolveHealth(health) {
|
|
|
355
363
|
}
|
|
356
364
|
return { path, status, body };
|
|
357
365
|
}
|
|
358
|
-
async function serveCustomRouteNode(handler, req, res) {
|
|
366
|
+
async function serveCustomRouteNode(handler, req, res, logger) {
|
|
359
367
|
const { toWebRequest } = await import("@modelcontextprotocol/node");
|
|
360
368
|
let response;
|
|
361
369
|
try {
|
|
362
370
|
response = await handler(await toWebRequest(req));
|
|
363
371
|
} catch (error) {
|
|
364
|
-
|
|
372
|
+
if (logger) logger.error("custom route handler failed", { component: "routes", error });
|
|
373
|
+
else console.error("[fastmcp] custom route handler failed:", error);
|
|
365
374
|
res.writeHead(500, { "Content-Type": "application/json" }).end(JSON.stringify({ error: "Internal Server Error" }));
|
|
366
375
|
return;
|
|
367
376
|
}
|
|
@@ -369,7 +378,8 @@ async function serveCustomRouteNode(handler, req, res) {
|
|
|
369
378
|
if (response.body) {
|
|
370
379
|
const stream = Readable.fromWeb(response.body);
|
|
371
380
|
stream.on("error", (error) => {
|
|
372
|
-
|
|
381
|
+
if (logger) logger.error("custom route response stream failed", { component: "routes", error });
|
|
382
|
+
else console.error("[fastmcp] custom route response stream failed:", error);
|
|
373
383
|
res.destroy();
|
|
374
384
|
});
|
|
375
385
|
stream.pipe(res);
|
|
@@ -428,21 +438,68 @@ function runMiddlewareChain(middleware, method, request, mcpContext, handler) {
|
|
|
428
438
|
return dispatch(0);
|
|
429
439
|
}
|
|
430
440
|
var LoggingMiddleware = class {
|
|
431
|
-
constructor(emit
|
|
441
|
+
constructor(emit) {
|
|
432
442
|
this.emit = emit;
|
|
433
443
|
}
|
|
434
444
|
emit;
|
|
445
|
+
logger;
|
|
446
|
+
/** @internal Called by FastMCP when this middleware is attached. */
|
|
447
|
+
_bindLogger(logger) {
|
|
448
|
+
if (!this.emit) this.logger = logger;
|
|
449
|
+
}
|
|
450
|
+
// Three output paths, each shaped for its audience:
|
|
451
|
+
// - explicit `emit`: byte-identical to the pre-logger-injection format
|
|
452
|
+
// (arrows and the ✗ glyph), since callers may already parse it.
|
|
453
|
+
// - `this.logger` (the LOGGER branch): the spec invariant that injected
|
|
454
|
+
// loggers receive no symbols — plain text plus structured meta.
|
|
455
|
+
// - stderr fallback (detached from a server): presentation output, like
|
|
456
|
+
// DefaultSink, so it keeps the arrow/glyph format too.
|
|
457
|
+
emitRequest(method) {
|
|
458
|
+
if (this.emit) {
|
|
459
|
+
this.emit(`[fastmcp] \u2192 ${method}`);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if (this.logger) {
|
|
463
|
+
this.logger.info(`request ${method}`);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
process.stderr.write(`[fastmcp] \u2192 ${method}
|
|
467
|
+
`);
|
|
468
|
+
}
|
|
469
|
+
emitResponse(method, ms) {
|
|
470
|
+
if (this.emit) {
|
|
471
|
+
this.emit(`[fastmcp] \u2190 ${method} (${ms}ms)`);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
if (this.logger) {
|
|
475
|
+
this.logger.info(`response ${method}`, { ms });
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
process.stderr.write(`[fastmcp] \u2190 ${method} (${ms}ms)
|
|
479
|
+
`);
|
|
480
|
+
}
|
|
481
|
+
emitFailure(method, ms, err) {
|
|
482
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
483
|
+
if (this.emit) {
|
|
484
|
+
this.emit(`[fastmcp] \u2717 ${method} (${ms}ms): ${message}`);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
if (this.logger) {
|
|
488
|
+
this.logger.warn(`request failed ${method}`, { ms, error: err });
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
process.stderr.write(`[fastmcp] \u2717 ${method} (${ms}ms): ${message}
|
|
492
|
+
`);
|
|
493
|
+
}
|
|
435
494
|
async onRequest(ctx, next) {
|
|
436
495
|
const t0 = Date.now();
|
|
437
|
-
this.
|
|
496
|
+
this.emitRequest(ctx.method);
|
|
438
497
|
try {
|
|
439
498
|
const result = await next();
|
|
440
|
-
this.
|
|
499
|
+
this.emitResponse(ctx.method, Date.now() - t0);
|
|
441
500
|
return result;
|
|
442
501
|
} catch (err) {
|
|
443
|
-
this.
|
|
444
|
-
`[fastmcp] \u2717 ${ctx.method} (${Date.now() - t0}ms): ${err instanceof Error ? err.message : String(err)}`
|
|
445
|
-
);
|
|
502
|
+
this.emitFailure(ctx.method, Date.now() - t0, err);
|
|
446
503
|
throw err;
|
|
447
504
|
}
|
|
448
505
|
}
|
|
@@ -744,7 +801,7 @@ var ToolResult = class {
|
|
|
744
801
|
}
|
|
745
802
|
result;
|
|
746
803
|
};
|
|
747
|
-
function convertResult(value, stateless) {
|
|
804
|
+
function convertResult(value, stateless, logger) {
|
|
748
805
|
if (isInputRequiredResult(value)) {
|
|
749
806
|
assertInputRequestsAllowedStateless(value, stateless);
|
|
750
807
|
return value;
|
|
@@ -781,9 +838,16 @@ function convertResult(value, stateless) {
|
|
|
781
838
|
return { content: [] };
|
|
782
839
|
}
|
|
783
840
|
if (Array.isArray(value)) {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
841
|
+
if (logger) {
|
|
842
|
+
logger.warn(
|
|
843
|
+
"Tool returned an array; structuredContent will not be set (MCP spec requires a plain object). Wrap in an object or use ToolResult to suppress this warning.",
|
|
844
|
+
{ component: "tool" }
|
|
845
|
+
);
|
|
846
|
+
} else {
|
|
847
|
+
console.warn(
|
|
848
|
+
"[fastmcp] Tool returned an array \u2014 structuredContent will not be set (MCP spec requires a plain object). Wrap in an object or use ToolResult to suppress this warning."
|
|
849
|
+
);
|
|
850
|
+
}
|
|
787
851
|
return {
|
|
788
852
|
content: [{ type: "text", text: JSON.stringify(value) }]
|
|
789
853
|
};
|
|
@@ -799,7 +863,7 @@ function convertResult(value, stateless) {
|
|
|
799
863
|
content: [{ type: "text", text: String(value) }]
|
|
800
864
|
};
|
|
801
865
|
}
|
|
802
|
-
async function toJsonSchema(schema, context) {
|
|
866
|
+
async function toJsonSchema(schema, context, logger) {
|
|
803
867
|
const maybeNative = schema;
|
|
804
868
|
if (typeof maybeNative.toJsonSchema === "function") {
|
|
805
869
|
try {
|
|
@@ -807,7 +871,8 @@ async function toJsonSchema(schema, context) {
|
|
|
807
871
|
if (result !== null && typeof result === "object") {
|
|
808
872
|
return result;
|
|
809
873
|
}
|
|
810
|
-
} catch {
|
|
874
|
+
} catch (err) {
|
|
875
|
+
logger?.debug("toJsonSchema strategy failed", { component: "tool", strategy: "native", error: err });
|
|
811
876
|
}
|
|
812
877
|
}
|
|
813
878
|
try {
|
|
@@ -815,12 +880,13 @@ async function toJsonSchema(schema, context) {
|
|
|
815
880
|
return z.toJSONSchema(
|
|
816
881
|
schema
|
|
817
882
|
);
|
|
818
|
-
} catch {
|
|
883
|
+
} catch (err) {
|
|
884
|
+
logger?.debug("toJsonSchema strategy failed", { component: "tool", strategy: "zod-v4", error: err });
|
|
819
885
|
}
|
|
820
886
|
const where = context ? ` for ${context}` : "";
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
);
|
|
887
|
+
const message = `Could not auto-generate JSON schema${where}. Sending { type: 'object' } to clients. Provide an explicit 'inputSchema' in ToolConfig to suppress this warning.`;
|
|
888
|
+
if (logger) logger.warn(message, { component: "tool" });
|
|
889
|
+
else console.warn(`[fastmcp] ${message}`);
|
|
824
890
|
return { type: "object" };
|
|
825
891
|
}
|
|
826
892
|
async function validateInput(schema, input, throwAsProtocolError = false) {
|
|
@@ -986,6 +1052,366 @@ function normalizeCompletion(raw) {
|
|
|
986
1052
|
}
|
|
987
1053
|
var EMPTY_COMPLETION = { values: [] };
|
|
988
1054
|
|
|
1055
|
+
// src/server/cors.ts
|
|
1056
|
+
var DEFAULT_CORS_METHODS = ["GET", "POST", "DELETE", "OPTIONS"];
|
|
1057
|
+
var DEFAULT_CORS_ALLOWED_HEADERS = [
|
|
1058
|
+
"Content-Type",
|
|
1059
|
+
"Authorization",
|
|
1060
|
+
"Mcp-Session-Id",
|
|
1061
|
+
"MCP-Protocol-Version",
|
|
1062
|
+
"Mcp-Method",
|
|
1063
|
+
"Mcp-Name"
|
|
1064
|
+
];
|
|
1065
|
+
var DEFAULT_CORS_EXPOSED_HEADERS = ["Mcp-Session-Id"];
|
|
1066
|
+
function hasInvalidHeaderChar(value) {
|
|
1067
|
+
return /\s/.test(value) || [...value].some((ch) => ch.charCodeAt(0) < 32 || ch.charCodeAt(0) === 127);
|
|
1068
|
+
}
|
|
1069
|
+
function mergedHeaderList(defaults, extra, option) {
|
|
1070
|
+
if (extra !== void 0 && !Array.isArray(extra)) {
|
|
1071
|
+
throw new Error(`Invalid cors.${option}: must be an array of header names, got: ${JSON.stringify(extra)}`);
|
|
1072
|
+
}
|
|
1073
|
+
const merged = [...defaults];
|
|
1074
|
+
const seen = new Set(defaults.map((h) => h.toLowerCase()));
|
|
1075
|
+
for (const header of extra ?? []) {
|
|
1076
|
+
if (typeof header !== "string" || header.trim() === "") {
|
|
1077
|
+
throw new Error(`Invalid cors.${option}: entries must be non-empty strings, got: ${JSON.stringify(header)}`);
|
|
1078
|
+
}
|
|
1079
|
+
if (hasInvalidHeaderChar(header)) {
|
|
1080
|
+
throw new Error(
|
|
1081
|
+
`Invalid cors.${option}: entries must not contain whitespace or control characters, got: ${JSON.stringify(header)}`
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
if (!seen.has(header.toLowerCase())) {
|
|
1085
|
+
seen.add(header.toLowerCase());
|
|
1086
|
+
merged.push(header);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
return merged.join(", ");
|
|
1090
|
+
}
|
|
1091
|
+
function resolveOriginMode(origin) {
|
|
1092
|
+
if (origin === void 0 || origin === "*") return { kind: "any" };
|
|
1093
|
+
if (typeof origin === "function") return { kind: "predicate", test: origin };
|
|
1094
|
+
const list = typeof origin === "string" ? [origin] : origin;
|
|
1095
|
+
if (!Array.isArray(list) || list.length === 0) {
|
|
1096
|
+
throw new Error(
|
|
1097
|
+
`Invalid cors.origin: expected '*', an origin, a non-empty array of origins, or a function, got: ${JSON.stringify(origin)}`
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
for (const entry of list) {
|
|
1101
|
+
if (typeof entry !== "string" || entry === "") {
|
|
1102
|
+
throw new Error(`Invalid cors.origin: entries must be non-empty strings, got: ${JSON.stringify(entry)}`);
|
|
1103
|
+
}
|
|
1104
|
+
if (entry === "*") {
|
|
1105
|
+
throw new Error(
|
|
1106
|
+
"Invalid cors.origin: '*' inside an origin array never matches (browsers never send it as a literal Origin); use origin: '*' (the string) for wildcard"
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
if (entry.endsWith("/")) {
|
|
1110
|
+
throw new Error(
|
|
1111
|
+
`Invalid cors.origin "${entry}": an Origin header never carries a trailing slash, so this entry can never match; drop the trailing slash`
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
return { kind: "list", origins: new Set(list) };
|
|
1116
|
+
}
|
|
1117
|
+
function resolveCors(cors) {
|
|
1118
|
+
if (cors === false) return null;
|
|
1119
|
+
const opts = cors === void 0 || cors === true ? {} : cors;
|
|
1120
|
+
if (typeof opts !== "object" || opts === null || Array.isArray(opts)) {
|
|
1121
|
+
throw new Error(`Invalid cors option: expected boolean or object, got ${typeof cors}`);
|
|
1122
|
+
}
|
|
1123
|
+
const originMode = resolveOriginMode(opts.origin);
|
|
1124
|
+
if (opts.credentials !== void 0 && typeof opts.credentials !== "boolean") {
|
|
1125
|
+
throw new Error(`Invalid cors.credentials: must be a boolean, got: ${JSON.stringify(opts.credentials)}`);
|
|
1126
|
+
}
|
|
1127
|
+
if (opts.credentials === true && originMode.kind === "any") {
|
|
1128
|
+
throw new Error(
|
|
1129
|
+
"Invalid cors config: credentials: true requires an explicit origin \u2014 browsers reject 'Access-Control-Allow-Origin: *' on credentialed requests; set cors.origin to your site origin(s)"
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
let methods = DEFAULT_CORS_METHODS;
|
|
1133
|
+
if (opts.methods !== void 0) {
|
|
1134
|
+
if (!Array.isArray(opts.methods) || opts.methods.length === 0) {
|
|
1135
|
+
throw new Error("Invalid cors.methods: must be a non-empty array of method names");
|
|
1136
|
+
}
|
|
1137
|
+
methods = [
|
|
1138
|
+
...new Set(
|
|
1139
|
+
opts.methods.map((m) => {
|
|
1140
|
+
if (typeof m !== "string" || m.trim() === "") {
|
|
1141
|
+
throw new Error(`Invalid cors.methods: entries must be non-empty strings, got: ${JSON.stringify(m)}`);
|
|
1142
|
+
}
|
|
1143
|
+
if (hasInvalidHeaderChar(m)) {
|
|
1144
|
+
throw new Error(
|
|
1145
|
+
`Invalid cors.methods: entries must not contain whitespace or control characters, got: ${JSON.stringify(m)}`
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
return m.toUpperCase();
|
|
1149
|
+
})
|
|
1150
|
+
)
|
|
1151
|
+
];
|
|
1152
|
+
}
|
|
1153
|
+
const maxAge = opts.maxAge ?? null;
|
|
1154
|
+
if (maxAge !== null && (!Number.isInteger(maxAge) || maxAge < 0)) {
|
|
1155
|
+
throw new Error(`Invalid cors.maxAge: must be a non-negative integer (seconds), got: ${JSON.stringify(opts.maxAge)}`);
|
|
1156
|
+
}
|
|
1157
|
+
return {
|
|
1158
|
+
originMode,
|
|
1159
|
+
methods: methods.join(", "),
|
|
1160
|
+
allowedHeaders: mergedHeaderList(DEFAULT_CORS_ALLOWED_HEADERS, opts.allowedHeaders, "allowedHeaders"),
|
|
1161
|
+
exposedHeaders: mergedHeaderList(DEFAULT_CORS_EXPOSED_HEADERS, opts.exposedHeaders, "exposedHeaders"),
|
|
1162
|
+
credentials: opts.credentials === true,
|
|
1163
|
+
maxAge
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
function allowOriginValue(cors, requestOrigin, logger) {
|
|
1167
|
+
switch (cors.originMode.kind) {
|
|
1168
|
+
case "any":
|
|
1169
|
+
return "*";
|
|
1170
|
+
case "list":
|
|
1171
|
+
return requestOrigin !== void 0 && cors.originMode.origins.has(requestOrigin) ? requestOrigin : null;
|
|
1172
|
+
case "predicate": {
|
|
1173
|
+
if (requestOrigin === void 0) return null;
|
|
1174
|
+
try {
|
|
1175
|
+
return cors.originMode.test(requestOrigin) ? requestOrigin : null;
|
|
1176
|
+
} catch (err) {
|
|
1177
|
+
if (logger) logger.error("cors.origin predicate threw; denying origin", { component: "cors", error: err });
|
|
1178
|
+
else console.error("[fastmcp] cors.origin predicate threw; denying origin:", err);
|
|
1179
|
+
return null;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
function baseHeaders(cors, requestOrigin, logger) {
|
|
1185
|
+
const headers = {};
|
|
1186
|
+
if (cors.originMode.kind !== "any") headers["Vary"] = "Origin";
|
|
1187
|
+
const origin = allowOriginValue(cors, requestOrigin, logger);
|
|
1188
|
+
if (origin === null) return headers;
|
|
1189
|
+
headers["Access-Control-Allow-Origin"] = origin;
|
|
1190
|
+
if (cors.credentials) headers["Access-Control-Allow-Credentials"] = "true";
|
|
1191
|
+
return headers;
|
|
1192
|
+
}
|
|
1193
|
+
function corsPreflightHeaders(cors, requestOrigin, logger) {
|
|
1194
|
+
const headers = baseHeaders(cors, requestOrigin, logger);
|
|
1195
|
+
if (headers["Access-Control-Allow-Origin"] === void 0) return headers;
|
|
1196
|
+
headers["Access-Control-Allow-Methods"] = cors.methods;
|
|
1197
|
+
headers["Access-Control-Allow-Headers"] = cors.allowedHeaders;
|
|
1198
|
+
if (cors.maxAge !== null) headers["Access-Control-Max-Age"] = String(cors.maxAge);
|
|
1199
|
+
return headers;
|
|
1200
|
+
}
|
|
1201
|
+
function corsResponseHeaders(cors, requestOrigin, logger) {
|
|
1202
|
+
const headers = baseHeaders(cors, requestOrigin, logger);
|
|
1203
|
+
if (headers["Access-Control-Allow-Origin"] === void 0) return headers;
|
|
1204
|
+
headers["Access-Control-Expose-Headers"] = cors.exposedHeaders;
|
|
1205
|
+
return headers;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// src/shared/terminal.ts
|
|
1209
|
+
import chalk from "chalk";
|
|
1210
|
+
var theme = {
|
|
1211
|
+
primary: chalk.bold.cyan,
|
|
1212
|
+
success: chalk.green,
|
|
1213
|
+
warning: chalk.yellow,
|
|
1214
|
+
error: chalk.red,
|
|
1215
|
+
muted: chalk.dim.gray,
|
|
1216
|
+
value: chalk.white,
|
|
1217
|
+
label: chalk.bold,
|
|
1218
|
+
url: chalk.cyan.underline,
|
|
1219
|
+
code: chalk.dim.white
|
|
1220
|
+
};
|
|
1221
|
+
var isUnicode = process.env["TERM"] !== "dumb" && !process.env["CI"];
|
|
1222
|
+
var symbols = {
|
|
1223
|
+
success: isUnicode ? "\u2713" : "\u221A",
|
|
1224
|
+
failure: isUnicode ? "\u2717" : "x",
|
|
1225
|
+
info: isUnicode ? "\u25C6" : "*",
|
|
1226
|
+
pointer: isUnicode ? "\u2192" : ">",
|
|
1227
|
+
warning: isUnicode ? "\u26A0" : "!",
|
|
1228
|
+
pending: isUnicode ? "\u25CB" : "-",
|
|
1229
|
+
active: isUnicode ? "\u25CF" : "*",
|
|
1230
|
+
ellipsis: isUnicode ? "\u2026" : "...",
|
|
1231
|
+
reload: isUnicode ? "\u21BA" : "~",
|
|
1232
|
+
separator: isUnicode ? "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" : "----------------------------------"
|
|
1233
|
+
};
|
|
1234
|
+
|
|
1235
|
+
// src/server/logger.ts
|
|
1236
|
+
var FRAMEWORK_LOG_LEVELS = ["debug", "info", "warn", "error", "silent"];
|
|
1237
|
+
var LEVEL_ORDER = { debug: 10, info: 20, warn: 30, error: 40, silent: 50 };
|
|
1238
|
+
function isFrameworkLogLevel(value) {
|
|
1239
|
+
return typeof value === "string" && FRAMEWORK_LOG_LEVELS.includes(value);
|
|
1240
|
+
}
|
|
1241
|
+
function envLogLevel() {
|
|
1242
|
+
const raw = process.env["FASTMCP_LOG_LEVEL"];
|
|
1243
|
+
if (raw === void 0 || raw.trim() === "") return void 0;
|
|
1244
|
+
const normalized = raw.trim().toLowerCase();
|
|
1245
|
+
if (!isFrameworkLogLevel(normalized)) {
|
|
1246
|
+
throw new Error(
|
|
1247
|
+
`[fastmcp] FASTMCP_LOG_LEVEL must be one of debug/info/warn/error/silent (case-insensitive). Received: ${JSON.stringify(raw)}`
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1250
|
+
return normalized;
|
|
1251
|
+
}
|
|
1252
|
+
function formatMetaValue(value) {
|
|
1253
|
+
if (value instanceof Error) return value.stack ?? String(value);
|
|
1254
|
+
if (typeof value === "string") return value;
|
|
1255
|
+
try {
|
|
1256
|
+
return JSON.stringify(value);
|
|
1257
|
+
} catch {
|
|
1258
|
+
return String(value);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
function jsonMeta(meta) {
|
|
1262
|
+
const plain = Object.fromEntries(Object.entries(meta).map(([k, v]) => [k, v instanceof Error ? String(v) : v]));
|
|
1263
|
+
try {
|
|
1264
|
+
return JSON.stringify(plain);
|
|
1265
|
+
} catch {
|
|
1266
|
+
return String(plain);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
var DefaultSink = class {
|
|
1270
|
+
mode;
|
|
1271
|
+
constructor(mode) {
|
|
1272
|
+
this.mode = mode ?? (process.stderr.isTTY === true && process.env["NO_COLOR"] === void 0 ? "pretty" : "plain");
|
|
1273
|
+
}
|
|
1274
|
+
write(line) {
|
|
1275
|
+
process.stderr.write(`${line}
|
|
1276
|
+
`);
|
|
1277
|
+
}
|
|
1278
|
+
plainLine(level, message, meta) {
|
|
1279
|
+
const metaStr = meta && Object.keys(meta).length > 0 ? ` ${jsonMeta(meta)}` : "";
|
|
1280
|
+
return `[fastmcp] ${level.toUpperCase()} ${message}${metaStr}`;
|
|
1281
|
+
}
|
|
1282
|
+
prettyMeta(meta) {
|
|
1283
|
+
if (!meta || Object.keys(meta).length === 0) return "";
|
|
1284
|
+
const pairs = Object.entries(meta).map(([k, v]) => `${k}=${formatMetaValue(v)}`).join(" ");
|
|
1285
|
+
return ` ${theme.muted(pairs)}`;
|
|
1286
|
+
}
|
|
1287
|
+
emit(level, message, meta) {
|
|
1288
|
+
if (this.mode === "plain") {
|
|
1289
|
+
this.write(this.plainLine(level, message, meta));
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
switch (level) {
|
|
1293
|
+
case "debug":
|
|
1294
|
+
this.write(theme.muted(`\xB7 ${message}`) + this.prettyMeta(meta));
|
|
1295
|
+
break;
|
|
1296
|
+
case "info":
|
|
1297
|
+
this.write(`${theme.muted(symbols.info)} ${message}${this.prettyMeta(meta)}`);
|
|
1298
|
+
break;
|
|
1299
|
+
case "warn":
|
|
1300
|
+
this.write(`${theme.warning(symbols.warning)} ${theme.warning(message)}${this.prettyMeta(meta)}`);
|
|
1301
|
+
break;
|
|
1302
|
+
case "error":
|
|
1303
|
+
this.write(`${theme.error(symbols.failure)} ${theme.error(message)}${this.prettyMeta(meta)}`);
|
|
1304
|
+
break;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
debug(message, meta) {
|
|
1308
|
+
this.emit("debug", message, meta);
|
|
1309
|
+
}
|
|
1310
|
+
info(message, meta) {
|
|
1311
|
+
this.emit("info", message, meta);
|
|
1312
|
+
}
|
|
1313
|
+
warn(message, meta) {
|
|
1314
|
+
this.emit("warn", message, meta);
|
|
1315
|
+
}
|
|
1316
|
+
error(message, meta) {
|
|
1317
|
+
this.emit("error", message, meta);
|
|
1318
|
+
}
|
|
1319
|
+
/** Startup card (pretty) or single plain info line. Mirrors the CLI's
|
|
1320
|
+
* output.kv/section look (src/cli/ui/output.ts). */
|
|
1321
|
+
banner(fields) {
|
|
1322
|
+
if (this.mode === "plain") {
|
|
1323
|
+
this.info(`starting ${fields.name} v${fields.version} (${fields.transport})`, fields.url !== void 0 ? { url: fields.url } : void 0);
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
const kv = (key, value) => ` ${theme.label(key.padEnd(12))} ${theme.value(value)}`;
|
|
1327
|
+
const rows = [
|
|
1328
|
+
"",
|
|
1329
|
+
` ${theme.primary("FastMCP")} ${theme.value(fields.name)} ${theme.muted(`v${fields.version}`)}`,
|
|
1330
|
+
` ${theme.muted(symbols.separator)}`,
|
|
1331
|
+
kv("transport", fields.transport),
|
|
1332
|
+
...fields.url !== void 0 ? [kv("url", fields.url)] : [],
|
|
1333
|
+
""
|
|
1334
|
+
];
|
|
1335
|
+
this.write(rows.join("\n"));
|
|
1336
|
+
}
|
|
1337
|
+
listening(url) {
|
|
1338
|
+
if (this.mode === "plain") {
|
|
1339
|
+
this.info(`listening on ${url}`);
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
this.write(`${theme.success(symbols.success)} listening on ${theme.url(url)}`);
|
|
1343
|
+
}
|
|
1344
|
+
};
|
|
1345
|
+
var ResolvedLogger = class {
|
|
1346
|
+
constructor(sink, level) {
|
|
1347
|
+
this.sink = sink;
|
|
1348
|
+
this.level = level;
|
|
1349
|
+
}
|
|
1350
|
+
sink;
|
|
1351
|
+
level;
|
|
1352
|
+
enabled(level) {
|
|
1353
|
+
return LEVEL_ORDER[level] >= LEVEL_ORDER[this.level];
|
|
1354
|
+
}
|
|
1355
|
+
// meta stays an omitted argument (not `undefined` passed explicitly) when the
|
|
1356
|
+
// caller left it out: a sink like `console` prints a literal "undefined" for a
|
|
1357
|
+
// second argument that is present but empty, and console.info/console.debug
|
|
1358
|
+
// write to stdout, so a stray argument there is a stdio protocol-safety bug too.
|
|
1359
|
+
debug(message, meta) {
|
|
1360
|
+
if (!this.enabled("debug")) return;
|
|
1361
|
+
if (meta === void 0) this.sink.debug(message);
|
|
1362
|
+
else this.sink.debug(message, meta);
|
|
1363
|
+
}
|
|
1364
|
+
info(message, meta) {
|
|
1365
|
+
if (!this.enabled("info")) return;
|
|
1366
|
+
if (meta === void 0) this.sink.info(message);
|
|
1367
|
+
else this.sink.info(message, meta);
|
|
1368
|
+
}
|
|
1369
|
+
warn(message, meta) {
|
|
1370
|
+
if (!this.enabled("warn")) return;
|
|
1371
|
+
if (meta === void 0) this.sink.warn(message);
|
|
1372
|
+
else this.sink.warn(message, meta);
|
|
1373
|
+
}
|
|
1374
|
+
error(message, meta) {
|
|
1375
|
+
if (!this.enabled("error")) return;
|
|
1376
|
+
if (meta === void 0) this.sink.error(message);
|
|
1377
|
+
else this.sink.error(message, meta);
|
|
1378
|
+
}
|
|
1379
|
+
/** Server-start card. Pretty DefaultSink renders a banner block;
|
|
1380
|
+
* every other sink gets one plain info line. Gated as info. */
|
|
1381
|
+
startupBanner(fields) {
|
|
1382
|
+
if (!this.enabled("info")) return;
|
|
1383
|
+
if (this.sink instanceof DefaultSink) {
|
|
1384
|
+
this.sink.banner(fields);
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
this.info(`starting ${fields.name} v${fields.version} (${fields.transport})`, fields.url !== void 0 ? { url: fields.url } : void 0);
|
|
1388
|
+
}
|
|
1389
|
+
/** Listening confirmation. The word "listening" is load-bearing: the CLI's
|
|
1390
|
+
* run command detects a successful start by sniffing stderr for it. */
|
|
1391
|
+
listening(url) {
|
|
1392
|
+
if (!this.enabled("info")) return;
|
|
1393
|
+
if (this.sink instanceof DefaultSink) {
|
|
1394
|
+
this.sink.listening(url);
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
this.info(`listening on ${url}`);
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
function resolveLogger(logger, logLevel) {
|
|
1401
|
+
if (logLevel !== void 0 && !isFrameworkLogLevel(logLevel)) {
|
|
1402
|
+
throw new Error(`Invalid logLevel: must be one of debug/info/warn/error/silent, got: ${JSON.stringify(logLevel)}`);
|
|
1403
|
+
}
|
|
1404
|
+
if (logger !== void 0) {
|
|
1405
|
+
for (const method of ["debug", "info", "warn", "error"]) {
|
|
1406
|
+
if (typeof logger[method] !== "function") {
|
|
1407
|
+
throw new Error(`Invalid logger: must implement debug/info/warn/error methods; missing ${method}()`);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
const level = logLevel ?? envLogLevel() ?? "info";
|
|
1412
|
+
return new ResolvedLogger(logger ?? new DefaultSink(), level);
|
|
1413
|
+
}
|
|
1414
|
+
|
|
989
1415
|
// src/server/FastMCP.ts
|
|
990
1416
|
function prefixResourceUri(uri, prefix) {
|
|
991
1417
|
const idx = uri.indexOf("://");
|
|
@@ -1013,7 +1439,7 @@ function toAccessToken(authInfo) {
|
|
|
1013
1439
|
};
|
|
1014
1440
|
}
|
|
1015
1441
|
var _cliEnvToken;
|
|
1016
|
-
async function resolveCliEnvToken(verifier) {
|
|
1442
|
+
async function resolveCliEnvToken(verifier, logger) {
|
|
1017
1443
|
if (!verifier || !("verify" in verifier)) return void 0;
|
|
1018
1444
|
if (_cliEnvToken !== void 0) return _cliEnvToken ?? void 0;
|
|
1019
1445
|
const raw = process.env["FASTMCP_CLI_AUTH_TOKEN"];
|
|
@@ -1024,7 +1450,8 @@ async function resolveCliEnvToken(verifier) {
|
|
|
1024
1450
|
try {
|
|
1025
1451
|
_cliEnvToken = await verifier.verify(raw);
|
|
1026
1452
|
return _cliEnvToken;
|
|
1027
|
-
} catch {
|
|
1453
|
+
} catch (err) {
|
|
1454
|
+
logger?.debug("FASTMCP_CLI_AUTH_TOKEN verification failed; continuing unauthenticated", { component: "auth", error: err });
|
|
1028
1455
|
_cliEnvToken = null;
|
|
1029
1456
|
return void 0;
|
|
1030
1457
|
}
|
|
@@ -1044,6 +1471,9 @@ var FastMCP = class {
|
|
|
1044
1471
|
name;
|
|
1045
1472
|
version;
|
|
1046
1473
|
_auth;
|
|
1474
|
+
/** @internal Framework logger; used by same-package modules (proxy, openapi). Not public API. */
|
|
1475
|
+
_logger;
|
|
1476
|
+
_cors;
|
|
1047
1477
|
_oauth;
|
|
1048
1478
|
_sensitiveHeaders;
|
|
1049
1479
|
_toolsPageSize;
|
|
@@ -1097,13 +1527,16 @@ var FastMCP = class {
|
|
|
1097
1527
|
constructor(options) {
|
|
1098
1528
|
this.name = options.name;
|
|
1099
1529
|
this.version = options.version ?? "0.0.1";
|
|
1530
|
+
this._logger = resolveLogger(options.logger, options.logLevel);
|
|
1100
1531
|
this._auth = options.auth;
|
|
1532
|
+
this._cors = resolveCors(options.http?.cors);
|
|
1101
1533
|
this._oauth = options.oauth;
|
|
1102
1534
|
this._sensitiveHeaders = resolveSensitiveHeaders(options.http);
|
|
1103
1535
|
this._toolsPageSize = options.toolsPageSize ?? 50;
|
|
1104
1536
|
this._resourcesPageSize = options.resourcesPageSize ?? 50;
|
|
1105
1537
|
this._promptsPageSize = options.promptsPageSize ?? 50;
|
|
1106
1538
|
this._middleware = options.middleware ? [...options.middleware] : [];
|
|
1539
|
+
for (const mw of this._middleware) if (mw instanceof LoggingMiddleware) mw._bindLogger(this._logger);
|
|
1107
1540
|
this._transforms = options.transforms ? [...options.transforms] : [];
|
|
1108
1541
|
this._cacheHints = options.cacheHints;
|
|
1109
1542
|
this._requestStateCodec = options.requestState ? createRequestStateCodec(options.requestState) : void 0;
|
|
@@ -1167,12 +1600,12 @@ var FastMCP = class {
|
|
|
1167
1600
|
return server;
|
|
1168
1601
|
}
|
|
1169
1602
|
async _resolveToken(authInfo) {
|
|
1170
|
-
return toAccessToken(authInfo) ?? await resolveCliEnvToken(this._auth);
|
|
1603
|
+
return toAccessToken(authInfo) ?? await resolveCliEnvToken(this._auth, this._logger);
|
|
1171
1604
|
}
|
|
1172
1605
|
_setupHandlers(server, sessionState, opts) {
|
|
1173
1606
|
server.setRequestHandler("tools/list", async (req, sdkCtx) => {
|
|
1174
1607
|
const token = await this._resolveToken(sdkCtx.http?.authInfo);
|
|
1175
|
-
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
|
|
1608
|
+
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders, this._logger);
|
|
1176
1609
|
return contextStore.run(
|
|
1177
1610
|
ctx,
|
|
1178
1611
|
() => runMiddlewareChain(this._middleware, "tools/list", req.params, ctx, async () => {
|
|
@@ -1223,8 +1656,8 @@ var FastMCP = class {
|
|
|
1223
1656
|
};
|
|
1224
1657
|
}
|
|
1225
1658
|
const t = entry.config;
|
|
1226
|
-
const inputSchema = t.inputSchema ?? (t.input ? await toJsonSchema(t.input, `tool "${t.name}" input
|
|
1227
|
-
const outputSchema = t.outputSchema ?? (t.output ? await toJsonSchema(t.output, `tool "${t.name}" output
|
|
1659
|
+
const inputSchema = t.inputSchema ?? (t.input ? await toJsonSchema(t.input, `tool "${t.name}" input`, this._logger) : { type: "object" });
|
|
1660
|
+
const outputSchema = t.outputSchema ?? (t.output ? await toJsonSchema(t.output, `tool "${t.name}" output`, this._logger) : void 0);
|
|
1228
1661
|
const uiMeta = clientIsUiCapable && t.ui ? {
|
|
1229
1662
|
resourceUri: t.ui.resourceUri ?? `ui://${t.name}`,
|
|
1230
1663
|
...t.ui.visibility ? { visibility: t.ui.visibility } : {}
|
|
@@ -1252,7 +1685,7 @@ var FastMCP = class {
|
|
|
1252
1685
|
const synthTool = synthesizedList.find((s) => s.name === requestedName);
|
|
1253
1686
|
if (synthTool) {
|
|
1254
1687
|
if (synthTool.auth) await runAuthCheck(synthTool.auth, token);
|
|
1255
|
-
const ctx2 = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
|
|
1688
|
+
const ctx2 = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders, this._logger);
|
|
1256
1689
|
try {
|
|
1257
1690
|
return await contextStore.run(
|
|
1258
1691
|
ctx2,
|
|
@@ -1273,7 +1706,7 @@ var FastMCP = class {
|
|
|
1273
1706
|
() => clearTimeout(timer)
|
|
1274
1707
|
);
|
|
1275
1708
|
}
|
|
1276
|
-
return convertResult(await executePromise, opts?.stateless);
|
|
1709
|
+
return convertResult(await executePromise, opts?.stateless, this._logger);
|
|
1277
1710
|
})
|
|
1278
1711
|
);
|
|
1279
1712
|
} catch (err) {
|
|
@@ -1305,7 +1738,7 @@ var FastMCP = class {
|
|
|
1305
1738
|
if (tool.config.auth) await runAuthCheck(tool.config.auth, token);
|
|
1306
1739
|
const resolvedTool = tool;
|
|
1307
1740
|
const rawArgs = req.params.arguments ?? {};
|
|
1308
|
-
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
|
|
1741
|
+
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders, this._logger);
|
|
1309
1742
|
try {
|
|
1310
1743
|
return await contextStore.run(
|
|
1311
1744
|
ctx,
|
|
@@ -1327,7 +1760,7 @@ var FastMCP = class {
|
|
|
1327
1760
|
}
|
|
1328
1761
|
let resultValue = await executePromise;
|
|
1329
1762
|
if (resolvedTool.config.output) resultValue = await validateInput(resolvedTool.config.output, resultValue);
|
|
1330
|
-
const callResult = convertResult(resultValue, opts?.stateless);
|
|
1763
|
+
const callResult = convertResult(resultValue, opts?.stateless, this._logger);
|
|
1331
1764
|
if (resolvedTool.config.ui) {
|
|
1332
1765
|
const clientIsUi = isUiCapable(server.getClientCapabilities());
|
|
1333
1766
|
if (!clientIsUi && callResult.structuredContent !== void 0) {
|
|
@@ -1347,7 +1780,7 @@ var FastMCP = class {
|
|
|
1347
1780
|
});
|
|
1348
1781
|
server.setRequestHandler("resources/list", async (req, sdkCtx) => {
|
|
1349
1782
|
const token = await this._resolveToken(sdkCtx.http?.authInfo);
|
|
1350
|
-
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
|
|
1783
|
+
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders, this._logger);
|
|
1351
1784
|
return contextStore.run(
|
|
1352
1785
|
ctx,
|
|
1353
1786
|
() => runMiddlewareChain(this._middleware, "resources/list", req.params, ctx, async () => {
|
|
@@ -1394,7 +1827,7 @@ var FastMCP = class {
|
|
|
1394
1827
|
});
|
|
1395
1828
|
server.setRequestHandler("resources/templates/list", async (req, sdkCtx) => {
|
|
1396
1829
|
const token = await this._resolveToken(sdkCtx.http?.authInfo);
|
|
1397
|
-
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
|
|
1830
|
+
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders, this._logger);
|
|
1398
1831
|
return contextStore.run(
|
|
1399
1832
|
ctx,
|
|
1400
1833
|
() => runMiddlewareChain(this._middleware, "resources/templates/list", req.params, ctx, async () => {
|
|
@@ -1446,7 +1879,7 @@ var FastMCP = class {
|
|
|
1446
1879
|
const resource = resolved.resource;
|
|
1447
1880
|
const templateParams = resolved.templateParams;
|
|
1448
1881
|
if (resource.config.auth) await runAuthCheck(resource.config.auth, token);
|
|
1449
|
-
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
|
|
1882
|
+
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders, this._logger);
|
|
1450
1883
|
return contextStore.run(
|
|
1451
1884
|
ctx,
|
|
1452
1885
|
() => runMiddlewareChain(this._middleware, "resources/read", req.params, ctx, async () => {
|
|
@@ -1480,7 +1913,7 @@ var FastMCP = class {
|
|
|
1480
1913
|
}
|
|
1481
1914
|
const resource = resolved.resource;
|
|
1482
1915
|
if (resource.config.auth) await runAuthCheck(resource.config.auth, token);
|
|
1483
|
-
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
|
|
1916
|
+
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders, this._logger);
|
|
1484
1917
|
return contextStore.run(
|
|
1485
1918
|
ctx,
|
|
1486
1919
|
() => runMiddlewareChain(this._middleware, "resources/subscribe", req.params, ctx, async () => {
|
|
@@ -1498,7 +1931,7 @@ var FastMCP = class {
|
|
|
1498
1931
|
if (opts?.stateless) throw new ProtocolError3(ProtocolErrorCode3.MethodNotFound, STATELESS_SUBSCRIBE_ERROR);
|
|
1499
1932
|
const uri = req.params.uri;
|
|
1500
1933
|
const token = await this._resolveToken(sdkCtx.http?.authInfo);
|
|
1501
|
-
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
|
|
1934
|
+
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders, this._logger);
|
|
1502
1935
|
return contextStore.run(
|
|
1503
1936
|
ctx,
|
|
1504
1937
|
() => runMiddlewareChain(this._middleware, "resources/unsubscribe", req.params, ctx, async () => {
|
|
@@ -1510,7 +1943,7 @@ var FastMCP = class {
|
|
|
1510
1943
|
});
|
|
1511
1944
|
server.setRequestHandler("prompts/list", async (req, sdkCtx) => {
|
|
1512
1945
|
const token = await this._resolveToken(sdkCtx.http?.authInfo);
|
|
1513
|
-
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
|
|
1946
|
+
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders, this._logger);
|
|
1514
1947
|
return contextStore.run(
|
|
1515
1948
|
ctx,
|
|
1516
1949
|
() => runMiddlewareChain(this._middleware, "prompts/list", req.params, ctx, async () => {
|
|
@@ -1590,7 +2023,7 @@ var FastMCP = class {
|
|
|
1590
2023
|
);
|
|
1591
2024
|
}
|
|
1592
2025
|
}
|
|
1593
|
-
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
|
|
2026
|
+
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders, this._logger);
|
|
1594
2027
|
return contextStore.run(
|
|
1595
2028
|
ctx,
|
|
1596
2029
|
() => runMiddlewareChain(this._middleware, "prompts/get", req.params, ctx, async () => {
|
|
@@ -1614,7 +2047,7 @@ var FastMCP = class {
|
|
|
1614
2047
|
});
|
|
1615
2048
|
server.setRequestHandler("completion/complete", async (req, sdkCtx) => {
|
|
1616
2049
|
const token = await this._resolveToken(sdkCtx.http?.authInfo);
|
|
1617
|
-
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
|
|
2050
|
+
const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders, this._logger);
|
|
1618
2051
|
return contextStore.run(
|
|
1619
2052
|
ctx,
|
|
1620
2053
|
() => runMiddlewareChain(this._middleware, "completion/complete", req.params, ctx, async () => {
|
|
@@ -1928,7 +2361,7 @@ var FastMCP = class {
|
|
|
1928
2361
|
}
|
|
1929
2362
|
let resultValue = await executePromise;
|
|
1930
2363
|
if (tool.config.output) resultValue = await validateInput(tool.config.output, resultValue);
|
|
1931
|
-
return convertResult(resultValue);
|
|
2364
|
+
return convertResult(resultValue, void 0, this._logger);
|
|
1932
2365
|
})
|
|
1933
2366
|
);
|
|
1934
2367
|
} catch (err) {
|
|
@@ -2205,6 +2638,7 @@ var FastMCP = class {
|
|
|
2205
2638
|
*/
|
|
2206
2639
|
use(mw) {
|
|
2207
2640
|
this._middleware.push(mw);
|
|
2641
|
+
if (mw instanceof LoggingMiddleware) mw._bindLogger(this._logger);
|
|
2208
2642
|
mw.setup?.(this._primaryServer);
|
|
2209
2643
|
return this;
|
|
2210
2644
|
}
|
|
@@ -2280,6 +2714,7 @@ var FastMCP = class {
|
|
|
2280
2714
|
this._stateless = options?.stateless ?? envBool("FASTMCP_STATELESS_HTTP") ?? false;
|
|
2281
2715
|
const resolvedHealth = resolveHealth(options?.health);
|
|
2282
2716
|
if (transport === "stdio") {
|
|
2717
|
+
this._logger.startupBanner({ name: this.name, version: this.version, transport: "stdio" });
|
|
2283
2718
|
const { StdioServerTransport, serveStdio } = await import("@modelcontextprotocol/server/stdio");
|
|
2284
2719
|
const stdioTransport = new StdioServerTransport(options?.stdin, options?.stdout);
|
|
2285
2720
|
this._stdioHandle = serveStdio(
|
|
@@ -2345,7 +2780,7 @@ var FastMCP = class {
|
|
|
2345
2780
|
if (!this._statelessLegacyHandler) {
|
|
2346
2781
|
this._statelessLegacyHandler = legacyStatelessFallback(
|
|
2347
2782
|
() => this._makeServer(/* @__PURE__ */ new Map(), { stateless: true }),
|
|
2348
|
-
(error) =>
|
|
2783
|
+
(error) => this._logger.error("stateless legacy serving failed", { component: "http", error })
|
|
2349
2784
|
);
|
|
2350
2785
|
}
|
|
2351
2786
|
return this._statelessLegacyHandler;
|
|
@@ -2404,6 +2839,7 @@ var FastMCP = class {
|
|
|
2404
2839
|
retryInterval: LEGACY_SSE_RETRY_MS,
|
|
2405
2840
|
onsessioninitialized: (id) => {
|
|
2406
2841
|
this._sessions.set(id, { transport: mcpTransport, server: sessionServer, state: sessionState });
|
|
2842
|
+
this._logger.debug("session opened", { component: "http", sessionId: id });
|
|
2407
2843
|
},
|
|
2408
2844
|
onsessionclosed: (id) => {
|
|
2409
2845
|
const session = this._sessions.get(id);
|
|
@@ -2412,6 +2848,7 @@ var FastMCP = class {
|
|
|
2412
2848
|
for (const cb of callbacks) cb();
|
|
2413
2849
|
}
|
|
2414
2850
|
this._sessions.delete(id);
|
|
2851
|
+
this._logger.debug("session closed", { component: "http", sessionId: id });
|
|
2415
2852
|
}
|
|
2416
2853
|
});
|
|
2417
2854
|
await sessionServer.connect(mcpTransport);
|
|
@@ -2427,8 +2864,9 @@ var FastMCP = class {
|
|
|
2427
2864
|
const opt = this._dnsRebindingOptions;
|
|
2428
2865
|
if (opt === void 0 && !isLoopbackHost(host) && !_dnsRebindingWarned) {
|
|
2429
2866
|
_dnsRebindingWarned = true;
|
|
2430
|
-
|
|
2431
|
-
"
|
|
2867
|
+
this._logger.warn(
|
|
2868
|
+
"This HTTP server accepts requests from any Host or Origin. Set dnsRebinding in FastMCPOptions to protect local deployments against DNS rebinding.",
|
|
2869
|
+
{ component: "http" }
|
|
2432
2870
|
);
|
|
2433
2871
|
}
|
|
2434
2872
|
const hasExplicitAllowlist = opt?.allowedHosts !== void 0 || opt?.allowedOrigins !== void 0;
|
|
@@ -2448,11 +2886,24 @@ var FastMCP = class {
|
|
|
2448
2886
|
const { requireBearerAuth } = await import("@modelcontextprotocol/server-legacy/auth");
|
|
2449
2887
|
const oauth = this._oauth;
|
|
2450
2888
|
const app = express();
|
|
2889
|
+
if (this._cors) {
|
|
2890
|
+
const cors = this._cors;
|
|
2891
|
+
app.use((req, res, next) => {
|
|
2892
|
+
if (req.method === "OPTIONS") {
|
|
2893
|
+
res.writeHead(204, corsPreflightHeaders(cors, req.headers.origin, this._logger)).end();
|
|
2894
|
+
return;
|
|
2895
|
+
}
|
|
2896
|
+
if (req.path === path) {
|
|
2897
|
+
for (const [k, v] of Object.entries(corsResponseHeaders(cors, req.headers.origin, this._logger))) res.setHeader(k, v);
|
|
2898
|
+
}
|
|
2899
|
+
next();
|
|
2900
|
+
});
|
|
2901
|
+
}
|
|
2451
2902
|
app.use((req, res, next) => {
|
|
2452
2903
|
const match = this._customRoutes.match(req.path, req.method ?? "");
|
|
2453
2904
|
if (!match) return next();
|
|
2454
2905
|
if (match.kind === "method-mismatch") return writeMethodNotAllowed(res, match.allow);
|
|
2455
|
-
void serveCustomRouteNode(match.handler, req, res);
|
|
2906
|
+
void serveCustomRouteNode(match.handler, req, res, this._logger);
|
|
2456
2907
|
});
|
|
2457
2908
|
const httpServer = await new Promise((resolve, reject) => {
|
|
2458
2909
|
const srv = app.listen(port, host, () => resolve(srv));
|
|
@@ -2476,39 +2927,34 @@ var FastMCP = class {
|
|
|
2476
2927
|
);
|
|
2477
2928
|
this._httpServer = httpServer;
|
|
2478
2929
|
this._address = { host: bound.address, port: bound.port, path };
|
|
2930
|
+
const url = `http://${bound.address}:${bound.port}${path}`;
|
|
2931
|
+
this._logger.startupBanner({ name: this.name, version: this.version, transport: "http", url });
|
|
2932
|
+
this._logger.listening(url);
|
|
2479
2933
|
}
|
|
2480
2934
|
async _runHttpSimple(port, host, path) {
|
|
2481
2935
|
this._resolveDnsRebindingGuards(host);
|
|
2482
2936
|
const { createServer } = await import("http");
|
|
2483
2937
|
const auth = this._auth;
|
|
2484
|
-
const
|
|
2485
|
-
"Access-Control-Allow-Origin": "*",
|
|
2486
|
-
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
|
|
2487
|
-
// Mcp-Session-Id: legacy (2025-era) session routing, still served alongside
|
|
2488
|
-
// modern traffic. MCP-Protocol-Version/Mcp-Method/Mcp-Name: required standard
|
|
2489
|
-
// headers for 2026-07-28 requests (SEP-2243). Mcp-Param-* (tool-argument
|
|
2490
|
-
// mirroring) is deliberately not listed: browser clients skip that mirroring
|
|
2491
|
-
// entirely (dynamically named headers cannot be statically allow-listed for
|
|
2492
|
-
// credentialed CORS), so no browser ever needs to send it.
|
|
2493
|
-
"Access-Control-Allow-Headers": "Content-Type, Authorization, Mcp-Session-Id, MCP-Protocol-Version, Mcp-Method, Mcp-Name"
|
|
2494
|
-
};
|
|
2938
|
+
const cors = this._cors;
|
|
2495
2939
|
const httpServer = createServer(async (req, res) => {
|
|
2496
|
-
if (req.method === "OPTIONS") {
|
|
2497
|
-
res.writeHead(204,
|
|
2940
|
+
if (req.method === "OPTIONS" && cors) {
|
|
2941
|
+
res.writeHead(204, corsPreflightHeaders(cors, req.headers.origin, this._logger)).end();
|
|
2498
2942
|
return;
|
|
2499
2943
|
}
|
|
2500
2944
|
const pathname = req.url?.split("?")[0] ?? "";
|
|
2501
2945
|
const routeMatch = this._customRoutes.match(pathname, req.method ?? "");
|
|
2502
2946
|
if (routeMatch) {
|
|
2503
2947
|
if (routeMatch.kind === "method-mismatch") writeMethodNotAllowed(res, routeMatch.allow);
|
|
2504
|
-
else await serveCustomRouteNode(routeMatch.handler, req, res);
|
|
2948
|
+
else await serveCustomRouteNode(routeMatch.handler, req, res, this._logger);
|
|
2505
2949
|
return;
|
|
2506
2950
|
}
|
|
2507
2951
|
if (pathname !== path) {
|
|
2508
2952
|
res.writeHead(404).end();
|
|
2509
2953
|
return;
|
|
2510
2954
|
}
|
|
2511
|
-
|
|
2955
|
+
if (cors) {
|
|
2956
|
+
for (const [k, v] of Object.entries(corsResponseHeaders(cors, req.headers.origin, this._logger))) res.setHeader(k, v);
|
|
2957
|
+
}
|
|
2512
2958
|
if (auth) {
|
|
2513
2959
|
if (isRequestVerifier(auth)) {
|
|
2514
2960
|
let accessToken;
|
|
@@ -2523,8 +2969,9 @@ var FastMCP = class {
|
|
|
2523
2969
|
return;
|
|
2524
2970
|
}
|
|
2525
2971
|
if (!accessToken.token) {
|
|
2526
|
-
|
|
2527
|
-
"
|
|
2972
|
+
this._logger.error(
|
|
2973
|
+
"RequestVerifier.verifyRequest must set AccessToken.token to a stable, non-empty per-identity value. It keys response-cache partitioning and downstream identity.",
|
|
2974
|
+
{ component: "auth" }
|
|
2528
2975
|
);
|
|
2529
2976
|
res.writeHead(500, { "Content-Type": "application/json" }).end(JSON.stringify({ error: "verifyRequest returned an AccessToken with an empty token" }));
|
|
2530
2977
|
return;
|
|
@@ -2572,6 +3019,9 @@ var FastMCP = class {
|
|
|
2572
3019
|
});
|
|
2573
3020
|
const bound = httpServer.address();
|
|
2574
3021
|
this._address = { host: bound.address, port: bound.port, path };
|
|
3022
|
+
const url = `http://${bound.address}:${bound.port}${path}`;
|
|
3023
|
+
this._logger.startupBanner({ name: this.name, version: this.version, transport: "http", url });
|
|
3024
|
+
this._logger.listening(url);
|
|
2575
3025
|
}
|
|
2576
3026
|
async close() {
|
|
2577
3027
|
await Promise.all(this._proxyCloseCallbacks.map((cb) => cb().catch(() => {
|
|
@@ -2916,12 +3366,26 @@ function oauthProxy(options) {
|
|
|
2916
3366
|
// src/server/proxy.ts
|
|
2917
3367
|
import { Client } from "@modelcontextprotocol/client";
|
|
2918
3368
|
import { parseTemplate } from "url-template";
|
|
3369
|
+
function logProbeFailure(logger, what, err) {
|
|
3370
|
+
const code = err?.code;
|
|
3371
|
+
if (code === -32601) {
|
|
3372
|
+
logger.debug(`proxied server does not support ${what}`, { component: "proxy" });
|
|
3373
|
+
} else {
|
|
3374
|
+
logger.warn(`proxied server ${what} probe failed; serving none`, { component: "proxy", error: err });
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
2919
3377
|
async function buildProxyFromClient(client, options) {
|
|
2920
3378
|
const cacheTtl = options?.cacheTtl ?? 3e4;
|
|
2921
3379
|
const serverInfo = client.getServerVersion();
|
|
2922
3380
|
const proxyName = options?.name ?? serverInfo?.name ?? "proxy";
|
|
2923
3381
|
const proxyVersion = serverInfo?.version ?? "0.0.1";
|
|
2924
|
-
const proxy = new FastMCP({
|
|
3382
|
+
const proxy = new FastMCP({
|
|
3383
|
+
name: proxyName,
|
|
3384
|
+
version: proxyVersion,
|
|
3385
|
+
...options?.logger !== void 0 ? { logger: options.logger } : {},
|
|
3386
|
+
...options?.logLevel !== void 0 ? { logLevel: options.logLevel } : {}
|
|
3387
|
+
});
|
|
3388
|
+
const logger = proxy._logger;
|
|
2925
3389
|
const proxiedTools = /* @__PURE__ */ new Set();
|
|
2926
3390
|
const proxiedResources = /* @__PURE__ */ new Set();
|
|
2927
3391
|
const proxiedPrompts = /* @__PURE__ */ new Set();
|
|
@@ -2982,7 +3446,8 @@ async function buildProxyFromClient(client, options) {
|
|
|
2982
3446
|
proxiedResources.add(resource.uri);
|
|
2983
3447
|
}
|
|
2984
3448
|
}
|
|
2985
|
-
} catch {
|
|
3449
|
+
} catch (err) {
|
|
3450
|
+
logProbeFailure(logger, "resources", err);
|
|
2986
3451
|
}
|
|
2987
3452
|
try {
|
|
2988
3453
|
const { resourceTemplates } = await client.listResourceTemplates();
|
|
@@ -3009,7 +3474,8 @@ async function buildProxyFromClient(client, options) {
|
|
|
3009
3474
|
proxiedResources.add(uriTemplate);
|
|
3010
3475
|
}
|
|
3011
3476
|
}
|
|
3012
|
-
} catch {
|
|
3477
|
+
} catch (err) {
|
|
3478
|
+
logProbeFailure(logger, "resource templates", err);
|
|
3013
3479
|
}
|
|
3014
3480
|
for (const uri of proxiedResources) {
|
|
3015
3481
|
if (!incoming.has(uri)) {
|
|
@@ -3116,7 +3582,12 @@ async function createProxy(config, name) {
|
|
|
3116
3582
|
{ capabilities: {} }
|
|
3117
3583
|
);
|
|
3118
3584
|
await client.connect(transport);
|
|
3119
|
-
return buildProxyFromClient(client, {
|
|
3585
|
+
return buildProxyFromClient(client, {
|
|
3586
|
+
cacheTtl: config.cacheTtl,
|
|
3587
|
+
name,
|
|
3588
|
+
logger: config.logger,
|
|
3589
|
+
logLevel: config.logLevel
|
|
3590
|
+
});
|
|
3120
3591
|
}
|
|
3121
3592
|
|
|
3122
3593
|
// src/server/openapi/internal.ts
|
|
@@ -3143,8 +3614,9 @@ function deepEqual(a, b) {
|
|
|
3143
3614
|
}
|
|
3144
3615
|
return false;
|
|
3145
3616
|
}
|
|
3146
|
-
function warn(message) {
|
|
3147
|
-
|
|
3617
|
+
function warn(message, logger) {
|
|
3618
|
+
if (logger) logger.warn(message, { component: "openapi" });
|
|
3619
|
+
else console.warn(`[fastmcp] ${message}`);
|
|
3148
3620
|
}
|
|
3149
3621
|
|
|
3150
3622
|
// src/server/openapi/parser.ts
|
|
@@ -4088,7 +4560,7 @@ var STYLE_DELIMITERS = {
|
|
|
4088
4560
|
spaceDelimited: " ",
|
|
4089
4561
|
pipeDelimited: "|"
|
|
4090
4562
|
};
|
|
4091
|
-
function unflattenArguments(route, flatArgs) {
|
|
4563
|
+
function unflattenArguments(route, flatArgs, logger) {
|
|
4092
4564
|
const pathParams = {};
|
|
4093
4565
|
const queryParams = {};
|
|
4094
4566
|
const headerParams = {};
|
|
@@ -4106,7 +4578,7 @@ function unflattenArguments(route, flatArgs) {
|
|
|
4106
4578
|
if (value === null || value === void 0) continue;
|
|
4107
4579
|
const mapping = route.parameterMap[argName];
|
|
4108
4580
|
if (mapping === void 0) {
|
|
4109
|
-
warn(`Argument '${argName}' not found in parameter map for ${route.operationId ?? route.path}
|
|
4581
|
+
warn(`Argument '${argName}' not found in parameter map for ${route.operationId ?? route.path}`, logger);
|
|
4110
4582
|
continue;
|
|
4111
4583
|
}
|
|
4112
4584
|
buckets[mapping.location][mapping.openapiName] = value;
|
|
@@ -4203,10 +4675,11 @@ function buildUrl(pathTemplate, pathParams, baseUrl) {
|
|
|
4203
4675
|
const base = `${baseUrl.replace(/\/+$/, "")}/`;
|
|
4204
4676
|
return new URL(urlPath.replace(/^\/+/, ""), base).toString();
|
|
4205
4677
|
}
|
|
4206
|
-
function buildRequest(route, flatArgs, baseUrl) {
|
|
4678
|
+
function buildRequest(route, flatArgs, baseUrl, logger) {
|
|
4207
4679
|
const { pathParams, queryParams, headerParams, cookieParams, body } = unflattenArguments(
|
|
4208
4680
|
route,
|
|
4209
|
-
flatArgs
|
|
4681
|
+
flatArgs,
|
|
4682
|
+
logger
|
|
4210
4683
|
);
|
|
4211
4684
|
const serializedQuery = serializeQueryParams(route, queryParams);
|
|
4212
4685
|
const url = new URL(buildUrl(route.path, pathParams, baseUrl));
|
|
@@ -4320,11 +4793,11 @@ function resolveClient(options, spec) {
|
|
|
4320
4793
|
function isTimeoutError(error) {
|
|
4321
4794
|
return error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError");
|
|
4322
4795
|
}
|
|
4323
|
-
async function sendRequest(client, route, args) {
|
|
4796
|
+
async function sendRequest(client, route, args, logger) {
|
|
4324
4797
|
let url;
|
|
4325
4798
|
let init;
|
|
4326
4799
|
try {
|
|
4327
|
-
const directed = buildRequest(route, args, client.baseUrl);
|
|
4800
|
+
const directed = buildRequest(route, args, client.baseUrl, logger);
|
|
4328
4801
|
const authHeaders = client.auth ? await client.auth.getHeaders() : {};
|
|
4329
4802
|
const headers = {};
|
|
4330
4803
|
for (const source of [client.headers, authHeaders, directed.headers]) {
|
|
@@ -4363,13 +4836,14 @@ async function sendRequest(client, route, args) {
|
|
|
4363
4836
|
}
|
|
4364
4837
|
return response;
|
|
4365
4838
|
}
|
|
4366
|
-
async function runToolRequest(client, route, args, outputSchema) {
|
|
4367
|
-
const response = await sendRequest(client, route, args);
|
|
4839
|
+
async function runToolRequest(client, route, args, outputSchema, logger) {
|
|
4840
|
+
const response = await sendRequest(client, route, args, logger);
|
|
4368
4841
|
const text = await response.text();
|
|
4369
4842
|
let result;
|
|
4370
4843
|
try {
|
|
4371
4844
|
result = JSON.parse(text);
|
|
4372
4845
|
} catch {
|
|
4846
|
+
logger?.debug("openapi response was not JSON; returning raw text", { component: "openapi" });
|
|
4373
4847
|
return new ToolResult({ content: [{ type: "text", text }] });
|
|
4374
4848
|
}
|
|
4375
4849
|
let structured;
|
|
@@ -4388,8 +4862,8 @@ async function runToolRequest(client, route, args, outputSchema) {
|
|
|
4388
4862
|
structuredContent: structured
|
|
4389
4863
|
});
|
|
4390
4864
|
}
|
|
4391
|
-
async function readResourceRequest(client, route, args, uri, mimeType) {
|
|
4392
|
-
const response = await sendRequest(client, route, args);
|
|
4865
|
+
async function readResourceRequest(client, route, args, uri, mimeType, logger) {
|
|
4866
|
+
const response = await sendRequest(client, route, args, logger);
|
|
4393
4867
|
const contentType = (response.headers.get("content-type") ?? "").toLowerCase();
|
|
4394
4868
|
if (contentType.includes("application/json")) {
|
|
4395
4869
|
const result = JSON.parse(await response.text());
|
|
@@ -4484,7 +4958,8 @@ function createOpenAPIServer(options) {
|
|
|
4484
4958
|
}
|
|
4485
4959
|
} catch (error) {
|
|
4486
4960
|
warn(
|
|
4487
|
-
`Error in routeMapFn for ${route.method} ${route.path}: ${error.message}. Using mapped type
|
|
4961
|
+
`Error in routeMapFn for ${route.method} ${route.path}: ${error.message}. Using mapped type.`,
|
|
4962
|
+
server._logger
|
|
4488
4963
|
);
|
|
4489
4964
|
}
|
|
4490
4965
|
}
|
|
@@ -4502,13 +4977,14 @@ function createOpenAPIServer(options) {
|
|
|
4502
4977
|
}
|
|
4503
4978
|
return server;
|
|
4504
4979
|
}
|
|
4505
|
-
function applyComponentFn(componentFn, route, component) {
|
|
4980
|
+
function applyComponentFn(componentFn, route, component, logger) {
|
|
4506
4981
|
if (componentFn === void 0) return;
|
|
4507
4982
|
try {
|
|
4508
4983
|
componentFn(route, component);
|
|
4509
4984
|
} catch (error) {
|
|
4510
4985
|
warn(
|
|
4511
|
-
`Error in componentFn for ${component.kind} '${component.name}': ${error.message}
|
|
4986
|
+
`Error in componentFn for ${component.kind} '${component.name}': ${error.message}`,
|
|
4987
|
+
logger
|
|
4512
4988
|
);
|
|
4513
4989
|
}
|
|
4514
4990
|
}
|
|
@@ -4533,7 +5009,7 @@ function createTool(server, client, route, name, tags, registry, validateOutput,
|
|
|
4533
5009
|
...outputSchema !== null ? { outputSchema } : {},
|
|
4534
5010
|
tags
|
|
4535
5011
|
};
|
|
4536
|
-
applyComponentFn(componentFn, route, component);
|
|
5012
|
+
applyComponentFn(componentFn, route, component, server._logger);
|
|
4537
5013
|
server.tool(
|
|
4538
5014
|
{
|
|
4539
5015
|
name: component.name,
|
|
@@ -4542,7 +5018,7 @@ function createTool(server, client, route, name, tags, registry, validateOutput,
|
|
|
4542
5018
|
...component.outputSchema !== void 0 ? { outputSchema: component.outputSchema } : {},
|
|
4543
5019
|
tags: component.tags
|
|
4544
5020
|
},
|
|
4545
|
-
async (args) => runToolRequest(client, route, isPlainObject(args) ? args : {}, component.outputSchema)
|
|
5021
|
+
async (args) => runToolRequest(client, route, isPlainObject(args) ? args : {}, component.outputSchema, server._logger)
|
|
4546
5022
|
);
|
|
4547
5023
|
}
|
|
4548
5024
|
function createResource(server, client, route, name, tags, registry, componentFn) {
|
|
@@ -4555,7 +5031,7 @@ function createResource(server, client, route, name, tags, registry, componentFn
|
|
|
4555
5031
|
mimeType: extractMimeTypeFromRoute(route),
|
|
4556
5032
|
tags
|
|
4557
5033
|
};
|
|
4558
|
-
applyComponentFn(componentFn, route, component);
|
|
5034
|
+
applyComponentFn(componentFn, route, component, server._logger);
|
|
4559
5035
|
server.resource(
|
|
4560
5036
|
{
|
|
4561
5037
|
uri: component.uri,
|
|
@@ -4564,7 +5040,7 @@ function createResource(server, client, route, name, tags, registry, componentFn
|
|
|
4564
5040
|
mimeType: component.mimeType,
|
|
4565
5041
|
tags: component.tags
|
|
4566
5042
|
},
|
|
4567
|
-
async () => readResourceRequest(client, route, {}, component.uri, component.mimeType)
|
|
5043
|
+
async () => readResourceRequest(client, route, {}, component.uri, component.mimeType, server._logger)
|
|
4568
5044
|
);
|
|
4569
5045
|
}
|
|
4570
5046
|
function createTemplate(server, client, route, name, tags, registry, componentFn) {
|
|
@@ -4596,7 +5072,7 @@ function createTemplate(server, client, route, name, tags, registry, componentFn
|
|
|
4596
5072
|
parameters: parametersSchema,
|
|
4597
5073
|
tags
|
|
4598
5074
|
};
|
|
4599
|
-
applyComponentFn(componentFn, route, component);
|
|
5075
|
+
applyComponentFn(componentFn, route, component, server._logger);
|
|
4600
5076
|
server.resource(
|
|
4601
5077
|
{
|
|
4602
5078
|
uri: component.uriTemplate,
|
|
@@ -4613,7 +5089,8 @@ function createTemplate(server, client, route, name, tags, registry, componentFn
|
|
|
4613
5089
|
route,
|
|
4614
5090
|
templateArguments(route, matched),
|
|
4615
5091
|
concreteUri,
|
|
4616
|
-
component.mimeType
|
|
5092
|
+
component.mimeType,
|
|
5093
|
+
server._logger
|
|
4617
5094
|
);
|
|
4618
5095
|
}
|
|
4619
5096
|
);
|
|
@@ -5081,7 +5558,7 @@ var FormInput = class {
|
|
|
5081
5558
|
this.server.tool(
|
|
5082
5559
|
{ name, description, ui: { visibility: ["model", "app"] } },
|
|
5083
5560
|
async () => {
|
|
5084
|
-
const jsonSchema = await toJsonSchema(schema, `form "${name}"
|
|
5561
|
+
const jsonSchema = await toJsonSchema(schema, `form "${name}"`, this.server._logger);
|
|
5085
5562
|
const properties = jsonSchema.properties ?? {};
|
|
5086
5563
|
const required = jsonSchema.required ?? [];
|
|
5087
5564
|
const fields = Object.entries(properties).map(
|