@mastra/mcp 0.10.5-alpha.0 → 0.10.5-alpha.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/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +23 -0
- package/dist/_tsup-dts-rollup.d.cts +167 -126
- package/dist/_tsup-dts-rollup.d.ts +167 -126
- package/dist/index.cjs +624 -391
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +620 -387
- package/integration-tests/node_modules/.bin/vitest +2 -2
- package/integration-tests/package.json +2 -2
- package/package.json +7 -7
- package/src/__fixtures__/tools.ts +0 -9
- package/src/client/client.test.ts +310 -1
- package/src/client/client.ts +66 -3
- package/src/client/configuration.ts +22 -2
- package/src/client/elicitationActions.ts +26 -0
- package/src/client/index.ts +1 -1
- package/src/server/server.test.ts +433 -0
- package/src/server/server.ts +431 -429
- package/src/server/types.ts +25 -1
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var base = require('@mastra/core/base');
|
|
4
|
+
var error = require('@mastra/core/error');
|
|
4
5
|
var tools = require('@mastra/core/tools');
|
|
5
6
|
var utils = require('@mastra/core/utils');
|
|
6
7
|
var index_js$1 = require('@modelcontextprotocol/sdk/client/index.js');
|
|
@@ -12,7 +13,6 @@ var types_js = require('@modelcontextprotocol/sdk/types.js');
|
|
|
12
13
|
var exitHook = require('exit-hook');
|
|
13
14
|
var zod = require('zod');
|
|
14
15
|
var zodFromJsonSchema = require('zod-from-json-schema');
|
|
15
|
-
var error = require('@mastra/core/error');
|
|
16
16
|
var equal = require('fast-deep-equal');
|
|
17
17
|
var uuid = require('uuid');
|
|
18
18
|
var crypto$1 = require('crypto');
|
|
@@ -23,13 +23,29 @@ var index_js = require('@modelcontextprotocol/sdk/server/index.js');
|
|
|
23
23
|
var sse_js = require('@modelcontextprotocol/sdk/server/sse.js');
|
|
24
24
|
var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
25
25
|
var streamableHttp_js = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
|
|
26
|
-
var streaming = require('hono/streaming');
|
|
27
26
|
|
|
28
27
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
29
28
|
|
|
30
29
|
var equal__default = /*#__PURE__*/_interopDefault(equal);
|
|
31
30
|
|
|
32
31
|
// src/client/client.ts
|
|
32
|
+
|
|
33
|
+
// src/client/elicitationActions.ts
|
|
34
|
+
var ElicitationClientActions = class {
|
|
35
|
+
client;
|
|
36
|
+
logger;
|
|
37
|
+
constructor({ client, logger }) {
|
|
38
|
+
this.client = client;
|
|
39
|
+
this.logger = logger;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Set a handler for elicitation requests.
|
|
43
|
+
* @param handler The callback function to handle the elicitation request.
|
|
44
|
+
*/
|
|
45
|
+
onRequest(handler) {
|
|
46
|
+
this.client.setElicitationRequestHandler(handler);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
33
49
|
var PromptClientActions = class {
|
|
34
50
|
client;
|
|
35
51
|
logger;
|
|
@@ -212,6 +228,7 @@ var InternalMastraMCPClient = class extends base.MastraBase {
|
|
|
212
228
|
currentOperationContext = null;
|
|
213
229
|
resources;
|
|
214
230
|
prompts;
|
|
231
|
+
elicitation;
|
|
215
232
|
constructor({
|
|
216
233
|
name,
|
|
217
234
|
version = "1.0.0",
|
|
@@ -225,18 +242,20 @@ var InternalMastraMCPClient = class extends base.MastraBase {
|
|
|
225
242
|
this.logHandler = server.logger;
|
|
226
243
|
this.enableServerLogs = server.enableServerLogs ?? true;
|
|
227
244
|
this.serverConfig = server;
|
|
245
|
+
const clientCapabilities = { ...capabilities, elicitation: {} };
|
|
228
246
|
this.client = new index_js$1.Client(
|
|
229
247
|
{
|
|
230
248
|
name,
|
|
231
249
|
version
|
|
232
250
|
},
|
|
233
251
|
{
|
|
234
|
-
capabilities
|
|
252
|
+
capabilities: clientCapabilities
|
|
235
253
|
}
|
|
236
254
|
);
|
|
237
255
|
this.setupLogging();
|
|
238
256
|
this.resources = new ResourceClientActions({ client: this, logger: this.logger });
|
|
239
257
|
this.prompts = new PromptClientActions({ client: this, logger: this.logger });
|
|
258
|
+
this.elicitation = new ElicitationClientActions({ client: this, logger: this.logger });
|
|
240
259
|
}
|
|
241
260
|
/**
|
|
242
261
|
* Log a message at the specified level
|
|
@@ -478,28 +497,70 @@ var InternalMastraMCPClient = class extends base.MastraBase {
|
|
|
478
497
|
handler();
|
|
479
498
|
});
|
|
480
499
|
}
|
|
500
|
+
setElicitationRequestHandler(handler) {
|
|
501
|
+
this.log("debug", "Setting elicitation request handler");
|
|
502
|
+
this.client.setRequestHandler(types_js.ElicitRequestSchema, async (request) => {
|
|
503
|
+
this.log("debug", `Received elicitation request: ${request.params.message}`);
|
|
504
|
+
return handler(request.params);
|
|
505
|
+
});
|
|
506
|
+
}
|
|
481
507
|
convertInputSchema(inputSchema) {
|
|
482
508
|
if (utils.isZodType(inputSchema)) {
|
|
483
509
|
return inputSchema;
|
|
484
510
|
}
|
|
485
511
|
try {
|
|
486
512
|
return zodFromJsonSchema.convertJsonSchemaToZod(inputSchema);
|
|
487
|
-
} catch (error) {
|
|
513
|
+
} catch (error$1) {
|
|
488
514
|
let errorDetails;
|
|
489
|
-
if (error instanceof Error) {
|
|
490
|
-
errorDetails = error.stack;
|
|
515
|
+
if (error$1 instanceof Error) {
|
|
516
|
+
errorDetails = error$1.stack;
|
|
491
517
|
} else {
|
|
492
518
|
try {
|
|
493
|
-
errorDetails = JSON.stringify(error);
|
|
519
|
+
errorDetails = JSON.stringify(error$1);
|
|
494
520
|
} catch {
|
|
495
|
-
errorDetails = String(error);
|
|
521
|
+
errorDetails = String(error$1);
|
|
496
522
|
}
|
|
497
523
|
}
|
|
498
524
|
this.log("error", "Failed to convert JSON schema to Zod schema using zodFromJsonSchema", {
|
|
499
525
|
error: errorDetails,
|
|
500
526
|
originalJsonSchema: inputSchema
|
|
501
527
|
});
|
|
502
|
-
throw new
|
|
528
|
+
throw new error.MastraError({
|
|
529
|
+
id: "MCP_TOOL_INPUT_SCHEMA_CONVERSION_FAILED",
|
|
530
|
+
domain: error.ErrorDomain.MCP,
|
|
531
|
+
category: error.ErrorCategory.USER,
|
|
532
|
+
details: { error: errorDetails ?? "Unknown error" }
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
convertOutputSchema(outputSchema) {
|
|
537
|
+
if (!outputSchema) return;
|
|
538
|
+
if (utils.isZodType(outputSchema)) {
|
|
539
|
+
return outputSchema;
|
|
540
|
+
}
|
|
541
|
+
try {
|
|
542
|
+
return zodFromJsonSchema.convertJsonSchemaToZod(outputSchema);
|
|
543
|
+
} catch (error$1) {
|
|
544
|
+
let errorDetails;
|
|
545
|
+
if (error$1 instanceof Error) {
|
|
546
|
+
errorDetails = error$1.stack;
|
|
547
|
+
} else {
|
|
548
|
+
try {
|
|
549
|
+
errorDetails = JSON.stringify(error$1);
|
|
550
|
+
} catch {
|
|
551
|
+
errorDetails = String(error$1);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
this.log("error", "Failed to convert JSON schema to Zod schema using zodFromJsonSchema", {
|
|
555
|
+
error: errorDetails,
|
|
556
|
+
originalJsonSchema: outputSchema
|
|
557
|
+
});
|
|
558
|
+
throw new error.MastraError({
|
|
559
|
+
id: "MCP_TOOL_OUTPUT_SCHEMA_CONVERSION_FAILED",
|
|
560
|
+
domain: error.ErrorDomain.MCP,
|
|
561
|
+
category: error.ErrorCategory.USER,
|
|
562
|
+
details: { error: errorDetails ?? "Unknown error" }
|
|
563
|
+
});
|
|
503
564
|
}
|
|
504
565
|
}
|
|
505
566
|
async tools() {
|
|
@@ -513,6 +574,7 @@ var InternalMastraMCPClient = class extends base.MastraBase {
|
|
|
513
574
|
id: `${this.name}_${tool.name}`,
|
|
514
575
|
description: tool.description || "",
|
|
515
576
|
inputSchema: this.convertInputSchema(tool.inputSchema),
|
|
577
|
+
outputSchema: this.convertOutputSchema(tool.outputSchema),
|
|
516
578
|
execute: async ({ context, runtimeContext }) => {
|
|
517
579
|
const previousContext = this.currentOperationContext;
|
|
518
580
|
this.currentOperationContext = runtimeContext || null;
|
|
@@ -606,6 +668,26 @@ To fix this you have three different options:
|
|
|
606
668
|
this.addToInstanceCache();
|
|
607
669
|
return this;
|
|
608
670
|
}
|
|
671
|
+
get elicitation() {
|
|
672
|
+
this.addToInstanceCache();
|
|
673
|
+
return {
|
|
674
|
+
onRequest: async (serverName, handler) => {
|
|
675
|
+
try {
|
|
676
|
+
const internalClient = await this.getConnectedClientForServer(serverName);
|
|
677
|
+
return internalClient.elicitation.onRequest(handler);
|
|
678
|
+
} catch (err) {
|
|
679
|
+
throw new error.MastraError({
|
|
680
|
+
id: "MCP_CLIENT_ON_REQUEST_ELICITATION_FAILED",
|
|
681
|
+
domain: error.ErrorDomain.MCP,
|
|
682
|
+
category: error.ErrorCategory.THIRD_PARTY,
|
|
683
|
+
details: {
|
|
684
|
+
serverName
|
|
685
|
+
}
|
|
686
|
+
}, err);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
}
|
|
609
691
|
get resources() {
|
|
610
692
|
this.addToInstanceCache();
|
|
611
693
|
return {
|
|
@@ -935,6 +1017,165 @@ var MCPConfiguration = class extends MCPClient {
|
|
|
935
1017
|
);
|
|
936
1018
|
}
|
|
937
1019
|
};
|
|
1020
|
+
|
|
1021
|
+
// ../../node_modules/.pnpm/hono@4.8.1/node_modules/hono/dist/utils/stream.js
|
|
1022
|
+
var StreamingApi = class {
|
|
1023
|
+
writer;
|
|
1024
|
+
encoder;
|
|
1025
|
+
writable;
|
|
1026
|
+
abortSubscribers = [];
|
|
1027
|
+
responseReadable;
|
|
1028
|
+
aborted = false;
|
|
1029
|
+
closed = false;
|
|
1030
|
+
constructor(writable, _readable) {
|
|
1031
|
+
this.writable = writable;
|
|
1032
|
+
this.writer = writable.getWriter();
|
|
1033
|
+
this.encoder = new TextEncoder();
|
|
1034
|
+
const reader = _readable.getReader();
|
|
1035
|
+
this.abortSubscribers.push(async () => {
|
|
1036
|
+
await reader.cancel();
|
|
1037
|
+
});
|
|
1038
|
+
this.responseReadable = new ReadableStream({
|
|
1039
|
+
async pull(controller) {
|
|
1040
|
+
const { done, value } = await reader.read();
|
|
1041
|
+
done ? controller.close() : controller.enqueue(value);
|
|
1042
|
+
},
|
|
1043
|
+
cancel: () => {
|
|
1044
|
+
this.abort();
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
async write(input) {
|
|
1049
|
+
try {
|
|
1050
|
+
if (typeof input === "string") {
|
|
1051
|
+
input = this.encoder.encode(input);
|
|
1052
|
+
}
|
|
1053
|
+
await this.writer.write(input);
|
|
1054
|
+
} catch {
|
|
1055
|
+
}
|
|
1056
|
+
return this;
|
|
1057
|
+
}
|
|
1058
|
+
async writeln(input) {
|
|
1059
|
+
await this.write(input + "\n");
|
|
1060
|
+
return this;
|
|
1061
|
+
}
|
|
1062
|
+
sleep(ms) {
|
|
1063
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
1064
|
+
}
|
|
1065
|
+
async close() {
|
|
1066
|
+
try {
|
|
1067
|
+
await this.writer.close();
|
|
1068
|
+
} catch {
|
|
1069
|
+
}
|
|
1070
|
+
this.closed = true;
|
|
1071
|
+
}
|
|
1072
|
+
async pipe(body) {
|
|
1073
|
+
this.writer.releaseLock();
|
|
1074
|
+
await body.pipeTo(this.writable, { preventClose: true });
|
|
1075
|
+
this.writer = this.writable.getWriter();
|
|
1076
|
+
}
|
|
1077
|
+
onAbort(listener) {
|
|
1078
|
+
this.abortSubscribers.push(listener);
|
|
1079
|
+
}
|
|
1080
|
+
abort() {
|
|
1081
|
+
if (!this.aborted) {
|
|
1082
|
+
this.aborted = true;
|
|
1083
|
+
this.abortSubscribers.forEach((subscriber) => subscriber());
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
// ../../node_modules/.pnpm/hono@4.8.1/node_modules/hono/dist/helper/streaming/utils.js
|
|
1089
|
+
var isOldBunVersion = () => {
|
|
1090
|
+
const version = typeof Bun !== "undefined" ? Bun.version : void 0;
|
|
1091
|
+
if (version === void 0) {
|
|
1092
|
+
return false;
|
|
1093
|
+
}
|
|
1094
|
+
const result = version.startsWith("1.1") || version.startsWith("1.0") || version.startsWith("0.");
|
|
1095
|
+
isOldBunVersion = () => result;
|
|
1096
|
+
return result;
|
|
1097
|
+
};
|
|
1098
|
+
|
|
1099
|
+
// ../../node_modules/.pnpm/hono@4.8.1/node_modules/hono/dist/utils/html.js
|
|
1100
|
+
var HtmlEscapedCallbackPhase = {
|
|
1101
|
+
Stringify: 1};
|
|
1102
|
+
var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) => {
|
|
1103
|
+
if (typeof str === "object" && !(str instanceof String)) {
|
|
1104
|
+
if (!(str instanceof Promise)) {
|
|
1105
|
+
str = str.toString();
|
|
1106
|
+
}
|
|
1107
|
+
if (str instanceof Promise) {
|
|
1108
|
+
str = await str;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
const callbacks = str.callbacks;
|
|
1112
|
+
if (!callbacks?.length) {
|
|
1113
|
+
return Promise.resolve(str);
|
|
1114
|
+
}
|
|
1115
|
+
if (buffer) {
|
|
1116
|
+
buffer[0] += str;
|
|
1117
|
+
} else {
|
|
1118
|
+
buffer = [str];
|
|
1119
|
+
}
|
|
1120
|
+
const resStr = Promise.all(callbacks.map((c) => c({ phase, buffer, context }))).then(
|
|
1121
|
+
(res) => Promise.all(
|
|
1122
|
+
res.filter(Boolean).map((str2) => resolveCallback(str2, phase, false, context, buffer))
|
|
1123
|
+
).then(() => buffer[0])
|
|
1124
|
+
);
|
|
1125
|
+
{
|
|
1126
|
+
return resStr;
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
|
|
1130
|
+
// ../../node_modules/.pnpm/hono@4.8.1/node_modules/hono/dist/helper/streaming/sse.js
|
|
1131
|
+
var SSEStreamingApi = class extends StreamingApi {
|
|
1132
|
+
constructor(writable, readable) {
|
|
1133
|
+
super(writable, readable);
|
|
1134
|
+
}
|
|
1135
|
+
async writeSSE(message) {
|
|
1136
|
+
const data = await resolveCallback(message.data, HtmlEscapedCallbackPhase.Stringify, false, {});
|
|
1137
|
+
const dataLines = data.split("\n").map((line) => {
|
|
1138
|
+
return `data: ${line}`;
|
|
1139
|
+
}).join("\n");
|
|
1140
|
+
const sseData = [
|
|
1141
|
+
message.event && `event: ${message.event}`,
|
|
1142
|
+
dataLines,
|
|
1143
|
+
message.id && `id: ${message.id}`,
|
|
1144
|
+
message.retry && `retry: ${message.retry}`
|
|
1145
|
+
].filter(Boolean).join("\n") + "\n\n";
|
|
1146
|
+
await this.write(sseData);
|
|
1147
|
+
}
|
|
1148
|
+
};
|
|
1149
|
+
var run = async (stream2, cb, onError) => {
|
|
1150
|
+
try {
|
|
1151
|
+
await cb(stream2);
|
|
1152
|
+
} catch (e) {
|
|
1153
|
+
{
|
|
1154
|
+
console.error(e);
|
|
1155
|
+
}
|
|
1156
|
+
} finally {
|
|
1157
|
+
stream2.close();
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
var contextStash = /* @__PURE__ */ new WeakMap();
|
|
1161
|
+
var streamSSE = (c, cb, onError) => {
|
|
1162
|
+
const { readable, writable } = new TransformStream();
|
|
1163
|
+
const stream2 = new SSEStreamingApi(writable, readable);
|
|
1164
|
+
if (isOldBunVersion()) {
|
|
1165
|
+
c.req.raw.signal.addEventListener("abort", () => {
|
|
1166
|
+
if (!stream2.closed) {
|
|
1167
|
+
stream2.abort();
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
contextStash.set(stream2.responseReadable, c);
|
|
1172
|
+
c.header("Transfer-Encoding", "chunked");
|
|
1173
|
+
c.header("Content-Type", "text/event-stream");
|
|
1174
|
+
c.header("Cache-Control", "no-cache");
|
|
1175
|
+
c.header("Connection", "keep-alive");
|
|
1176
|
+
run(stream2, cb);
|
|
1177
|
+
return c.newResponse(stream2.responseReadable);
|
|
1178
|
+
};
|
|
938
1179
|
var MAXIMUM_MESSAGE_SIZE = 4 * 1024 * 1024;
|
|
939
1180
|
var SSETransport = class {
|
|
940
1181
|
messageUrl;
|
|
@@ -946,9 +1187,9 @@ var SSETransport = class {
|
|
|
946
1187
|
/**
|
|
947
1188
|
* Creates a new SSETransport, which will direct the MPC client to POST messages to messageUrl
|
|
948
1189
|
*/
|
|
949
|
-
constructor(messageUrl,
|
|
1190
|
+
constructor(messageUrl, stream2) {
|
|
950
1191
|
this.messageUrl = messageUrl;
|
|
951
|
-
this.stream =
|
|
1192
|
+
this.stream = stream2;
|
|
952
1193
|
this._sessionId = crypto.randomUUID();
|
|
953
1194
|
this.stream.onAbort(() => {
|
|
954
1195
|
void this.close();
|
|
@@ -965,6 +1206,10 @@ var SSETransport = class {
|
|
|
965
1206
|
if (this.stream.closed) {
|
|
966
1207
|
throw new Error("SSE transport already closed!");
|
|
967
1208
|
}
|
|
1209
|
+
await this.stream.writeSSE({
|
|
1210
|
+
event: "ping",
|
|
1211
|
+
data: ""
|
|
1212
|
+
});
|
|
968
1213
|
await this.stream.writeSSE({
|
|
969
1214
|
event: "endpoint",
|
|
970
1215
|
data: `${this.messageUrl}?sessionId=${this.sessionId}`
|
|
@@ -1138,15 +1383,8 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
1138
1383
|
sseTransport;
|
|
1139
1384
|
sseHonoTransports;
|
|
1140
1385
|
streamableHTTPTransports = /* @__PURE__ */ new Map();
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
listResourcesHandlerIsRegistered = false;
|
|
1144
|
-
readResourceHandlerIsRegistered = false;
|
|
1145
|
-
listResourceTemplatesHandlerIsRegistered = false;
|
|
1146
|
-
subscribeResourceHandlerIsRegistered = false;
|
|
1147
|
-
unsubscribeResourceHandlerIsRegistered = false;
|
|
1148
|
-
listPromptsHandlerIsRegistered = false;
|
|
1149
|
-
getPromptHandlerIsRegistered = false;
|
|
1386
|
+
// Track server instances for each HTTP session
|
|
1387
|
+
httpServerInstances = /* @__PURE__ */ new Map();
|
|
1150
1388
|
definedResources;
|
|
1151
1389
|
definedResourceTemplates;
|
|
1152
1390
|
resourceOptions;
|
|
@@ -1155,6 +1393,7 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
1155
1393
|
subscriptions = /* @__PURE__ */ new Set();
|
|
1156
1394
|
resources;
|
|
1157
1395
|
prompts;
|
|
1396
|
+
elicitation;
|
|
1158
1397
|
/**
|
|
1159
1398
|
* Get the current stdio transport.
|
|
1160
1399
|
*/
|
|
@@ -1189,7 +1428,8 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
1189
1428
|
this.promptOptions = opts.prompts;
|
|
1190
1429
|
const capabilities = {
|
|
1191
1430
|
tools: {},
|
|
1192
|
-
logging: { enabled: true }
|
|
1431
|
+
logging: { enabled: true },
|
|
1432
|
+
elicitation: {}
|
|
1193
1433
|
};
|
|
1194
1434
|
if (opts.resources) {
|
|
1195
1435
|
capabilities.resources = { subscribe: true, listChanged: true };
|
|
@@ -1202,23 +1442,7 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
1202
1442
|
`Initialized MCPServer '${this.name}' v${this.version} (ID: ${this.id}) with tools: ${Object.keys(this.convertedTools).join(", ")} and resources. Capabilities: ${JSON.stringify(capabilities)}`
|
|
1203
1443
|
);
|
|
1204
1444
|
this.sseHonoTransports = /* @__PURE__ */ new Map();
|
|
1205
|
-
this.
|
|
1206
|
-
this.registerCallToolHandler();
|
|
1207
|
-
if (opts.resources) {
|
|
1208
|
-
this.registerListResourcesHandler();
|
|
1209
|
-
this.registerReadResourceHandler({ getResourcesCallback: opts.resources.getResourceContent });
|
|
1210
|
-
this.registerSubscribeResourceHandler();
|
|
1211
|
-
this.registerUnsubscribeResourceHandler();
|
|
1212
|
-
if (opts.resources.resourceTemplates) {
|
|
1213
|
-
this.registerListResourceTemplatesHandler();
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
if (opts.prompts) {
|
|
1217
|
-
this.registerListPromptsHandler();
|
|
1218
|
-
this.registerGetPromptHandler({
|
|
1219
|
-
getPromptMessagesCallback: opts.prompts.getPromptMessages
|
|
1220
|
-
});
|
|
1221
|
-
}
|
|
1445
|
+
this.registerHandlersOnServer(this.server);
|
|
1222
1446
|
this.resources = new ServerResourceActions({
|
|
1223
1447
|
getSubscriptions: () => this.subscriptions,
|
|
1224
1448
|
getLogger: () => this.logger,
|
|
@@ -1237,6 +1461,342 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
1237
1461
|
this.definedPrompts = void 0;
|
|
1238
1462
|
}
|
|
1239
1463
|
});
|
|
1464
|
+
this.elicitation = {
|
|
1465
|
+
sendRequest: async (request) => {
|
|
1466
|
+
return this.handleElicitationRequest(request);
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Handle an elicitation request by sending it to the connected client.
|
|
1472
|
+
* This method sends an elicitation/create request to the client and waits for the response.
|
|
1473
|
+
*
|
|
1474
|
+
* @param request - The elicitation request containing message and schema
|
|
1475
|
+
* @param serverInstance - Optional server instance to use; defaults to main server for backward compatibility
|
|
1476
|
+
* @returns Promise that resolves to the client's response
|
|
1477
|
+
*/
|
|
1478
|
+
async handleElicitationRequest(request, serverInstance) {
|
|
1479
|
+
this.logger.debug(`Sending elicitation request: ${request.message}`);
|
|
1480
|
+
const server = serverInstance || this.server;
|
|
1481
|
+
const response = await server.elicitInput(request);
|
|
1482
|
+
this.logger.debug(`Received elicitation response: ${JSON.stringify(response)}`);
|
|
1483
|
+
return response;
|
|
1484
|
+
}
|
|
1485
|
+
/**
|
|
1486
|
+
* Creates a new Server instance configured with all handlers for HTTP sessions.
|
|
1487
|
+
* Each HTTP client connection gets its own Server instance to avoid routing conflicts.
|
|
1488
|
+
*/
|
|
1489
|
+
createServerInstance() {
|
|
1490
|
+
const capabilities = {
|
|
1491
|
+
tools: {},
|
|
1492
|
+
logging: { enabled: true },
|
|
1493
|
+
elicitation: {}
|
|
1494
|
+
};
|
|
1495
|
+
if (this.resourceOptions) {
|
|
1496
|
+
capabilities.resources = { subscribe: true, listChanged: true };
|
|
1497
|
+
}
|
|
1498
|
+
if (this.promptOptions) {
|
|
1499
|
+
capabilities.prompts = { listChanged: true };
|
|
1500
|
+
}
|
|
1501
|
+
const serverInstance = new index_js.Server({ name: this.name, version: this.version }, { capabilities });
|
|
1502
|
+
this.registerHandlersOnServer(serverInstance);
|
|
1503
|
+
return serverInstance;
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Registers all MCP handlers on a given server instance.
|
|
1507
|
+
* This allows us to create multiple server instances with identical functionality.
|
|
1508
|
+
*/
|
|
1509
|
+
registerHandlersOnServer(serverInstance) {
|
|
1510
|
+
serverInstance.setRequestHandler(types_js.ListToolsRequestSchema, async () => {
|
|
1511
|
+
this.logger.debug("Handling ListTools request");
|
|
1512
|
+
return {
|
|
1513
|
+
tools: Object.values(this.convertedTools).map((tool) => {
|
|
1514
|
+
const toolSpec = {
|
|
1515
|
+
name: tool.name,
|
|
1516
|
+
description: tool.description,
|
|
1517
|
+
inputSchema: tool.parameters.jsonSchema
|
|
1518
|
+
};
|
|
1519
|
+
if (tool.outputSchema) {
|
|
1520
|
+
toolSpec.outputSchema = tool.outputSchema.jsonSchema;
|
|
1521
|
+
}
|
|
1522
|
+
return toolSpec;
|
|
1523
|
+
})
|
|
1524
|
+
};
|
|
1525
|
+
});
|
|
1526
|
+
serverInstance.setRequestHandler(types_js.CallToolRequestSchema, async (request) => {
|
|
1527
|
+
const startTime = Date.now();
|
|
1528
|
+
try {
|
|
1529
|
+
const tool = this.convertedTools[request.params.name];
|
|
1530
|
+
if (!tool) {
|
|
1531
|
+
this.logger.warn(`CallTool: Unknown tool '${request.params.name}' requested.`);
|
|
1532
|
+
return {
|
|
1533
|
+
content: [{ type: "text", text: `Unknown tool: ${request.params.name}` }],
|
|
1534
|
+
isError: true
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
const validation = tool.parameters.validate?.(request.params.arguments ?? {});
|
|
1538
|
+
if (validation && !validation.success) {
|
|
1539
|
+
this.logger.warn(`CallTool: Invalid tool arguments for '${request.params.name}'`, {
|
|
1540
|
+
errors: validation.error
|
|
1541
|
+
});
|
|
1542
|
+
return {
|
|
1543
|
+
content: [{ type: "text", text: `Invalid tool arguments: ${JSON.stringify(validation.error)}` }],
|
|
1544
|
+
isError: true
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
if (!tool.execute) {
|
|
1548
|
+
this.logger.warn(`CallTool: Tool '${request.params.name}' does not have an execute function.`);
|
|
1549
|
+
return {
|
|
1550
|
+
content: [{ type: "text", text: `Tool '${request.params.name}' does not have an execute function.` }],
|
|
1551
|
+
isError: true
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
const sessionElicitation = {
|
|
1555
|
+
sendRequest: async (request2) => {
|
|
1556
|
+
return this.handleElicitationRequest(request2, serverInstance);
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1559
|
+
const result = await tool.execute(validation?.value, {
|
|
1560
|
+
messages: [],
|
|
1561
|
+
toolCallId: "",
|
|
1562
|
+
elicitation: sessionElicitation
|
|
1563
|
+
});
|
|
1564
|
+
this.logger.debug(`CallTool: Tool '${request.params.name}' executed successfully with result:`, result);
|
|
1565
|
+
const duration = Date.now() - startTime;
|
|
1566
|
+
this.logger.info(`Tool '${request.params.name}' executed successfully in ${duration}ms.`);
|
|
1567
|
+
const response = { isError: false, content: [] };
|
|
1568
|
+
if (tool.outputSchema) {
|
|
1569
|
+
if (!result.structuredContent) {
|
|
1570
|
+
throw new Error(`Tool ${request.params.name} has an output schema but no structured content was provided.`);
|
|
1571
|
+
}
|
|
1572
|
+
const outputValidation = tool.outputSchema.validate?.(result.structuredContent ?? {});
|
|
1573
|
+
if (outputValidation && !outputValidation.success) {
|
|
1574
|
+
this.logger.warn(`CallTool: Invalid structured content for '${request.params.name}'`, {
|
|
1575
|
+
errors: outputValidation.error
|
|
1576
|
+
});
|
|
1577
|
+
throw new Error(
|
|
1578
|
+
`Invalid structured content for tool ${request.params.name}: ${JSON.stringify(outputValidation.error)}`
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1581
|
+
response.structuredContent = result.structuredContent;
|
|
1582
|
+
}
|
|
1583
|
+
if (response.structuredContent) {
|
|
1584
|
+
response.content = [{ type: "text", text: JSON.stringify(response.structuredContent) }];
|
|
1585
|
+
} else {
|
|
1586
|
+
response.content = [
|
|
1587
|
+
{
|
|
1588
|
+
type: "text",
|
|
1589
|
+
text: typeof result === "string" ? result : JSON.stringify(result)
|
|
1590
|
+
}
|
|
1591
|
+
];
|
|
1592
|
+
}
|
|
1593
|
+
return response;
|
|
1594
|
+
} catch (error) {
|
|
1595
|
+
const duration = Date.now() - startTime;
|
|
1596
|
+
if (error instanceof zod.z.ZodError) {
|
|
1597
|
+
this.logger.warn("Invalid tool arguments", {
|
|
1598
|
+
tool: request.params.name,
|
|
1599
|
+
errors: error.errors,
|
|
1600
|
+
duration: `${duration}ms`
|
|
1601
|
+
});
|
|
1602
|
+
return {
|
|
1603
|
+
content: [
|
|
1604
|
+
{
|
|
1605
|
+
type: "text",
|
|
1606
|
+
text: `Invalid arguments: ${error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ")}`
|
|
1607
|
+
}
|
|
1608
|
+
],
|
|
1609
|
+
isError: true
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
this.logger.error(`Tool execution failed: ${request.params.name}`, { error });
|
|
1613
|
+
return {
|
|
1614
|
+
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
1615
|
+
isError: true
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
if (this.resourceOptions) {
|
|
1620
|
+
this.registerResourceHandlersOnServer(serverInstance);
|
|
1621
|
+
}
|
|
1622
|
+
if (this.promptOptions) {
|
|
1623
|
+
this.registerPromptHandlersOnServer(serverInstance);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Registers resource-related handlers on a server instance.
|
|
1628
|
+
*/
|
|
1629
|
+
registerResourceHandlersOnServer(serverInstance) {
|
|
1630
|
+
const capturedResourceOptions = this.resourceOptions;
|
|
1631
|
+
if (!capturedResourceOptions) return;
|
|
1632
|
+
if (capturedResourceOptions.listResources) {
|
|
1633
|
+
serverInstance.setRequestHandler(types_js.ListResourcesRequestSchema, async () => {
|
|
1634
|
+
this.logger.debug("Handling ListResources request");
|
|
1635
|
+
if (this.definedResources) {
|
|
1636
|
+
return { resources: this.definedResources };
|
|
1637
|
+
} else {
|
|
1638
|
+
try {
|
|
1639
|
+
const resources = await capturedResourceOptions.listResources();
|
|
1640
|
+
this.definedResources = resources;
|
|
1641
|
+
this.logger.debug(`Fetched and cached ${this.definedResources.length} resources.`);
|
|
1642
|
+
return { resources: this.definedResources };
|
|
1643
|
+
} catch (error) {
|
|
1644
|
+
this.logger.error("Error fetching resources via listResources():", { error });
|
|
1645
|
+
throw error;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
if (capturedResourceOptions.getResourceContent) {
|
|
1651
|
+
serverInstance.setRequestHandler(types_js.ReadResourceRequestSchema, async (request) => {
|
|
1652
|
+
const startTime = Date.now();
|
|
1653
|
+
const uri = request.params.uri;
|
|
1654
|
+
this.logger.debug(`Handling ReadResource request for URI: ${uri}`);
|
|
1655
|
+
if (!this.definedResources) {
|
|
1656
|
+
const resources = await this.resourceOptions?.listResources?.();
|
|
1657
|
+
if (!resources) throw new Error("Failed to load resources");
|
|
1658
|
+
this.definedResources = resources;
|
|
1659
|
+
}
|
|
1660
|
+
const resource = this.definedResources?.find((r) => r.uri === uri);
|
|
1661
|
+
if (!resource) {
|
|
1662
|
+
this.logger.warn(`ReadResource: Unknown resource URI '${uri}' requested.`);
|
|
1663
|
+
throw new Error(`Resource not found: ${uri}`);
|
|
1664
|
+
}
|
|
1665
|
+
try {
|
|
1666
|
+
const resourcesOrResourceContent = await capturedResourceOptions.getResourceContent({ uri });
|
|
1667
|
+
const resourcesContent = Array.isArray(resourcesOrResourceContent) ? resourcesOrResourceContent : [resourcesOrResourceContent];
|
|
1668
|
+
const contents = resourcesContent.map((resourceContent) => {
|
|
1669
|
+
const contentItem = {
|
|
1670
|
+
uri: resource.uri,
|
|
1671
|
+
mimeType: resource.mimeType
|
|
1672
|
+
};
|
|
1673
|
+
if ("text" in resourceContent) {
|
|
1674
|
+
contentItem.text = resourceContent.text;
|
|
1675
|
+
}
|
|
1676
|
+
if ("blob" in resourceContent) {
|
|
1677
|
+
contentItem.blob = resourceContent.blob;
|
|
1678
|
+
}
|
|
1679
|
+
return contentItem;
|
|
1680
|
+
});
|
|
1681
|
+
const duration = Date.now() - startTime;
|
|
1682
|
+
this.logger.info(`Resource '${uri}' read successfully in ${duration}ms.`);
|
|
1683
|
+
return {
|
|
1684
|
+
contents
|
|
1685
|
+
};
|
|
1686
|
+
} catch (error) {
|
|
1687
|
+
const duration = Date.now() - startTime;
|
|
1688
|
+
this.logger.error(`Failed to get content for resource URI '${uri}' in ${duration}ms`, { error });
|
|
1689
|
+
throw error;
|
|
1690
|
+
}
|
|
1691
|
+
});
|
|
1692
|
+
}
|
|
1693
|
+
if (capturedResourceOptions.resourceTemplates) {
|
|
1694
|
+
serverInstance.setRequestHandler(types_js.ListResourceTemplatesRequestSchema, async () => {
|
|
1695
|
+
this.logger.debug("Handling ListResourceTemplates request");
|
|
1696
|
+
if (this.definedResourceTemplates) {
|
|
1697
|
+
return { resourceTemplates: this.definedResourceTemplates };
|
|
1698
|
+
} else {
|
|
1699
|
+
try {
|
|
1700
|
+
const templates = await capturedResourceOptions.resourceTemplates();
|
|
1701
|
+
this.definedResourceTemplates = templates;
|
|
1702
|
+
this.logger.debug(`Fetched and cached ${this.definedResourceTemplates.length} resource templates.`);
|
|
1703
|
+
return { resourceTemplates: this.definedResourceTemplates };
|
|
1704
|
+
} catch (error) {
|
|
1705
|
+
this.logger.error("Error fetching resource templates via resourceTemplates():", { error });
|
|
1706
|
+
throw error;
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
serverInstance.setRequestHandler(types_js.SubscribeRequestSchema, async (request) => {
|
|
1712
|
+
const uri = request.params.uri;
|
|
1713
|
+
this.logger.info(`Received resources/subscribe request for URI: ${uri}`);
|
|
1714
|
+
this.subscriptions.add(uri);
|
|
1715
|
+
return {};
|
|
1716
|
+
});
|
|
1717
|
+
serverInstance.setRequestHandler(types_js.UnsubscribeRequestSchema, async (request) => {
|
|
1718
|
+
const uri = request.params.uri;
|
|
1719
|
+
this.logger.info(`Received resources/unsubscribe request for URI: ${uri}`);
|
|
1720
|
+
this.subscriptions.delete(uri);
|
|
1721
|
+
return {};
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
/**
|
|
1725
|
+
* Registers prompt-related handlers on a server instance.
|
|
1726
|
+
*/
|
|
1727
|
+
registerPromptHandlersOnServer(serverInstance) {
|
|
1728
|
+
const capturedPromptOptions = this.promptOptions;
|
|
1729
|
+
if (!capturedPromptOptions) return;
|
|
1730
|
+
if (capturedPromptOptions.listPrompts) {
|
|
1731
|
+
serverInstance.setRequestHandler(types_js.ListPromptsRequestSchema, async () => {
|
|
1732
|
+
this.logger.debug("Handling ListPrompts request");
|
|
1733
|
+
if (this.definedPrompts) {
|
|
1734
|
+
return {
|
|
1735
|
+
prompts: this.definedPrompts?.map((p) => ({ ...p, version: p.version ?? void 0 }))
|
|
1736
|
+
};
|
|
1737
|
+
} else {
|
|
1738
|
+
try {
|
|
1739
|
+
const prompts = await capturedPromptOptions.listPrompts();
|
|
1740
|
+
for (const prompt of prompts) {
|
|
1741
|
+
types_js.PromptSchema.parse(prompt);
|
|
1742
|
+
}
|
|
1743
|
+
this.definedPrompts = prompts;
|
|
1744
|
+
this.logger.debug(`Fetched and cached ${this.definedPrompts.length} prompts.`);
|
|
1745
|
+
return {
|
|
1746
|
+
prompts: this.definedPrompts?.map((p) => ({ ...p, version: p.version ?? void 0 }))
|
|
1747
|
+
};
|
|
1748
|
+
} catch (error) {
|
|
1749
|
+
this.logger.error("Error fetching prompts via listPrompts():", {
|
|
1750
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1751
|
+
});
|
|
1752
|
+
throw error;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1757
|
+
if (capturedPromptOptions.getPromptMessages) {
|
|
1758
|
+
serverInstance.setRequestHandler(
|
|
1759
|
+
types_js.GetPromptRequestSchema,
|
|
1760
|
+
async (request) => {
|
|
1761
|
+
const startTime = Date.now();
|
|
1762
|
+
const { name, version, arguments: args } = request.params;
|
|
1763
|
+
if (!this.definedPrompts) {
|
|
1764
|
+
const prompts = await this.promptOptions?.listPrompts?.();
|
|
1765
|
+
if (!prompts) throw new Error("Failed to load prompts");
|
|
1766
|
+
this.definedPrompts = prompts;
|
|
1767
|
+
}
|
|
1768
|
+
let prompt;
|
|
1769
|
+
if (version) {
|
|
1770
|
+
prompt = this.definedPrompts?.find((p) => p.name === name && p.version === version);
|
|
1771
|
+
} else {
|
|
1772
|
+
prompt = this.definedPrompts?.find((p) => p.name === name);
|
|
1773
|
+
}
|
|
1774
|
+
if (!prompt) throw new Error(`Prompt "${name}"${version ? ` (version ${version})` : ""} not found`);
|
|
1775
|
+
if (prompt.arguments) {
|
|
1776
|
+
for (const arg of prompt.arguments) {
|
|
1777
|
+
if (arg.required && (args?.[arg.name] === void 0 || args?.[arg.name] === null)) {
|
|
1778
|
+
throw new Error(`Missing required argument: ${arg.name}`);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
try {
|
|
1783
|
+
let messages = [];
|
|
1784
|
+
if (capturedPromptOptions.getPromptMessages) {
|
|
1785
|
+
messages = await capturedPromptOptions.getPromptMessages({ name, version, args });
|
|
1786
|
+
}
|
|
1787
|
+
const duration = Date.now() - startTime;
|
|
1788
|
+
this.logger.info(
|
|
1789
|
+
`Prompt '${name}'${version ? ` (version ${version})` : ""} retrieved successfully in ${duration}ms.`
|
|
1790
|
+
);
|
|
1791
|
+
return { prompt, messages };
|
|
1792
|
+
} catch (error) {
|
|
1793
|
+
const duration = Date.now() - startTime;
|
|
1794
|
+
this.logger.error(`Failed to get content for prompt '${name}' in ${duration}ms`, { error });
|
|
1795
|
+
throw error;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
);
|
|
1799
|
+
}
|
|
1240
1800
|
}
|
|
1241
1801
|
convertAgentsToTools(agentsConfig, definedConvertedTools) {
|
|
1242
1802
|
const agentTools = {};
|
|
@@ -1336,8 +1896,8 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
1336
1896
|
context
|
|
1337
1897
|
);
|
|
1338
1898
|
try {
|
|
1339
|
-
const
|
|
1340
|
-
const response = await
|
|
1899
|
+
const run2 = workflow.createRun({ runId: runtimeContext?.get("runId") });
|
|
1900
|
+
const response = await run2.start({ inputData: context, runtimeContext });
|
|
1341
1901
|
return response;
|
|
1342
1902
|
} catch (error) {
|
|
1343
1903
|
this.logger.error(
|
|
@@ -1360,6 +1920,7 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
1360
1920
|
name: workflowToolName,
|
|
1361
1921
|
description: coreTool.description,
|
|
1362
1922
|
parameters: coreTool.parameters,
|
|
1923
|
+
outputSchema: coreTool.outputSchema,
|
|
1363
1924
|
execute: coreTool.execute,
|
|
1364
1925
|
toolType: "workflow"
|
|
1365
1926
|
};
|
|
@@ -1399,6 +1960,7 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
1399
1960
|
name: toolName,
|
|
1400
1961
|
description: coreTool.description,
|
|
1401
1962
|
parameters: coreTool.parameters,
|
|
1963
|
+
outputSchema: coreTool.outputSchema,
|
|
1402
1964
|
execute: coreTool.execute
|
|
1403
1965
|
};
|
|
1404
1966
|
this.logger.info(`Registered explicit tool: '${toolName}'`);
|
|
@@ -1434,342 +1996,6 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
1434
1996
|
);
|
|
1435
1997
|
return allConvertedTools;
|
|
1436
1998
|
}
|
|
1437
|
-
/**
|
|
1438
|
-
* Register the ListTools handler for listing all available tools.
|
|
1439
|
-
*/
|
|
1440
|
-
registerListToolsHandler() {
|
|
1441
|
-
if (this.listToolsHandlerIsRegistered) {
|
|
1442
|
-
return;
|
|
1443
|
-
}
|
|
1444
|
-
this.listToolsHandlerIsRegistered = true;
|
|
1445
|
-
this.server.setRequestHandler(types_js.ListToolsRequestSchema, async () => {
|
|
1446
|
-
this.logger.debug("Handling ListTools request");
|
|
1447
|
-
return {
|
|
1448
|
-
tools: Object.values(this.convertedTools).map((tool) => ({
|
|
1449
|
-
name: tool.name,
|
|
1450
|
-
description: tool.description,
|
|
1451
|
-
inputSchema: tool.parameters.jsonSchema
|
|
1452
|
-
}))
|
|
1453
|
-
};
|
|
1454
|
-
});
|
|
1455
|
-
}
|
|
1456
|
-
/**
|
|
1457
|
-
* Register the CallTool handler for executing a tool by name.
|
|
1458
|
-
*/
|
|
1459
|
-
registerCallToolHandler() {
|
|
1460
|
-
if (this.callToolHandlerIsRegistered) {
|
|
1461
|
-
return;
|
|
1462
|
-
}
|
|
1463
|
-
this.callToolHandlerIsRegistered = true;
|
|
1464
|
-
this.server.setRequestHandler(types_js.CallToolRequestSchema, async (request) => {
|
|
1465
|
-
const startTime = Date.now();
|
|
1466
|
-
try {
|
|
1467
|
-
const tool = this.convertedTools[request.params.name];
|
|
1468
|
-
if (!tool) {
|
|
1469
|
-
this.logger.warn(`CallTool: Unknown tool '${request.params.name}' requested.`);
|
|
1470
|
-
return {
|
|
1471
|
-
content: [{ type: "text", text: `Unknown tool: ${request.params.name}` }],
|
|
1472
|
-
isError: true
|
|
1473
|
-
};
|
|
1474
|
-
}
|
|
1475
|
-
this.logger.debug(`CallTool: Invoking '${request.params.name}' with arguments:`, request.params.arguments);
|
|
1476
|
-
const validation = tool.parameters.validate?.(request.params.arguments ?? {});
|
|
1477
|
-
if (validation && !validation.success) {
|
|
1478
|
-
this.logger.warn(`CallTool: Invalid tool arguments for '${request.params.name}'`, {
|
|
1479
|
-
errors: validation.error
|
|
1480
|
-
});
|
|
1481
|
-
return {
|
|
1482
|
-
content: [{ type: "text", text: `Invalid tool arguments: ${JSON.stringify(validation.error)}` }],
|
|
1483
|
-
isError: true
|
|
1484
|
-
};
|
|
1485
|
-
}
|
|
1486
|
-
if (!tool.execute) {
|
|
1487
|
-
this.logger.warn(`CallTool: Tool '${request.params.name}' does not have an execute function.`);
|
|
1488
|
-
return {
|
|
1489
|
-
content: [{ type: "text", text: `Tool '${request.params.name}' does not have an execute function.` }],
|
|
1490
|
-
isError: true
|
|
1491
|
-
};
|
|
1492
|
-
}
|
|
1493
|
-
const result = await tool.execute(validation?.value, { messages: [], toolCallId: "" });
|
|
1494
|
-
const duration = Date.now() - startTime;
|
|
1495
|
-
this.logger.info(`Tool '${request.params.name}' executed successfully in ${duration}ms.`);
|
|
1496
|
-
return {
|
|
1497
|
-
content: [
|
|
1498
|
-
{
|
|
1499
|
-
type: "text",
|
|
1500
|
-
text: typeof result === "string" ? result : JSON.stringify(result)
|
|
1501
|
-
}
|
|
1502
|
-
],
|
|
1503
|
-
isError: false
|
|
1504
|
-
};
|
|
1505
|
-
} catch (error) {
|
|
1506
|
-
const duration = Date.now() - startTime;
|
|
1507
|
-
if (error instanceof zod.z.ZodError) {
|
|
1508
|
-
this.logger.warn("Invalid tool arguments", {
|
|
1509
|
-
tool: request.params.name,
|
|
1510
|
-
errors: error.errors,
|
|
1511
|
-
duration: `${duration}ms`
|
|
1512
|
-
});
|
|
1513
|
-
return {
|
|
1514
|
-
content: [
|
|
1515
|
-
{
|
|
1516
|
-
type: "text",
|
|
1517
|
-
text: `Invalid arguments: ${error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ")}`
|
|
1518
|
-
}
|
|
1519
|
-
],
|
|
1520
|
-
isError: true
|
|
1521
|
-
};
|
|
1522
|
-
}
|
|
1523
|
-
this.logger.error(`Tool execution failed: ${request.params.name}`, { error });
|
|
1524
|
-
return {
|
|
1525
|
-
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
1526
|
-
isError: true
|
|
1527
|
-
};
|
|
1528
|
-
}
|
|
1529
|
-
});
|
|
1530
|
-
}
|
|
1531
|
-
/**
|
|
1532
|
-
* Register the ListResources handler for listing all available resources.
|
|
1533
|
-
*/
|
|
1534
|
-
registerListResourcesHandler() {
|
|
1535
|
-
if (this.listResourcesHandlerIsRegistered) {
|
|
1536
|
-
return;
|
|
1537
|
-
}
|
|
1538
|
-
this.listResourcesHandlerIsRegistered = true;
|
|
1539
|
-
const capturedResourceOptions = this.resourceOptions;
|
|
1540
|
-
if (!capturedResourceOptions?.listResources) {
|
|
1541
|
-
this.logger.warn("ListResources capability not supported by server configuration.");
|
|
1542
|
-
return;
|
|
1543
|
-
}
|
|
1544
|
-
this.server.setRequestHandler(types_js.ListResourcesRequestSchema, async () => {
|
|
1545
|
-
this.logger.debug("Handling ListResources request");
|
|
1546
|
-
if (this.definedResources) {
|
|
1547
|
-
return { resources: this.definedResources };
|
|
1548
|
-
} else {
|
|
1549
|
-
try {
|
|
1550
|
-
const resources = await capturedResourceOptions.listResources();
|
|
1551
|
-
this.definedResources = resources;
|
|
1552
|
-
this.logger.debug(`Fetched and cached ${this.definedResources.length} resources.`);
|
|
1553
|
-
return { resources: this.definedResources };
|
|
1554
|
-
} catch (error) {
|
|
1555
|
-
this.logger.error("Error fetching resources via listResources():", { error });
|
|
1556
|
-
throw error;
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
});
|
|
1560
|
-
}
|
|
1561
|
-
/**
|
|
1562
|
-
* Register the ReadResource handler for reading a resource by URI.
|
|
1563
|
-
*/
|
|
1564
|
-
registerReadResourceHandler({
|
|
1565
|
-
getResourcesCallback
|
|
1566
|
-
}) {
|
|
1567
|
-
if (this.readResourceHandlerIsRegistered) {
|
|
1568
|
-
return;
|
|
1569
|
-
}
|
|
1570
|
-
this.readResourceHandlerIsRegistered = true;
|
|
1571
|
-
this.server.setRequestHandler(types_js.ReadResourceRequestSchema, async (request) => {
|
|
1572
|
-
const startTime = Date.now();
|
|
1573
|
-
const uri = request.params.uri;
|
|
1574
|
-
this.logger.debug(`Handling ReadResource request for URI: ${uri}`);
|
|
1575
|
-
if (!this.definedResources) {
|
|
1576
|
-
const resources = await this.resourceOptions?.listResources?.();
|
|
1577
|
-
if (!resources) throw new Error("Failed to load resources");
|
|
1578
|
-
this.definedResources = resources;
|
|
1579
|
-
}
|
|
1580
|
-
const resource = this.definedResources?.find((r) => r.uri === uri);
|
|
1581
|
-
if (!resource) {
|
|
1582
|
-
this.logger.warn(`ReadResource: Unknown resource URI '${uri}' requested.`);
|
|
1583
|
-
throw new Error(`Resource not found: ${uri}`);
|
|
1584
|
-
}
|
|
1585
|
-
try {
|
|
1586
|
-
const resourcesOrResourceContent = await getResourcesCallback({ uri });
|
|
1587
|
-
const resourcesContent = Array.isArray(resourcesOrResourceContent) ? resourcesOrResourceContent : [resourcesOrResourceContent];
|
|
1588
|
-
const contents = resourcesContent.map((resourceContent) => {
|
|
1589
|
-
const contentItem = {
|
|
1590
|
-
uri: resource.uri,
|
|
1591
|
-
mimeType: resource.mimeType
|
|
1592
|
-
};
|
|
1593
|
-
if ("text" in resourceContent) {
|
|
1594
|
-
contentItem.text = resourceContent.text;
|
|
1595
|
-
}
|
|
1596
|
-
if ("blob" in resourceContent) {
|
|
1597
|
-
contentItem.blob = resourceContent.blob;
|
|
1598
|
-
}
|
|
1599
|
-
return contentItem;
|
|
1600
|
-
});
|
|
1601
|
-
const duration = Date.now() - startTime;
|
|
1602
|
-
this.logger.info(`Resource '${uri}' read successfully in ${duration}ms.`);
|
|
1603
|
-
return {
|
|
1604
|
-
contents
|
|
1605
|
-
};
|
|
1606
|
-
} catch (error) {
|
|
1607
|
-
const duration = Date.now() - startTime;
|
|
1608
|
-
this.logger.error(`Failed to get content for resource URI '${uri}' in ${duration}ms`, { error });
|
|
1609
|
-
throw error;
|
|
1610
|
-
}
|
|
1611
|
-
});
|
|
1612
|
-
}
|
|
1613
|
-
/**
|
|
1614
|
-
* Register the ListResourceTemplates handler.
|
|
1615
|
-
*/
|
|
1616
|
-
registerListResourceTemplatesHandler() {
|
|
1617
|
-
if (this.listResourceTemplatesHandlerIsRegistered) {
|
|
1618
|
-
return;
|
|
1619
|
-
}
|
|
1620
|
-
if (!this.resourceOptions || typeof this.resourceOptions.resourceTemplates !== "function") {
|
|
1621
|
-
this.logger.warn(
|
|
1622
|
-
"ListResourceTemplates handler called, but resourceTemplates function is not available on resourceOptions or not a function."
|
|
1623
|
-
);
|
|
1624
|
-
this.server.setRequestHandler(types_js.ListResourceTemplatesRequestSchema, async () => {
|
|
1625
|
-
this.logger.debug("Handling ListResourceTemplates request (no templates configured or resourceOptions issue)");
|
|
1626
|
-
return { resourceTemplates: [] };
|
|
1627
|
-
});
|
|
1628
|
-
this.listResourceTemplatesHandlerIsRegistered = true;
|
|
1629
|
-
return;
|
|
1630
|
-
}
|
|
1631
|
-
const resourceTemplatesFn = this.resourceOptions.resourceTemplates;
|
|
1632
|
-
this.listResourceTemplatesHandlerIsRegistered = true;
|
|
1633
|
-
this.server.setRequestHandler(types_js.ListResourceTemplatesRequestSchema, async () => {
|
|
1634
|
-
this.logger.debug("Handling ListResourceTemplates request");
|
|
1635
|
-
if (this.definedResourceTemplates) {
|
|
1636
|
-
return { resourceTemplates: this.definedResourceTemplates };
|
|
1637
|
-
} else {
|
|
1638
|
-
try {
|
|
1639
|
-
const templates = await resourceTemplatesFn();
|
|
1640
|
-
this.definedResourceTemplates = templates;
|
|
1641
|
-
this.logger.debug(`Fetched and cached ${this.definedResourceTemplates.length} resource templates.`);
|
|
1642
|
-
return { resourceTemplates: this.definedResourceTemplates };
|
|
1643
|
-
} catch (error) {
|
|
1644
|
-
this.logger.error("Error fetching resource templates via resourceTemplates():", { error });
|
|
1645
|
-
throw error;
|
|
1646
|
-
}
|
|
1647
|
-
}
|
|
1648
|
-
});
|
|
1649
|
-
}
|
|
1650
|
-
/**
|
|
1651
|
-
* Register the SubscribeResource handler.
|
|
1652
|
-
*/
|
|
1653
|
-
registerSubscribeResourceHandler() {
|
|
1654
|
-
if (this.subscribeResourceHandlerIsRegistered) {
|
|
1655
|
-
return;
|
|
1656
|
-
}
|
|
1657
|
-
if (!types_js.SubscribeRequestSchema) {
|
|
1658
|
-
this.logger.warn("SubscribeRequestSchema not available, cannot register SubscribeResource handler.");
|
|
1659
|
-
return;
|
|
1660
|
-
}
|
|
1661
|
-
this.subscribeResourceHandlerIsRegistered = true;
|
|
1662
|
-
this.server.setRequestHandler(types_js.SubscribeRequestSchema, async (request) => {
|
|
1663
|
-
const uri = request.params.uri;
|
|
1664
|
-
this.logger.info(`Received resources/subscribe request for URI: ${uri}`);
|
|
1665
|
-
this.subscriptions.add(uri);
|
|
1666
|
-
return {};
|
|
1667
|
-
});
|
|
1668
|
-
}
|
|
1669
|
-
/**
|
|
1670
|
-
* Register the UnsubscribeResource handler.
|
|
1671
|
-
*/
|
|
1672
|
-
registerUnsubscribeResourceHandler() {
|
|
1673
|
-
if (this.unsubscribeResourceHandlerIsRegistered) {
|
|
1674
|
-
return;
|
|
1675
|
-
}
|
|
1676
|
-
this.unsubscribeResourceHandlerIsRegistered = true;
|
|
1677
|
-
this.server.setRequestHandler(types_js.UnsubscribeRequestSchema, async (request) => {
|
|
1678
|
-
const uri = request.params.uri;
|
|
1679
|
-
this.logger.info(`Received resources/unsubscribe request for URI: ${uri}`);
|
|
1680
|
-
this.subscriptions.delete(uri);
|
|
1681
|
-
return {};
|
|
1682
|
-
});
|
|
1683
|
-
}
|
|
1684
|
-
/**
|
|
1685
|
-
* Register the ListPrompts handler.
|
|
1686
|
-
*/
|
|
1687
|
-
registerListPromptsHandler() {
|
|
1688
|
-
if (this.listPromptsHandlerIsRegistered) {
|
|
1689
|
-
return;
|
|
1690
|
-
}
|
|
1691
|
-
this.listPromptsHandlerIsRegistered = true;
|
|
1692
|
-
const capturedPromptOptions = this.promptOptions;
|
|
1693
|
-
if (!capturedPromptOptions?.listPrompts) {
|
|
1694
|
-
this.logger.warn("ListPrompts capability not supported by server configuration.");
|
|
1695
|
-
return;
|
|
1696
|
-
}
|
|
1697
|
-
this.server.setRequestHandler(types_js.ListPromptsRequestSchema, async () => {
|
|
1698
|
-
this.logger.debug("Handling ListPrompts request");
|
|
1699
|
-
if (this.definedPrompts) {
|
|
1700
|
-
return {
|
|
1701
|
-
prompts: this.definedPrompts?.map((p) => ({ ...p, version: p.version ?? void 0 }))
|
|
1702
|
-
};
|
|
1703
|
-
} else {
|
|
1704
|
-
try {
|
|
1705
|
-
const prompts = await capturedPromptOptions.listPrompts();
|
|
1706
|
-
for (const prompt of prompts) {
|
|
1707
|
-
types_js.PromptSchema.parse(prompt);
|
|
1708
|
-
}
|
|
1709
|
-
this.definedPrompts = prompts;
|
|
1710
|
-
this.logger.debug(`Fetched and cached ${this.definedPrompts.length} prompts.`);
|
|
1711
|
-
return {
|
|
1712
|
-
prompts: this.definedPrompts?.map((p) => ({ ...p, version: p.version ?? void 0 }))
|
|
1713
|
-
};
|
|
1714
|
-
} catch (error) {
|
|
1715
|
-
this.logger.error("Error fetching prompts via listPrompts():", {
|
|
1716
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1717
|
-
});
|
|
1718
|
-
throw error;
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
|
-
});
|
|
1722
|
-
}
|
|
1723
|
-
/**
|
|
1724
|
-
* Register the GetPrompt handler.
|
|
1725
|
-
*/
|
|
1726
|
-
registerGetPromptHandler({
|
|
1727
|
-
getPromptMessagesCallback
|
|
1728
|
-
}) {
|
|
1729
|
-
if (this.getPromptHandlerIsRegistered) return;
|
|
1730
|
-
this.getPromptHandlerIsRegistered = true;
|
|
1731
|
-
this.server.setRequestHandler(
|
|
1732
|
-
types_js.GetPromptRequestSchema,
|
|
1733
|
-
async (request) => {
|
|
1734
|
-
const startTime = Date.now();
|
|
1735
|
-
const { name, version, arguments: args } = request.params;
|
|
1736
|
-
if (!this.definedPrompts) {
|
|
1737
|
-
const prompts = await this.promptOptions?.listPrompts?.();
|
|
1738
|
-
if (!prompts) throw new Error("Failed to load prompts");
|
|
1739
|
-
this.definedPrompts = prompts;
|
|
1740
|
-
}
|
|
1741
|
-
let prompt;
|
|
1742
|
-
if (version) {
|
|
1743
|
-
prompt = this.definedPrompts?.find((p) => p.name === name && p.version === version);
|
|
1744
|
-
} else {
|
|
1745
|
-
prompt = this.definedPrompts?.find((p) => p.name === name);
|
|
1746
|
-
}
|
|
1747
|
-
if (!prompt) throw new Error(`Prompt "${name}"${version ? ` (version ${version})` : ""} not found`);
|
|
1748
|
-
if (prompt.arguments) {
|
|
1749
|
-
for (const arg of prompt.arguments) {
|
|
1750
|
-
if (arg.required && (args?.[arg.name] === void 0 || args?.[arg.name] === null)) {
|
|
1751
|
-
throw new Error(`Missing required argument: ${arg.name}`);
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
}
|
|
1755
|
-
try {
|
|
1756
|
-
let messages = [];
|
|
1757
|
-
if (getPromptMessagesCallback) {
|
|
1758
|
-
messages = await getPromptMessagesCallback({ name, version, args });
|
|
1759
|
-
}
|
|
1760
|
-
const duration = Date.now() - startTime;
|
|
1761
|
-
this.logger.info(
|
|
1762
|
-
`Prompt '${name}'${version ? ` (version ${version})` : ""} retrieved successfully in ${duration}ms.`
|
|
1763
|
-
);
|
|
1764
|
-
return { prompt, messages };
|
|
1765
|
-
} catch (error) {
|
|
1766
|
-
const duration = Date.now() - startTime;
|
|
1767
|
-
this.logger.error(`Failed to get content for prompt '${name}' in ${duration}ms`, { error });
|
|
1768
|
-
throw error;
|
|
1769
|
-
}
|
|
1770
|
-
}
|
|
1771
|
-
);
|
|
1772
|
-
}
|
|
1773
1999
|
/**
|
|
1774
2000
|
* Start the MCP server using stdio transport (for Windsurf integration).
|
|
1775
2001
|
*/
|
|
@@ -1855,10 +2081,10 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
1855
2081
|
async startHonoSSE({ url, ssePath, messagePath, context }) {
|
|
1856
2082
|
try {
|
|
1857
2083
|
if (url.pathname === ssePath) {
|
|
1858
|
-
return
|
|
2084
|
+
return streamSSE(context, async (stream2) => {
|
|
1859
2085
|
await this.connectHonoSSE({
|
|
1860
2086
|
messagePath,
|
|
1861
|
-
stream
|
|
2087
|
+
stream: stream2
|
|
1862
2088
|
});
|
|
1863
2089
|
});
|
|
1864
2090
|
} else if (url.pathname === messagePath) {
|
|
@@ -1982,11 +2208,17 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
1982
2208
|
`startHTTP: Streamable HTTP transport closed for session ${closedSessionId}, removing from map.`
|
|
1983
2209
|
);
|
|
1984
2210
|
this.streamableHTTPTransports.delete(closedSessionId);
|
|
2211
|
+
if (this.httpServerInstances.has(closedSessionId)) {
|
|
2212
|
+
this.httpServerInstances.delete(closedSessionId);
|
|
2213
|
+
this.logger.debug(`startHTTP: Cleaned up server instance for closed session ${closedSessionId}`);
|
|
2214
|
+
}
|
|
1985
2215
|
}
|
|
1986
2216
|
};
|
|
1987
|
-
|
|
2217
|
+
const sessionServerInstance = this.createServerInstance();
|
|
2218
|
+
await sessionServerInstance.connect(transport);
|
|
1988
2219
|
if (transport.sessionId) {
|
|
1989
2220
|
this.streamableHTTPTransports.set(transport.sessionId, transport);
|
|
2221
|
+
this.httpServerInstances.set(transport.sessionId, sessionServerInstance);
|
|
1990
2222
|
this.logger.debug(
|
|
1991
2223
|
`startHTTP: Streamable HTTP session initialized and stored with ID: ${transport.sessionId}`
|
|
1992
2224
|
);
|
|
@@ -2084,13 +2316,13 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
2084
2316
|
throw mastraError;
|
|
2085
2317
|
}
|
|
2086
2318
|
}
|
|
2087
|
-
async connectHonoSSE({ messagePath, stream }) {
|
|
2319
|
+
async connectHonoSSE({ messagePath, stream: stream2 }) {
|
|
2088
2320
|
this.logger.debug("Received SSE connection");
|
|
2089
|
-
const sseTransport = new SSETransport(messagePath,
|
|
2321
|
+
const sseTransport = new SSETransport(messagePath, stream2);
|
|
2090
2322
|
const sessionId = sseTransport.sessionId;
|
|
2091
2323
|
this.logger.debug("SSE Transport created with sessionId:", { sessionId });
|
|
2092
2324
|
this.sseHonoTransports.set(sessionId, sseTransport);
|
|
2093
|
-
|
|
2325
|
+
stream2.onAbort(() => {
|
|
2094
2326
|
this.logger.debug("SSE Transport aborted with sessionId:", { sessionId });
|
|
2095
2327
|
this.sseHonoTransports.delete(sessionId);
|
|
2096
2328
|
});
|
|
@@ -2104,8 +2336,8 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
2104
2336
|
while (true) {
|
|
2105
2337
|
const sessionIds = Array.from(this.sseHonoTransports.keys() || []);
|
|
2106
2338
|
this.logger.debug("Active Hono SSE sessions:", { sessionIds });
|
|
2107
|
-
await
|
|
2108
|
-
await
|
|
2339
|
+
await stream2.write(":keep-alive\n\n");
|
|
2340
|
+
await stream2.sleep(6e4);
|
|
2109
2341
|
}
|
|
2110
2342
|
} catch (e) {
|
|
2111
2343
|
const mastraError = new error.MastraError(
|
|
@@ -2128,13 +2360,6 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
2128
2360
|
* Close the MCP server and all its connections
|
|
2129
2361
|
*/
|
|
2130
2362
|
async close() {
|
|
2131
|
-
this.callToolHandlerIsRegistered = false;
|
|
2132
|
-
this.listToolsHandlerIsRegistered = false;
|
|
2133
|
-
this.listResourcesHandlerIsRegistered = false;
|
|
2134
|
-
this.readResourceHandlerIsRegistered = false;
|
|
2135
|
-
this.listResourceTemplatesHandlerIsRegistered = false;
|
|
2136
|
-
this.subscribeResourceHandlerIsRegistered = false;
|
|
2137
|
-
this.unsubscribeResourceHandlerIsRegistered = false;
|
|
2138
2363
|
try {
|
|
2139
2364
|
if (this.stdioTransport) {
|
|
2140
2365
|
await this.stdioTransport.close?.();
|
|
@@ -2156,6 +2381,12 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
2156
2381
|
}
|
|
2157
2382
|
this.streamableHTTPTransports.clear();
|
|
2158
2383
|
}
|
|
2384
|
+
if (this.httpServerInstances) {
|
|
2385
|
+
for (const serverInstance of this.httpServerInstances.values()) {
|
|
2386
|
+
await serverInstance.close?.();
|
|
2387
|
+
}
|
|
2388
|
+
this.httpServerInstances.clear();
|
|
2389
|
+
}
|
|
2159
2390
|
await this.server.close();
|
|
2160
2391
|
this.logger.info("MCP server closed.");
|
|
2161
2392
|
} catch (error$1) {
|
|
@@ -2214,6 +2445,7 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
2214
2445
|
name: tool.name,
|
|
2215
2446
|
description: tool.description,
|
|
2216
2447
|
inputSchema: tool.parameters?.jsonSchema || tool.parameters,
|
|
2448
|
+
outputSchema: tool.outputSchema?.jsonSchema || tool.outputSchema,
|
|
2217
2449
|
toolType: tool.toolType
|
|
2218
2450
|
}))
|
|
2219
2451
|
};
|
|
@@ -2234,6 +2466,7 @@ var MCPServer = class extends mcp.MCPServerBase {
|
|
|
2234
2466
|
name: tool.name,
|
|
2235
2467
|
description: tool.description,
|
|
2236
2468
|
inputSchema: tool.parameters?.jsonSchema || tool.parameters,
|
|
2469
|
+
outputSchema: tool.outputSchema?.jsonSchema || tool.outputSchema,
|
|
2237
2470
|
toolType: tool.toolType
|
|
2238
2471
|
};
|
|
2239
2472
|
}
|