@elizaos/plugin-mcp 1.7.1 → 1.8.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/actions/dynamic-tool-actions.d.ts +16 -0
- package/dist/actions/dynamic-tool-actions.d.ts.map +1 -0
- package/dist/cache/index.d.ts +5 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/schema-cache.d.ts +34 -0
- package/dist/cache/schema-cache.d.ts.map +1 -0
- package/dist/cjs/index.cjs +1161 -1646
- package/dist/cjs/index.js.map +23 -24
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1166 -1667
- package/dist/index.js.map +23 -24
- package/dist/provider.d.ts.map +1 -1
- package/dist/service.d.ts +23 -21
- package/dist/service.d.ts.map +1 -1
- package/dist/tool-compatibility/base.d.ts +25 -0
- package/dist/tool-compatibility/base.d.ts.map +1 -0
- package/dist/tool-compatibility/index.d.ts +7 -52
- package/dist/tool-compatibility/index.d.ts.map +1 -1
- package/dist/tool-compatibility/providers/anthropic.d.ts +2 -3
- package/dist/tool-compatibility/providers/anthropic.d.ts.map +1 -1
- package/dist/tool-compatibility/providers/google.d.ts +2 -3
- package/dist/tool-compatibility/providers/google.d.ts.map +1 -1
- package/dist/tool-compatibility/providers/openai.d.ts +2 -3
- package/dist/tool-compatibility/providers/openai.d.ts.map +1 -1
- package/dist/types.d.ts +53 -88
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/action-naming.d.ts +9 -0
- package/dist/utils/action-naming.d.ts.map +1 -0
- package/dist/utils/error.d.ts +1 -11
- package/dist/utils/error.d.ts.map +1 -1
- package/dist/utils/json.d.ts.map +1 -1
- package/dist/utils/processing.d.ts +5 -11
- package/dist/utils/processing.d.ts.map +1 -1
- package/dist/utils/schema-converter.d.ts +9 -0
- package/dist/utils/schema-converter.d.ts.map +1 -0
- package/dist/utils/validation.d.ts +1 -24
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/wrapper.d.ts +6 -15
- package/dist/utils/wrapper.d.ts.map +1 -1
- package/package.json +3 -1
- package/dist/actions/callToolAction.d.ts +0 -3
- package/dist/actions/callToolAction.d.ts.map +0 -1
- package/dist/templates/feedbackTemplate.d.ts +0 -2
- package/dist/templates/feedbackTemplate.d.ts.map +0 -1
- package/dist/templates/toolReasoningTemplate.d.ts +0 -2
- package/dist/templates/toolReasoningTemplate.d.ts.map +0 -1
- package/dist/templates/toolSelectionTemplate.d.ts +0 -3
- package/dist/templates/toolSelectionTemplate.d.ts.map +0 -1
- package/dist/utils/handler.d.ts +0 -3
- package/dist/utils/handler.d.ts.map +0 -1
- package/dist/utils/schemas.d.ts +0 -69
- package/dist/utils/schemas.d.ts.map +0 -1
- package/dist/utils/selection.d.ts +0 -38
- package/dist/utils/selection.d.ts.map +0 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -38,521 +38,23 @@ var __export = (target, all) => {
|
|
|
38
38
|
set: (newValue) => all[name] = () => newValue
|
|
39
39
|
});
|
|
40
40
|
};
|
|
41
|
-
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
42
|
-
|
|
43
|
-
// src/tool-compatibility/providers/openai.ts
|
|
44
|
-
var exports_openai = {};
|
|
45
|
-
__export(exports_openai, {
|
|
46
|
-
OpenAIReasoningMcpCompatibility: () => OpenAIReasoningMcpCompatibility,
|
|
47
|
-
OpenAIMcpCompatibility: () => OpenAIMcpCompatibility2
|
|
48
|
-
});
|
|
49
|
-
var OpenAIMcpCompatibility2, OpenAIReasoningMcpCompatibility;
|
|
50
|
-
var init_openai = __esm(() => {
|
|
51
|
-
OpenAIMcpCompatibility2 = class OpenAIMcpCompatibility2 extends McpToolCompatibility {
|
|
52
|
-
constructor(modelInfo2) {
|
|
53
|
-
super(modelInfo2);
|
|
54
|
-
}
|
|
55
|
-
shouldApply() {
|
|
56
|
-
return this.modelInfo.provider === "openai" && (!this.modelInfo.supportsStructuredOutputs || this.modelInfo.isReasoningModel === true);
|
|
57
|
-
}
|
|
58
|
-
getUnsupportedStringProperties() {
|
|
59
|
-
const baseUnsupported = ["format"];
|
|
60
|
-
if (this.modelInfo.isReasoningModel === true) {
|
|
61
|
-
return [...baseUnsupported, "pattern"];
|
|
62
|
-
}
|
|
63
|
-
if (this.modelInfo.modelId.includes("gpt-3.5") || this.modelInfo.modelId.includes("davinci")) {
|
|
64
|
-
return [...baseUnsupported, "pattern"];
|
|
65
|
-
}
|
|
66
|
-
return baseUnsupported;
|
|
67
|
-
}
|
|
68
|
-
getUnsupportedNumberProperties() {
|
|
69
|
-
if (this.modelInfo.isReasoningModel === true) {
|
|
70
|
-
return ["exclusiveMinimum", "exclusiveMaximum", "multipleOf"];
|
|
71
|
-
}
|
|
72
|
-
return [];
|
|
73
|
-
}
|
|
74
|
-
getUnsupportedArrayProperties() {
|
|
75
|
-
if (this.modelInfo.isReasoningModel === true) {
|
|
76
|
-
return ["uniqueItems"];
|
|
77
|
-
}
|
|
78
|
-
return [];
|
|
79
|
-
}
|
|
80
|
-
getUnsupportedObjectProperties() {
|
|
81
|
-
return ["minProperties", "maxProperties"];
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
OpenAIReasoningMcpCompatibility = class OpenAIReasoningMcpCompatibility extends McpToolCompatibility {
|
|
85
|
-
constructor(modelInfo2) {
|
|
86
|
-
super(modelInfo2);
|
|
87
|
-
}
|
|
88
|
-
shouldApply() {
|
|
89
|
-
return this.modelInfo.provider === "openai" && this.modelInfo.isReasoningModel === true;
|
|
90
|
-
}
|
|
91
|
-
getUnsupportedStringProperties() {
|
|
92
|
-
return ["format", "pattern", "minLength", "maxLength"];
|
|
93
|
-
}
|
|
94
|
-
getUnsupportedNumberProperties() {
|
|
95
|
-
return ["exclusiveMinimum", "exclusiveMaximum", "multipleOf"];
|
|
96
|
-
}
|
|
97
|
-
getUnsupportedArrayProperties() {
|
|
98
|
-
return ["uniqueItems", "minItems", "maxItems"];
|
|
99
|
-
}
|
|
100
|
-
getUnsupportedObjectProperties() {
|
|
101
|
-
return ["minProperties", "maxProperties", "additionalProperties"];
|
|
102
|
-
}
|
|
103
|
-
mergeDescription(originalDescription, constraints) {
|
|
104
|
-
const constraintText = this.formatConstraintsForReasoningModel(constraints);
|
|
105
|
-
if (originalDescription) {
|
|
106
|
-
return `${originalDescription}
|
|
107
|
-
|
|
108
|
-
IMPORTANT: ${constraintText}`;
|
|
109
|
-
}
|
|
110
|
-
return `IMPORTANT: ${constraintText}`;
|
|
111
|
-
}
|
|
112
|
-
formatConstraintsForReasoningModel(constraints) {
|
|
113
|
-
const rules = [];
|
|
114
|
-
if (constraints.minLength) {
|
|
115
|
-
rules.push(`minimum ${constraints.minLength} characters`);
|
|
116
|
-
}
|
|
117
|
-
if (constraints.maxLength) {
|
|
118
|
-
rules.push(`maximum ${constraints.maxLength} characters`);
|
|
119
|
-
}
|
|
120
|
-
if (constraints.minimum !== undefined) {
|
|
121
|
-
rules.push(`must be >= ${constraints.minimum}`);
|
|
122
|
-
}
|
|
123
|
-
if (constraints.maximum !== undefined) {
|
|
124
|
-
rules.push(`must be <= ${constraints.maximum}`);
|
|
125
|
-
}
|
|
126
|
-
if (constraints.format === "email") {
|
|
127
|
-
rules.push(`must be a valid email address`);
|
|
128
|
-
}
|
|
129
|
-
if (constraints.format === "uri" || constraints.format === "url") {
|
|
130
|
-
rules.push(`must be a valid URL`);
|
|
131
|
-
}
|
|
132
|
-
if (constraints.format === "uuid") {
|
|
133
|
-
rules.push(`must be a valid UUID`);
|
|
134
|
-
}
|
|
135
|
-
if (constraints.pattern) {
|
|
136
|
-
rules.push(`must match pattern: ${constraints.pattern}`);
|
|
137
|
-
}
|
|
138
|
-
if (constraints.enum) {
|
|
139
|
-
rules.push(`must be one of: ${constraints.enum.join(", ")}`);
|
|
140
|
-
}
|
|
141
|
-
if (constraints.minItems) {
|
|
142
|
-
rules.push(`array must have at least ${constraints.minItems} items`);
|
|
143
|
-
}
|
|
144
|
-
if (constraints.maxItems) {
|
|
145
|
-
rules.push(`array must have at most ${constraints.maxItems} items`);
|
|
146
|
-
}
|
|
147
|
-
return rules.length > 0 ? rules.join(", ") : JSON.stringify(constraints);
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// src/tool-compatibility/providers/anthropic.ts
|
|
153
|
-
var exports_anthropic = {};
|
|
154
|
-
__export(exports_anthropic, {
|
|
155
|
-
AnthropicMcpCompatibility: () => AnthropicMcpCompatibility2
|
|
156
|
-
});
|
|
157
|
-
var AnthropicMcpCompatibility2;
|
|
158
|
-
var init_anthropic = __esm(() => {
|
|
159
|
-
AnthropicMcpCompatibility2 = class AnthropicMcpCompatibility2 extends McpToolCompatibility {
|
|
160
|
-
constructor(modelInfo2) {
|
|
161
|
-
super(modelInfo2);
|
|
162
|
-
}
|
|
163
|
-
shouldApply() {
|
|
164
|
-
return this.modelInfo.provider === "anthropic";
|
|
165
|
-
}
|
|
166
|
-
getUnsupportedStringProperties() {
|
|
167
|
-
return [];
|
|
168
|
-
}
|
|
169
|
-
getUnsupportedNumberProperties() {
|
|
170
|
-
return [];
|
|
171
|
-
}
|
|
172
|
-
getUnsupportedArrayProperties() {
|
|
173
|
-
return [];
|
|
174
|
-
}
|
|
175
|
-
getUnsupportedObjectProperties() {
|
|
176
|
-
return ["additionalProperties"];
|
|
177
|
-
}
|
|
178
|
-
mergeDescription(originalDescription, constraints) {
|
|
179
|
-
const constraintHints = this.formatConstraintsForAnthropic(constraints);
|
|
180
|
-
if (originalDescription && constraintHints) {
|
|
181
|
-
return `${originalDescription}. ${constraintHints}`;
|
|
182
|
-
} else if (constraintHints) {
|
|
183
|
-
return constraintHints;
|
|
184
|
-
}
|
|
185
|
-
return originalDescription || "";
|
|
186
|
-
}
|
|
187
|
-
formatConstraintsForAnthropic(constraints) {
|
|
188
|
-
const hints = [];
|
|
189
|
-
if (constraints.additionalProperties === false) {
|
|
190
|
-
hints.push("Only use the specified properties");
|
|
191
|
-
}
|
|
192
|
-
if (constraints.format === "date-time") {
|
|
193
|
-
hints.push("Use ISO 8601 date-time format");
|
|
194
|
-
}
|
|
195
|
-
if (constraints.pattern) {
|
|
196
|
-
hints.push(`Must match the pattern: ${constraints.pattern}`);
|
|
197
|
-
}
|
|
198
|
-
return hints.join(". ");
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// src/tool-compatibility/providers/google.ts
|
|
204
|
-
var exports_google = {};
|
|
205
|
-
__export(exports_google, {
|
|
206
|
-
GoogleMcpCompatibility: () => GoogleMcpCompatibility2
|
|
207
|
-
});
|
|
208
|
-
var GoogleMcpCompatibility2;
|
|
209
|
-
var init_google = __esm(() => {
|
|
210
|
-
GoogleMcpCompatibility2 = class GoogleMcpCompatibility2 extends McpToolCompatibility {
|
|
211
|
-
constructor(modelInfo2) {
|
|
212
|
-
super(modelInfo2);
|
|
213
|
-
}
|
|
214
|
-
shouldApply() {
|
|
215
|
-
return this.modelInfo.provider === "google";
|
|
216
|
-
}
|
|
217
|
-
getUnsupportedStringProperties() {
|
|
218
|
-
return ["minLength", "maxLength", "pattern", "format"];
|
|
219
|
-
}
|
|
220
|
-
getUnsupportedNumberProperties() {
|
|
221
|
-
return ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"];
|
|
222
|
-
}
|
|
223
|
-
getUnsupportedArrayProperties() {
|
|
224
|
-
return ["minItems", "maxItems", "uniqueItems"];
|
|
225
|
-
}
|
|
226
|
-
getUnsupportedObjectProperties() {
|
|
227
|
-
return ["minProperties", "maxProperties", "additionalProperties"];
|
|
228
|
-
}
|
|
229
|
-
mergeDescription(originalDescription, constraints) {
|
|
230
|
-
const constraintText = this.formatConstraintsForGoogle(constraints);
|
|
231
|
-
if (originalDescription && constraintText) {
|
|
232
|
-
return `${originalDescription}
|
|
233
|
-
|
|
234
|
-
Constraints: ${constraintText}`;
|
|
235
|
-
} else if (constraintText) {
|
|
236
|
-
return `Constraints: ${constraintText}`;
|
|
237
|
-
}
|
|
238
|
-
return originalDescription || "";
|
|
239
|
-
}
|
|
240
|
-
formatConstraintsForGoogle(constraints) {
|
|
241
|
-
const rules = [];
|
|
242
|
-
if (constraints.minLength) {
|
|
243
|
-
rules.push(`text must be at least ${constraints.minLength} characters long`);
|
|
244
|
-
}
|
|
245
|
-
if (constraints.maxLength) {
|
|
246
|
-
rules.push(`text must be no more than ${constraints.maxLength} characters long`);
|
|
247
|
-
}
|
|
248
|
-
if (constraints.minimum !== undefined) {
|
|
249
|
-
rules.push(`number must be at least ${constraints.minimum}`);
|
|
250
|
-
}
|
|
251
|
-
if (constraints.maximum !== undefined) {
|
|
252
|
-
rules.push(`number must be no more than ${constraints.maximum}`);
|
|
253
|
-
}
|
|
254
|
-
if (constraints.exclusiveMinimum !== undefined) {
|
|
255
|
-
rules.push(`number must be greater than ${constraints.exclusiveMinimum}`);
|
|
256
|
-
}
|
|
257
|
-
if (constraints.exclusiveMaximum !== undefined) {
|
|
258
|
-
rules.push(`number must be less than ${constraints.exclusiveMaximum}`);
|
|
259
|
-
}
|
|
260
|
-
if (constraints.multipleOf) {
|
|
261
|
-
rules.push(`number must be a multiple of ${constraints.multipleOf}`);
|
|
262
|
-
}
|
|
263
|
-
if (constraints.format === "email") {
|
|
264
|
-
rules.push(`must be a valid email address`);
|
|
265
|
-
}
|
|
266
|
-
if (constraints.format === "uri" || constraints.format === "url") {
|
|
267
|
-
rules.push(`must be a valid URL starting with http:// or https://`);
|
|
268
|
-
}
|
|
269
|
-
if (constraints.format === "uuid") {
|
|
270
|
-
rules.push(`must be a valid UUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`);
|
|
271
|
-
}
|
|
272
|
-
if (constraints.format === "date-time") {
|
|
273
|
-
rules.push(`must be a valid ISO 8601 date-time (e.g., 2023-12-25T10:30:00Z)`);
|
|
274
|
-
}
|
|
275
|
-
if (constraints.pattern) {
|
|
276
|
-
rules.push(`must match the regular expression pattern: ${constraints.pattern}`);
|
|
277
|
-
}
|
|
278
|
-
if (constraints.enum && Array.isArray(constraints.enum)) {
|
|
279
|
-
rules.push(`must be exactly one of these values: ${constraints.enum.join(", ")}`);
|
|
280
|
-
}
|
|
281
|
-
if (constraints.minItems) {
|
|
282
|
-
rules.push(`array must contain at least ${constraints.minItems} items`);
|
|
283
|
-
}
|
|
284
|
-
if (constraints.maxItems) {
|
|
285
|
-
rules.push(`array must contain no more than ${constraints.maxItems} items`);
|
|
286
|
-
}
|
|
287
|
-
if (constraints.uniqueItems === true) {
|
|
288
|
-
rules.push(`array items must all be unique (no duplicates)`);
|
|
289
|
-
}
|
|
290
|
-
if (constraints.minProperties) {
|
|
291
|
-
rules.push(`object must have at least ${constraints.minProperties} properties`);
|
|
292
|
-
}
|
|
293
|
-
if (constraints.maxProperties) {
|
|
294
|
-
rules.push(`object must have no more than ${constraints.maxProperties} properties`);
|
|
295
|
-
}
|
|
296
|
-
if (constraints.additionalProperties === false) {
|
|
297
|
-
rules.push(`object must only contain the specified properties, no additional properties allowed`);
|
|
298
|
-
}
|
|
299
|
-
return rules.join("; ");
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
// src/tool-compatibility/index.ts
|
|
305
|
-
class McpToolCompatibility {
|
|
306
|
-
modelInfo;
|
|
307
|
-
constructor(modelInfo2) {
|
|
308
|
-
this.modelInfo = modelInfo2;
|
|
309
|
-
}
|
|
310
|
-
transformToolSchema(toolSchema) {
|
|
311
|
-
if (!this.shouldApply()) {
|
|
312
|
-
return toolSchema;
|
|
313
|
-
}
|
|
314
|
-
return this.processSchema(toolSchema);
|
|
315
|
-
}
|
|
316
|
-
processSchema(schema) {
|
|
317
|
-
const processed = { ...schema };
|
|
318
|
-
switch (processed.type) {
|
|
319
|
-
case "string":
|
|
320
|
-
return this.processStringSchema(processed);
|
|
321
|
-
case "number":
|
|
322
|
-
case "integer":
|
|
323
|
-
return this.processNumberSchema(processed);
|
|
324
|
-
case "array":
|
|
325
|
-
return this.processArraySchema(processed);
|
|
326
|
-
case "object":
|
|
327
|
-
return this.processObjectSchema(processed);
|
|
328
|
-
default:
|
|
329
|
-
return this.processGenericSchema(processed);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
processStringSchema(schema) {
|
|
333
|
-
const constraints = {};
|
|
334
|
-
const processed = { ...schema };
|
|
335
|
-
if (typeof schema.minLength === "number") {
|
|
336
|
-
constraints.minLength = schema.minLength;
|
|
337
|
-
}
|
|
338
|
-
if (typeof schema.maxLength === "number") {
|
|
339
|
-
constraints.maxLength = schema.maxLength;
|
|
340
|
-
}
|
|
341
|
-
if (typeof schema.pattern === "string") {
|
|
342
|
-
constraints.pattern = schema.pattern;
|
|
343
|
-
}
|
|
344
|
-
if (typeof schema.format === "string") {
|
|
345
|
-
constraints.format = schema.format;
|
|
346
|
-
}
|
|
347
|
-
if (Array.isArray(schema.enum)) {
|
|
348
|
-
constraints.enum = schema.enum;
|
|
349
|
-
}
|
|
350
|
-
const unsupportedProps = this.getUnsupportedStringProperties();
|
|
351
|
-
for (const prop of unsupportedProps) {
|
|
352
|
-
if (prop in processed) {
|
|
353
|
-
delete processed[prop];
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
if (Object.keys(constraints).length > 0) {
|
|
357
|
-
processed.description = this.mergeDescription(schema.description, constraints);
|
|
358
|
-
}
|
|
359
|
-
return processed;
|
|
360
|
-
}
|
|
361
|
-
processNumberSchema(schema) {
|
|
362
|
-
const constraints = {};
|
|
363
|
-
const processed = { ...schema };
|
|
364
|
-
if (typeof schema.minimum === "number") {
|
|
365
|
-
constraints.minimum = schema.minimum;
|
|
366
|
-
}
|
|
367
|
-
if (typeof schema.maximum === "number") {
|
|
368
|
-
constraints.maximum = schema.maximum;
|
|
369
|
-
}
|
|
370
|
-
if (typeof schema.exclusiveMinimum === "number") {
|
|
371
|
-
constraints.exclusiveMinimum = schema.exclusiveMinimum;
|
|
372
|
-
}
|
|
373
|
-
if (typeof schema.exclusiveMaximum === "number") {
|
|
374
|
-
constraints.exclusiveMaximum = schema.exclusiveMaximum;
|
|
375
|
-
}
|
|
376
|
-
if (typeof schema.multipleOf === "number") {
|
|
377
|
-
constraints.multipleOf = schema.multipleOf;
|
|
378
|
-
}
|
|
379
|
-
const unsupportedProps = this.getUnsupportedNumberProperties();
|
|
380
|
-
for (const prop of unsupportedProps) {
|
|
381
|
-
if (prop in processed) {
|
|
382
|
-
delete processed[prop];
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
if (Object.keys(constraints).length > 0) {
|
|
386
|
-
processed.description = this.mergeDescription(schema.description, constraints);
|
|
387
|
-
}
|
|
388
|
-
return processed;
|
|
389
|
-
}
|
|
390
|
-
processArraySchema(schema) {
|
|
391
|
-
const constraints = {};
|
|
392
|
-
const processed = { ...schema };
|
|
393
|
-
if (typeof schema.minItems === "number") {
|
|
394
|
-
constraints.minItems = schema.minItems;
|
|
395
|
-
}
|
|
396
|
-
if (typeof schema.maxItems === "number") {
|
|
397
|
-
constraints.maxItems = schema.maxItems;
|
|
398
|
-
}
|
|
399
|
-
if (typeof schema.uniqueItems === "boolean") {
|
|
400
|
-
constraints.uniqueItems = schema.uniqueItems;
|
|
401
|
-
}
|
|
402
|
-
if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
|
|
403
|
-
processed.items = this.processSchema(schema.items);
|
|
404
|
-
}
|
|
405
|
-
const unsupportedProps = this.getUnsupportedArrayProperties();
|
|
406
|
-
for (const prop of unsupportedProps) {
|
|
407
|
-
if (prop in processed) {
|
|
408
|
-
delete processed[prop];
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
if (Object.keys(constraints).length > 0) {
|
|
412
|
-
processed.description = this.mergeDescription(schema.description, constraints);
|
|
413
|
-
}
|
|
414
|
-
return processed;
|
|
415
|
-
}
|
|
416
|
-
processObjectSchema(schema) {
|
|
417
|
-
const constraints = {};
|
|
418
|
-
const processed = { ...schema };
|
|
419
|
-
if (typeof schema.minProperties === "number") {
|
|
420
|
-
constraints.minProperties = schema.minProperties;
|
|
421
|
-
}
|
|
422
|
-
if (typeof schema.maxProperties === "number") {
|
|
423
|
-
constraints.maxProperties = schema.maxProperties;
|
|
424
|
-
}
|
|
425
|
-
if (typeof schema.additionalProperties === "boolean") {
|
|
426
|
-
constraints.additionalProperties = schema.additionalProperties;
|
|
427
|
-
}
|
|
428
|
-
if (schema.properties && typeof schema.properties === "object") {
|
|
429
|
-
processed.properties = {};
|
|
430
|
-
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
431
|
-
if (typeof prop === "object" && !Array.isArray(prop)) {
|
|
432
|
-
processed.properties[key] = this.processSchema(prop);
|
|
433
|
-
} else {
|
|
434
|
-
processed.properties[key] = prop;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
const unsupportedProps = this.getUnsupportedObjectProperties();
|
|
439
|
-
for (const prop of unsupportedProps) {
|
|
440
|
-
if (prop in processed) {
|
|
441
|
-
delete processed[prop];
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
if (Object.keys(constraints).length > 0) {
|
|
445
|
-
processed.description = this.mergeDescription(schema.description, constraints);
|
|
446
|
-
}
|
|
447
|
-
return processed;
|
|
448
|
-
}
|
|
449
|
-
processGenericSchema(schema) {
|
|
450
|
-
const processed = { ...schema };
|
|
451
|
-
if (Array.isArray(schema.oneOf)) {
|
|
452
|
-
processed.oneOf = schema.oneOf.map((s) => typeof s === "object" ? this.processSchema(s) : s);
|
|
453
|
-
}
|
|
454
|
-
if (Array.isArray(schema.anyOf)) {
|
|
455
|
-
processed.anyOf = schema.anyOf.map((s) => typeof s === "object" ? this.processSchema(s) : s);
|
|
456
|
-
}
|
|
457
|
-
if (Array.isArray(schema.allOf)) {
|
|
458
|
-
processed.allOf = schema.allOf.map((s) => typeof s === "object" ? this.processSchema(s) : s);
|
|
459
|
-
}
|
|
460
|
-
return processed;
|
|
461
|
-
}
|
|
462
|
-
mergeDescription(originalDescription, constraints) {
|
|
463
|
-
const constraintJson = JSON.stringify(constraints);
|
|
464
|
-
if (originalDescription) {
|
|
465
|
-
return `${originalDescription}
|
|
466
|
-
${constraintJson}`;
|
|
467
|
-
}
|
|
468
|
-
return constraintJson;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
function detectModelProvider(runtime2) {
|
|
472
|
-
const modelString = runtime2?.modelProvider || runtime2?.model || "";
|
|
473
|
-
const modelId = String(modelString).toLowerCase();
|
|
474
|
-
let provider2 = "unknown";
|
|
475
|
-
let supportsStructuredOutputs = false;
|
|
476
|
-
let isReasoningModel = false;
|
|
477
|
-
if (modelId.includes("openai") || modelId.includes("gpt-") || modelId.includes("o1-") || modelId.includes("o3-")) {
|
|
478
|
-
provider2 = "openai";
|
|
479
|
-
supportsStructuredOutputs = modelId.includes("gpt-4") || modelId.includes("o1") || modelId.includes("o3");
|
|
480
|
-
isReasoningModel = modelId.includes("o1") || modelId.includes("o3");
|
|
481
|
-
} else if (modelId.includes("anthropic") || modelId.includes("claude")) {
|
|
482
|
-
provider2 = "anthropic";
|
|
483
|
-
supportsStructuredOutputs = true;
|
|
484
|
-
} else if (modelId.includes("google") || modelId.includes("gemini")) {
|
|
485
|
-
provider2 = "google";
|
|
486
|
-
supportsStructuredOutputs = true;
|
|
487
|
-
} else if (modelId.includes("openrouter")) {
|
|
488
|
-
provider2 = "openrouter";
|
|
489
|
-
supportsStructuredOutputs = false;
|
|
490
|
-
}
|
|
491
|
-
return {
|
|
492
|
-
provider: provider2,
|
|
493
|
-
modelId,
|
|
494
|
-
supportsStructuredOutputs,
|
|
495
|
-
isReasoningModel
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
async function createMcpToolCompatibility(runtime2) {
|
|
499
|
-
const modelInfo2 = detectModelProvider(runtime2);
|
|
500
|
-
try {
|
|
501
|
-
switch (modelInfo2.provider) {
|
|
502
|
-
case "openai":
|
|
503
|
-
const { OpenAIMcpCompatibility: OpenAIMcpCompatibility3 } = await Promise.resolve().then(() => (init_openai(), exports_openai));
|
|
504
|
-
return new OpenAIMcpCompatibility3(modelInfo2);
|
|
505
|
-
case "anthropic":
|
|
506
|
-
const { AnthropicMcpCompatibility: AnthropicMcpCompatibility3 } = await Promise.resolve().then(() => (init_anthropic(), exports_anthropic));
|
|
507
|
-
return new AnthropicMcpCompatibility3(modelInfo2);
|
|
508
|
-
case "google":
|
|
509
|
-
const { GoogleMcpCompatibility: GoogleMcpCompatibility3 } = await Promise.resolve().then(() => (init_google(), exports_google));
|
|
510
|
-
return new GoogleMcpCompatibility3(modelInfo2);
|
|
511
|
-
default:
|
|
512
|
-
return null;
|
|
513
|
-
}
|
|
514
|
-
} catch (error) {
|
|
515
|
-
console.warn("Failed to load compatibility provider:", error);
|
|
516
|
-
return null;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
function createMcpToolCompatibilitySync(runtime) {
|
|
520
|
-
const modelInfo = detectModelProvider(runtime);
|
|
521
|
-
try {
|
|
522
|
-
switch (modelInfo.provider) {
|
|
523
|
-
case "openai":
|
|
524
|
-
const OpenAIModule = eval("require")("./providers/openai");
|
|
525
|
-
const { OpenAIMcpCompatibility } = OpenAIModule;
|
|
526
|
-
return new OpenAIMcpCompatibility(modelInfo);
|
|
527
|
-
case "anthropic":
|
|
528
|
-
const AnthropicModule = eval("require")("./providers/anthropic");
|
|
529
|
-
const { AnthropicMcpCompatibility } = AnthropicModule;
|
|
530
|
-
return new AnthropicMcpCompatibility(modelInfo);
|
|
531
|
-
case "google":
|
|
532
|
-
const GoogleModule = eval("require")("./providers/google");
|
|
533
|
-
const { GoogleMcpCompatibility } = GoogleModule;
|
|
534
|
-
return new GoogleMcpCompatibility(modelInfo);
|
|
535
|
-
default:
|
|
536
|
-
return null;
|
|
537
|
-
}
|
|
538
|
-
} catch (error) {
|
|
539
|
-
console.warn("Failed to load compatibility provider:", error);
|
|
540
|
-
return null;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
41
|
|
|
544
42
|
// src/index.ts
|
|
545
43
|
var exports_src = {};
|
|
546
44
|
__export(exports_src, {
|
|
45
|
+
isMcpToolAction: () => isMcpToolAction,
|
|
46
|
+
getSchemaCache: () => getSchemaCache,
|
|
47
|
+
getMcpToolActionsForServer: () => getMcpToolActionsForServer,
|
|
547
48
|
detectModelProvider: () => detectModelProvider,
|
|
548
49
|
default: () => src_default,
|
|
549
50
|
createMcpToolCompatibilitySync: () => createMcpToolCompatibilitySync,
|
|
550
51
|
createMcpToolCompatibility: () => createMcpToolCompatibility,
|
|
551
|
-
|
|
52
|
+
createMcpToolActions: () => createMcpToolActions,
|
|
53
|
+
createMcpToolAction: () => createMcpToolAction,
|
|
552
54
|
ResourceSelectionSchema: () => ResourceSelectionSchema,
|
|
553
55
|
McpToolCompatibility: () => McpToolCompatibility,
|
|
554
56
|
McpService: () => McpService,
|
|
555
|
-
|
|
57
|
+
McpSchemaCache: () => McpSchemaCache,
|
|
556
58
|
MCP_SERVICE_NAME: () => MCP_SERVICE_NAME,
|
|
557
59
|
MAX_RECONNECT_ATTEMPTS: () => MAX_RECONNECT_ATTEMPTS,
|
|
558
60
|
INITIAL_RETRY_DELAY: () => INITIAL_RETRY_DELAY,
|
|
@@ -564,70 +66,74 @@ __export(exports_src, {
|
|
|
564
66
|
module.exports = __toCommonJS(exports_src);
|
|
565
67
|
var import_core9 = require("@elizaos/core");
|
|
566
68
|
|
|
567
|
-
// src/actions/
|
|
568
|
-
var
|
|
69
|
+
// src/actions/readResourceAction.ts
|
|
70
|
+
var import_core5 = require("@elizaos/core");
|
|
71
|
+
|
|
72
|
+
// src/templates/resourceSelectionTemplate.ts
|
|
73
|
+
var resourceSelectionTemplate = `
|
|
74
|
+
{{{mcpProvider.text}}}
|
|
75
|
+
|
|
76
|
+
{{{recentMessages}}}
|
|
77
|
+
|
|
78
|
+
# Prompt
|
|
79
|
+
|
|
80
|
+
You are an intelligent assistant helping select the right resource to address a user's request.
|
|
81
|
+
|
|
82
|
+
CRITICAL INSTRUCTIONS:
|
|
83
|
+
1. You MUST specify both a valid serverName AND uri from the list above
|
|
84
|
+
2. The serverName value should match EXACTLY the server name shown in parentheses (Server: X)
|
|
85
|
+
CORRECT: "serverName": "github" (if the server is called "github")
|
|
86
|
+
WRONG: "serverName": "GitHub" or "Github" or any other variation
|
|
87
|
+
3. The uri value should match EXACTLY the resource uri listed
|
|
88
|
+
CORRECT: "uri": "weather://San Francisco/current" (if that's the exact uri)
|
|
89
|
+
WRONG: "uri": "weather://sanfrancisco/current" or any variation
|
|
90
|
+
4. Identify the user's information need from the conversation context
|
|
91
|
+
5. Select the most appropriate resource based on its description and the request
|
|
92
|
+
6. If no resource seems appropriate, output {"noResourceAvailable": true}
|
|
93
|
+
|
|
94
|
+
!!! YOUR RESPONSE MUST BE A VALID JSON OBJECT ONLY !!!
|
|
95
|
+
|
|
96
|
+
STRICT FORMAT REQUIREMENTS:
|
|
97
|
+
- NO code block formatting (NO backticks or \`\`\`)
|
|
98
|
+
- NO comments (NO // or /* */)
|
|
99
|
+
- NO placeholders like "replace with...", "example", "your...", "actual", etc.
|
|
100
|
+
- Every parameter value must be a concrete, usable value (not instructions to replace)
|
|
101
|
+
- Use proper JSON syntax with double quotes for strings
|
|
102
|
+
- NO explanatory text before or after the JSON object
|
|
103
|
+
|
|
104
|
+
EXAMPLE RESPONSE:
|
|
105
|
+
{
|
|
106
|
+
"serverName": "weather-server",
|
|
107
|
+
"uri": "weather://San Francisco/current",
|
|
108
|
+
"reasoning": "Based on the conversation, the user is asking about current weather in San Francisco. This resource provides up-to-date weather information for that city."
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
REMEMBER: Your response will be parsed directly as JSON. If it fails to parse, the operation will fail completely!
|
|
112
|
+
`;
|
|
569
113
|
|
|
570
114
|
// src/types.ts
|
|
571
115
|
var MCP_SERVICE_NAME = "mcp";
|
|
572
116
|
var DEFAULT_MCP_TIMEOUT_SECONDS = 60000;
|
|
573
|
-
var MIN_MCP_TIMEOUT_SECONDS = 1;
|
|
574
117
|
var DEFAULT_MAX_RETRIES = 2;
|
|
575
|
-
var
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
},
|
|
584
|
-
toolName: {
|
|
585
|
-
type: "string",
|
|
586
|
-
minLength: 1,
|
|
587
|
-
errorMessage: "toolName must not be empty"
|
|
588
|
-
},
|
|
589
|
-
arguments: {
|
|
590
|
-
type: "object"
|
|
591
|
-
},
|
|
592
|
-
reasoning: {
|
|
593
|
-
type: "string"
|
|
594
|
-
},
|
|
595
|
-
noToolAvailable: {
|
|
596
|
-
type: "boolean"
|
|
597
|
-
}
|
|
598
|
-
}
|
|
118
|
+
var MAX_RECONNECT_ATTEMPTS = 5;
|
|
119
|
+
var BACKOFF_MULTIPLIER = 2;
|
|
120
|
+
var INITIAL_RETRY_DELAY = 2000;
|
|
121
|
+
var DEFAULT_PING_CONFIG = {
|
|
122
|
+
enabled: true,
|
|
123
|
+
intervalMs: 1e4,
|
|
124
|
+
timeoutMs: 5000,
|
|
125
|
+
failuresBeforeDisconnect: 3
|
|
599
126
|
};
|
|
600
127
|
var ResourceSelectionSchema = {
|
|
601
128
|
type: "object",
|
|
602
129
|
required: ["serverName", "uri"],
|
|
603
130
|
properties: {
|
|
604
|
-
serverName: {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
},
|
|
609
|
-
uri: {
|
|
610
|
-
type: "string",
|
|
611
|
-
minLength: 1,
|
|
612
|
-
errorMessage: "uri must not be empty"
|
|
613
|
-
},
|
|
614
|
-
reasoning: {
|
|
615
|
-
type: "string"
|
|
616
|
-
},
|
|
617
|
-
noResourceAvailable: {
|
|
618
|
-
type: "boolean"
|
|
619
|
-
}
|
|
131
|
+
serverName: { type: "string", minLength: 1 },
|
|
132
|
+
uri: { type: "string", minLength: 1 },
|
|
133
|
+
reasoning: { type: "string" },
|
|
134
|
+
noResourceAvailable: { type: "boolean" }
|
|
620
135
|
}
|
|
621
136
|
};
|
|
622
|
-
var DEFAULT_PING_CONFIG = {
|
|
623
|
-
enabled: true,
|
|
624
|
-
intervalMs: 1e4,
|
|
625
|
-
timeoutMs: 5000,
|
|
626
|
-
failuresBeforeDisconnect: 3
|
|
627
|
-
};
|
|
628
|
-
var MAX_RECONNECT_ATTEMPTS = 5;
|
|
629
|
-
var BACKOFF_MULTIPLIER = 2;
|
|
630
|
-
var INITIAL_RETRY_DELAY = 2000;
|
|
631
137
|
|
|
632
138
|
// src/utils/error.ts
|
|
633
139
|
var import_core = require("@elizaos/core");
|
|
@@ -655,89 +161,33 @@ Your response:
|
|
|
655
161
|
`;
|
|
656
162
|
|
|
657
163
|
// src/utils/error.ts
|
|
658
|
-
async function handleMcpError(state, mcpProvider, error,
|
|
164
|
+
async function handleMcpError(state, mcpProvider, error, runtime, message, type, callback) {
|
|
659
165
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
660
|
-
import_core.logger.error({ error, mcpType: type }, `
|
|
661
|
-
|
|
662
|
-
let
|
|
166
|
+
import_core.logger.error({ error, mcpType: type }, `MCP ${type} error: ${errorMessage}`);
|
|
167
|
+
const fallbackText = `I wasn't able to complete that request. There's an issue with the ${type}. Can I help with something else?`;
|
|
168
|
+
let responseText = fallbackText;
|
|
663
169
|
if (callback) {
|
|
664
|
-
const enhancedState = {
|
|
665
|
-
...state,
|
|
666
|
-
values: {
|
|
667
|
-
...state.values,
|
|
668
|
-
mcpProvider,
|
|
669
|
-
userMessage: message.content.text || "",
|
|
670
|
-
error: errorMessage
|
|
671
|
-
}
|
|
672
|
-
};
|
|
673
|
-
const prompt = import_core.composePromptFromState({
|
|
674
|
-
state: enhancedState,
|
|
675
|
-
template: errorAnalysisPrompt
|
|
676
|
-
});
|
|
677
170
|
try {
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
thought: thoughtText,
|
|
685
|
-
text: responseText,
|
|
686
|
-
actions: ["REPLY"]
|
|
687
|
-
});
|
|
688
|
-
} catch (modelError) {
|
|
689
|
-
import_core.logger.error({ error: modelError instanceof Error ? modelError.message : String(modelError) }, "Failed to generate error response");
|
|
690
|
-
await callback({
|
|
691
|
-
thought: thoughtText,
|
|
692
|
-
text: responseText,
|
|
693
|
-
actions: ["REPLY"]
|
|
171
|
+
const prompt = import_core.composePromptFromState({
|
|
172
|
+
state: {
|
|
173
|
+
...state,
|
|
174
|
+
values: { ...state.values, mcpProvider, userMessage: message.content.text || "", error: errorMessage }
|
|
175
|
+
},
|
|
176
|
+
template: errorAnalysisPrompt
|
|
694
177
|
});
|
|
695
|
-
|
|
178
|
+
responseText = await runtime.useModel(import_core.ModelType.TEXT_SMALL, { prompt });
|
|
179
|
+
} catch {}
|
|
180
|
+
await callback({ thought: `MCP ${type} error: ${errorMessage}`, text: responseText, actions: ["REPLY"] });
|
|
696
181
|
}
|
|
697
182
|
return {
|
|
698
183
|
text: `Failed to execute MCP ${type}`,
|
|
699
|
-
values: {
|
|
700
|
-
|
|
701
|
-
error: errorMessage,
|
|
702
|
-
errorType: type
|
|
703
|
-
},
|
|
704
|
-
data: {
|
|
705
|
-
actionName: type === "tool" ? "CALL_MCP_TOOL" : "READ_MCP_RESOURCE",
|
|
706
|
-
error: errorMessage,
|
|
707
|
-
mcpType: type
|
|
708
|
-
},
|
|
184
|
+
values: { success: false, error: errorMessage, errorType: type },
|
|
185
|
+
data: { actionName: type === "tool" ? "CALL_MCP_TOOL" : "READ_MCP_RESOURCE", error: errorMessage },
|
|
709
186
|
success: false,
|
|
710
187
|
error: error instanceof Error ? error : new Error(errorMessage)
|
|
711
188
|
};
|
|
712
189
|
}
|
|
713
190
|
|
|
714
|
-
// src/utils/handler.ts
|
|
715
|
-
async function handleNoToolAvailable(callback, toolSelection) {
|
|
716
|
-
const responseText = "I don't have a specific tool that can help with that request. Let me try to assist you directly instead.";
|
|
717
|
-
const thoughtText = "No appropriate MCP tool available for this request. Falling back to direct assistance.";
|
|
718
|
-
if (callback && toolSelection?.noToolAvailable) {
|
|
719
|
-
await callback({
|
|
720
|
-
text: responseText,
|
|
721
|
-
thought: thoughtText,
|
|
722
|
-
actions: ["REPLY"]
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
|
-
return {
|
|
726
|
-
text: responseText,
|
|
727
|
-
values: {
|
|
728
|
-
success: true,
|
|
729
|
-
noToolAvailable: true,
|
|
730
|
-
fallbackToDirectAssistance: true
|
|
731
|
-
},
|
|
732
|
-
data: {
|
|
733
|
-
actionName: "CALL_MCP_TOOL",
|
|
734
|
-
noToolAvailable: true,
|
|
735
|
-
reason: toolSelection?.reasoning || "No appropriate tool available"
|
|
736
|
-
},
|
|
737
|
-
success: true
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
|
-
|
|
741
191
|
// src/utils/processing.ts
|
|
742
192
|
var import_core2 = require("@elizaos/core");
|
|
743
193
|
var import_core3 = require("@elizaos/core");
|
|
@@ -771,49 +221,19 @@ Instructions:
|
|
|
771
221
|
Your response (written as if directly to the user):
|
|
772
222
|
`;
|
|
773
223
|
|
|
774
|
-
// src/templates/toolReasoningTemplate.ts
|
|
775
|
-
var toolReasoningTemplate = `
|
|
776
|
-
{{{mcpProvider.text}}}
|
|
777
|
-
|
|
778
|
-
{{{recentMessages}}}
|
|
779
|
-
|
|
780
|
-
# Prompt
|
|
781
|
-
|
|
782
|
-
You are a helpful assistant responding to a user's request. You've just used the "{{{toolName}}}" tool from the "{{{serverName}}}" server to help answer this request.
|
|
783
|
-
|
|
784
|
-
Original user request: "{{{userMessage}}}"
|
|
785
|
-
|
|
786
|
-
Tool response:
|
|
787
|
-
{{{toolOutput}}}
|
|
788
|
-
|
|
789
|
-
{{#if hasAttachments}}
|
|
790
|
-
The tool also returned images or other media that will be shared with the user.
|
|
791
|
-
{{/if}}
|
|
792
|
-
|
|
793
|
-
Instructions:
|
|
794
|
-
1. Analyze how well the tool's response addresses the user's specific question or need
|
|
795
|
-
2. Identify the most relevant information from the tool's output
|
|
796
|
-
3. Create a natural, conversational response that incorporates this information
|
|
797
|
-
4. If the tool's response is insufficient, acknowledge its limitations and explain what you can determine
|
|
798
|
-
5. Do not start with phrases like "I used the X tool" or "Here's what I found" - instead, integrate the information naturally
|
|
799
|
-
6. Maintain your helpful, intelligent assistant personality while presenting the information
|
|
800
|
-
|
|
801
|
-
Your response (written as if directly to the user):
|
|
802
|
-
`;
|
|
803
|
-
|
|
804
224
|
// src/utils/mcp.ts
|
|
805
225
|
var NO_DESC = "No description";
|
|
806
|
-
async function createMcpMemory(
|
|
807
|
-
const memory = await
|
|
226
|
+
async function createMcpMemory(runtime, message, type, serverName, content, metadata) {
|
|
227
|
+
const memory = await runtime.addEmbeddingToMemory({
|
|
808
228
|
entityId: message.entityId,
|
|
809
|
-
agentId:
|
|
229
|
+
agentId: runtime.agentId,
|
|
810
230
|
roomId: message.roomId,
|
|
811
231
|
content: {
|
|
812
232
|
text: `Used "${type}" from "${serverName}". Content: ${content}`,
|
|
813
233
|
metadata: { ...metadata, serverName }
|
|
814
234
|
}
|
|
815
235
|
});
|
|
816
|
-
await
|
|
236
|
+
await runtime.createMemory(memory, type === "resource" ? "resources" : "tools", true);
|
|
817
237
|
}
|
|
818
238
|
function buildMcpProviderData(servers) {
|
|
819
239
|
if (servers.length === 0) {
|
|
@@ -870,24 +290,20 @@ function processResourceResult(result, uri) {
|
|
|
870
290
|
let resourceContent = "";
|
|
871
291
|
let resourceMeta = "";
|
|
872
292
|
for (const content of result.contents) {
|
|
873
|
-
|
|
874
|
-
resourceContent += content.text;
|
|
875
|
-
} else if (content.blob) {
|
|
876
|
-
resourceContent += `[Binary data - ${content.mimeType || "unknown type"}]`;
|
|
877
|
-
}
|
|
293
|
+
resourceContent += content.text || (content.blob ? `[Binary: ${content.mimeType || "unknown"}]` : "");
|
|
878
294
|
resourceMeta += `Resource: ${content.uri || uri}
|
|
879
295
|
`;
|
|
880
|
-
if (content.mimeType)
|
|
296
|
+
if (content.mimeType)
|
|
881
297
|
resourceMeta += `Type: ${content.mimeType}
|
|
882
298
|
`;
|
|
883
|
-
}
|
|
884
299
|
}
|
|
885
300
|
return { resourceContent, resourceMeta };
|
|
886
301
|
}
|
|
887
|
-
function processToolResult(result, serverName, toolName,
|
|
302
|
+
function processToolResult(result, serverName, toolName, runtime, messageEntityId) {
|
|
888
303
|
let toolOutput = "";
|
|
889
304
|
let hasAttachments = false;
|
|
890
305
|
const attachments = [];
|
|
306
|
+
let attachmentIndex = 0;
|
|
891
307
|
for (const content of result.content) {
|
|
892
308
|
if (content.type === "text") {
|
|
893
309
|
toolOutput += content.text;
|
|
@@ -896,136 +312,115 @@ function processToolResult(result, serverName, toolName, runtime2, messageEntity
|
|
|
896
312
|
attachments.push({
|
|
897
313
|
contentType: getMimeTypeToContentType(content.mimeType),
|
|
898
314
|
url: `data:${content.mimeType};base64,${content.data}`,
|
|
899
|
-
id: import_core2.createUniqueUuid(
|
|
315
|
+
id: import_core2.createUniqueUuid(runtime, `${messageEntityId}-attachment-${attachmentIndex++}`),
|
|
900
316
|
title: "Generated image",
|
|
901
317
|
source: `${serverName}/${toolName}`,
|
|
902
318
|
description: "Tool-generated image",
|
|
903
319
|
text: "Generated image"
|
|
904
320
|
});
|
|
905
|
-
} else if (content.type === "resource") {
|
|
906
|
-
const
|
|
907
|
-
|
|
908
|
-
toolOutput += `
|
|
321
|
+
} else if (content.type === "resource" && content.resource) {
|
|
322
|
+
const r = content.resource;
|
|
323
|
+
toolOutput += r.text ? `
|
|
909
324
|
|
|
910
|
-
Resource (${
|
|
911
|
-
${
|
|
912
|
-
} else if (resource && "blob" in resource) {
|
|
913
|
-
toolOutput += `
|
|
325
|
+
Resource (${r.uri}):
|
|
326
|
+
${r.text}` : `
|
|
914
327
|
|
|
915
|
-
Resource (${
|
|
916
|
-
}
|
|
328
|
+
Resource (${r.uri}): [Binary]`;
|
|
917
329
|
}
|
|
918
330
|
}
|
|
919
331
|
return { toolOutput, hasAttachments, attachments };
|
|
920
332
|
}
|
|
921
|
-
async function handleResourceAnalysis(
|
|
922
|
-
await createMcpMemory(
|
|
333
|
+
async function handleResourceAnalysis(runtime, message, uri, serverName, resourceContent, resourceMeta, callback) {
|
|
334
|
+
await createMcpMemory(runtime, message, "resource", serverName, resourceContent, {
|
|
923
335
|
uri,
|
|
924
336
|
isResourceAccess: true
|
|
925
337
|
});
|
|
926
|
-
const
|
|
927
|
-
|
|
928
|
-
|
|
338
|
+
const prompt = import_core3.composePromptFromState({
|
|
339
|
+
state: {
|
|
340
|
+
data: {},
|
|
341
|
+
text: "",
|
|
342
|
+
values: { uri, userMessage: message.content.text || "", resourceContent, resourceMeta }
|
|
343
|
+
},
|
|
344
|
+
template: resourceAnalysisTemplate
|
|
929
345
|
});
|
|
346
|
+
const response = await runtime.useModel(import_core2.ModelType.TEXT_SMALL, { prompt });
|
|
930
347
|
if (callback) {
|
|
931
348
|
await callback({
|
|
932
|
-
text:
|
|
933
|
-
thought: `
|
|
349
|
+
text: response,
|
|
350
|
+
thought: `Analyzed resource ${uri} from ${serverName}`,
|
|
934
351
|
actions: ["READ_MCP_RESOURCE"]
|
|
935
352
|
});
|
|
936
353
|
}
|
|
937
354
|
}
|
|
938
|
-
async function handleToolResponse(runtime2, message, serverName, toolName, toolArgs, toolOutput, hasAttachments, attachments, state, mcpProvider, callback) {
|
|
939
|
-
await createMcpMemory(runtime2, message, "tool", serverName, toolOutput, {
|
|
940
|
-
toolName,
|
|
941
|
-
arguments: toolArgs,
|
|
942
|
-
isToolCall: true
|
|
943
|
-
});
|
|
944
|
-
const reasoningPrompt = createReasoningPrompt(state, mcpProvider, toolName, serverName, message.content.text || "", toolOutput, hasAttachments);
|
|
945
|
-
import_core2.logger.info({ reasoningPrompt }, "reasoning prompt");
|
|
946
|
-
const reasonedResponse = await runtime2.useModel(import_core2.ModelType.TEXT_SMALL, {
|
|
947
|
-
prompt: reasoningPrompt
|
|
948
|
-
});
|
|
949
|
-
const agentId = message.agentId || runtime2.agentId;
|
|
950
|
-
const replyMemory = {
|
|
951
|
-
entityId: agentId,
|
|
952
|
-
roomId: message.roomId,
|
|
953
|
-
worldId: message.worldId,
|
|
954
|
-
content: {
|
|
955
|
-
text: reasonedResponse,
|
|
956
|
-
thought: `I analyzed the output from the ${toolName} tool on ${serverName} and crafted a thoughtful response that addresses the user's request while maintaining my conversational style.`,
|
|
957
|
-
actions: ["CALL_MCP_TOOL"],
|
|
958
|
-
attachments: hasAttachments && attachments.length > 0 ? attachments : undefined
|
|
959
|
-
}
|
|
960
|
-
};
|
|
961
|
-
await runtime2.createMemory(replyMemory, "messages");
|
|
962
|
-
if (callback) {
|
|
963
|
-
await callback({
|
|
964
|
-
text: reasonedResponse,
|
|
965
|
-
thought: `I analyzed the output from the ${toolName} tool on ${serverName} and crafted a thoughtful response that addresses the user's request while maintaining my conversational style.`,
|
|
966
|
-
actions: ["CALL_MCP_TOOL"],
|
|
967
|
-
attachments: hasAttachments && attachments.length > 0 ? attachments : undefined
|
|
968
|
-
});
|
|
969
|
-
}
|
|
970
|
-
return replyMemory;
|
|
971
|
-
}
|
|
972
355
|
async function sendInitialResponse(callback) {
|
|
973
356
|
if (callback) {
|
|
974
|
-
|
|
975
|
-
thought: "
|
|
357
|
+
await callback({
|
|
358
|
+
thought: "Retrieving MCP resource...",
|
|
976
359
|
text: "I'll retrieve that information for you. Let me access the resource...",
|
|
977
360
|
actions: ["READ_MCP_RESOURCE"]
|
|
978
|
-
};
|
|
979
|
-
await callback(responseContent);
|
|
361
|
+
});
|
|
980
362
|
}
|
|
981
363
|
}
|
|
982
|
-
function createAnalysisPrompt(uri, userMessage, resourceContent, resourceMeta) {
|
|
983
|
-
const enhancedState = {
|
|
984
|
-
data: {},
|
|
985
|
-
text: "",
|
|
986
|
-
values: {
|
|
987
|
-
uri,
|
|
988
|
-
userMessage,
|
|
989
|
-
resourceContent,
|
|
990
|
-
resourceMeta
|
|
991
|
-
}
|
|
992
|
-
};
|
|
993
|
-
return import_core3.composePromptFromState({
|
|
994
|
-
state: enhancedState,
|
|
995
|
-
template: resourceAnalysisTemplate
|
|
996
|
-
});
|
|
997
|
-
}
|
|
998
|
-
function createReasoningPrompt(state, mcpProvider, toolName, serverName, userMessage, toolOutput, hasAttachments) {
|
|
999
|
-
const enhancedState = {
|
|
1000
|
-
...state,
|
|
1001
|
-
values: {
|
|
1002
|
-
...state.values,
|
|
1003
|
-
mcpProvider,
|
|
1004
|
-
toolName,
|
|
1005
|
-
serverName,
|
|
1006
|
-
userMessage,
|
|
1007
|
-
toolOutput,
|
|
1008
|
-
hasAttachments
|
|
1009
|
-
}
|
|
1010
|
-
};
|
|
1011
|
-
return import_core3.composePromptFromState({
|
|
1012
|
-
state: enhancedState,
|
|
1013
|
-
template: toolReasoningTemplate
|
|
1014
|
-
});
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
// src/utils/selection.ts
|
|
1018
|
-
var import_core5 = require("@elizaos/core");
|
|
1019
364
|
|
|
1020
365
|
// src/utils/json.ts
|
|
1021
366
|
var import_ajv = __toESM(require("ajv"));
|
|
1022
367
|
var import_json5 = __toESM(require("json5"));
|
|
368
|
+
function findMatchingClose(str, start, open, close) {
|
|
369
|
+
let depth = 0;
|
|
370
|
+
let inString = false;
|
|
371
|
+
let escape = false;
|
|
372
|
+
for (let i = start;i < str.length; i++) {
|
|
373
|
+
const ch = str[i];
|
|
374
|
+
if (escape) {
|
|
375
|
+
escape = false;
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (ch === "\\" && inString) {
|
|
379
|
+
escape = true;
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
if (ch === '"') {
|
|
383
|
+
inString = !inString;
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (inString)
|
|
387
|
+
continue;
|
|
388
|
+
if (ch === open)
|
|
389
|
+
depth++;
|
|
390
|
+
else if (ch === close) {
|
|
391
|
+
depth--;
|
|
392
|
+
if (depth === 0)
|
|
393
|
+
return i;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return -1;
|
|
397
|
+
}
|
|
1023
398
|
function parseJSON(input) {
|
|
1024
399
|
let cleanedInput = input.replace(/^```(?:json)?\s*|\s*```$/g, "").trim();
|
|
1025
400
|
const firstBrace = cleanedInput.indexOf("{");
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
|
|
401
|
+
const firstBracket = cleanedInput.indexOf("[");
|
|
402
|
+
let start = -1;
|
|
403
|
+
let isArray = false;
|
|
404
|
+
if (firstBrace === -1 && firstBracket === -1) {
|
|
405
|
+
return import_json5.default.parse(cleanedInput);
|
|
406
|
+
} else if (firstBrace === -1) {
|
|
407
|
+
start = firstBracket;
|
|
408
|
+
isArray = true;
|
|
409
|
+
} else if (firstBracket === -1) {
|
|
410
|
+
start = firstBrace;
|
|
411
|
+
isArray = false;
|
|
412
|
+
} else {
|
|
413
|
+
if (firstBracket < firstBrace) {
|
|
414
|
+
start = firstBracket;
|
|
415
|
+
isArray = true;
|
|
416
|
+
} else {
|
|
417
|
+
start = firstBrace;
|
|
418
|
+
isArray = false;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const end = isArray ? findMatchingClose(cleanedInput, start, "[", "]") : findMatchingClose(cleanedInput, start, "{", "}");
|
|
422
|
+
if (end !== -1) {
|
|
423
|
+
cleanedInput = cleanedInput.substring(start, end + 1);
|
|
1029
424
|
}
|
|
1030
425
|
return import_json5.default.parse(cleanedInput);
|
|
1031
426
|
}
|
|
@@ -1047,570 +442,100 @@ function validateJsonSchema(data, schema) {
|
|
|
1047
442
|
return { success: true, data };
|
|
1048
443
|
} catch (error) {
|
|
1049
444
|
return {
|
|
1050
|
-
success: false,
|
|
1051
|
-
error: `Schema validation error: ${error instanceof Error ? error.message : String(error)}`
|
|
1052
|
-
};
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
// src/utils/
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
runtime: runtime2,
|
|
1060
|
-
message,
|
|
1061
|
-
state,
|
|
1062
|
-
callback,
|
|
1063
|
-
input,
|
|
1064
|
-
validationFn,
|
|
1065
|
-
createFeedbackPromptFn,
|
|
1066
|
-
failureMsg,
|
|
1067
|
-
retryCount = 0
|
|
1068
|
-
}) {
|
|
1069
|
-
const maxRetries = getMaxRetries(runtime2);
|
|
1070
|
-
try {
|
|
1071
|
-
import_core4.logger.info(`[WITH-MODEL-RETRY] Raw selection input:
|
|
1072
|
-
${input}`);
|
|
1073
|
-
const parsedJson = typeof input === "string" ? parseJSON(input) : input;
|
|
1074
|
-
import_core4.logger.debug(`[WITH-MODEL-RETRY] Parsed selection input:
|
|
1075
|
-
${JSON.stringify(parsedJson, null, 2)}`);
|
|
1076
|
-
const validationResult = validationFn(parsedJson);
|
|
1077
|
-
if (validationResult.success === false) {
|
|
1078
|
-
throw new Error(validationResult.error);
|
|
1079
|
-
}
|
|
1080
|
-
return validationResult.data;
|
|
1081
|
-
} catch (parseError) {
|
|
1082
|
-
const errorMessage = parseError instanceof Error ? parseError.message : "Unknown parsing error";
|
|
1083
|
-
import_core4.logger.error({ errorMessage }, `[WITH-MODEL-RETRY] Failed to parse response: ${errorMessage}`);
|
|
1084
|
-
if (retryCount < maxRetries) {
|
|
1085
|
-
import_core4.logger.debug(`[WITH-MODEL-RETRY] Retrying (attempt ${retryCount + 1}/${maxRetries})`);
|
|
1086
|
-
const feedbackPrompt = createFeedbackPromptFn(input, errorMessage, state, message.content.text || "");
|
|
1087
|
-
const retrySelection = await runtime2.useModel(import_core4.ModelType.OBJECT_LARGE, {
|
|
1088
|
-
prompt: feedbackPrompt
|
|
1089
|
-
});
|
|
1090
|
-
return withModelRetry({
|
|
1091
|
-
runtime: runtime2,
|
|
1092
|
-
input: retrySelection,
|
|
1093
|
-
validationFn,
|
|
1094
|
-
message,
|
|
1095
|
-
state,
|
|
1096
|
-
createFeedbackPromptFn,
|
|
1097
|
-
callback,
|
|
1098
|
-
failureMsg,
|
|
1099
|
-
retryCount: retryCount + 1
|
|
1100
|
-
});
|
|
1101
|
-
}
|
|
1102
|
-
if (callback && failureMsg) {
|
|
1103
|
-
await callback({
|
|
1104
|
-
text: failureMsg,
|
|
1105
|
-
thought: "Failed to parse response after multiple retries. Requesting clarification from user.",
|
|
1106
|
-
actions: ["REPLY"]
|
|
1107
|
-
});
|
|
1108
|
-
}
|
|
1109
|
-
return null;
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
function getMaxRetries(runtime2) {
|
|
1113
|
-
try {
|
|
1114
|
-
const rawSettings = runtime2.getSetting("mcp");
|
|
1115
|
-
const settings = rawSettings;
|
|
1116
|
-
if (settings && typeof settings.maxRetries === "number") {
|
|
1117
|
-
const configValue = settings.maxRetries;
|
|
1118
|
-
if (!Number.isNaN(configValue) && configValue >= 0) {
|
|
1119
|
-
import_core4.logger.debug(`[WITH-MODEL-RETRY] Using configured selection retries: ${configValue}`);
|
|
1120
|
-
return configValue;
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
} catch (error) {
|
|
1124
|
-
import_core4.logger.debug({ error: error instanceof Error ? error.message : String(error) }, "[WITH-MODEL-RETRY] Error reading selection retries config");
|
|
1125
|
-
}
|
|
1126
|
-
return DEFAULT_MAX_RETRIES;
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
// src/templates/toolSelectionTemplate.ts
|
|
1130
|
-
var toolSelectionNameTemplate = `
|
|
1131
|
-
{{mcpProvider.text}}
|
|
1132
|
-
|
|
1133
|
-
{{recentMessages}}
|
|
1134
|
-
|
|
1135
|
-
# TASK: Select the Most Appropriate Tool and Server
|
|
1136
|
-
|
|
1137
|
-
You must select the most appropriate tool from the list above to fulfill the user's request. Your response must be a valid JSON object with the required properties.
|
|
1138
|
-
|
|
1139
|
-
## CRITICAL INSTRUCTIONS
|
|
1140
|
-
1. Provide both "serverName" and "toolName" from the options listed above.
|
|
1141
|
-
2. Each name must match EXACTLY as shown in the list:
|
|
1142
|
-
- Example (correct): "serverName": "github"
|
|
1143
|
-
- Example (incorrect): "serverName": "GitHub", "Github", or variations
|
|
1144
|
-
3. Extract ACTUAL parameter values from the conversation context.
|
|
1145
|
-
- Do not invent or use placeholders like "octocat" or "Hello-World" unless the user said so.
|
|
1146
|
-
4. Include a "reasoning" field explaining why the selected tool fits the request.
|
|
1147
|
-
5. If no tool is appropriate, respond with:
|
|
1148
|
-
{
|
|
1149
|
-
"noToolAvailable": true
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
!!! YOUR RESPONSE MUST BE A VALID JSON OBJECT ONLY !!!
|
|
1153
|
-
|
|
1154
|
-
CRITICAL: Your response must START with { and END with }. DO NOT include ANY text before or after the JSON.
|
|
1155
|
-
|
|
1156
|
-
## STRICT FORMAT REQUIREMENTS
|
|
1157
|
-
- The response MUST be a single valid JSON object.
|
|
1158
|
-
- DO NOT wrap the JSON in triple backticks (\`\`\`), code blocks, or include any explanatory text.
|
|
1159
|
-
- DO NOT include comments (// or /* */) anywhere.
|
|
1160
|
-
- DO NOT use placeholders (e.g., "replace with...", "example", "your...", etc.)
|
|
1161
|
-
- ALL strings must use double quotes.
|
|
1162
|
-
|
|
1163
|
-
## CRITICAL NOTES
|
|
1164
|
-
- All values must be fully grounded in user input or inferred contextually.
|
|
1165
|
-
- No missing fields unless they are explicitly optional in the schema.
|
|
1166
|
-
- All types must match the schema (strings, numbers, booleans).
|
|
1167
|
-
|
|
1168
|
-
## JSON OBJECT STRUCTURE
|
|
1169
|
-
Your response MUST contain ONLY these top-level keys:
|
|
1170
|
-
1. "serverName" — The name of the server (e.g., "github", "notion")
|
|
1171
|
-
2. "toolName" — The name of the tool (e.g., "get_file_contents", "search")
|
|
1172
|
-
3. "reasoning" — A string explaining how the values were inferred from the conversation.
|
|
1173
|
-
4. "noToolAvailable" — A boolean indicating if no tool is available (true/false)
|
|
1174
|
-
|
|
1175
|
-
## EXAMPLE RESPONSE
|
|
1176
|
-
{
|
|
1177
|
-
"serverName": "github",
|
|
1178
|
-
"toolName": "get_file_contents",
|
|
1179
|
-
"reasoning": "The user wants to retrieve the README from the facebook/react repository.",
|
|
1180
|
-
"noToolAvailable": false
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
## REMINDERS
|
|
1184
|
-
- Use "github" as serverName for GitHub tools.
|
|
1185
|
-
- Use "notion" as serverName for Notion tools.
|
|
1186
|
-
- For search and knowledge-based tasks, MCP tools are often appropriate.
|
|
1187
|
-
|
|
1188
|
-
REMEMBER: This output will be parsed directly as JSON. If the format is incorrect, the operation will fail.
|
|
1189
|
-
`;
|
|
1190
|
-
var toolSelectionArgumentTemplate = `
|
|
1191
|
-
{{recentMessages}}
|
|
1192
|
-
|
|
1193
|
-
# TASK: Generate a Strictly Valid JSON Object for Tool Execution
|
|
1194
|
-
|
|
1195
|
-
You have chosen the "{{toolSelectionName.toolName}}" tool from the "{{toolSelectionName.serverName}}" server to address the user's request.
|
|
1196
|
-
The reasoning behind this selection is: "{{toolSelectionName.reasoning}}"
|
|
1197
|
-
|
|
1198
|
-
## CRITICAL INSTRUCTIONS
|
|
1199
|
-
1. Ensure the "toolArguments" object strictly adheres to the structure and requirements defined in the schema.
|
|
1200
|
-
2. All parameter values must be extracted from the conversation context and must be concrete, usable values.
|
|
1201
|
-
3. Avoid placeholders or generic terms unless explicitly provided by the user.
|
|
1202
|
-
|
|
1203
|
-
!!! YOUR RESPONSE MUST BE A VALID JSON OBJECT ONLY !!!
|
|
1204
|
-
|
|
1205
|
-
## STRICT FORMAT REQUIREMENTS
|
|
1206
|
-
- The response MUST be a single valid JSON object.
|
|
1207
|
-
- DO NOT wrap the JSON in triple backticks (\`\`\`), code blocks, or include any explanatory text.
|
|
1208
|
-
- DO NOT include comments (// or /* */) anywhere.
|
|
1209
|
-
- DO NOT use placeholders (e.g., "replace with...", "example", "your...", etc.)
|
|
1210
|
-
- ALL strings must use double quotes
|
|
1211
|
-
|
|
1212
|
-
## CRITICAL NOTES
|
|
1213
|
-
- All values must be fully grounded in user input or inferred contextually.
|
|
1214
|
-
- No missing fields unless they are explicitly optional in the schema.
|
|
1215
|
-
- All types must match the schema (strings, numbers, booleans).
|
|
1216
|
-
|
|
1217
|
-
## JSON OBJECT STRUCTURE
|
|
1218
|
-
Your response MUST contain ONLY these two top-level keys:
|
|
1219
|
-
1. "toolArguments" — An object matching the input schema: {{toolInputSchema}}
|
|
1220
|
-
2. "reasoning" — A string explaining how the values were inferred from the conversation.
|
|
1221
|
-
|
|
1222
|
-
## EXAMPLE RESPONSE
|
|
1223
|
-
{
|
|
1224
|
-
"toolArguments": {
|
|
1225
|
-
"owner": "facebook",
|
|
1226
|
-
"repo": "react",
|
|
1227
|
-
"path": "README.md",
|
|
1228
|
-
"branch": "main"
|
|
1229
|
-
},
|
|
1230
|
-
"reasoning": "The user wants to see the README from the facebook/react repository based on our conversation."
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
REMEMBER: Your response will be parsed directly as JSON. If it fails to parse, the operation will fail completely.
|
|
1234
|
-
`;
|
|
1235
|
-
|
|
1236
|
-
// src/utils/schemas.ts
|
|
1237
|
-
var toolSelectionNameSchema = {
|
|
1238
|
-
type: "object",
|
|
1239
|
-
required: ["serverName", "toolName"],
|
|
1240
|
-
properties: {
|
|
1241
|
-
serverName: {
|
|
1242
|
-
type: "string",
|
|
1243
|
-
minLength: 1,
|
|
1244
|
-
errorMessage: "serverName must not be empty"
|
|
1245
|
-
},
|
|
1246
|
-
toolName: {
|
|
1247
|
-
type: "string",
|
|
1248
|
-
minLength: 1,
|
|
1249
|
-
errorMessage: "toolName must not be empty"
|
|
1250
|
-
},
|
|
1251
|
-
reasoning: {
|
|
1252
|
-
type: "string"
|
|
1253
|
-
},
|
|
1254
|
-
noToolAvailable: {
|
|
1255
|
-
type: "boolean"
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
};
|
|
1259
|
-
var toolSelectionArgumentSchema = {
|
|
1260
|
-
type: "object",
|
|
1261
|
-
required: ["toolArguments"],
|
|
1262
|
-
properties: {
|
|
1263
|
-
toolArguments: {
|
|
1264
|
-
type: "object"
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
};
|
|
1268
|
-
|
|
1269
|
-
// src/utils/validation.ts
|
|
1270
|
-
function validateToolSelectionName(parsed, state) {
|
|
1271
|
-
const basicResult = validateJsonSchema(parsed, toolSelectionNameSchema);
|
|
1272
|
-
if (basicResult.success === false) {
|
|
1273
|
-
return { success: false, error: basicResult.error };
|
|
1274
|
-
}
|
|
1275
|
-
const data = basicResult.data;
|
|
1276
|
-
const mcpData = state.values.mcp || {};
|
|
1277
|
-
const server = mcpData[data.serverName];
|
|
1278
|
-
if (!server || server.status !== "connected") {
|
|
1279
|
-
return {
|
|
1280
|
-
success: false,
|
|
1281
|
-
error: `Server "${data.serverName}" not found or not connected`
|
|
1282
|
-
};
|
|
1283
|
-
}
|
|
1284
|
-
const toolInfo = server.tools?.[data.toolName];
|
|
1285
|
-
if (!toolInfo) {
|
|
1286
|
-
return {
|
|
1287
|
-
success: false,
|
|
1288
|
-
error: `Tool "${data.toolName}" not found on server "${data.serverName}"`
|
|
1289
|
-
};
|
|
1290
|
-
}
|
|
1291
|
-
return { success: true, data };
|
|
1292
|
-
}
|
|
1293
|
-
function validateToolSelectionArgument(parsed, toolInputSchema) {
|
|
1294
|
-
const basicResult = validateJsonSchema(parsed, toolSelectionArgumentSchema);
|
|
1295
|
-
if (basicResult.success === false) {
|
|
1296
|
-
return { success: false, error: basicResult.error };
|
|
1297
|
-
}
|
|
1298
|
-
const data = basicResult.data;
|
|
1299
|
-
const validationResult = validateJsonSchema(data.toolArguments, toolInputSchema);
|
|
1300
|
-
if (validationResult.success === false) {
|
|
1301
|
-
return {
|
|
1302
|
-
success: false,
|
|
1303
|
-
error: `Invalid arguments: ${validationResult.error}`
|
|
1304
|
-
};
|
|
1305
|
-
}
|
|
1306
|
-
return { success: true, data };
|
|
1307
|
-
}
|
|
1308
|
-
function validateResourceSelection(selection) {
|
|
1309
|
-
return validateJsonSchema(selection, ResourceSelectionSchema);
|
|
1310
|
-
}
|
|
1311
|
-
function createResourceSelectionFeedbackPrompt(originalResponse, errorMessage, composedState, userMessage) {
|
|
1312
|
-
let resourcesDescription = "";
|
|
1313
|
-
for (const [serverName, server] of Object.entries(composedState.values.mcp || {})) {
|
|
1314
|
-
if (server.status !== "connected")
|
|
1315
|
-
continue;
|
|
1316
|
-
for (const [uri, resource] of Object.entries(server.resources || {})) {
|
|
1317
|
-
resourcesDescription += `Resource: ${uri} (Server: ${serverName})
|
|
1318
|
-
`;
|
|
1319
|
-
resourcesDescription += `Name: ${resource.name || "No name available"}
|
|
1320
|
-
`;
|
|
1321
|
-
resourcesDescription += `Description: ${resource.description || "No description available"}
|
|
1322
|
-
|
|
1323
|
-
`;
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
return createFeedbackPrompt(originalResponse, errorMessage, "resource", resourcesDescription, userMessage);
|
|
1327
|
-
}
|
|
1328
|
-
function createFeedbackPrompt(originalResponse, errorMessage, itemType, itemsDescription, userMessage) {
|
|
1329
|
-
return `Error parsing JSON: ${errorMessage}
|
|
1330
|
-
|
|
1331
|
-
Your original response:
|
|
1332
|
-
${originalResponse}
|
|
1333
|
-
|
|
1334
|
-
Please try again with valid JSON for ${itemType} selection.
|
|
1335
|
-
Available ${itemType}s:
|
|
1336
|
-
${itemsDescription}
|
|
1337
|
-
|
|
1338
|
-
User request: ${userMessage}`;
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
// src/utils/selection.ts
|
|
1342
|
-
async function createToolSelectionName({
|
|
1343
|
-
runtime: runtime2,
|
|
1344
|
-
state,
|
|
1345
|
-
message,
|
|
1346
|
-
callback,
|
|
1347
|
-
mcpProvider
|
|
1348
|
-
}) {
|
|
1349
|
-
const toolSelectionPrompt = import_core5.composePromptFromState({
|
|
1350
|
-
state: { ...state, values: { ...state.values, mcpProvider } },
|
|
1351
|
-
template: toolSelectionNameTemplate
|
|
1352
|
-
});
|
|
1353
|
-
import_core5.logger.debug(`[SELECTION] Tool Selection Name Prompt:
|
|
1354
|
-
${toolSelectionPrompt}`);
|
|
1355
|
-
const toolSelectionName = await runtime2.useModel(import_core5.ModelType.TEXT_LARGE, {
|
|
1356
|
-
prompt: toolSelectionPrompt
|
|
1357
|
-
});
|
|
1358
|
-
import_core5.logger.debug(`[SELECTION] Tool Selection Name Response:
|
|
1359
|
-
${toolSelectionName}`);
|
|
1360
|
-
return await withModelRetry({
|
|
1361
|
-
runtime: runtime2,
|
|
1362
|
-
message,
|
|
1363
|
-
state,
|
|
1364
|
-
callback,
|
|
1365
|
-
input: toolSelectionName,
|
|
1366
|
-
validationFn: (parsed) => validateToolSelectionName(parsed, state),
|
|
1367
|
-
createFeedbackPromptFn: (originalResponse, errorMessage, state2, userMessage) => createToolSelectionFeedbackPrompt(originalResponse, errorMessage, state2, userMessage),
|
|
1368
|
-
failureMsg: "I'm having trouble figuring out the best way to help with your request."
|
|
1369
|
-
});
|
|
1370
|
-
}
|
|
1371
|
-
async function createToolSelectionArgument({
|
|
1372
|
-
runtime: runtime2,
|
|
1373
|
-
state,
|
|
1374
|
-
message,
|
|
1375
|
-
callback,
|
|
1376
|
-
mcpProvider,
|
|
1377
|
-
toolSelectionName
|
|
1378
|
-
}) {
|
|
1379
|
-
if (!toolSelectionName) {
|
|
1380
|
-
import_core5.logger.warn("[SELECTION] Tool selection name is not provided. Cannot create tool selection argument.");
|
|
1381
|
-
return null;
|
|
1382
|
-
}
|
|
1383
|
-
const { serverName, toolName } = toolSelectionName;
|
|
1384
|
-
const toolInputSchema = mcpProvider.data.mcp[serverName].tools[toolName].inputSchema;
|
|
1385
|
-
import_core5.logger.trace(`[SELECTION] Tool Input Schema:
|
|
1386
|
-
${JSON.stringify({ toolInputSchema }, null, 2)}`);
|
|
1387
|
-
const toolSelectionArgumentPrompt = import_core5.composePromptFromState({
|
|
1388
|
-
state: {
|
|
1389
|
-
...state,
|
|
1390
|
-
values: {
|
|
1391
|
-
...state.values,
|
|
1392
|
-
toolSelectionName,
|
|
1393
|
-
toolInputSchema: JSON.stringify(toolInputSchema)
|
|
1394
|
-
}
|
|
1395
|
-
},
|
|
1396
|
-
template: toolSelectionArgumentTemplate
|
|
1397
|
-
});
|
|
1398
|
-
import_core5.logger.debug(`[SELECTION] Tool Selection Prompt:
|
|
1399
|
-
${toolSelectionArgumentPrompt}`);
|
|
1400
|
-
const toolSelectionArgument = await runtime2.useModel(import_core5.ModelType.TEXT_LARGE, {
|
|
1401
|
-
prompt: toolSelectionArgumentPrompt
|
|
1402
|
-
});
|
|
1403
|
-
import_core5.logger.debug(`[SELECTION] Tool Selection Argument Response:
|
|
1404
|
-
${toolSelectionArgument}`);
|
|
1405
|
-
return await withModelRetry({
|
|
1406
|
-
runtime: runtime2,
|
|
1407
|
-
message,
|
|
1408
|
-
state,
|
|
1409
|
-
callback,
|
|
1410
|
-
input: toolSelectionArgument,
|
|
1411
|
-
validationFn: (parsed) => validateToolSelectionArgument(parsed, state),
|
|
1412
|
-
createFeedbackPromptFn: (originalResponse, errorMessage, state2, userMessage) => createToolSelectionFeedbackPrompt(originalResponse, errorMessage, state2, userMessage),
|
|
1413
|
-
failureMsg: "I'm having trouble figuring out the best way to help with your request."
|
|
1414
|
-
});
|
|
445
|
+
success: false,
|
|
446
|
+
error: `Schema validation error: ${error instanceof Error ? error.message : String(error)}`
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// src/utils/validation.ts
|
|
452
|
+
function validateResourceSelection(selection) {
|
|
453
|
+
return validateJsonSchema(selection, ResourceSelectionSchema);
|
|
1415
454
|
}
|
|
1416
|
-
function
|
|
1417
|
-
let
|
|
1418
|
-
for (const [serverName, server] of Object.entries(
|
|
455
|
+
function createResourceSelectionFeedbackPrompt(originalResponse, errorMessage, composedState, userMessage) {
|
|
456
|
+
let description = "";
|
|
457
|
+
for (const [serverName, server] of Object.entries(composedState.values.mcp || {})) {
|
|
1419
458
|
if (server.status !== "connected")
|
|
1420
459
|
continue;
|
|
1421
|
-
for (const [
|
|
1422
|
-
|
|
460
|
+
for (const [uri, resource] of Object.entries(server.resources || {})) {
|
|
461
|
+
description += `Resource: ${uri} (Server: ${serverName})
|
|
1423
462
|
`;
|
|
1424
|
-
|
|
463
|
+
description += `Name: ${resource.name || "No name"}
|
|
464
|
+
`;
|
|
465
|
+
description += `Description: ${resource.description || "No description"}
|
|
1425
466
|
|
|
1426
467
|
`;
|
|
1427
468
|
}
|
|
1428
469
|
}
|
|
1429
|
-
const feedbackPrompt = createFeedbackPrompt2(originalResponse, errorMessage, "tool", toolsDescription, userMessage);
|
|
1430
|
-
import_core5.logger.debug(`[SELECTION] Tool Selection Feedback Prompt:
|
|
1431
|
-
${feedbackPrompt}`);
|
|
1432
|
-
return feedbackPrompt;
|
|
1433
|
-
}
|
|
1434
|
-
function createFeedbackPrompt2(originalResponse, errorMessage, itemType, itemsDescription, userMessage) {
|
|
1435
470
|
return `Error parsing JSON: ${errorMessage}
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
471
|
+
|
|
472
|
+
Your original response:
|
|
473
|
+
${originalResponse}
|
|
474
|
+
|
|
475
|
+
Please try again with valid JSON for resource selection.
|
|
476
|
+
Available resources:
|
|
477
|
+
${description}
|
|
478
|
+
|
|
479
|
+
User request: ${userMessage}`;
|
|
1445
480
|
}
|
|
1446
481
|
|
|
1447
|
-
// src/
|
|
1448
|
-
var
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
try {
|
|
1478
|
-
const toolSelectionName = await createToolSelectionName({
|
|
1479
|
-
runtime: runtime2,
|
|
1480
|
-
state: composedState,
|
|
1481
|
-
message,
|
|
1482
|
-
callback,
|
|
1483
|
-
mcpProvider
|
|
1484
|
-
});
|
|
1485
|
-
if (!toolSelectionName || toolSelectionName.noToolAvailable) {
|
|
1486
|
-
import_core6.logger.warn("[NO_TOOL_AVAILABLE] No appropriate tool available for the request");
|
|
1487
|
-
return await handleNoToolAvailable(callback, toolSelectionName);
|
|
1488
|
-
}
|
|
1489
|
-
const { serverName, toolName, reasoning } = toolSelectionName;
|
|
1490
|
-
import_core6.logger.info(`[CALLING] Calling tool "${serverName}/${toolName}" on server with reasoning: "${reasoning}"`);
|
|
1491
|
-
const toolSelectionArgument = await createToolSelectionArgument({
|
|
1492
|
-
runtime: runtime2,
|
|
1493
|
-
state: composedState,
|
|
482
|
+
// src/utils/wrapper.ts
|
|
483
|
+
var import_core4 = require("@elizaos/core");
|
|
484
|
+
async function withModelRetry({
|
|
485
|
+
runtime,
|
|
486
|
+
message,
|
|
487
|
+
state,
|
|
488
|
+
callback,
|
|
489
|
+
input,
|
|
490
|
+
validationFn,
|
|
491
|
+
createFeedbackPromptFn,
|
|
492
|
+
failureMsg,
|
|
493
|
+
retryCount = 0
|
|
494
|
+
}) {
|
|
495
|
+
const maxRetries = getMaxRetries(runtime);
|
|
496
|
+
try {
|
|
497
|
+
const parsed = typeof input === "string" ? parseJSON(input) : input;
|
|
498
|
+
const result = validationFn(parsed);
|
|
499
|
+
if (!result.success)
|
|
500
|
+
throw new Error(result.error);
|
|
501
|
+
return result.data;
|
|
502
|
+
} catch (e) {
|
|
503
|
+
const error = e instanceof Error ? e.message : "Parse error";
|
|
504
|
+
import_core4.logger.error({ error }, "[Retry] Parse failed");
|
|
505
|
+
if (retryCount < maxRetries) {
|
|
506
|
+
const feedback = createFeedbackPromptFn(input, error, state, message.content.text || "");
|
|
507
|
+
const retry = await runtime.useModel(import_core4.ModelType.OBJECT_LARGE, { prompt: feedback });
|
|
508
|
+
return withModelRetry({
|
|
509
|
+
runtime,
|
|
510
|
+
input: retry,
|
|
511
|
+
validationFn,
|
|
1494
512
|
message,
|
|
513
|
+
state,
|
|
514
|
+
createFeedbackPromptFn,
|
|
1495
515
|
callback,
|
|
1496
|
-
|
|
1497
|
-
|
|
516
|
+
failureMsg,
|
|
517
|
+
retryCount: retryCount + 1
|
|
1498
518
|
});
|
|
1499
|
-
if (!toolSelectionArgument) {
|
|
1500
|
-
import_core6.logger.warn("[NO_TOOL_SELECTION_ARGUMENT] No appropriate tool selection argument available");
|
|
1501
|
-
return await handleNoToolAvailable(callback, toolSelectionName);
|
|
1502
|
-
}
|
|
1503
|
-
import_core6.logger.info(`[SELECTED] Tool Selection result:
|
|
1504
|
-
${JSON.stringify(toolSelectionArgument, null, 2)}`);
|
|
1505
|
-
const result = await mcpService.callTool(serverName, toolName, toolSelectionArgument.toolArguments);
|
|
1506
|
-
const { toolOutput, hasAttachments, attachments } = processToolResult(result, serverName, toolName, runtime2, message.entityId);
|
|
1507
|
-
const replyMemory = await handleToolResponse(runtime2, message, serverName, toolName, toolSelectionArgument.toolArguments, toolOutput, hasAttachments, attachments, composedState, mcpProvider, callback);
|
|
1508
|
-
const actionResult = {
|
|
1509
|
-
text: `Successfully called tool: ${serverName}/${toolName}. Reasoned response: ${replyMemory.content.text}`,
|
|
1510
|
-
values: {
|
|
1511
|
-
success: true,
|
|
1512
|
-
toolExecuted: true,
|
|
1513
|
-
serverName,
|
|
1514
|
-
toolName,
|
|
1515
|
-
hasAttachments,
|
|
1516
|
-
output: toolOutput
|
|
1517
|
-
},
|
|
1518
|
-
data: {
|
|
1519
|
-
actionName: "CALL_MCP_TOOL",
|
|
1520
|
-
serverName,
|
|
1521
|
-
toolName,
|
|
1522
|
-
toolArguments: toolSelectionArgument.toolArguments,
|
|
1523
|
-
reasoning: toolSelectionName.reasoning,
|
|
1524
|
-
output: toolOutput,
|
|
1525
|
-
attachments: attachments || []
|
|
1526
|
-
},
|
|
1527
|
-
success: true
|
|
1528
|
-
};
|
|
1529
|
-
import_core6.logger.info({
|
|
1530
|
-
serverName,
|
|
1531
|
-
toolName,
|
|
1532
|
-
hasOutput: !!toolOutput,
|
|
1533
|
-
outputLength: toolOutput?.length || 0,
|
|
1534
|
-
hasAttachments,
|
|
1535
|
-
reasoning: toolSelectionName.reasoning
|
|
1536
|
-
}, `[CALL_MCP_TOOL] Action result`);
|
|
1537
|
-
return actionResult;
|
|
1538
|
-
} catch (error) {
|
|
1539
|
-
return await handleMcpError(composedState, mcpProvider, error, runtime2, message, "tool", callback);
|
|
1540
519
|
}
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
content: {
|
|
1560
|
-
text: `I found the following information about climate change:
|
|
1561
|
-
|
|
1562
|
-
Climate change refers to long-term shifts in temperatures and weather patterns. These shifts may be natural, but since the 1800s, human activities have been the main driver of climate change, primarily due to the burning of fossil fuels like coal, oil, and gas, which produces heat-trapping gases.`,
|
|
1563
|
-
actions: ["CALL_MCP_TOOL"]
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
]
|
|
1567
|
-
]
|
|
1568
|
-
};
|
|
1569
|
-
|
|
1570
|
-
// src/actions/readResourceAction.ts
|
|
1571
|
-
var import_core7 = require("@elizaos/core");
|
|
1572
|
-
|
|
1573
|
-
// src/templates/resourceSelectionTemplate.ts
|
|
1574
|
-
var resourceSelectionTemplate = `
|
|
1575
|
-
{{{mcpProvider.text}}}
|
|
1576
|
-
|
|
1577
|
-
{{{recentMessages}}}
|
|
1578
|
-
|
|
1579
|
-
# Prompt
|
|
1580
|
-
|
|
1581
|
-
You are an intelligent assistant helping select the right resource to address a user's request.
|
|
1582
|
-
|
|
1583
|
-
CRITICAL INSTRUCTIONS:
|
|
1584
|
-
1. You MUST specify both a valid serverName AND uri from the list above
|
|
1585
|
-
2. The serverName value should match EXACTLY the server name shown in parentheses (Server: X)
|
|
1586
|
-
CORRECT: "serverName": "github" (if the server is called "github")
|
|
1587
|
-
WRONG: "serverName": "GitHub" or "Github" or any other variation
|
|
1588
|
-
3. The uri value should match EXACTLY the resource uri listed
|
|
1589
|
-
CORRECT: "uri": "weather://San Francisco/current" (if that's the exact uri)
|
|
1590
|
-
WRONG: "uri": "weather://sanfrancisco/current" or any variation
|
|
1591
|
-
4. Identify the user's information need from the conversation context
|
|
1592
|
-
5. Select the most appropriate resource based on its description and the request
|
|
1593
|
-
6. If no resource seems appropriate, output {"noResourceAvailable": true}
|
|
1594
|
-
|
|
1595
|
-
!!! YOUR RESPONSE MUST BE A VALID JSON OBJECT ONLY !!!
|
|
1596
|
-
|
|
1597
|
-
STRICT FORMAT REQUIREMENTS:
|
|
1598
|
-
- NO code block formatting (NO backticks or \`\`\`)
|
|
1599
|
-
- NO comments (NO // or /* */)
|
|
1600
|
-
- NO placeholders like "replace with...", "example", "your...", "actual", etc.
|
|
1601
|
-
- Every parameter value must be a concrete, usable value (not instructions to replace)
|
|
1602
|
-
- Use proper JSON syntax with double quotes for strings
|
|
1603
|
-
- NO explanatory text before or after the JSON object
|
|
1604
|
-
|
|
1605
|
-
EXAMPLE RESPONSE:
|
|
1606
|
-
{
|
|
1607
|
-
"serverName": "weather-server",
|
|
1608
|
-
"uri": "weather://San Francisco/current",
|
|
1609
|
-
"reasoning": "Based on the conversation, the user is asking about current weather in San Francisco. This resource provides up-to-date weather information for that city."
|
|
520
|
+
if (callback && failureMsg) {
|
|
521
|
+
await callback({ text: failureMsg, thought: "Parse failed after retries", actions: ["REPLY"] });
|
|
522
|
+
}
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function getMaxRetries(runtime) {
|
|
527
|
+
try {
|
|
528
|
+
const mcp = runtime.getSetting("mcp");
|
|
529
|
+
if (mcp?.maxRetries !== undefined) {
|
|
530
|
+
const val = Number(mcp.maxRetries);
|
|
531
|
+
if (!isNaN(val) && val >= 0)
|
|
532
|
+
return val;
|
|
533
|
+
}
|
|
534
|
+
} catch (e) {
|
|
535
|
+
import_core4.logger.debug({ error: e instanceof Error ? e.message : e }, "[Retry] Failed to get maxRetries setting");
|
|
536
|
+
}
|
|
537
|
+
return DEFAULT_MAX_RETRIES;
|
|
1610
538
|
}
|
|
1611
|
-
|
|
1612
|
-
REMEMBER: Your response will be parsed directly as JSON. If it fails to parse, the operation will fail completely!
|
|
1613
|
-
`;
|
|
1614
539
|
|
|
1615
540
|
// src/actions/readResourceAction.ts
|
|
1616
541
|
function createResourceSelectionPrompt(composedState, userMessage) {
|
|
@@ -1643,7 +568,7 @@ function createResourceSelectionPrompt(composedState, userMessage) {
|
|
|
1643
568
|
userMessage
|
|
1644
569
|
}
|
|
1645
570
|
};
|
|
1646
|
-
return
|
|
571
|
+
return import_core5.composePromptFromState({
|
|
1647
572
|
state: enhancedState,
|
|
1648
573
|
template: resourceSelectionTemplate
|
|
1649
574
|
});
|
|
@@ -1661,16 +586,16 @@ var readResourceAction = {
|
|
|
1661
586
|
"ACCESS_MCP_RESOURCE"
|
|
1662
587
|
],
|
|
1663
588
|
description: "Reads a resource from an MCP server",
|
|
1664
|
-
validate: async (
|
|
1665
|
-
const mcpService =
|
|
589
|
+
validate: async (runtime, _message, _state) => {
|
|
590
|
+
const mcpService = runtime.getService(MCP_SERVICE_NAME);
|
|
1666
591
|
if (!mcpService)
|
|
1667
592
|
return false;
|
|
1668
593
|
const servers = mcpService.getServers();
|
|
1669
594
|
return servers.length > 0 && servers.some((server) => server.status === "connected" && server.resources && server.resources.length > 0);
|
|
1670
595
|
},
|
|
1671
|
-
handler: async (
|
|
1672
|
-
const composedState = await
|
|
1673
|
-
const mcpService =
|
|
596
|
+
handler: async (runtime, message, _state, _options, callback) => {
|
|
597
|
+
const composedState = await runtime.composeState(message, ["RECENT_MESSAGES", "MCP"]);
|
|
598
|
+
const mcpService = runtime.getService(MCP_SERVICE_NAME);
|
|
1674
599
|
if (!mcpService) {
|
|
1675
600
|
throw new Error("MCP service not available");
|
|
1676
601
|
}
|
|
@@ -1678,11 +603,11 @@ var readResourceAction = {
|
|
|
1678
603
|
try {
|
|
1679
604
|
await sendInitialResponse(callback);
|
|
1680
605
|
const resourceSelectionPrompt = createResourceSelectionPrompt(composedState, message.content.text || "");
|
|
1681
|
-
const resourceSelection = await
|
|
606
|
+
const resourceSelection = await runtime.useModel(import_core5.ModelType.TEXT_SMALL, {
|
|
1682
607
|
prompt: resourceSelectionPrompt
|
|
1683
608
|
});
|
|
1684
609
|
const parsedSelection = await withModelRetry({
|
|
1685
|
-
runtime
|
|
610
|
+
runtime,
|
|
1686
611
|
state: composedState,
|
|
1687
612
|
message,
|
|
1688
613
|
callback,
|
|
@@ -1718,11 +643,11 @@ var readResourceAction = {
|
|
|
1718
643
|
};
|
|
1719
644
|
}
|
|
1720
645
|
const { serverName, uri, reasoning } = parsedSelection;
|
|
1721
|
-
|
|
646
|
+
import_core5.logger.debug(`Selected resource "${uri}" on server "${serverName}" because: ${reasoning}`);
|
|
1722
647
|
const result = await mcpService.readResource(serverName, uri);
|
|
1723
|
-
|
|
648
|
+
import_core5.logger.debug(`Read resource ${uri} from server ${serverName}`);
|
|
1724
649
|
const { resourceContent, resourceMeta } = processResourceResult(result, uri);
|
|
1725
|
-
await handleResourceAnalysis(
|
|
650
|
+
await handleResourceAnalysis(runtime, message, uri, serverName, resourceContent, resourceMeta, callback);
|
|
1726
651
|
return {
|
|
1727
652
|
text: `Successfully read resource: ${uri}`,
|
|
1728
653
|
values: {
|
|
@@ -1741,271 +666,889 @@ var readResourceAction = {
|
|
|
1741
666
|
},
|
|
1742
667
|
success: true
|
|
1743
668
|
};
|
|
1744
|
-
} catch (error) {
|
|
1745
|
-
return await handleMcpError(composedState, mcpProvider, error,
|
|
669
|
+
} catch (error) {
|
|
670
|
+
return await handleMcpError(composedState, mcpProvider, error, runtime, message, "resource", callback);
|
|
671
|
+
}
|
|
672
|
+
},
|
|
673
|
+
examples: [
|
|
674
|
+
[
|
|
675
|
+
{
|
|
676
|
+
name: "{{user}}",
|
|
677
|
+
content: {
|
|
678
|
+
text: "Can you get the documentation about installing ElizaOS?"
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
name: "{{assistant}}",
|
|
683
|
+
content: {
|
|
684
|
+
text: `I'll retrieve that information for you. Let me access the resource...`,
|
|
685
|
+
actions: ["READ_MCP_RESOURCE"]
|
|
686
|
+
}
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
name: "{{assistant}}",
|
|
690
|
+
content: {
|
|
691
|
+
text: `ElizaOS installation is straightforward. You'll need Node.js 23+ and Git installed. For Windows users, WSL 2 is required. The quickest way to get started is by cloning the ElizaOS starter repository with \`git clone https://github.com/elizaos/eliza-starter.git\`, then run \`cd eliza-starter && cp .env.example .env && bun i && bun run build && bun start\`. This will set up a development environment with the core features enabled. After starting, you can access the web interface at http://localhost:3000 to interact with your agent.`,
|
|
692
|
+
actions: ["READ_MCP_RESOURCE"]
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
]
|
|
696
|
+
]
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
// src/provider.ts
|
|
700
|
+
var EMPTY_PROVIDER = { values: { mcp: {} }, data: { mcp: {} }, text: "No MCP servers available." };
|
|
701
|
+
var provider = {
|
|
702
|
+
name: "MCP",
|
|
703
|
+
description: "Connected MCP servers, tools, and resources",
|
|
704
|
+
get: async (runtime, _message, _state) => {
|
|
705
|
+
const svc = runtime.getService(MCP_SERVICE_NAME);
|
|
706
|
+
if (!svc)
|
|
707
|
+
return EMPTY_PROVIDER;
|
|
708
|
+
await svc.waitForInitialization();
|
|
709
|
+
return svc.getProviderData();
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
// src/service.ts
|
|
714
|
+
var import_core8 = require("@elizaos/core");
|
|
715
|
+
var import_client = require("@modelcontextprotocol/sdk/client/index.js");
|
|
716
|
+
var import_sse = require("@modelcontextprotocol/sdk/client/sse.js");
|
|
717
|
+
var import_streamableHttp = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
718
|
+
var import_stdio = require("@modelcontextprotocol/sdk/client/stdio.js");
|
|
719
|
+
|
|
720
|
+
// src/actions/dynamic-tool-actions.ts
|
|
721
|
+
var import_core6 = require("@elizaos/core");
|
|
722
|
+
|
|
723
|
+
// src/utils/schema-converter.ts
|
|
724
|
+
function mapJsonSchemaType(jsonType) {
|
|
725
|
+
if (Array.isArray(jsonType)) {
|
|
726
|
+
return mapJsonSchemaType(jsonType.find((t) => t !== "null"));
|
|
727
|
+
}
|
|
728
|
+
switch (jsonType) {
|
|
729
|
+
case "string":
|
|
730
|
+
return "string";
|
|
731
|
+
case "number":
|
|
732
|
+
case "integer":
|
|
733
|
+
return "number";
|
|
734
|
+
case "boolean":
|
|
735
|
+
return "boolean";
|
|
736
|
+
case "array":
|
|
737
|
+
return "array";
|
|
738
|
+
default:
|
|
739
|
+
return "object";
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
function buildDescription(name, prop) {
|
|
743
|
+
const parts = [prop.description || `Parameter: ${name}`];
|
|
744
|
+
if (prop.enum?.length)
|
|
745
|
+
parts.push(`Allowed: ${prop.enum.map((v) => JSON.stringify(v)).join(", ")}`);
|
|
746
|
+
if (prop.format)
|
|
747
|
+
parts.push(`Format: ${prop.format}`);
|
|
748
|
+
if (prop.minimum !== undefined || prop.maximum !== undefined) {
|
|
749
|
+
parts.push(`Range: ${[prop.minimum !== undefined ? `min: ${prop.minimum}` : "", prop.maximum !== undefined ? `max: ${prop.maximum}` : ""].filter(Boolean).join(", ")}`);
|
|
750
|
+
}
|
|
751
|
+
if (prop.minLength !== undefined || prop.maxLength !== undefined) {
|
|
752
|
+
parts.push(`Length: ${[prop.minLength !== undefined ? `min: ${prop.minLength}` : "", prop.maxLength !== undefined ? `max: ${prop.maxLength}` : ""].filter(Boolean).join(", ")}`);
|
|
753
|
+
}
|
|
754
|
+
if (prop.pattern)
|
|
755
|
+
parts.push(`Pattern: ${prop.pattern}`);
|
|
756
|
+
if (prop.default !== undefined)
|
|
757
|
+
parts.push(`Default: ${JSON.stringify(prop.default)}`);
|
|
758
|
+
if (prop.type === "array" && prop.items)
|
|
759
|
+
parts.push(`Array of ${prop.items.type || "any"}`);
|
|
760
|
+
if (prop.type === "object" && prop.properties) {
|
|
761
|
+
const keys = Object.keys(prop.properties);
|
|
762
|
+
parts.push(`Object with: ${keys.join(", ")}`);
|
|
763
|
+
}
|
|
764
|
+
return parts.join(". ");
|
|
765
|
+
}
|
|
766
|
+
function convertJsonSchemaToActionParams(schema) {
|
|
767
|
+
const properties = schema?.properties;
|
|
768
|
+
if (!properties || Object.keys(properties).length === 0)
|
|
769
|
+
return;
|
|
770
|
+
const required = new Set(schema?.required || []);
|
|
771
|
+
const params = {};
|
|
772
|
+
for (const [name, prop] of Object.entries(properties)) {
|
|
773
|
+
params[name] = {
|
|
774
|
+
type: mapJsonSchemaType(prop.type),
|
|
775
|
+
description: buildDescription(name, prop),
|
|
776
|
+
required: required.has(name)
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
return Object.keys(params).length > 0 ? params : undefined;
|
|
780
|
+
}
|
|
781
|
+
function validateParamsAgainstSchema(params, schema) {
|
|
782
|
+
if (!schema)
|
|
783
|
+
return [];
|
|
784
|
+
const errors = [];
|
|
785
|
+
const properties = schema.properties;
|
|
786
|
+
const required = new Set(schema.required || []);
|
|
787
|
+
for (const field of required) {
|
|
788
|
+
if (params[field] === undefined || params[field] === null) {
|
|
789
|
+
errors.push(`Missing required parameter: ${field}`);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
if (properties) {
|
|
793
|
+
for (const [name, value] of Object.entries(params)) {
|
|
794
|
+
const prop = properties[name];
|
|
795
|
+
if (!prop)
|
|
796
|
+
continue;
|
|
797
|
+
const expected = mapJsonSchemaType(prop.type);
|
|
798
|
+
const actual = getValueType(value);
|
|
799
|
+
if (actual !== expected && value !== null && value !== undefined) {
|
|
800
|
+
errors.push(`Parameter '${name}' expected ${expected}, got ${actual}`);
|
|
801
|
+
}
|
|
802
|
+
if (prop.enum && !prop.enum.includes(value)) {
|
|
803
|
+
errors.push(`Parameter '${name}' must be one of: ${prop.enum.map((v) => JSON.stringify(v)).join(", ")}`);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return errors;
|
|
808
|
+
}
|
|
809
|
+
function getValueType(value) {
|
|
810
|
+
if (Array.isArray(value))
|
|
811
|
+
return "array";
|
|
812
|
+
if (value === null)
|
|
813
|
+
return "object";
|
|
814
|
+
switch (typeof value) {
|
|
815
|
+
case "string":
|
|
816
|
+
return "string";
|
|
817
|
+
case "number":
|
|
818
|
+
return "number";
|
|
819
|
+
case "boolean":
|
|
820
|
+
return "boolean";
|
|
821
|
+
default:
|
|
822
|
+
return "object";
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// src/utils/action-naming.ts
|
|
827
|
+
function normalize(str) {
|
|
828
|
+
return str.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
829
|
+
}
|
|
830
|
+
function toActionName(serverName, toolName) {
|
|
831
|
+
const server = normalize(serverName);
|
|
832
|
+
const tool = normalize(toolName);
|
|
833
|
+
if (!server)
|
|
834
|
+
return tool || "_";
|
|
835
|
+
if (!tool)
|
|
836
|
+
return server + "_";
|
|
837
|
+
if (tool.startsWith(server + "_"))
|
|
838
|
+
return tool;
|
|
839
|
+
return `${server}_${tool}`;
|
|
840
|
+
}
|
|
841
|
+
function generateSimiles(serverName, toolName) {
|
|
842
|
+
const tool = normalize(toolName);
|
|
843
|
+
const fullName = toActionName(serverName, toolName);
|
|
844
|
+
const similes = [
|
|
845
|
+
tool,
|
|
846
|
+
`${serverName}/${toolName}`,
|
|
847
|
+
`${serverName.toLowerCase()}/${toolName.toLowerCase()}`,
|
|
848
|
+
`MCP_${fullName}`
|
|
849
|
+
];
|
|
850
|
+
const parts = toolName.split("_");
|
|
851
|
+
if (parts.length === 2) {
|
|
852
|
+
const reversed = `${parts[1]}_${parts[0]}`.toUpperCase();
|
|
853
|
+
if (reversed !== tool)
|
|
854
|
+
similes.push(reversed);
|
|
855
|
+
}
|
|
856
|
+
return [...new Set(similes)].filter((s) => s !== fullName);
|
|
857
|
+
}
|
|
858
|
+
function makeUniqueActionName(serverName, toolName, existing) {
|
|
859
|
+
let name = toActionName(serverName, toolName);
|
|
860
|
+
if (!existing.has(name))
|
|
861
|
+
return name;
|
|
862
|
+
let suffix = 2;
|
|
863
|
+
while (existing.has(`${name}_${suffix}`))
|
|
864
|
+
suffix++;
|
|
865
|
+
return `${name}_${suffix}`;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// src/actions/dynamic-tool-actions.ts
|
|
869
|
+
function extractParams(message, state) {
|
|
870
|
+
const content = message.content;
|
|
871
|
+
return content.actionParams || content.actionInput || state?.data?.actionParams || {};
|
|
872
|
+
}
|
|
873
|
+
function createMcpToolAction(serverName, tool, existingNames) {
|
|
874
|
+
const actionName = makeUniqueActionName(serverName, tool.name, existingNames);
|
|
875
|
+
const description = `${tool.description || `Execute ${tool.name}`} (MCP: ${serverName}/${tool.name})`;
|
|
876
|
+
return {
|
|
877
|
+
name: actionName,
|
|
878
|
+
description,
|
|
879
|
+
similes: generateSimiles(serverName, tool.name),
|
|
880
|
+
parameters: convertJsonSchemaToActionParams(tool.inputSchema),
|
|
881
|
+
validate: async (runtime) => {
|
|
882
|
+
const svc = runtime.getService(MCP_SERVICE_NAME);
|
|
883
|
+
if (!svc)
|
|
884
|
+
return false;
|
|
885
|
+
if (svc.isLazyConnection(serverName))
|
|
886
|
+
return true;
|
|
887
|
+
const server = svc.getServers().find((s) => s.name === serverName);
|
|
888
|
+
return server?.status === "connected" && !!server.tools?.some((t) => t.name === tool.name);
|
|
889
|
+
},
|
|
890
|
+
handler: async (runtime, message, state, _options, callback) => {
|
|
891
|
+
const svc = runtime.getService(MCP_SERVICE_NAME);
|
|
892
|
+
if (!svc) {
|
|
893
|
+
return { success: false, error: "MCP service not available", data: { actionName, serverName, toolName: tool.name } };
|
|
894
|
+
}
|
|
895
|
+
const params = extractParams(message, state);
|
|
896
|
+
import_core6.logger.info({ serverName, toolName: tool.name, params }, `[MCP] Executing ${actionName}`);
|
|
897
|
+
const errors = validateParamsAgainstSchema(params, tool.inputSchema);
|
|
898
|
+
const missing = errors.filter((e) => e.startsWith("Missing required"));
|
|
899
|
+
if (missing.length > 0) {
|
|
900
|
+
import_core6.logger.error({ missing, params }, `[MCP] Missing required params for ${actionName}`);
|
|
901
|
+
return { success: false, error: missing.join(", "), data: { actionName, serverName, toolName: tool.name } };
|
|
902
|
+
}
|
|
903
|
+
const warnings = errors.filter((e) => !e.startsWith("Missing required"));
|
|
904
|
+
if (warnings.length > 0) {
|
|
905
|
+
import_core6.logger.warn({ warnings, params }, `[MCP] Type warnings for ${actionName}`);
|
|
906
|
+
}
|
|
907
|
+
const result = await svc.callTool(serverName, tool.name, params);
|
|
908
|
+
const { toolOutput, hasAttachments, attachments } = processToolResult(result, serverName, tool.name, runtime, message.entityId);
|
|
909
|
+
if (result.isError) {
|
|
910
|
+
import_core6.logger.error({ serverName, toolName: tool.name, output: toolOutput }, `[MCP] Tool error`);
|
|
911
|
+
return {
|
|
912
|
+
success: false,
|
|
913
|
+
error: toolOutput || "Tool execution failed",
|
|
914
|
+
text: toolOutput,
|
|
915
|
+
data: { actionName, serverName, toolName: tool.name, toolArguments: params, isError: true }
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
if (callback && hasAttachments && attachments.length > 0) {
|
|
919
|
+
await callback({ text: `Executed ${serverName}/${tool.name}`, attachments });
|
|
920
|
+
}
|
|
921
|
+
return {
|
|
922
|
+
success: true,
|
|
923
|
+
text: toolOutput,
|
|
924
|
+
values: { success: true, serverName, toolName: tool.name, hasAttachments, output: toolOutput },
|
|
925
|
+
data: { actionName, serverName, toolName: tool.name, toolArguments: params, output: toolOutput, attachments: attachments.length > 0 ? attachments : undefined }
|
|
926
|
+
};
|
|
927
|
+
},
|
|
928
|
+
examples: [[
|
|
929
|
+
{ name: "{{user}}", content: { text: `Can you use ${tool.name}?` } },
|
|
930
|
+
{ name: "{{assistant}}", content: { text: `I'll execute ${tool.name} for you.`, actions: [actionName] } }
|
|
931
|
+
]],
|
|
932
|
+
_mcpMeta: { serverName, toolName: tool.name, originalSchema: tool.inputSchema }
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
function createMcpToolActions(serverName, tools, existingNames) {
|
|
936
|
+
const actions = tools.map((tool) => {
|
|
937
|
+
const action = createMcpToolAction(serverName, tool, existingNames);
|
|
938
|
+
existingNames.add(action.name);
|
|
939
|
+
import_core6.logger.debug({ actionName: action.name, serverName, toolName: tool.name }, `[MCP] Created action`);
|
|
940
|
+
return action;
|
|
941
|
+
});
|
|
942
|
+
import_core6.logger.info({ serverName, toolCount: actions.length }, `[MCP] Created ${actions.length} actions for ${serverName}`);
|
|
943
|
+
return actions;
|
|
944
|
+
}
|
|
945
|
+
function isMcpToolAction(action) {
|
|
946
|
+
return "_mcpMeta" in action && typeof action._mcpMeta === "object";
|
|
947
|
+
}
|
|
948
|
+
function getMcpToolActionsForServer(actions, serverName) {
|
|
949
|
+
return actions.filter((a) => isMcpToolAction(a) && a._mcpMeta.serverName === serverName);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// src/cache/schema-cache.ts
|
|
953
|
+
var import_crypto = require("crypto");
|
|
954
|
+
var import_core7 = require("@elizaos/core");
|
|
955
|
+
var CACHE_KEY_PREFIX = "mcp:schema:v1";
|
|
956
|
+
var DEFAULT_TTL = 3600;
|
|
957
|
+
|
|
958
|
+
class UpstashClient {
|
|
959
|
+
url;
|
|
960
|
+
token;
|
|
961
|
+
constructor(url, token) {
|
|
962
|
+
this.url = url;
|
|
963
|
+
this.token = token;
|
|
964
|
+
if (url.startsWith("redis://") || url.startsWith("rediss://")) {
|
|
965
|
+
throw new Error("MCP_CACHE_REDIS_URL must be Upstash REST URL (https://...), not Redis protocol URL");
|
|
966
|
+
}
|
|
967
|
+
this.url = url.replace(/\/$/, "");
|
|
968
|
+
}
|
|
969
|
+
async exec(cmd) {
|
|
970
|
+
try {
|
|
971
|
+
const res = await fetch(this.url, {
|
|
972
|
+
method: "POST",
|
|
973
|
+
headers: { Authorization: `Bearer ${this.token}`, "Content-Type": "application/json" },
|
|
974
|
+
body: JSON.stringify(cmd)
|
|
975
|
+
});
|
|
976
|
+
if (!res.ok)
|
|
977
|
+
return null;
|
|
978
|
+
const data = await res.json();
|
|
979
|
+
return data.result;
|
|
980
|
+
} catch {
|
|
981
|
+
return null;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
get = (key) => this.exec(["GET", key]);
|
|
985
|
+
setex = (key, ttl, val) => this.exec(["SETEX", key, String(ttl), val]);
|
|
986
|
+
del = (key) => this.exec(["DEL", key]);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
class McpSchemaCache {
|
|
990
|
+
client = null;
|
|
991
|
+
ttl;
|
|
992
|
+
constructor() {
|
|
993
|
+
const enabled = process.env.MCP_SCHEMA_CACHE_ENABLED === "true";
|
|
994
|
+
const url = process.env.MCP_CACHE_REDIS_URL;
|
|
995
|
+
const token = process.env.MCP_CACHE_REDIS_TOKEN;
|
|
996
|
+
this.ttl = parseInt(process.env.MCP_SCHEMA_CACHE_TTL || String(DEFAULT_TTL), 10);
|
|
997
|
+
if (!enabled) {
|
|
998
|
+
import_core7.logger.debug("[McpSchemaCache] Disabled");
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
if (!url || !token) {
|
|
1002
|
+
import_core7.logger.warn("[McpSchemaCache] Enabled but missing MCP_CACHE_REDIS_URL or MCP_CACHE_REDIS_TOKEN");
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
try {
|
|
1006
|
+
this.client = new UpstashClient(url, token);
|
|
1007
|
+
import_core7.logger.info(`[McpSchemaCache] Enabled (TTL: ${this.ttl}s)`);
|
|
1008
|
+
} catch (e) {
|
|
1009
|
+
import_core7.logger.error(`[McpSchemaCache] Init failed: ${e instanceof Error ? e.message : e}`);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
get isEnabled() {
|
|
1013
|
+
return this.client !== null;
|
|
1014
|
+
}
|
|
1015
|
+
hashConfig(config) {
|
|
1016
|
+
const sortedJson = JSON.stringify(config, (_, value) => value && typeof value === "object" && !Array.isArray(value) ? Object.keys(value).sort().reduce((sorted, key) => {
|
|
1017
|
+
sorted[key] = value[key];
|
|
1018
|
+
return sorted;
|
|
1019
|
+
}, {}) : value);
|
|
1020
|
+
return import_crypto.createHash("sha256").update(sortedJson).digest("hex").slice(0, 16);
|
|
1021
|
+
}
|
|
1022
|
+
key(agentId, serverName) {
|
|
1023
|
+
return `${CACHE_KEY_PREFIX}:${agentId}:${serverName}`;
|
|
1024
|
+
}
|
|
1025
|
+
async getSchemas(agentId, serverName, configHash) {
|
|
1026
|
+
if (!this.client)
|
|
1027
|
+
return null;
|
|
1028
|
+
try {
|
|
1029
|
+
const raw = await this.client.get(this.key(agentId, serverName));
|
|
1030
|
+
if (!raw)
|
|
1031
|
+
return null;
|
|
1032
|
+
const cached = JSON.parse(raw);
|
|
1033
|
+
if (cached.configHash !== configHash) {
|
|
1034
|
+
await this.client.del(this.key(agentId, serverName));
|
|
1035
|
+
import_core7.logger.debug(`[McpSchemaCache] Config changed for ${serverName}, invalidated`);
|
|
1036
|
+
return null;
|
|
1037
|
+
}
|
|
1038
|
+
import_core7.logger.info(`[McpSchemaCache] Hit: ${serverName} (${cached.tools.length} tools)`);
|
|
1039
|
+
return cached;
|
|
1040
|
+
} catch {
|
|
1041
|
+
return null;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
async setSchemas(agentId, serverName, configHash, tools) {
|
|
1045
|
+
if (!this.client)
|
|
1046
|
+
return;
|
|
1047
|
+
try {
|
|
1048
|
+
const cached = {
|
|
1049
|
+
serverName,
|
|
1050
|
+
tools: tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema })),
|
|
1051
|
+
cachedAt: Date.now(),
|
|
1052
|
+
configHash
|
|
1053
|
+
};
|
|
1054
|
+
await this.client.setex(this.key(agentId, serverName), this.ttl, JSON.stringify(cached));
|
|
1055
|
+
import_core7.logger.info(`[McpSchemaCache] Cached: ${serverName} (${tools.length} tools)`);
|
|
1056
|
+
} catch {}
|
|
1057
|
+
}
|
|
1058
|
+
static toTools(cached) {
|
|
1059
|
+
return cached.tools.map((t) => ({
|
|
1060
|
+
name: t.name,
|
|
1061
|
+
description: t.description,
|
|
1062
|
+
inputSchema: t.inputSchema || { type: "object", properties: {} }
|
|
1063
|
+
}));
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
var instance = null;
|
|
1067
|
+
function getSchemaCache() {
|
|
1068
|
+
if (!instance)
|
|
1069
|
+
instance = new McpSchemaCache;
|
|
1070
|
+
return instance;
|
|
1071
|
+
}
|
|
1072
|
+
// src/tool-compatibility/base.ts
|
|
1073
|
+
class McpToolCompatibility {
|
|
1074
|
+
modelInfo;
|
|
1075
|
+
constructor(modelInfo) {
|
|
1076
|
+
this.modelInfo = modelInfo;
|
|
1077
|
+
}
|
|
1078
|
+
transformToolSchema(toolSchema) {
|
|
1079
|
+
return this.shouldApply() ? this.processSchema(toolSchema) : toolSchema;
|
|
1080
|
+
}
|
|
1081
|
+
processSchema(schema) {
|
|
1082
|
+
const processed = { ...schema };
|
|
1083
|
+
switch (processed.type) {
|
|
1084
|
+
case "string":
|
|
1085
|
+
return this.processTypeSchema(processed, this.getUnsupportedStringProperties());
|
|
1086
|
+
case "number":
|
|
1087
|
+
case "integer":
|
|
1088
|
+
return this.processTypeSchema(processed, this.getUnsupportedNumberProperties());
|
|
1089
|
+
case "array":
|
|
1090
|
+
return this.processArraySchema(processed);
|
|
1091
|
+
case "object":
|
|
1092
|
+
return this.processObjectSchema(processed);
|
|
1093
|
+
default:
|
|
1094
|
+
return this.processGenericSchema(processed);
|
|
1746
1095
|
}
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1096
|
+
}
|
|
1097
|
+
processTypeSchema(schema, unsupported) {
|
|
1098
|
+
const processed = { ...schema };
|
|
1099
|
+
const constraints = {};
|
|
1100
|
+
for (const prop of [
|
|
1101
|
+
"minLength",
|
|
1102
|
+
"maxLength",
|
|
1103
|
+
"pattern",
|
|
1104
|
+
"format",
|
|
1105
|
+
"enum",
|
|
1106
|
+
"minimum",
|
|
1107
|
+
"maximum",
|
|
1108
|
+
"exclusiveMinimum",
|
|
1109
|
+
"exclusiveMaximum",
|
|
1110
|
+
"multipleOf",
|
|
1111
|
+
"minItems",
|
|
1112
|
+
"maxItems",
|
|
1113
|
+
"uniqueItems"
|
|
1114
|
+
]) {
|
|
1115
|
+
if (schema[prop] !== undefined) {
|
|
1116
|
+
constraints[prop] = schema[prop];
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
for (const prop of unsupported) {
|
|
1120
|
+
delete processed[prop];
|
|
1121
|
+
}
|
|
1122
|
+
if (Object.keys(constraints).length > 0) {
|
|
1123
|
+
processed.description = this.mergeDescription(schema.description, constraints);
|
|
1124
|
+
}
|
|
1125
|
+
return processed;
|
|
1126
|
+
}
|
|
1127
|
+
processArraySchema(schema) {
|
|
1128
|
+
const processed = this.processTypeSchema(schema, this.getUnsupportedArrayProperties());
|
|
1129
|
+
if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
|
|
1130
|
+
processed.items = this.processSchema(schema.items);
|
|
1131
|
+
}
|
|
1132
|
+
return processed;
|
|
1133
|
+
}
|
|
1134
|
+
processObjectSchema(schema) {
|
|
1135
|
+
const processed = this.processTypeSchema(schema, this.getUnsupportedObjectProperties());
|
|
1136
|
+
if (schema.properties) {
|
|
1137
|
+
processed.properties = {};
|
|
1138
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
1139
|
+
processed.properties[key] = typeof prop === "object" && !Array.isArray(prop) ? this.processSchema(prop) : prop;
|
|
1769
1140
|
}
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
}
|
|
1141
|
+
}
|
|
1142
|
+
return processed;
|
|
1143
|
+
}
|
|
1144
|
+
processGenericSchema(schema) {
|
|
1145
|
+
const processed = { ...schema };
|
|
1146
|
+
for (const key of ["oneOf", "anyOf", "allOf"]) {
|
|
1147
|
+
if (Array.isArray(schema[key])) {
|
|
1148
|
+
processed[key] = schema[key].map((s) => typeof s === "object" ? this.processSchema(s) : s);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return processed;
|
|
1152
|
+
}
|
|
1153
|
+
mergeDescription(original, constraints) {
|
|
1154
|
+
const json = JSON.stringify(constraints);
|
|
1155
|
+
return original ? `${original}
|
|
1156
|
+
${json}` : json;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
// src/tool-compatibility/providers/openai.ts
|
|
1160
|
+
class OpenAIMcpCompatibility extends McpToolCompatibility {
|
|
1161
|
+
constructor(modelInfo) {
|
|
1162
|
+
super(modelInfo);
|
|
1163
|
+
}
|
|
1164
|
+
shouldApply() {
|
|
1165
|
+
return this.modelInfo.provider === "openai" && (!this.modelInfo.supportsStructuredOutputs || this.modelInfo.isReasoningModel === true);
|
|
1166
|
+
}
|
|
1167
|
+
getUnsupportedStringProperties() {
|
|
1168
|
+
return this.modelInfo.isReasoningModel || this.modelInfo.modelId.includes("gpt-3.5") ? ["format", "pattern"] : ["format"];
|
|
1169
|
+
}
|
|
1170
|
+
getUnsupportedNumberProperties() {
|
|
1171
|
+
return this.modelInfo.isReasoningModel ? ["exclusiveMinimum", "exclusiveMaximum", "multipleOf"] : [];
|
|
1172
|
+
}
|
|
1173
|
+
getUnsupportedArrayProperties() {
|
|
1174
|
+
return this.modelInfo.isReasoningModel ? ["uniqueItems"] : [];
|
|
1175
|
+
}
|
|
1176
|
+
getUnsupportedObjectProperties() {
|
|
1177
|
+
return ["minProperties", "maxProperties"];
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1773
1180
|
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
return
|
|
1181
|
+
class OpenAIReasoningMcpCompatibility extends McpToolCompatibility {
|
|
1182
|
+
constructor(modelInfo) {
|
|
1183
|
+
super(modelInfo);
|
|
1184
|
+
}
|
|
1185
|
+
shouldApply() {
|
|
1186
|
+
return this.modelInfo.provider === "openai" && this.modelInfo.isReasoningModel === true;
|
|
1187
|
+
}
|
|
1188
|
+
getUnsupportedStringProperties() {
|
|
1189
|
+
return ["format", "pattern", "minLength", "maxLength"];
|
|
1190
|
+
}
|
|
1191
|
+
getUnsupportedNumberProperties() {
|
|
1192
|
+
return ["exclusiveMinimum", "exclusiveMaximum", "multipleOf"];
|
|
1193
|
+
}
|
|
1194
|
+
getUnsupportedArrayProperties() {
|
|
1195
|
+
return ["uniqueItems", "minItems", "maxItems"];
|
|
1196
|
+
}
|
|
1197
|
+
getUnsupportedObjectProperties() {
|
|
1198
|
+
return ["minProperties", "maxProperties", "additionalProperties"];
|
|
1199
|
+
}
|
|
1200
|
+
mergeDescription(original, constraints) {
|
|
1201
|
+
const rules = [];
|
|
1202
|
+
if (constraints.minLength)
|
|
1203
|
+
rules.push(`minimum ${constraints.minLength} characters`);
|
|
1204
|
+
if (constraints.maxLength)
|
|
1205
|
+
rules.push(`maximum ${constraints.maxLength} characters`);
|
|
1206
|
+
if (constraints.minimum !== undefined)
|
|
1207
|
+
rules.push(`must be >= ${constraints.minimum}`);
|
|
1208
|
+
if (constraints.maximum !== undefined)
|
|
1209
|
+
rules.push(`must be <= ${constraints.maximum}`);
|
|
1210
|
+
if (constraints.format === "email")
|
|
1211
|
+
rules.push(`must be a valid email`);
|
|
1212
|
+
if (constraints.format === "uri" || constraints.format === "url")
|
|
1213
|
+
rules.push(`must be a valid URL`);
|
|
1214
|
+
if (constraints.pattern)
|
|
1215
|
+
rules.push(`must match: ${constraints.pattern}`);
|
|
1216
|
+
if (constraints.enum)
|
|
1217
|
+
rules.push(`must be one of: ${constraints.enum.join(", ")}`);
|
|
1218
|
+
if (constraints.minItems)
|
|
1219
|
+
rules.push(`at least ${constraints.minItems} items`);
|
|
1220
|
+
if (constraints.maxItems)
|
|
1221
|
+
rules.push(`at most ${constraints.maxItems} items`);
|
|
1222
|
+
const text = rules.length > 0 ? `IMPORTANT: ${rules.join(", ")}` : JSON.stringify(constraints);
|
|
1223
|
+
return original ? `${original}
|
|
1224
|
+
|
|
1225
|
+
${text}` : text;
|
|
1789
1226
|
}
|
|
1790
|
-
}
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// src/tool-compatibility/providers/anthropic.ts
|
|
1230
|
+
class AnthropicMcpCompatibility extends McpToolCompatibility {
|
|
1231
|
+
constructor(modelInfo) {
|
|
1232
|
+
super(modelInfo);
|
|
1233
|
+
}
|
|
1234
|
+
shouldApply() {
|
|
1235
|
+
return this.modelInfo.provider === "anthropic";
|
|
1236
|
+
}
|
|
1237
|
+
getUnsupportedStringProperties() {
|
|
1238
|
+
return [];
|
|
1239
|
+
}
|
|
1240
|
+
getUnsupportedNumberProperties() {
|
|
1241
|
+
return [];
|
|
1242
|
+
}
|
|
1243
|
+
getUnsupportedArrayProperties() {
|
|
1244
|
+
return [];
|
|
1245
|
+
}
|
|
1246
|
+
getUnsupportedObjectProperties() {
|
|
1247
|
+
return ["additionalProperties"];
|
|
1248
|
+
}
|
|
1249
|
+
mergeDescription(original, constraints) {
|
|
1250
|
+
const hints = [];
|
|
1251
|
+
if (constraints.additionalProperties === false)
|
|
1252
|
+
hints.push("Only use the specified properties");
|
|
1253
|
+
if (constraints.format === "date-time")
|
|
1254
|
+
hints.push("Use ISO 8601 format");
|
|
1255
|
+
if (constraints.pattern)
|
|
1256
|
+
hints.push(`Must match: ${constraints.pattern}`);
|
|
1257
|
+
const text = hints.join(". ");
|
|
1258
|
+
return original && text ? `${original}. ${text}` : original || text || "";
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
// src/tool-compatibility/providers/google.ts
|
|
1263
|
+
class GoogleMcpCompatibility extends McpToolCompatibility {
|
|
1264
|
+
constructor(modelInfo) {
|
|
1265
|
+
super(modelInfo);
|
|
1266
|
+
}
|
|
1267
|
+
shouldApply() {
|
|
1268
|
+
return this.modelInfo.provider === "google";
|
|
1269
|
+
}
|
|
1270
|
+
getUnsupportedStringProperties() {
|
|
1271
|
+
return ["minLength", "maxLength", "pattern", "format"];
|
|
1272
|
+
}
|
|
1273
|
+
getUnsupportedNumberProperties() {
|
|
1274
|
+
return ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"];
|
|
1275
|
+
}
|
|
1276
|
+
getUnsupportedArrayProperties() {
|
|
1277
|
+
return ["minItems", "maxItems", "uniqueItems"];
|
|
1278
|
+
}
|
|
1279
|
+
getUnsupportedObjectProperties() {
|
|
1280
|
+
return ["minProperties", "maxProperties", "additionalProperties"];
|
|
1281
|
+
}
|
|
1282
|
+
mergeDescription(original, constraints) {
|
|
1283
|
+
const rules = [];
|
|
1284
|
+
if (constraints.minLength)
|
|
1285
|
+
rules.push(`at least ${constraints.minLength} chars`);
|
|
1286
|
+
if (constraints.maxLength)
|
|
1287
|
+
rules.push(`at most ${constraints.maxLength} chars`);
|
|
1288
|
+
if (constraints.minimum !== undefined)
|
|
1289
|
+
rules.push(`>= ${constraints.minimum}`);
|
|
1290
|
+
if (constraints.maximum !== undefined)
|
|
1291
|
+
rules.push(`<= ${constraints.maximum}`);
|
|
1292
|
+
if (constraints.format === "email")
|
|
1293
|
+
rules.push(`valid email`);
|
|
1294
|
+
if (constraints.format === "uri" || constraints.format === "url")
|
|
1295
|
+
rules.push(`valid URL`);
|
|
1296
|
+
if (constraints.pattern)
|
|
1297
|
+
rules.push(`matches ${constraints.pattern}`);
|
|
1298
|
+
if (constraints.enum)
|
|
1299
|
+
rules.push(`one of: ${constraints.enum.join(", ")}`);
|
|
1300
|
+
if (constraints.minItems)
|
|
1301
|
+
rules.push(`>= ${constraints.minItems} items`);
|
|
1302
|
+
if (constraints.maxItems)
|
|
1303
|
+
rules.push(`<= ${constraints.maxItems} items`);
|
|
1304
|
+
if (constraints.uniqueItems)
|
|
1305
|
+
rules.push(`unique items`);
|
|
1306
|
+
const text = rules.length > 0 ? `Constraints: ${rules.join("; ")}` : "";
|
|
1307
|
+
return original && text ? `${original}
|
|
1308
|
+
|
|
1309
|
+
${text}` : original || text;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// src/tool-compatibility/index.ts
|
|
1314
|
+
function detectModelProvider(runtime) {
|
|
1315
|
+
const providerString = String(runtime?.modelProvider || "").toLowerCase();
|
|
1316
|
+
const modelString = String(runtime?.model || "").toLowerCase();
|
|
1317
|
+
let provider2 = "unknown";
|
|
1318
|
+
let supportsStructuredOutputs = false;
|
|
1319
|
+
let isReasoningModel = false;
|
|
1320
|
+
if (providerString.includes("openai") || modelString.includes("gpt-") || modelString.includes("o1") || modelString.includes("o3")) {
|
|
1321
|
+
provider2 = "openai";
|
|
1322
|
+
supportsStructuredOutputs = modelString.includes("gpt-4") || modelString.includes("o1") || modelString.includes("o3");
|
|
1323
|
+
isReasoningModel = modelString.includes("o1") || modelString.includes("o3");
|
|
1324
|
+
} else if (providerString.includes("anthropic") || modelString.includes("claude")) {
|
|
1325
|
+
provider2 = "anthropic";
|
|
1326
|
+
supportsStructuredOutputs = true;
|
|
1327
|
+
} else if (providerString.includes("google") || modelString.includes("gemini")) {
|
|
1328
|
+
provider2 = "google";
|
|
1329
|
+
supportsStructuredOutputs = true;
|
|
1330
|
+
} else if (providerString.includes("openrouter") || modelString.includes("openrouter")) {
|
|
1331
|
+
provider2 = "openrouter";
|
|
1332
|
+
}
|
|
1333
|
+
return { provider: provider2, modelId: modelString || providerString || "unknown", supportsStructuredOutputs, isReasoningModel };
|
|
1334
|
+
}
|
|
1335
|
+
function createMcpToolCompatibilitySync(runtime) {
|
|
1336
|
+
const info = detectModelProvider(runtime);
|
|
1337
|
+
switch (info.provider) {
|
|
1338
|
+
case "openai":
|
|
1339
|
+
return info.isReasoningModel ? new OpenAIReasoningMcpCompatibility(info) : new OpenAIMcpCompatibility(info);
|
|
1340
|
+
case "anthropic":
|
|
1341
|
+
return new AnthropicMcpCompatibility(info);
|
|
1342
|
+
case "google":
|
|
1343
|
+
return new GoogleMcpCompatibility(info);
|
|
1344
|
+
default:
|
|
1345
|
+
return null;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
var createMcpToolCompatibility = createMcpToolCompatibilitySync;
|
|
1791
1349
|
|
|
1792
1350
|
// src/service.ts
|
|
1793
|
-
var
|
|
1794
|
-
var import_client = require("@modelcontextprotocol/sdk/client/index.js");
|
|
1795
|
-
var import_sse = require("@modelcontextprotocol/sdk/client/sse.js");
|
|
1796
|
-
var import_stdio = require("@modelcontextprotocol/sdk/client/stdio.js");
|
|
1797
|
-
var import_streamableHttp = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
1798
|
-
var errMsg = (e) => e instanceof Error ? e.message : String(e);
|
|
1351
|
+
var err = (e) => e instanceof Error ? e.message : String(e);
|
|
1799
1352
|
|
|
1800
1353
|
class McpService extends import_core8.Service {
|
|
1801
1354
|
static serviceType = MCP_SERVICE_NAME;
|
|
1802
|
-
capabilityDescription = "Enables the agent to interact with MCP
|
|
1355
|
+
capabilityDescription = "Enables the agent to interact with MCP servers";
|
|
1803
1356
|
connections = new Map;
|
|
1804
1357
|
connectionStates = new Map;
|
|
1805
|
-
mcpProvider = {
|
|
1806
|
-
values: { mcp: {}, mcpText: "" },
|
|
1807
|
-
data: { mcp: {} },
|
|
1808
|
-
text: ""
|
|
1809
|
-
};
|
|
1358
|
+
mcpProvider = { values: { mcp: {}, mcpText: "" }, data: { mcp: {} }, text: "" };
|
|
1810
1359
|
pingConfig = DEFAULT_PING_CONFIG;
|
|
1811
1360
|
toolCompatibility = null;
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
return
|
|
1361
|
+
initPromise = null;
|
|
1362
|
+
registeredActions = new Map;
|
|
1363
|
+
schemaCache = getSchemaCache();
|
|
1364
|
+
lazyConnections = new Map;
|
|
1365
|
+
constructor(runtime) {
|
|
1366
|
+
super(runtime);
|
|
1367
|
+
this.initPromise = this.init();
|
|
1368
|
+
}
|
|
1369
|
+
static async start(runtime) {
|
|
1370
|
+
const svc = new McpService(runtime);
|
|
1371
|
+
await svc.initPromise;
|
|
1372
|
+
return svc;
|
|
1824
1373
|
}
|
|
1825
1374
|
async waitForInitialization() {
|
|
1826
|
-
|
|
1827
|
-
await this.initializationPromise;
|
|
1828
|
-
}
|
|
1375
|
+
await this.initPromise;
|
|
1829
1376
|
}
|
|
1830
1377
|
async stop() {
|
|
1831
1378
|
for (const name of this.connections.keys()) {
|
|
1832
|
-
await this.
|
|
1833
|
-
}
|
|
1834
|
-
for (const [name, state] of this.connectionStates.entries()) {
|
|
1835
|
-
if (state.pingInterval)
|
|
1836
|
-
clearInterval(state.pingInterval);
|
|
1837
|
-
if (state.reconnectTimeout)
|
|
1838
|
-
clearTimeout(state.reconnectTimeout);
|
|
1839
|
-
this.connectionStates.delete(name);
|
|
1379
|
+
await this.disconnect(name);
|
|
1840
1380
|
}
|
|
1841
1381
|
}
|
|
1842
|
-
async
|
|
1382
|
+
async init() {
|
|
1843
1383
|
try {
|
|
1844
|
-
const
|
|
1845
|
-
if (!
|
|
1384
|
+
const settings = this.getSettings();
|
|
1385
|
+
if (!settings?.servers || Object.keys(settings.servers).length === 0) {
|
|
1846
1386
|
this.mcpProvider = buildMcpProviderData([]);
|
|
1847
1387
|
return;
|
|
1848
1388
|
}
|
|
1849
|
-
const
|
|
1850
|
-
|
|
1851
|
-
const
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1389
|
+
const start = Date.now();
|
|
1390
|
+
const entries = Object.entries(settings.servers);
|
|
1391
|
+
const results = { cached: [], connected: [], failed: [] };
|
|
1392
|
+
await Promise.allSettled(entries.map(async ([name, config]) => {
|
|
1393
|
+
try {
|
|
1394
|
+
const hash = this.schemaCache.hashConfig(config);
|
|
1395
|
+
if (this.schemaCache.isEnabled) {
|
|
1396
|
+
const cached = await this.schemaCache.getSchemas(this.runtime.agentId, name, hash);
|
|
1397
|
+
if (cached) {
|
|
1398
|
+
this.registerToolsAsActions(name, McpSchemaCache.toTools(cached));
|
|
1399
|
+
this.lazyConnections.set(name, config);
|
|
1400
|
+
results.cached.push(`${name}:${cached.tools.length}`);
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
await this.connect(name, config);
|
|
1405
|
+
const server = this.connections.get(name)?.server;
|
|
1406
|
+
if (this.schemaCache.isEnabled && server?.tools?.length) {
|
|
1407
|
+
await this.schemaCache.setSchemas(this.runtime.agentId, name, hash, server.tools);
|
|
1408
|
+
}
|
|
1409
|
+
results.connected.push(`${name}:${server?.tools?.length || 0}`);
|
|
1410
|
+
} catch (e) {
|
|
1411
|
+
import_core8.logger.error({ error: err(e), server: name }, `[MCP] Failed: ${name}`);
|
|
1412
|
+
results.failed.push(name);
|
|
1413
|
+
}
|
|
1414
|
+
}));
|
|
1415
|
+
const total = results.cached.length + results.connected.length;
|
|
1416
|
+
import_core8.logger.info(`[MCP] Ready ${total}/${entries.length} in ${Date.now() - start}ms ` + `(${results.cached.length} cached, ${results.connected.length} connected, ${results.failed.length} failed)`);
|
|
1417
|
+
this.mcpProvider = buildMcpProviderData(this.getServers());
|
|
1418
|
+
} catch (e) {
|
|
1419
|
+
import_core8.logger.error({ error: err(e) }, "[MCP] Init failed");
|
|
1867
1420
|
this.mcpProvider = buildMcpProviderData([]);
|
|
1868
1421
|
}
|
|
1869
1422
|
}
|
|
1870
|
-
|
|
1871
|
-
let
|
|
1872
|
-
if (!
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
}
|
|
1878
|
-
return settings && typeof settings === "object" && settings.servers ? settings : undefined;
|
|
1879
|
-
}
|
|
1880
|
-
async updateServerConnections(serverConfigs) {
|
|
1881
|
-
const newNames = new Set(Object.keys(serverConfigs));
|
|
1882
|
-
for (const name of this.connections.keys()) {
|
|
1883
|
-
if (!newNames.has(name))
|
|
1884
|
-
await this.deleteConnection(name);
|
|
1885
|
-
}
|
|
1886
|
-
await Promise.allSettled(Object.entries(serverConfigs).map(async ([name, config]) => {
|
|
1887
|
-
const current = this.connections.get(name);
|
|
1888
|
-
if (!current) {
|
|
1889
|
-
await this.initializeConnection(name, config).catch((e) => import_core8.logger.error({ error: errMsg(e), serverName: name }, `[MCP] Failed: ${name}`));
|
|
1890
|
-
} else if (JSON.stringify(config) !== current.server.config) {
|
|
1891
|
-
await this.deleteConnection(name);
|
|
1892
|
-
await this.initializeConnection(name, config).catch((e) => import_core8.logger.error({ error: errMsg(e), serverName: name }, `[MCP] Failed: ${name}`));
|
|
1893
|
-
}
|
|
1894
|
-
}));
|
|
1423
|
+
getSettings() {
|
|
1424
|
+
let s = this.runtime.getSetting("mcp");
|
|
1425
|
+
if (!s?.servers)
|
|
1426
|
+
s = this.runtime.character?.settings?.mcp;
|
|
1427
|
+
if (!s?.servers)
|
|
1428
|
+
s = this.runtime.settings?.mcp;
|
|
1429
|
+
return s?.servers ? s : undefined;
|
|
1895
1430
|
}
|
|
1896
|
-
async
|
|
1897
|
-
await this.
|
|
1898
|
-
const state = {
|
|
1899
|
-
status: "connecting",
|
|
1900
|
-
reconnectAttempts: 0,
|
|
1901
|
-
consecutivePingFailures: 0
|
|
1902
|
-
};
|
|
1431
|
+
async connect(name, config) {
|
|
1432
|
+
await this.disconnect(name);
|
|
1433
|
+
const state = { status: "connecting", reconnectAttempts: 0, consecutivePingFailures: 0 };
|
|
1903
1434
|
this.connectionStates.set(name, state);
|
|
1904
1435
|
try {
|
|
1905
1436
|
const client = new import_client.Client({ name: "ElizaOS", version: "1.0.0" }, { capabilities: {} });
|
|
1906
|
-
const transport = config.type === "stdio" ?
|
|
1907
|
-
const
|
|
1437
|
+
const transport = config.type === "stdio" ? this.createStdioTransport(name, config) : this.createHttpTransport(name, config);
|
|
1438
|
+
const conn = {
|
|
1908
1439
|
server: { name, config: JSON.stringify(config), status: "connecting" },
|
|
1909
1440
|
client,
|
|
1910
1441
|
transport
|
|
1911
1442
|
};
|
|
1912
|
-
this.connections.set(name,
|
|
1913
|
-
this.setupTransportHandlers(name,
|
|
1914
|
-
const connectPromise = client.connect(transport);
|
|
1915
|
-
connectPromise.catch(() => {});
|
|
1443
|
+
this.connections.set(name, conn);
|
|
1444
|
+
this.setupTransportHandlers(name, conn, state, config.type === "stdio");
|
|
1916
1445
|
await Promise.race([
|
|
1917
|
-
|
|
1918
|
-
new Promise((_,
|
|
1446
|
+
client.connect(transport),
|
|
1447
|
+
new Promise((_, rej) => setTimeout(() => rej(new Error("Connection timeout")), 60000))
|
|
1919
1448
|
]);
|
|
1920
|
-
const
|
|
1921
|
-
const tools = await this.
|
|
1922
|
-
const resources =
|
|
1923
|
-
const resourceTemplates =
|
|
1924
|
-
|
|
1925
|
-
status: "connected",
|
|
1926
|
-
name,
|
|
1927
|
-
config: JSON.stringify(config),
|
|
1928
|
-
error: "",
|
|
1929
|
-
tools,
|
|
1930
|
-
resources,
|
|
1931
|
-
resourceTemplates
|
|
1932
|
-
};
|
|
1449
|
+
const caps = client.getServerCapabilities();
|
|
1450
|
+
const tools = await this.fetchTools(name);
|
|
1451
|
+
const resources = caps?.resources ? await this.fetchResources(name) : [];
|
|
1452
|
+
const resourceTemplates = caps?.resources ? await this.fetchResourceTemplates(name) : [];
|
|
1453
|
+
conn.server = { status: "connected", name, config: JSON.stringify(config), error: "", tools, resources, resourceTemplates };
|
|
1933
1454
|
state.status = "connected";
|
|
1934
1455
|
state.lastConnected = new Date;
|
|
1935
1456
|
state.reconnectAttempts = 0;
|
|
1936
1457
|
state.consecutivePingFailures = 0;
|
|
1937
|
-
|
|
1938
|
-
|
|
1458
|
+
if (config.type === "stdio")
|
|
1459
|
+
this.startPingMonitor(name);
|
|
1460
|
+
this.registerToolsAsActions(name, tools);
|
|
1461
|
+
import_core8.logger.info(`[MCP] Connected: ${name} (${tools?.length || 0} tools)`);
|
|
1462
|
+
} catch (e) {
|
|
1939
1463
|
state.status = "disconnected";
|
|
1940
|
-
state.lastError =
|
|
1941
|
-
this.
|
|
1942
|
-
throw
|
|
1464
|
+
state.lastError = e instanceof Error ? e : new Error(String(e));
|
|
1465
|
+
this.handleDisconnect(name, e);
|
|
1466
|
+
throw e;
|
|
1943
1467
|
}
|
|
1944
1468
|
}
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
const
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
this.appendErrorMessage(connection, msg);
|
|
1469
|
+
async disconnect(name) {
|
|
1470
|
+
this.unregisterToolsAsActions(name);
|
|
1471
|
+
const conn = this.connections.get(name);
|
|
1472
|
+
if (conn) {
|
|
1473
|
+
try {
|
|
1474
|
+
await conn.transport.close();
|
|
1475
|
+
await conn.client.close();
|
|
1476
|
+
} catch (e) {
|
|
1477
|
+
import_core8.logger.debug({ error: err(e), server: name }, `[MCP] Error during disconnect (expected during shutdown)`);
|
|
1955
1478
|
}
|
|
1956
|
-
|
|
1957
|
-
|
|
1479
|
+
this.connections.delete(name);
|
|
1480
|
+
}
|
|
1481
|
+
const state = this.connectionStates.get(name);
|
|
1482
|
+
if (state) {
|
|
1483
|
+
if (state.pingInterval)
|
|
1484
|
+
clearInterval(state.pingInterval);
|
|
1485
|
+
if (state.reconnectTimeout)
|
|
1486
|
+
clearTimeout(state.reconnectTimeout);
|
|
1487
|
+
this.connectionStates.delete(name);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
createStdioTransport(name, config) {
|
|
1491
|
+
if (!config.command)
|
|
1492
|
+
throw new Error(`Missing command for stdio server ${name}`);
|
|
1493
|
+
return new import_stdio.StdioClientTransport({
|
|
1494
|
+
command: config.command,
|
|
1495
|
+
args: config.args,
|
|
1496
|
+
env: { ...config.env, ...process.env.PATH ? { PATH: process.env.PATH } : {} },
|
|
1497
|
+
stderr: "pipe",
|
|
1498
|
+
cwd: config.cwd
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1501
|
+
createHttpTransport(name, config) {
|
|
1502
|
+
if (!config.url)
|
|
1503
|
+
throw new Error(`Missing URL for server ${name}`);
|
|
1504
|
+
const url = new URL(config.url);
|
|
1505
|
+
const opts = config.headers ? { requestInit: { headers: config.headers } } : undefined;
|
|
1506
|
+
return config.type === "sse" ? new import_sse.SSEClientTransport(url, opts) : new import_streamableHttp.StreamableHTTPClientTransport(url, opts);
|
|
1507
|
+
}
|
|
1508
|
+
setupTransportHandlers(name, conn, state, isStdio) {
|
|
1509
|
+
conn.transport.onerror = async (e) => {
|
|
1510
|
+
const msg = e?.message || "";
|
|
1511
|
+
if (isStdio || !msg.includes("SSE") && !msg.includes("timeout") && msg !== "undefined") {
|
|
1512
|
+
import_core8.logger.error({ error: e, server: name }, `[MCP] Transport error: ${name}`);
|
|
1513
|
+
conn.server.status = "disconnected";
|
|
1514
|
+
conn.server.error = (conn.server.error || "") + `
|
|
1515
|
+
` + msg;
|
|
1516
|
+
}
|
|
1517
|
+
if (isStdio)
|
|
1518
|
+
this.handleDisconnect(name, e);
|
|
1958
1519
|
};
|
|
1959
|
-
|
|
1960
|
-
if (
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
this.handleDisconnection(name, new Error("Transport closed"));
|
|
1520
|
+
conn.transport.onclose = async () => {
|
|
1521
|
+
if (isStdio) {
|
|
1522
|
+
conn.server.status = "disconnected";
|
|
1523
|
+
this.handleDisconnect(name, new Error("Transport closed"));
|
|
1964
1524
|
}
|
|
1965
1525
|
};
|
|
1966
1526
|
}
|
|
1967
|
-
|
|
1968
|
-
const connection = this.connections.get(name);
|
|
1969
|
-
if (!connection)
|
|
1970
|
-
return;
|
|
1971
|
-
const config = JSON.parse(connection.server.config);
|
|
1972
|
-
if (config.type !== "stdio")
|
|
1973
|
-
return;
|
|
1527
|
+
startPingMonitor(name) {
|
|
1974
1528
|
const state = this.connectionStates.get(name);
|
|
1975
1529
|
if (!state || !this.pingConfig.enabled)
|
|
1976
1530
|
return;
|
|
1977
1531
|
if (state.pingInterval)
|
|
1978
1532
|
clearInterval(state.pingInterval);
|
|
1979
|
-
state.pingInterval = setInterval(() => {
|
|
1980
|
-
this.
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1533
|
+
state.pingInterval = setInterval(async () => {
|
|
1534
|
+
const conn = this.connections.get(name);
|
|
1535
|
+
if (!conn)
|
|
1536
|
+
return;
|
|
1537
|
+
try {
|
|
1538
|
+
await Promise.race([
|
|
1539
|
+
conn.client.listTools(),
|
|
1540
|
+
new Promise((_, r) => setTimeout(() => r(new Error("Ping timeout")), this.pingConfig.timeoutMs))
|
|
1541
|
+
]);
|
|
1542
|
+
state.consecutivePingFailures = 0;
|
|
1543
|
+
} catch (e) {
|
|
1544
|
+
state.consecutivePingFailures++;
|
|
1545
|
+
if (state.consecutivePingFailures >= this.pingConfig.failuresBeforeDisconnect) {
|
|
1546
|
+
this.handleDisconnect(name, e);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1984
1549
|
}, this.pingConfig.intervalMs);
|
|
1985
1550
|
}
|
|
1986
|
-
|
|
1987
|
-
const connection = this.connections.get(name);
|
|
1988
|
-
if (!connection)
|
|
1989
|
-
throw new Error(`No connection: ${name}`);
|
|
1990
|
-
await Promise.race([
|
|
1991
|
-
connection.client.listTools(),
|
|
1992
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error("Ping timeout")), this.pingConfig.timeoutMs))
|
|
1993
|
-
]);
|
|
1994
|
-
const state = this.connectionStates.get(name);
|
|
1995
|
-
if (state)
|
|
1996
|
-
state.consecutivePingFailures = 0;
|
|
1997
|
-
}
|
|
1998
|
-
handlePingFailure(name, error) {
|
|
1999
|
-
const state = this.connectionStates.get(name);
|
|
2000
|
-
if (!state)
|
|
2001
|
-
return;
|
|
2002
|
-
state.consecutivePingFailures++;
|
|
2003
|
-
if (state.consecutivePingFailures >= this.pingConfig.failuresBeforeDisconnect) {
|
|
2004
|
-
import_core8.logger.warn(`Ping failures exceeded for ${name}, disconnecting and attempting reconnect.`);
|
|
2005
|
-
this.handleDisconnection(name, error);
|
|
2006
|
-
}
|
|
2007
|
-
}
|
|
2008
|
-
handleDisconnection(name, error) {
|
|
1551
|
+
handleDisconnect(name, error) {
|
|
2009
1552
|
const state = this.connectionStates.get(name);
|
|
2010
1553
|
if (!state)
|
|
2011
1554
|
return;
|
|
@@ -2016,140 +1559,131 @@ class McpService extends import_core8.Service {
|
|
|
2016
1559
|
if (state.reconnectTimeout)
|
|
2017
1560
|
clearTimeout(state.reconnectTimeout);
|
|
2018
1561
|
if (state.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
2019
|
-
import_core8.logger.error(`Max reconnect attempts
|
|
1562
|
+
import_core8.logger.error(`[MCP] Max reconnect attempts for ${name}`);
|
|
2020
1563
|
return;
|
|
2021
1564
|
}
|
|
2022
1565
|
const delay = INITIAL_RETRY_DELAY * Math.pow(BACKOFF_MULTIPLIER, state.reconnectAttempts);
|
|
2023
1566
|
state.reconnectTimeout = setTimeout(async () => {
|
|
2024
1567
|
state.reconnectAttempts++;
|
|
2025
|
-
import_core8.logger.info(`Attempting to reconnect to ${name} (attempt ${state.reconnectAttempts})...`);
|
|
2026
1568
|
const config = this.connections.get(name)?.server.config;
|
|
2027
1569
|
if (config) {
|
|
2028
1570
|
try {
|
|
2029
|
-
await this.
|
|
2030
|
-
} catch (
|
|
2031
|
-
|
|
2032
|
-
this.handleDisconnection(name, err);
|
|
1571
|
+
await this.connect(name, JSON.parse(config));
|
|
1572
|
+
} catch (e) {
|
|
1573
|
+
this.handleDisconnect(name, e);
|
|
2033
1574
|
}
|
|
2034
1575
|
}
|
|
2035
1576
|
}, delay);
|
|
2036
1577
|
}
|
|
2037
|
-
async
|
|
2038
|
-
const
|
|
2039
|
-
if (
|
|
2040
|
-
try {
|
|
2041
|
-
await connection.transport.close();
|
|
2042
|
-
await connection.client.close();
|
|
2043
|
-
} catch (error) {
|
|
2044
|
-
import_core8.logger.error({ error: errMsg(error), serverName: name }, `Failed to close transport for ${name}`);
|
|
2045
|
-
}
|
|
2046
|
-
this.connections.delete(name);
|
|
2047
|
-
}
|
|
2048
|
-
const state = this.connectionStates.get(name);
|
|
2049
|
-
if (state) {
|
|
2050
|
-
if (state.pingInterval)
|
|
2051
|
-
clearInterval(state.pingInterval);
|
|
2052
|
-
if (state.reconnectTimeout)
|
|
2053
|
-
clearTimeout(state.reconnectTimeout);
|
|
2054
|
-
this.connectionStates.delete(name);
|
|
2055
|
-
}
|
|
2056
|
-
}
|
|
2057
|
-
async buildStdioClientTransport(name, config) {
|
|
2058
|
-
if (!config.command) {
|
|
2059
|
-
throw new Error(`Missing command for stdio MCP server ${name}`);
|
|
2060
|
-
}
|
|
2061
|
-
return new import_stdio.StdioClientTransport({
|
|
2062
|
-
command: config.command,
|
|
2063
|
-
args: config.args,
|
|
2064
|
-
env: {
|
|
2065
|
-
...config.env,
|
|
2066
|
-
...process.env.PATH ? { PATH: process.env.PATH } : {}
|
|
2067
|
-
},
|
|
2068
|
-
stderr: "pipe",
|
|
2069
|
-
cwd: config.cwd
|
|
2070
|
-
});
|
|
2071
|
-
}
|
|
2072
|
-
async buildHttpClientTransport(name, config) {
|
|
2073
|
-
if (!config.url)
|
|
2074
|
-
throw new Error(`Missing URL for MCP server ${name}`);
|
|
2075
|
-
const url = new URL(config.url);
|
|
2076
|
-
const opts = config.headers ? { requestInit: { headers: config.headers } } : undefined;
|
|
2077
|
-
if (config.type === "sse") {
|
|
2078
|
-
import_core8.logger.warn(`[MCP] "${name}": SSE requires Redis. Use "streamable-http" instead.`);
|
|
2079
|
-
return new import_sse.SSEClientTransport(url, opts);
|
|
2080
|
-
}
|
|
2081
|
-
return new import_streamableHttp.StreamableHTTPClientTransport(url, opts);
|
|
2082
|
-
}
|
|
2083
|
-
appendErrorMessage(connection, error) {
|
|
2084
|
-
connection.server.error = connection.server.error ? `${connection.server.error}
|
|
2085
|
-
${error}` : error;
|
|
2086
|
-
}
|
|
2087
|
-
async fetchToolsList(serverName) {
|
|
2088
|
-
const connection = this.connections.get(serverName);
|
|
2089
|
-
if (!connection)
|
|
1578
|
+
async fetchTools(name) {
|
|
1579
|
+
const conn = this.connections.get(name);
|
|
1580
|
+
if (!conn)
|
|
2090
1581
|
return [];
|
|
2091
1582
|
try {
|
|
2092
|
-
const
|
|
2093
|
-
return (
|
|
2094
|
-
if (!
|
|
2095
|
-
return
|
|
2096
|
-
if (!this.
|
|
2097
|
-
this.
|
|
1583
|
+
const res = await conn.client.listTools();
|
|
1584
|
+
return (res?.tools || []).map((t) => {
|
|
1585
|
+
if (!t.inputSchema)
|
|
1586
|
+
return t;
|
|
1587
|
+
if (!this.toolCompatibility)
|
|
1588
|
+
this.toolCompatibility = createMcpToolCompatibilitySync(this.runtime);
|
|
2098
1589
|
try {
|
|
2099
|
-
return { ...
|
|
2100
|
-
} catch {
|
|
2101
|
-
|
|
1590
|
+
return { ...t, inputSchema: this.toolCompatibility.transformToolSchema(t.inputSchema) };
|
|
1591
|
+
} catch (e) {
|
|
1592
|
+
import_core8.logger.debug({ error: err(e), tool: t.name }, `[MCP] Schema transform failed, using original`);
|
|
1593
|
+
return t;
|
|
2102
1594
|
}
|
|
2103
1595
|
});
|
|
2104
|
-
} catch (
|
|
2105
|
-
import_core8.logger.
|
|
1596
|
+
} catch (e) {
|
|
1597
|
+
import_core8.logger.warn({ error: err(e), server: name }, `[MCP] Failed to fetch tools`);
|
|
2106
1598
|
return [];
|
|
2107
1599
|
}
|
|
2108
1600
|
}
|
|
2109
|
-
async
|
|
2110
|
-
const conn = this.connections.get(name);
|
|
2111
|
-
if (!conn)
|
|
2112
|
-
return [];
|
|
1601
|
+
async fetchResources(name) {
|
|
2113
1602
|
try {
|
|
2114
|
-
return (await
|
|
2115
|
-
} catch (
|
|
2116
|
-
import_core8.logger.debug({ error:
|
|
1603
|
+
return (await this.connections.get(name)?.client.listResources())?.resources || [];
|
|
1604
|
+
} catch (e) {
|
|
1605
|
+
import_core8.logger.debug({ error: err(e), server: name }, `[MCP] Failed to fetch resources`);
|
|
2117
1606
|
return [];
|
|
2118
1607
|
}
|
|
2119
1608
|
}
|
|
2120
|
-
async
|
|
2121
|
-
const conn = this.connections.get(name);
|
|
2122
|
-
if (!conn)
|
|
2123
|
-
return [];
|
|
1609
|
+
async fetchResourceTemplates(name) {
|
|
2124
1610
|
try {
|
|
2125
|
-
return (await
|
|
2126
|
-
} catch (
|
|
2127
|
-
import_core8.logger.debug({ error:
|
|
1611
|
+
return (await this.connections.get(name)?.client.listResourceTemplates())?.resourceTemplates || [];
|
|
1612
|
+
} catch (e) {
|
|
1613
|
+
import_core8.logger.debug({ error: err(e), server: name }, `[MCP] Failed to fetch resource templates`);
|
|
2128
1614
|
return [];
|
|
2129
1615
|
}
|
|
2130
1616
|
}
|
|
1617
|
+
registerToolsAsActions(serverName, tools) {
|
|
1618
|
+
if (!tools?.length)
|
|
1619
|
+
return;
|
|
1620
|
+
const existing = new Set([...this.runtime.actions.map((a) => a.name), ...this.registeredActions.keys()]);
|
|
1621
|
+
const actions = createMcpToolActions(serverName, tools, existing);
|
|
1622
|
+
for (const action of actions) {
|
|
1623
|
+
if (!this.registeredActions.has(action.name)) {
|
|
1624
|
+
this.runtime.registerAction(action);
|
|
1625
|
+
this.registeredActions.set(action.name, action);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
import_core8.logger.info(`[MCP] Registered ${actions.length} actions for ${serverName}`);
|
|
1629
|
+
}
|
|
1630
|
+
unregisterToolsAsActions(serverName) {
|
|
1631
|
+
const toRemove = [];
|
|
1632
|
+
for (const [name, action] of this.registeredActions) {
|
|
1633
|
+
if (action._mcpMeta.serverName === serverName)
|
|
1634
|
+
toRemove.push(name);
|
|
1635
|
+
}
|
|
1636
|
+
for (const name of toRemove) {
|
|
1637
|
+
const idx = this.runtime.actions.findIndex((a) => a.name === name);
|
|
1638
|
+
if (idx !== -1)
|
|
1639
|
+
this.runtime.actions.splice(idx, 1);
|
|
1640
|
+
this.registeredActions.delete(name);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
2131
1643
|
getServers() {
|
|
2132
|
-
return Array.from(this.connections.values()).filter((
|
|
1644
|
+
return Array.from(this.connections.values()).filter((c) => !c.server.disabled).map((c) => c.server);
|
|
2133
1645
|
}
|
|
2134
1646
|
getProviderData() {
|
|
2135
1647
|
return this.mcpProvider;
|
|
2136
1648
|
}
|
|
1649
|
+
getRegisteredActions() {
|
|
1650
|
+
return Array.from(this.registeredActions.values());
|
|
1651
|
+
}
|
|
1652
|
+
isLazyConnection(serverName) {
|
|
1653
|
+
return this.lazyConnections.has(serverName);
|
|
1654
|
+
}
|
|
1655
|
+
async ensureConnected(serverName) {
|
|
1656
|
+
if (this.connections.has(serverName))
|
|
1657
|
+
return;
|
|
1658
|
+
const config = this.lazyConnections.get(serverName);
|
|
1659
|
+
if (!config)
|
|
1660
|
+
throw new Error(`Unknown server: ${serverName}`);
|
|
1661
|
+
const start = Date.now();
|
|
1662
|
+
await this.connect(serverName, config);
|
|
1663
|
+
this.lazyConnections.delete(serverName);
|
|
1664
|
+
const server = this.connections.get(serverName)?.server;
|
|
1665
|
+
if (this.schemaCache.isEnabled && server?.tools?.length) {
|
|
1666
|
+
await this.schemaCache.setSchemas(this.runtime.agentId, serverName, this.schemaCache.hashConfig(config), server.tools);
|
|
1667
|
+
}
|
|
1668
|
+
import_core8.logger.info(`[MCP] Lazy connected: ${serverName} in ${Date.now() - start}ms`);
|
|
1669
|
+
this.mcpProvider = buildMcpProviderData(this.getServers());
|
|
1670
|
+
}
|
|
2137
1671
|
async callTool(serverName, toolName, args) {
|
|
1672
|
+
await this.ensureConnected(serverName);
|
|
2138
1673
|
const conn = this.connections.get(serverName);
|
|
2139
1674
|
if (!conn)
|
|
2140
1675
|
throw new Error(`No connection: ${serverName}`);
|
|
2141
1676
|
if (conn.server.disabled)
|
|
2142
1677
|
throw new Error(`Server disabled: ${serverName}`);
|
|
2143
1678
|
const config = JSON.parse(conn.server.config);
|
|
2144
|
-
const timeout =
|
|
2145
|
-
const result = await conn.client.callTool({ name: toolName, arguments: args }, undefined, {
|
|
2146
|
-
timeout
|
|
2147
|
-
});
|
|
1679
|
+
const timeout = config.timeoutInMillis || DEFAULT_MCP_TIMEOUT_SECONDS;
|
|
1680
|
+
const result = await conn.client.callTool({ name: toolName, arguments: args }, undefined, { timeout });
|
|
2148
1681
|
if (!result.content)
|
|
2149
|
-
throw new Error("Invalid tool result
|
|
1682
|
+
throw new Error("Invalid tool result");
|
|
2150
1683
|
return result;
|
|
2151
1684
|
}
|
|
2152
1685
|
async readResource(serverName, uri) {
|
|
1686
|
+
await this.ensureConnected(serverName);
|
|
2153
1687
|
const conn = this.connections.get(serverName);
|
|
2154
1688
|
if (!conn)
|
|
2155
1689
|
throw new Error(`No connection: ${serverName}`);
|
|
@@ -2162,27 +1696,8 @@ ${error}` : error;
|
|
|
2162
1696
|
if (!conn)
|
|
2163
1697
|
throw new Error(`No connection: ${serverName}`);
|
|
2164
1698
|
const config = conn.server.config;
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
await this.deleteConnection(serverName);
|
|
2168
|
-
await this.initializeConnection(serverName, JSON.parse(config));
|
|
2169
|
-
}
|
|
2170
|
-
initializeToolCompatibility() {
|
|
2171
|
-
if (this.compatibilityInitialized)
|
|
2172
|
-
return;
|
|
2173
|
-
this.toolCompatibility = createMcpToolCompatibilitySync(this.runtime);
|
|
2174
|
-
this.compatibilityInitialized = true;
|
|
2175
|
-
}
|
|
2176
|
-
applyToolCompatibility(toolSchema) {
|
|
2177
|
-
if (!this.compatibilityInitialized)
|
|
2178
|
-
this.initializeToolCompatibility();
|
|
2179
|
-
if (!this.toolCompatibility || !toolSchema)
|
|
2180
|
-
return toolSchema;
|
|
2181
|
-
try {
|
|
2182
|
-
return this.toolCompatibility.transformToolSchema(toolSchema);
|
|
2183
|
-
} catch {
|
|
2184
|
-
return toolSchema;
|
|
2185
|
-
}
|
|
1699
|
+
await this.disconnect(serverName);
|
|
1700
|
+
await this.connect(serverName, JSON.parse(config));
|
|
2186
1701
|
}
|
|
2187
1702
|
}
|
|
2188
1703
|
|
|
@@ -2194,9 +1709,9 @@ var mcpPlugin = {
|
|
|
2194
1709
|
import_core9.logger.info("Initializing MCP plugin...");
|
|
2195
1710
|
},
|
|
2196
1711
|
services: [McpService],
|
|
2197
|
-
actions: [
|
|
1712
|
+
actions: [readResourceAction],
|
|
2198
1713
|
providers: [provider]
|
|
2199
1714
|
};
|
|
2200
1715
|
var src_default = mcpPlugin;
|
|
2201
1716
|
|
|
2202
|
-
//# debugId=
|
|
1717
|
+
//# debugId=48F4A8AA4A8260E964756E2164756E21
|