@elizaos/plugin-mcp 1.7.0 → 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.
Files changed (56) hide show
  1. package/dist/actions/dynamic-tool-actions.d.ts +16 -0
  2. package/dist/actions/dynamic-tool-actions.d.ts.map +1 -0
  3. package/dist/cache/index.d.ts +5 -0
  4. package/dist/cache/index.d.ts.map +1 -0
  5. package/dist/cache/schema-cache.d.ts +34 -0
  6. package/dist/cache/schema-cache.d.ts.map +1 -0
  7. package/dist/cjs/index.cjs +1359 -1954
  8. package/dist/cjs/index.js.map +24 -25
  9. package/dist/index.d.ts +3 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1401 -2012
  12. package/dist/index.js.map +24 -25
  13. package/dist/provider.d.ts.map +1 -1
  14. package/dist/service.d.ts +26 -28
  15. package/dist/service.d.ts.map +1 -1
  16. package/dist/tool-compatibility/base.d.ts +25 -0
  17. package/dist/tool-compatibility/base.d.ts.map +1 -0
  18. package/dist/tool-compatibility/index.d.ts +7 -52
  19. package/dist/tool-compatibility/index.d.ts.map +1 -1
  20. package/dist/tool-compatibility/providers/anthropic.d.ts +2 -3
  21. package/dist/tool-compatibility/providers/anthropic.d.ts.map +1 -1
  22. package/dist/tool-compatibility/providers/google.d.ts +2 -3
  23. package/dist/tool-compatibility/providers/google.d.ts.map +1 -1
  24. package/dist/tool-compatibility/providers/openai.d.ts +2 -3
  25. package/dist/tool-compatibility/providers/openai.d.ts.map +1 -1
  26. package/dist/types.d.ts +52 -85
  27. package/dist/types.d.ts.map +1 -1
  28. package/dist/utils/action-naming.d.ts +9 -0
  29. package/dist/utils/action-naming.d.ts.map +1 -0
  30. package/dist/utils/error.d.ts +1 -11
  31. package/dist/utils/error.d.ts.map +1 -1
  32. package/dist/utils/json.d.ts.map +1 -1
  33. package/dist/utils/mcp.d.ts.map +1 -1
  34. package/dist/utils/processing.d.ts +5 -11
  35. package/dist/utils/processing.d.ts.map +1 -1
  36. package/dist/utils/schema-converter.d.ts +9 -0
  37. package/dist/utils/schema-converter.d.ts.map +1 -0
  38. package/dist/utils/validation.d.ts +1 -24
  39. package/dist/utils/validation.d.ts.map +1 -1
  40. package/dist/utils/wrapper.d.ts +6 -15
  41. package/dist/utils/wrapper.d.ts.map +1 -1
  42. package/package.json +3 -1
  43. package/dist/actions/callToolAction.d.ts +0 -3
  44. package/dist/actions/callToolAction.d.ts.map +0 -1
  45. package/dist/templates/feedbackTemplate.d.ts +0 -2
  46. package/dist/templates/feedbackTemplate.d.ts.map +0 -1
  47. package/dist/templates/toolReasoningTemplate.d.ts +0 -2
  48. package/dist/templates/toolReasoningTemplate.d.ts.map +0 -1
  49. package/dist/templates/toolSelectionTemplate.d.ts +0 -3
  50. package/dist/templates/toolSelectionTemplate.d.ts.map +0 -1
  51. package/dist/utils/handler.d.ts +0 -3
  52. package/dist/utils/handler.d.ts.map +0 -1
  53. package/dist/utils/schemas.d.ts +0 -69
  54. package/dist/utils/schemas.d.ts.map +0 -1
  55. package/dist/utils/selection.d.ts +0 -38
  56. package/dist/utils/selection.d.ts.map +0 -1
@@ -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
- ToolSelectionSchema: () => ToolSelectionSchema,
52
+ createMcpToolActions: () => createMcpToolActions,
53
+ createMcpToolAction: () => createMcpToolAction,
552
54
  ResourceSelectionSchema: () => ResourceSelectionSchema,
553
55
  McpToolCompatibility: () => McpToolCompatibility,
554
56
  McpService: () => McpService,
555
- MIN_MCP_TIMEOUT_SECONDS: () => MIN_MCP_TIMEOUT_SECONDS,
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,72 +66,76 @@ __export(exports_src, {
564
66
  module.exports = __toCommonJS(exports_src);
565
67
  var import_core9 = require("@elizaos/core");
566
68
 
567
- // src/actions/callToolAction.ts
568
- var import_core6 = require("@elizaos/core");
69
+ // src/actions/readResourceAction.ts
70
+ var import_core5 = require("@elizaos/core");
569
71
 
570
- // src/types.ts
571
- var MCP_SERVICE_NAME = "mcp";
572
- var DEFAULT_MCP_TIMEOUT_SECONDS = 60000;
573
- var MIN_MCP_TIMEOUT_SECONDS = 1;
574
- var DEFAULT_MAX_RETRIES = 2;
575
- var ToolSelectionSchema = {
576
- type: "object",
577
- required: ["serverName", "toolName", "arguments"],
578
- properties: {
579
- serverName: {
580
- type: "string",
581
- minLength: 1,
582
- errorMessage: "serverName must not be empty"
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
- }
599
- };
600
- var ResourceSelectionSchema = {
601
- type: "object",
602
- required: ["serverName", "uri"],
603
- properties: {
604
- serverName: {
605
- type: "string",
606
- minLength: 1,
607
- errorMessage: "serverName must not be empty"
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
- }
620
- }
621
- };
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;
72
+ // src/templates/resourceSelectionTemplate.ts
73
+ var resourceSelectionTemplate = `
74
+ {{{mcpProvider.text}}}
631
75
 
632
- // src/utils/error.ts
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
+ `;
113
+
114
+ // src/types.ts
115
+ var MCP_SERVICE_NAME = "mcp";
116
+ var DEFAULT_MCP_TIMEOUT_SECONDS = 60000;
117
+ var DEFAULT_MAX_RETRIES = 2;
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
126
+ };
127
+ var ResourceSelectionSchema = {
128
+ type: "object",
129
+ required: ["serverName", "uri"],
130
+ properties: {
131
+ serverName: { type: "string", minLength: 1 },
132
+ uri: { type: "string", minLength: 1 },
133
+ reasoning: { type: "string" },
134
+ noResourceAvailable: { type: "boolean" }
135
+ }
136
+ };
137
+
138
+ // src/utils/error.ts
633
139
  var import_core = require("@elizaos/core");
634
140
 
635
141
  // src/templates/errorAnalysisPrompt.ts
@@ -655,57 +161,28 @@ Your response:
655
161
  `;
656
162
 
657
163
  // src/utils/error.ts
658
- async function handleMcpError(state, mcpProvider, error, runtime2, message, type, callback) {
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 }, `Error executing MCP ${type}: ${errorMessage}`);
661
- let responseText = `I'm sorry, I wasn't able to get the information you requested. There seems to be an issue with the ${type} right now. Is there something else I can help you with?`;
662
- let thoughtText = `Error calling MCP ${type} and failed to generate a custom response. Providing a generic fallback response.`;
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 errorResponse = await runtime2.useModel(import_core.ModelType.TEXT_SMALL, {
679
- prompt
680
- });
681
- responseText = errorResponse;
682
- thoughtText = `Error calling MCP ${type}: ${errorMessage}. Providing a helpful response to the user.`;
683
- await callback({
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
- success: false,
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
  };
@@ -715,921 +192,350 @@ async function handleMcpError(state, mcpProvider, error, runtime2, message, type
715
192
  var import_core2 = require("@elizaos/core");
716
193
  var import_core3 = require("@elizaos/core");
717
194
 
718
- // src/templates/resourceAnalysisTemplate.ts
719
- var resourceAnalysisTemplate = `
720
- {{{mcpProvider.text}}}
721
-
722
- {{{recentMessages}}}
723
-
724
- # Prompt
725
-
726
- You are a helpful assistant responding to a user's request. You've just accessed the resource "{{{uri}}}" to help answer this request.
727
-
728
- Original user request: "{{{userMessage}}}"
729
-
730
- Resource metadata:
731
- {{{resourceMeta}}
732
-
733
- Resource content:
734
- {{{resourceContent}}
735
-
736
- Instructions:
737
- 1. Analyze how well the resource's content addresses the user's specific question or need
738
- 2. Identify the most relevant information from the resource
739
- 3. Create a natural, conversational response that incorporates this information
740
- 4. If the resource content is insufficient, acknowledge its limitations and explain what you can determine
741
- 5. Do not start with phrases like "According to the resource" or "Here's what I found" - instead, integrate the information naturally
742
- 6. Maintain your helpful, intelligent assistant personality while presenting the information
743
-
744
- Your response (written as if directly to the user):
745
- `;
746
-
747
- // src/templates/toolReasoningTemplate.ts
748
- var toolReasoningTemplate = `
749
- {{{mcpProvider.text}}}
750
-
751
- {{{recentMessages}}}
752
-
753
- # Prompt
754
-
755
- 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.
756
-
757
- Original user request: "{{{userMessage}}}"
758
-
759
- Tool response:
760
- {{{toolOutput}}}
761
-
762
- {{#if hasAttachments}}
763
- The tool also returned images or other media that will be shared with the user.
764
- {{/if}}
765
-
766
- Instructions:
767
- 1. Analyze how well the tool's response addresses the user's specific question or need
768
- 2. Identify the most relevant information from the tool's output
769
- 3. Create a natural, conversational response that incorporates this information
770
- 4. If the tool's response is insufficient, acknowledge its limitations and explain what you can determine
771
- 5. Do not start with phrases like "I used the X tool" or "Here's what I found" - instead, integrate the information naturally
772
- 6. Maintain your helpful, intelligent assistant personality while presenting the information
773
-
774
- Your response (written as if directly to the user):
775
- `;
776
-
777
- // src/utils/mcp.ts
778
- async function createMcpMemory(runtime2, message, type, serverName, content, metadata) {
779
- const memory = await runtime2.addEmbeddingToMemory({
780
- entityId: message.entityId,
781
- agentId: runtime2.agentId,
782
- roomId: message.roomId,
783
- content: {
784
- text: `Used the "${type}" from "${serverName}" server.
785
- Content: ${content}`,
786
- metadata: {
787
- ...metadata,
788
- serverName
789
- }
790
- }
791
- });
792
- await runtime2.createMemory(memory, type === "resource" ? "resources" : "tools", true);
793
- }
794
- function buildMcpProviderData(servers) {
795
- const mcpData = {};
796
- let textContent = "";
797
- if (servers.length === 0) {
798
- return {
799
- values: { mcp: {} },
800
- data: { mcp: {} },
801
- text: "No MCP servers are currently connected."
802
- };
803
- }
804
- for (const server of servers) {
805
- mcpData[server.name] = {
806
- status: server.status,
807
- tools: {},
808
- resources: {}
809
- };
810
- textContent += `## Server: ${server.name} (${server.status})
811
-
812
- `;
813
- if (server.tools && server.tools.length > 0) {
814
- textContent += `### Tools:
815
-
816
- `;
817
- for (const tool of server.tools) {
818
- mcpData[server.name].tools[tool.name] = {
819
- description: tool.description || "No description available",
820
- inputSchema: tool.inputSchema || {}
821
- };
822
- textContent += `- **${tool.name}**: ${tool.description || "No description available"}
823
- `;
824
- }
825
- textContent += `
826
- `;
827
- }
828
- if (server.resources && server.resources.length > 0) {
829
- textContent += `### Resources:
830
-
831
- `;
832
- for (const resource of server.resources) {
833
- mcpData[server.name].resources[resource.uri] = {
834
- name: resource.name,
835
- description: resource.description || "No description available",
836
- mimeType: resource.mimeType
837
- };
838
- textContent += `- **${resource.name}** (${resource.uri}): ${resource.description || "No description available"}
839
- `;
840
- }
841
- textContent += `
842
- `;
843
- }
844
- }
845
- return {
846
- values: { mcp: mcpData, mcpText: `# MCP Configuration
847
-
848
- ${textContent}` },
849
- data: { mcp: mcpData },
850
- text: `# MCP Configuration
851
-
852
- ${textContent}`
853
- };
854
- }
855
-
856
- // src/utils/processing.ts
857
- function getMimeTypeToContentType(mimeType) {
858
- if (!mimeType)
859
- return;
860
- if (mimeType.startsWith("image/"))
861
- return import_core2.ContentType.IMAGE;
862
- if (mimeType.startsWith("video/"))
863
- return import_core2.ContentType.VIDEO;
864
- if (mimeType.startsWith("audio/"))
865
- return import_core2.ContentType.AUDIO;
866
- if (mimeType.includes("pdf") || mimeType.includes("document"))
867
- return import_core2.ContentType.DOCUMENT;
868
- return;
869
- }
870
- function processResourceResult(result, uri) {
871
- let resourceContent = "";
872
- let resourceMeta = "";
873
- for (const content of result.contents) {
874
- if (content.text) {
875
- resourceContent += content.text;
876
- } else if (content.blob) {
877
- resourceContent += `[Binary data - ${content.mimeType || "unknown type"}]`;
878
- }
879
- resourceMeta += `Resource: ${content.uri || uri}
880
- `;
881
- if (content.mimeType) {
882
- resourceMeta += `Type: ${content.mimeType}
883
- `;
884
- }
885
- }
886
- return { resourceContent, resourceMeta };
887
- }
888
- function processToolResult(result, serverName, toolName, runtime2, messageEntityId) {
889
- let toolOutput = "";
890
- let hasAttachments = false;
891
- const attachments = [];
892
- for (const content of result.content) {
893
- if (content.type === "text") {
894
- toolOutput += content.text;
895
- } else if (content.type === "image") {
896
- hasAttachments = true;
897
- attachments.push({
898
- contentType: getMimeTypeToContentType(content.mimeType),
899
- url: `data:${content.mimeType};base64,${content.data}`,
900
- id: import_core2.createUniqueUuid(runtime2, messageEntityId),
901
- title: "Generated image",
902
- source: `${serverName}/${toolName}`,
903
- description: "Tool-generated image",
904
- text: "Generated image"
905
- });
906
- } else if (content.type === "resource") {
907
- const resource = content.resource;
908
- if (resource && "text" in resource) {
909
- toolOutput += `
910
-
911
- Resource (${resource.uri}):
912
- ${resource.text}`;
913
- } else if (resource && "blob" in resource) {
914
- toolOutput += `
915
-
916
- Resource (${resource.uri}): [Binary data]`;
917
- }
918
- }
919
- }
920
- return { toolOutput, hasAttachments, attachments };
921
- }
922
- async function handleResourceAnalysis(runtime2, message, uri, serverName, resourceContent, resourceMeta, callback) {
923
- await createMcpMemory(runtime2, message, "resource", serverName, resourceContent, {
924
- uri,
925
- isResourceAccess: true
926
- });
927
- const analysisPrompt = createAnalysisPrompt(uri, message.content.text || "", resourceContent, resourceMeta);
928
- const analyzedResponse = await runtime2.useModel(import_core2.ModelType.TEXT_SMALL, {
929
- prompt: analysisPrompt
930
- });
931
- if (callback) {
932
- await callback({
933
- text: analyzedResponse,
934
- thought: `I analyzed the content from the ${uri} resource on ${serverName} and crafted a thoughtful response that addresses the user's request while maintaining my conversational style.`,
935
- actions: ["READ_MCP_RESOURCE"]
936
- });
937
- }
938
- }
939
- async function handleToolResponse(runtime2, message, serverName, toolName, toolArgs, toolOutput, hasAttachments, attachments, state, mcpProvider, callback) {
940
- await createMcpMemory(runtime2, message, "tool", serverName, toolOutput, {
941
- toolName,
942
- arguments: toolArgs,
943
- isToolCall: true
944
- });
945
- const reasoningPrompt = createReasoningPrompt(state, mcpProvider, toolName, serverName, message.content.text || "", toolOutput, hasAttachments);
946
- import_core2.logger.info({ reasoningPrompt }, "reasoning prompt");
947
- const reasonedResponse = await runtime2.useModel(import_core2.ModelType.TEXT_SMALL, {
948
- prompt: reasoningPrompt
949
- });
950
- const agentId = message.agentId || runtime2.agentId;
951
- const replyMemory = {
952
- entityId: agentId,
953
- roomId: message.roomId,
954
- worldId: message.worldId,
955
- content: {
956
- text: reasonedResponse,
957
- 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.`,
958
- actions: ["CALL_MCP_TOOL"],
959
- attachments: hasAttachments && attachments.length > 0 ? attachments : undefined
960
- }
961
- };
962
- await runtime2.createMemory(replyMemory, "messages");
963
- if (callback) {
964
- await callback({
965
- text: reasonedResponse,
966
- 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.`,
967
- actions: ["CALL_MCP_TOOL"],
968
- attachments: hasAttachments && attachments.length > 0 ? attachments : undefined
969
- });
970
- }
971
- return replyMemory;
972
- }
973
- async function sendInitialResponse(callback) {
974
- if (callback) {
975
- const responseContent = {
976
- thought: "The user is asking for information that can be found in an MCP resource. I will retrieve and analyze the appropriate resource.",
977
- text: "I'll retrieve that information for you. Let me access the resource...",
978
- actions: ["READ_MCP_RESOURCE"]
979
- };
980
- await callback(responseContent);
981
- }
982
- }
983
- function createAnalysisPrompt(uri, userMessage, resourceContent, resourceMeta) {
984
- const enhancedState = {
985
- data: {},
986
- text: "",
987
- values: {
988
- uri,
989
- userMessage,
990
- resourceContent,
991
- resourceMeta
992
- }
993
- };
994
- return import_core3.composePromptFromState({
995
- state: enhancedState,
996
- template: resourceAnalysisTemplate
997
- });
998
- }
999
- function createReasoningPrompt(state, mcpProvider, toolName, serverName, userMessage, toolOutput, hasAttachments) {
1000
- const enhancedState = {
1001
- ...state,
1002
- values: {
1003
- ...state.values,
1004
- mcpProvider,
1005
- toolName,
1006
- serverName,
1007
- userMessage,
1008
- toolOutput,
1009
- hasAttachments
1010
- }
1011
- };
1012
- return import_core3.composePromptFromState({
1013
- state: enhancedState,
1014
- template: toolReasoningTemplate
1015
- });
1016
- }
1017
-
1018
- // src/utils/selection.ts
1019
- var import_core5 = require("@elizaos/core");
1020
-
1021
- // src/utils/json.ts
1022
- var import_ajv = __toESM(require("ajv"));
1023
- var import_json5 = __toESM(require("json5"));
1024
- function parseJSON(input) {
1025
- let cleanedInput = input.replace(/^```(?:json)?\s*|\s*```$/g, "").trim();
1026
- const firstBrace = cleanedInput.indexOf("{");
1027
- const lastBrace = cleanedInput.lastIndexOf("}");
1028
- if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
1029
- cleanedInput = cleanedInput.substring(firstBrace, lastBrace + 1);
1030
- }
1031
- return import_json5.default.parse(cleanedInput);
1032
- }
1033
- var ajv = new import_ajv.default({
1034
- allErrors: true,
1035
- strict: false
1036
- });
1037
- function validateJsonSchema(data, schema) {
1038
- try {
1039
- const validate = ajv.compile(schema);
1040
- const valid = validate(data);
1041
- if (!valid) {
1042
- const errors = (validate.errors || []).map((err) => {
1043
- const path = err.instancePath ? `${err.instancePath.replace(/^\//, "")}` : "value";
1044
- return `${path}: ${err.message}`;
1045
- });
1046
- return { success: false, error: errors.join(", ") };
1047
- }
1048
- return { success: true, data };
1049
- } catch (error) {
1050
- return {
1051
- success: false,
1052
- error: `Schema validation error: ${error instanceof Error ? error.message : String(error)}`
1053
- };
1054
- }
1055
- }
1056
-
1057
- // src/utils/wrapper.ts
1058
- var import_core4 = require("@elizaos/core");
1059
- async function withModelRetry({
1060
- runtime: runtime2,
1061
- message,
1062
- state,
1063
- callback,
1064
- input,
1065
- validationFn,
1066
- createFeedbackPromptFn,
1067
- failureMsg,
1068
- retryCount = 0
1069
- }) {
1070
- const maxRetries = getMaxRetries(runtime2);
1071
- try {
1072
- import_core4.logger.info(`[WITH-MODEL-RETRY] Raw selection input:
1073
- ${input}`);
1074
- const parsedJson = typeof input === "string" ? parseJSON(input) : input;
1075
- import_core4.logger.debug(`[WITH-MODEL-RETRY] Parsed selection input:
1076
- ${JSON.stringify(parsedJson, null, 2)}`);
1077
- const validationResult = validationFn(parsedJson);
1078
- if (validationResult.success === false) {
1079
- throw new Error(validationResult.error);
1080
- }
1081
- return validationResult.data;
1082
- } catch (parseError) {
1083
- const errorMessage = parseError instanceof Error ? parseError.message : "Unknown parsing error";
1084
- import_core4.logger.error({ errorMessage }, `[WITH-MODEL-RETRY] Failed to parse response: ${errorMessage}`);
1085
- if (retryCount < maxRetries) {
1086
- import_core4.logger.debug(`[WITH-MODEL-RETRY] Retrying (attempt ${retryCount + 1}/${maxRetries})`);
1087
- const feedbackPrompt = createFeedbackPromptFn(input, errorMessage, state, message.content.text || "");
1088
- const retrySelection = await runtime2.useModel(import_core4.ModelType.OBJECT_LARGE, {
1089
- prompt: feedbackPrompt
1090
- });
1091
- return withModelRetry({
1092
- runtime: runtime2,
1093
- input: retrySelection,
1094
- validationFn,
1095
- message,
1096
- state,
1097
- createFeedbackPromptFn,
1098
- callback,
1099
- failureMsg,
1100
- retryCount: retryCount + 1
1101
- });
1102
- }
1103
- if (callback && failureMsg) {
1104
- await callback({
1105
- text: failureMsg,
1106
- thought: "Failed to parse response after multiple retries. Requesting clarification from user.",
1107
- actions: ["REPLY"]
1108
- });
1109
- }
1110
- return null;
1111
- }
1112
- }
1113
- function getMaxRetries(runtime2) {
1114
- try {
1115
- const rawSettings = runtime2.getSetting("mcp");
1116
- const settings = rawSettings;
1117
- if (settings && typeof settings.maxRetries === "number") {
1118
- const configValue = settings.maxRetries;
1119
- if (!Number.isNaN(configValue) && configValue >= 0) {
1120
- import_core4.logger.debug(`[WITH-MODEL-RETRY] Using configured selection retries: ${configValue}`);
1121
- return configValue;
1122
- }
1123
- }
1124
- } catch (error) {
1125
- import_core4.logger.debug({ error: error instanceof Error ? error.message : String(error) }, "[WITH-MODEL-RETRY] Error reading selection retries config");
1126
- }
1127
- return DEFAULT_MAX_RETRIES;
1128
- }
1129
-
1130
- // src/templates/toolSelectionTemplate.ts
1131
- var toolSelectionNameTemplate = `
1132
- {{mcpProvider.text}}
1133
-
1134
- {{recentMessages}}
1135
-
1136
- # TASK: Select the Most Appropriate Tool and Server
1137
-
1138
- 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.
1139
-
1140
- ## CRITICAL INSTRUCTIONS
1141
- 1. Provide both "serverName" and "toolName" from the options listed above.
1142
- 2. Each name must match EXACTLY as shown in the list:
1143
- - Example (correct): "serverName": "github"
1144
- - Example (incorrect): "serverName": "GitHub", "Github", or variations
1145
- 3. Extract ACTUAL parameter values from the conversation context.
1146
- - Do not invent or use placeholders like "octocat" or "Hello-World" unless the user said so.
1147
- 4. Include a "reasoning" field explaining why the selected tool fits the request.
1148
- 5. If no tool is appropriate, respond with:
1149
- {
1150
- "noToolAvailable": true
1151
- }
1152
-
1153
- !!! YOUR RESPONSE MUST BE A VALID JSON OBJECT ONLY !!!
1154
-
1155
- CRITICAL: Your response must START with { and END with }. DO NOT include ANY text before or after the JSON.
1156
-
1157
- ## STRICT FORMAT REQUIREMENTS
1158
- - The response MUST be a single valid JSON object.
1159
- - DO NOT wrap the JSON in triple backticks (\`\`\`), code blocks, or include any explanatory text.
1160
- - DO NOT include comments (// or /* */) anywhere.
1161
- - DO NOT use placeholders (e.g., "replace with...", "example", "your...", etc.)
1162
- - ALL strings must use double quotes.
1163
-
1164
- ## CRITICAL NOTES
1165
- - All values must be fully grounded in user input or inferred contextually.
1166
- - No missing fields unless they are explicitly optional in the schema.
1167
- - All types must match the schema (strings, numbers, booleans).
1168
-
1169
- ## JSON OBJECT STRUCTURE
1170
- Your response MUST contain ONLY these top-level keys:
1171
- 1. "serverName" — The name of the server (e.g., "github", "notion")
1172
- 2. "toolName" — The name of the tool (e.g., "get_file_contents", "search")
1173
- 3. "reasoning" — A string explaining how the values were inferred from the conversation.
1174
- 4. "noToolAvailable" — A boolean indicating if no tool is available (true/false)
1175
-
1176
- ## EXAMPLE RESPONSE
1177
- {
1178
- "serverName": "github",
1179
- "toolName": "get_file_contents",
1180
- "reasoning": "The user wants to retrieve the README from the facebook/react repository.",
1181
- "noToolAvailable": false
1182
- }
1183
-
1184
- ## REMINDERS
1185
- - Use "github" as serverName for GitHub tools.
1186
- - Use "notion" as serverName for Notion tools.
1187
- - For search and knowledge-based tasks, MCP tools are often appropriate.
1188
-
1189
- REMEMBER: This output will be parsed directly as JSON. If the format is incorrect, the operation will fail.
1190
- `;
1191
- var toolSelectionArgumentTemplate = `
1192
- {{recentMessages}}
1193
-
1194
- # TASK: Generate a Strictly Valid JSON Object for Tool Execution
195
+ // src/templates/resourceAnalysisTemplate.ts
196
+ var resourceAnalysisTemplate = `
197
+ {{{mcpProvider.text}}}
1195
198
 
1196
- You have chosen the "{{toolSelectionName.toolName}}" tool from the "{{toolSelectionName.serverName}}" server to address the user's request.
1197
- The reasoning behind this selection is: "{{toolSelectionName.reasoning}}"
199
+ {{{recentMessages}}}
1198
200
 
1199
- ## CRITICAL INSTRUCTIONS
1200
- 1. Ensure the "toolArguments" object strictly adheres to the structure and requirements defined in the schema.
1201
- 2. All parameter values must be extracted from the conversation context and must be concrete, usable values.
1202
- 3. Avoid placeholders or generic terms unless explicitly provided by the user.
201
+ # Prompt
1203
202
 
1204
- !!! YOUR RESPONSE MUST BE A VALID JSON OBJECT ONLY !!!
203
+ You are a helpful assistant responding to a user's request. You've just accessed the resource "{{{uri}}}" to help answer this request.
1205
204
 
1206
- ## STRICT FORMAT REQUIREMENTS
1207
- - The response MUST be a single valid JSON object.
1208
- - DO NOT wrap the JSON in triple backticks (\`\`\`), code blocks, or include any explanatory text.
1209
- - DO NOT include comments (// or /* */) anywhere.
1210
- - DO NOT use placeholders (e.g., "replace with...", "example", "your...", etc.)
1211
- - ALL strings must use double quotes
205
+ Original user request: "{{{userMessage}}}"
1212
206
 
1213
- ## CRITICAL NOTES
1214
- - All values must be fully grounded in user input or inferred contextually.
1215
- - No missing fields unless they are explicitly optional in the schema.
1216
- - All types must match the schema (strings, numbers, booleans).
207
+ Resource metadata:
208
+ {{{resourceMeta}}
1217
209
 
1218
- ## JSON OBJECT STRUCTURE
1219
- Your response MUST contain ONLY these two top-level keys:
1220
- 1. "toolArguments" — An object matching the input schema: {{toolInputSchema}}
1221
- 2. "reasoning" — A string explaining how the values were inferred from the conversation.
210
+ Resource content:
211
+ {{{resourceContent}}
1222
212
 
1223
- ## EXAMPLE RESPONSE
1224
- {
1225
- "toolArguments": {
1226
- "owner": "facebook",
1227
- "repo": "react",
1228
- "path": "README.md",
1229
- "branch": "main"
1230
- },
1231
- "reasoning": "The user wants to see the README from the facebook/react repository based on our conversation."
1232
- }
213
+ Instructions:
214
+ 1. Analyze how well the resource's content addresses the user's specific question or need
215
+ 2. Identify the most relevant information from the resource
216
+ 3. Create a natural, conversational response that incorporates this information
217
+ 4. If the resource content is insufficient, acknowledge its limitations and explain what you can determine
218
+ 5. Do not start with phrases like "According to the resource" or "Here's what I found" - instead, integrate the information naturally
219
+ 6. Maintain your helpful, intelligent assistant personality while presenting the information
1233
220
 
1234
- REMEMBER: Your response will be parsed directly as JSON. If it fails to parse, the operation will fail completely.
221
+ Your response (written as if directly to the user):
1235
222
  `;
1236
223
 
1237
- // src/utils/schemas.ts
1238
- var toolSelectionNameSchema = {
1239
- type: "object",
1240
- required: ["serverName", "toolName"],
1241
- properties: {
1242
- serverName: {
1243
- type: "string",
1244
- minLength: 1,
1245
- errorMessage: "serverName must not be empty"
1246
- },
1247
- toolName: {
1248
- type: "string",
1249
- minLength: 1,
1250
- errorMessage: "toolName must not be empty"
1251
- },
1252
- reasoning: {
1253
- type: "string"
1254
- },
1255
- noToolAvailable: {
1256
- type: "boolean"
1257
- }
1258
- }
1259
- };
1260
- var toolSelectionArgumentSchema = {
1261
- type: "object",
1262
- required: ["toolArguments"],
1263
- properties: {
1264
- toolArguments: {
1265
- type: "object"
224
+ // src/utils/mcp.ts
225
+ var NO_DESC = "No description";
226
+ async function createMcpMemory(runtime, message, type, serverName, content, metadata) {
227
+ const memory = await runtime.addEmbeddingToMemory({
228
+ entityId: message.entityId,
229
+ agentId: runtime.agentId,
230
+ roomId: message.roomId,
231
+ content: {
232
+ text: `Used "${type}" from "${serverName}". Content: ${content}`,
233
+ metadata: { ...metadata, serverName }
1266
234
  }
1267
- }
1268
- };
1269
-
1270
- // src/utils/validation.ts
1271
- function validateToolSelectionName(parsed, state) {
1272
- const basicResult = validateJsonSchema(parsed, toolSelectionNameSchema);
1273
- if (basicResult.success === false) {
1274
- return { success: false, error: basicResult.error };
1275
- }
1276
- const data = basicResult.data;
1277
- const mcpData = state.values.mcp || {};
1278
- const server = mcpData[data.serverName];
1279
- if (!server || server.status !== "connected") {
1280
- return {
1281
- success: false,
1282
- error: `Server "${data.serverName}" not found or not connected`
1283
- };
1284
- }
1285
- const toolInfo = server.tools?.[data.toolName];
1286
- if (!toolInfo) {
1287
- return {
1288
- success: false,
1289
- error: `Tool "${data.toolName}" not found on server "${data.serverName}"`
1290
- };
1291
- }
1292
- return { success: true, data };
235
+ });
236
+ await runtime.createMemory(memory, type === "resource" ? "resources" : "tools", true);
1293
237
  }
1294
- function validateToolSelectionArgument(parsed, toolInputSchema) {
1295
- const basicResult = validateJsonSchema(parsed, toolSelectionArgumentSchema);
1296
- if (basicResult.success === false) {
1297
- return { success: false, error: basicResult.error };
1298
- }
1299
- const data = basicResult.data;
1300
- const validationResult = validateJsonSchema(data.toolArguments, toolInputSchema);
1301
- if (validationResult.success === false) {
1302
- return {
1303
- success: false,
1304
- error: `Invalid arguments: ${validationResult.error}`
1305
- };
238
+ function buildMcpProviderData(servers) {
239
+ if (servers.length === 0) {
240
+ return { values: { mcp: {} }, data: { mcp: {} }, text: "No MCP servers connected." };
1306
241
  }
1307
- return { success: true, data };
242
+ const mcpData = {};
243
+ const lines = [`# MCP Configuration
244
+ `];
245
+ for (const server of servers) {
246
+ const tools = {};
247
+ const resources = {};
248
+ lines.push(`## ${server.name} (${server.status})
249
+ `);
250
+ if (server.tools?.length) {
251
+ lines.push(`### Tools
252
+ `);
253
+ for (const t of server.tools) {
254
+ tools[t.name] = { description: t.description || NO_DESC, inputSchema: t.inputSchema || {} };
255
+ lines.push(`- **${t.name}**: ${t.description || NO_DESC}`);
256
+ }
257
+ lines.push("");
258
+ }
259
+ if (server.resources?.length) {
260
+ lines.push(`### Resources
261
+ `);
262
+ for (const r of server.resources) {
263
+ resources[r.uri] = { name: r.name, description: r.description || NO_DESC, mimeType: r.mimeType };
264
+ lines.push(`- **${r.name}** (${r.uri}): ${r.description || NO_DESC}`);
265
+ }
266
+ lines.push("");
267
+ }
268
+ mcpData[server.name] = { status: server.status, tools, resources };
269
+ }
270
+ const text = lines.join(`
271
+ `);
272
+ return { values: { mcp: mcpData, mcpText: text }, data: { mcp: mcpData }, text };
1308
273
  }
1309
- function validateResourceSelection(selection) {
1310
- return validateJsonSchema(selection, ResourceSelectionSchema);
274
+
275
+ // src/utils/processing.ts
276
+ function getMimeTypeToContentType(mimeType) {
277
+ if (!mimeType)
278
+ return;
279
+ if (mimeType.startsWith("image/"))
280
+ return import_core2.ContentType.IMAGE;
281
+ if (mimeType.startsWith("video/"))
282
+ return import_core2.ContentType.VIDEO;
283
+ if (mimeType.startsWith("audio/"))
284
+ return import_core2.ContentType.AUDIO;
285
+ if (mimeType.includes("pdf") || mimeType.includes("document"))
286
+ return import_core2.ContentType.DOCUMENT;
287
+ return;
1311
288
  }
1312
- function createResourceSelectionFeedbackPrompt(originalResponse, errorMessage, composedState, userMessage) {
1313
- let resourcesDescription = "";
1314
- for (const [serverName, server] of Object.entries(composedState.values.mcp || {})) {
1315
- if (server.status !== "connected")
1316
- continue;
1317
- for (const [uri, resource] of Object.entries(server.resources || {})) {
1318
- resourcesDescription += `Resource: ${uri} (Server: ${serverName})
1319
- `;
1320
- resourcesDescription += `Name: ${resource.name || "No name available"}
289
+ function processResourceResult(result, uri) {
290
+ let resourceContent = "";
291
+ let resourceMeta = "";
292
+ for (const content of result.contents) {
293
+ resourceContent += content.text || (content.blob ? `[Binary: ${content.mimeType || "unknown"}]` : "");
294
+ resourceMeta += `Resource: ${content.uri || uri}
1321
295
  `;
1322
- resourcesDescription += `Description: ${resource.description || "No description available"}
1323
-
296
+ if (content.mimeType)
297
+ resourceMeta += `Type: ${content.mimeType}
1324
298
  `;
1325
- }
1326
299
  }
1327
- return createFeedbackPrompt(originalResponse, errorMessage, "resource", resourcesDescription, userMessage);
300
+ return { resourceContent, resourceMeta };
1328
301
  }
1329
- function createFeedbackPrompt(originalResponse, errorMessage, itemType, itemsDescription, userMessage) {
1330
- return `Error parsing JSON: ${errorMessage}
1331
-
1332
- Your original response:
1333
- ${originalResponse}
302
+ function processToolResult(result, serverName, toolName, runtime, messageEntityId) {
303
+ let toolOutput = "";
304
+ let hasAttachments = false;
305
+ const attachments = [];
306
+ let attachmentIndex = 0;
307
+ for (const content of result.content) {
308
+ if (content.type === "text") {
309
+ toolOutput += content.text;
310
+ } else if (content.type === "image") {
311
+ hasAttachments = true;
312
+ attachments.push({
313
+ contentType: getMimeTypeToContentType(content.mimeType),
314
+ url: `data:${content.mimeType};base64,${content.data}`,
315
+ id: import_core2.createUniqueUuid(runtime, `${messageEntityId}-attachment-${attachmentIndex++}`),
316
+ title: "Generated image",
317
+ source: `${serverName}/${toolName}`,
318
+ description: "Tool-generated image",
319
+ text: "Generated image"
320
+ });
321
+ } else if (content.type === "resource" && content.resource) {
322
+ const r = content.resource;
323
+ toolOutput += r.text ? `
1334
324
 
1335
- Please try again with valid JSON for ${itemType} selection.
1336
- Available ${itemType}s:
1337
- ${itemsDescription}
325
+ Resource (${r.uri}):
326
+ ${r.text}` : `
1338
327
 
1339
- User request: ${userMessage}`;
328
+ Resource (${r.uri}): [Binary]`;
329
+ }
330
+ }
331
+ return { toolOutput, hasAttachments, attachments };
1340
332
  }
1341
-
1342
- // src/utils/selection.ts
1343
- async function createToolSelectionName({
1344
- runtime: runtime2,
1345
- state,
1346
- message,
1347
- callback,
1348
- mcpProvider
1349
- }) {
1350
- const toolSelectionPrompt = import_core5.composePromptFromState({
1351
- state: { ...state, values: { ...state.values, mcpProvider } },
1352
- template: toolSelectionNameTemplate
1353
- });
1354
- import_core5.logger.debug(`[SELECTION] Tool Selection Name Prompt:
1355
- ${toolSelectionPrompt}`);
1356
- const toolSelectionName = await runtime2.useModel(import_core5.ModelType.TEXT_LARGE, {
1357
- prompt: toolSelectionPrompt
1358
- });
1359
- import_core5.logger.debug(`[SELECTION] Tool Selection Name Response:
1360
- ${toolSelectionName}`);
1361
- return await withModelRetry({
1362
- runtime: runtime2,
1363
- message,
1364
- state,
1365
- callback,
1366
- input: toolSelectionName,
1367
- validationFn: (parsed) => validateToolSelectionName(parsed, state),
1368
- createFeedbackPromptFn: (originalResponse, errorMessage, state2, userMessage) => createToolSelectionFeedbackPrompt(originalResponse, errorMessage, state2, userMessage),
1369
- failureMsg: "I'm having trouble figuring out the best way to help with your request."
333
+ async function handleResourceAnalysis(runtime, message, uri, serverName, resourceContent, resourceMeta, callback) {
334
+ await createMcpMemory(runtime, message, "resource", serverName, resourceContent, {
335
+ uri,
336
+ isResourceAccess: true
1370
337
  });
1371
- }
1372
- async function createToolSelectionArgument({
1373
- runtime: runtime2,
1374
- state,
1375
- message,
1376
- callback,
1377
- mcpProvider,
1378
- toolSelectionName
1379
- }) {
1380
- if (!toolSelectionName) {
1381
- import_core5.logger.warn("[SELECTION] Tool selection name is not provided. Cannot create tool selection argument.");
1382
- return null;
1383
- }
1384
- const { serverName, toolName } = toolSelectionName;
1385
- const toolInputSchema = mcpProvider.data.mcp[serverName].tools[toolName].inputSchema;
1386
- import_core5.logger.trace(`[SELECTION] Tool Input Schema:
1387
- ${JSON.stringify({ toolInputSchema }, null, 2)}`);
1388
- const toolSelectionArgumentPrompt = import_core5.composePromptFromState({
338
+ const prompt = import_core3.composePromptFromState({
1389
339
  state: {
1390
- ...state,
1391
- values: {
1392
- ...state.values,
1393
- toolSelectionName,
1394
- toolInputSchema: JSON.stringify(toolInputSchema)
1395
- }
340
+ data: {},
341
+ text: "",
342
+ values: { uri, userMessage: message.content.text || "", resourceContent, resourceMeta }
1396
343
  },
1397
- template: toolSelectionArgumentTemplate
1398
- });
1399
- import_core5.logger.debug(`[SELECTION] Tool Selection Prompt:
1400
- ${toolSelectionArgumentPrompt}`);
1401
- const toolSelectionArgument = await runtime2.useModel(import_core5.ModelType.TEXT_LARGE, {
1402
- prompt: toolSelectionArgumentPrompt
1403
- });
1404
- import_core5.logger.debug(`[SELECTION] Tool Selection Argument Response:
1405
- ${toolSelectionArgument}`);
1406
- return await withModelRetry({
1407
- runtime: runtime2,
1408
- message,
1409
- state,
1410
- callback,
1411
- input: toolSelectionArgument,
1412
- validationFn: (parsed) => validateToolSelectionArgument(parsed, state),
1413
- createFeedbackPromptFn: (originalResponse, errorMessage, state2, userMessage) => createToolSelectionFeedbackPrompt(originalResponse, errorMessage, state2, userMessage),
1414
- failureMsg: "I'm having trouble figuring out the best way to help with your request."
344
+ template: resourceAnalysisTemplate
1415
345
  });
1416
- }
1417
- function createToolSelectionFeedbackPrompt(originalResponse, errorMessage, state, userMessage) {
1418
- let toolsDescription = "";
1419
- for (const [serverName, server] of Object.entries(state.values.mcp || {})) {
1420
- if (server.status !== "connected")
1421
- continue;
1422
- for (const [toolName, tool] of Object.entries(server.tools || {})) {
1423
- toolsDescription += `Tool: ${toolName} (Server: ${serverName})
1424
- `;
1425
- toolsDescription += `Description: ${tool.description || "No description available"}
1426
-
1427
- `;
1428
- }
346
+ const response = await runtime.useModel(import_core2.ModelType.TEXT_SMALL, { prompt });
347
+ if (callback) {
348
+ await callback({
349
+ text: response,
350
+ thought: `Analyzed resource ${uri} from ${serverName}`,
351
+ actions: ["READ_MCP_RESOURCE"]
352
+ });
1429
353
  }
1430
- const feedbackPrompt = createFeedbackPrompt2(originalResponse, errorMessage, "tool", toolsDescription, userMessage);
1431
- import_core5.logger.debug(`[SELECTION] Tool Selection Feedback Prompt:
1432
- ${feedbackPrompt}`);
1433
- return feedbackPrompt;
1434
- }
1435
- function createFeedbackPrompt2(originalResponse, errorMessage, itemType, itemsDescription, userMessage) {
1436
- return `Error parsing JSON: ${errorMessage}
1437
-
1438
- Your original response:
1439
- ${originalResponse}
1440
-
1441
- Please try again with valid JSON for ${itemType} selection.
1442
- Available ${itemType}s:
1443
- ${itemsDescription}
1444
-
1445
- User request: ${userMessage}`;
1446
354
  }
1447
-
1448
- // src/utils/handler.ts
1449
- async function handleNoToolAvailable(callback, toolSelection) {
1450
- const responseText = "I don't have a specific tool that can help with that request. Let me try to assist you directly instead.";
1451
- const thoughtText = "No appropriate MCP tool available for this request. Falling back to direct assistance.";
1452
- if (callback && toolSelection?.noToolAvailable) {
355
+ async function sendInitialResponse(callback) {
356
+ if (callback) {
1453
357
  await callback({
1454
- text: responseText,
1455
- thought: thoughtText,
1456
- actions: ["REPLY"]
358
+ thought: "Retrieving MCP resource...",
359
+ text: "I'll retrieve that information for you. Let me access the resource...",
360
+ actions: ["READ_MCP_RESOURCE"]
1457
361
  });
1458
362
  }
1459
- return {
1460
- text: responseText,
1461
- values: {
1462
- success: true,
1463
- noToolAvailable: true,
1464
- fallbackToDirectAssistance: true
1465
- },
1466
- data: {
1467
- actionName: "CALL_MCP_TOOL",
1468
- noToolAvailable: true,
1469
- reason: toolSelection?.reasoning || "No appropriate tool available"
1470
- },
1471
- success: true
1472
- };
1473
363
  }
1474
364
 
1475
- // src/actions/callToolAction.ts
1476
- var callToolAction = {
1477
- name: "CALL_MCP_TOOL",
1478
- similes: [
1479
- "CALL_TOOL",
1480
- "CALL_MCP_TOOL",
1481
- "USE_TOOL",
1482
- "USE_MCP_TOOL",
1483
- "EXECUTE_TOOL",
1484
- "EXECUTE_MCP_TOOL",
1485
- "RUN_TOOL",
1486
- "RUN_MCP_TOOL",
1487
- "INVOKE_TOOL",
1488
- "INVOKE_MCP_TOOL"
1489
- ],
1490
- description: "Calls a tool from an MCP server to perform a specific task",
1491
- validate: async (runtime2, _message, _state) => {
1492
- const mcpService = runtime2.getService(MCP_SERVICE_NAME);
1493
- if (!mcpService)
1494
- return false;
1495
- const servers = mcpService.getServers();
1496
- return servers.length > 0 && servers.some((server) => server.status === "connected" && server.tools && server.tools.length > 0);
1497
- },
1498
- handler: async (runtime2, message, _state, _options, callback) => {
1499
- const composedState = await runtime2.composeState(message, ["RECENT_MESSAGES", "MCP"]);
1500
- const mcpService = runtime2.getService(MCP_SERVICE_NAME);
1501
- if (!mcpService) {
1502
- throw new Error("MCP service not available");
365
+ // src/utils/json.ts
366
+ var import_ajv = __toESM(require("ajv"));
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;
1503
377
  }
1504
- const mcpProvider = mcpService.getProviderData();
1505
- try {
1506
- const toolSelectionName = await createToolSelectionName({
1507
- runtime: runtime2,
1508
- state: composedState,
1509
- message,
1510
- callback,
1511
- mcpProvider
1512
- });
1513
- if (!toolSelectionName || toolSelectionName.noToolAvailable) {
1514
- import_core6.logger.warn("[NO_TOOL_AVAILABLE] No appropriate tool available for the request");
1515
- return await handleNoToolAvailable(callback, toolSelectionName);
1516
- }
1517
- const { serverName, toolName, reasoning } = toolSelectionName;
1518
- import_core6.logger.info(`[CALLING] Calling tool "${serverName}/${toolName}" on server with reasoning: "${reasoning}"`);
1519
- const toolSelectionArgument = await createToolSelectionArgument({
1520
- runtime: runtime2,
1521
- state: composedState,
1522
- message,
1523
- callback,
1524
- mcpProvider,
1525
- toolSelectionName
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
+ }
398
+ function parseJSON(input) {
399
+ let cleanedInput = input.replace(/^```(?:json)?\s*|\s*```$/g, "").trim();
400
+ const firstBrace = cleanedInput.indexOf("{");
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);
424
+ }
425
+ return import_json5.default.parse(cleanedInput);
426
+ }
427
+ var ajv = new import_ajv.default({
428
+ allErrors: true,
429
+ strict: false
430
+ });
431
+ function validateJsonSchema(data, schema) {
432
+ try {
433
+ const validate = ajv.compile(schema);
434
+ const valid = validate(data);
435
+ if (!valid) {
436
+ const errors = (validate.errors || []).map((err) => {
437
+ const path = err.instancePath ? `${err.instancePath.replace(/^\//, "")}` : "value";
438
+ return `${path}: ${err.message}`;
1526
439
  });
1527
- if (!toolSelectionArgument) {
1528
- import_core6.logger.warn("[NO_TOOL_SELECTION_ARGUMENT] No appropriate tool selection argument available");
1529
- return await handleNoToolAvailable(callback, toolSelectionName);
1530
- }
1531
- import_core6.logger.info(`[SELECTED] Tool Selection result:
1532
- ${JSON.stringify(toolSelectionArgument, null, 2)}`);
1533
- const result = await mcpService.callTool(serverName, toolName, toolSelectionArgument.toolArguments);
1534
- const { toolOutput, hasAttachments, attachments } = processToolResult(result, serverName, toolName, runtime2, message.entityId);
1535
- const replyMemory = await handleToolResponse(runtime2, message, serverName, toolName, toolSelectionArgument.toolArguments, toolOutput, hasAttachments, attachments, composedState, mcpProvider, callback);
1536
- return {
1537
- text: `Successfully called tool: ${serverName}/${toolName}. Reasoned response: ${replyMemory.content.text}`,
1538
- values: {
1539
- success: true,
1540
- toolExecuted: true,
1541
- serverName,
1542
- toolName,
1543
- hasAttachments,
1544
- output: toolOutput
1545
- },
1546
- data: {
1547
- actionName: "CALL_MCP_TOOL",
1548
- serverName,
1549
- toolName,
1550
- toolArguments: toolSelectionArgument.toolArguments,
1551
- reasoning: toolSelectionName.reasoning,
1552
- output: toolOutput,
1553
- attachments: attachments || []
1554
- },
1555
- success: true
1556
- };
1557
- } catch (error) {
1558
- return await handleMcpError(composedState, mcpProvider, error, runtime2, message, "tool", callback);
440
+ return { success: false, error: errors.join(", ") };
1559
441
  }
1560
- },
1561
- examples: [
1562
- [
1563
- {
1564
- name: "{{user}}",
1565
- content: {
1566
- text: "Can you search for information about climate change?"
1567
- }
1568
- },
1569
- {
1570
- name: "{{assistant}}",
1571
- content: {
1572
- text: "I'll help you with that request. Let me access the right tool...",
1573
- actions: ["CALL_MCP_TOOL"]
1574
- }
1575
- },
1576
- {
1577
- name: "{{assistant}}",
1578
- content: {
1579
- text: `I found the following information about climate change:
1580
-
1581
- 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.`,
1582
- actions: ["CALL_MCP_TOOL"]
1583
- }
1584
- }
1585
- ]
1586
- ]
1587
- };
1588
-
1589
- // src/actions/readResourceAction.ts
1590
- var import_core7 = require("@elizaos/core");
1591
-
1592
- // src/templates/resourceSelectionTemplate.ts
1593
- var resourceSelectionTemplate = `
1594
- {{{mcpProvider.text}}}
1595
-
1596
- {{{recentMessages}}}
1597
-
1598
- # Prompt
442
+ return { success: true, data };
443
+ } catch (error) {
444
+ return {
445
+ success: false,
446
+ error: `Schema validation error: ${error instanceof Error ? error.message : String(error)}`
447
+ };
448
+ }
449
+ }
1599
450
 
1600
- You are an intelligent assistant helping select the right resource to address a user's request.
451
+ // src/utils/validation.ts
452
+ function validateResourceSelection(selection) {
453
+ return validateJsonSchema(selection, ResourceSelectionSchema);
454
+ }
455
+ function createResourceSelectionFeedbackPrompt(originalResponse, errorMessage, composedState, userMessage) {
456
+ let description = "";
457
+ for (const [serverName, server] of Object.entries(composedState.values.mcp || {})) {
458
+ if (server.status !== "connected")
459
+ continue;
460
+ for (const [uri, resource] of Object.entries(server.resources || {})) {
461
+ description += `Resource: ${uri} (Server: ${serverName})
462
+ `;
463
+ description += `Name: ${resource.name || "No name"}
464
+ `;
465
+ description += `Description: ${resource.description || "No description"}
1601
466
 
1602
- CRITICAL INSTRUCTIONS:
1603
- 1. You MUST specify both a valid serverName AND uri from the list above
1604
- 2. The serverName value should match EXACTLY the server name shown in parentheses (Server: X)
1605
- CORRECT: "serverName": "github" (if the server is called "github")
1606
- WRONG: "serverName": "GitHub" or "Github" or any other variation
1607
- 3. The uri value should match EXACTLY the resource uri listed
1608
- CORRECT: "uri": "weather://San Francisco/current" (if that's the exact uri)
1609
- WRONG: "uri": "weather://sanfrancisco/current" or any variation
1610
- 4. Identify the user's information need from the conversation context
1611
- 5. Select the most appropriate resource based on its description and the request
1612
- 6. If no resource seems appropriate, output {"noResourceAvailable": true}
467
+ `;
468
+ }
469
+ }
470
+ return `Error parsing JSON: ${errorMessage}
1613
471
 
1614
- !!! YOUR RESPONSE MUST BE A VALID JSON OBJECT ONLY !!!
472
+ Your original response:
473
+ ${originalResponse}
1615
474
 
1616
- STRICT FORMAT REQUIREMENTS:
1617
- - NO code block formatting (NO backticks or \`\`\`)
1618
- - NO comments (NO // or /* */)
1619
- - NO placeholders like "replace with...", "example", "your...", "actual", etc.
1620
- - Every parameter value must be a concrete, usable value (not instructions to replace)
1621
- - Use proper JSON syntax with double quotes for strings
1622
- - NO explanatory text before or after the JSON object
475
+ Please try again with valid JSON for resource selection.
476
+ Available resources:
477
+ ${description}
1623
478
 
1624
- EXAMPLE RESPONSE:
1625
- {
1626
- "serverName": "weather-server",
1627
- "uri": "weather://San Francisco/current",
1628
- "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."
479
+ User request: ${userMessage}`;
1629
480
  }
1630
481
 
1631
- REMEMBER: Your response will be parsed directly as JSON. If it fails to parse, the operation will fail completely!
1632
- `;
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,
512
+ message,
513
+ state,
514
+ createFeedbackPromptFn,
515
+ callback,
516
+ failureMsg,
517
+ retryCount: retryCount + 1
518
+ });
519
+ }
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;
538
+ }
1633
539
 
1634
540
  // src/actions/readResourceAction.ts
1635
541
  function createResourceSelectionPrompt(composedState, userMessage) {
@@ -1662,7 +568,7 @@ function createResourceSelectionPrompt(composedState, userMessage) {
1662
568
  userMessage
1663
569
  }
1664
570
  };
1665
- return import_core7.composePromptFromState({
571
+ return import_core5.composePromptFromState({
1666
572
  state: enhancedState,
1667
573
  template: resourceSelectionTemplate
1668
574
  });
@@ -1680,16 +586,16 @@ var readResourceAction = {
1680
586
  "ACCESS_MCP_RESOURCE"
1681
587
  ],
1682
588
  description: "Reads a resource from an MCP server",
1683
- validate: async (runtime2, _message, _state) => {
1684
- const mcpService = runtime2.getService(MCP_SERVICE_NAME);
589
+ validate: async (runtime, _message, _state) => {
590
+ const mcpService = runtime.getService(MCP_SERVICE_NAME);
1685
591
  if (!mcpService)
1686
592
  return false;
1687
593
  const servers = mcpService.getServers();
1688
594
  return servers.length > 0 && servers.some((server) => server.status === "connected" && server.resources && server.resources.length > 0);
1689
595
  },
1690
- handler: async (runtime2, message, _state, _options, callback) => {
1691
- const composedState = await runtime2.composeState(message, ["RECENT_MESSAGES", "MCP"]);
1692
- const mcpService = runtime2.getService(MCP_SERVICE_NAME);
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);
1693
599
  if (!mcpService) {
1694
600
  throw new Error("MCP service not available");
1695
601
  }
@@ -1697,11 +603,11 @@ var readResourceAction = {
1697
603
  try {
1698
604
  await sendInitialResponse(callback);
1699
605
  const resourceSelectionPrompt = createResourceSelectionPrompt(composedState, message.content.text || "");
1700
- const resourceSelection = await runtime2.useModel(import_core7.ModelType.TEXT_SMALL, {
606
+ const resourceSelection = await runtime.useModel(import_core5.ModelType.TEXT_SMALL, {
1701
607
  prompt: resourceSelectionPrompt
1702
608
  });
1703
609
  const parsedSelection = await withModelRetry({
1704
- runtime: runtime2,
610
+ runtime,
1705
611
  state: composedState,
1706
612
  message,
1707
613
  callback,
@@ -1736,347 +642,913 @@ var readResourceAction = {
1736
642
  success: true
1737
643
  };
1738
644
  }
1739
- const { serverName, uri, reasoning } = parsedSelection;
1740
- import_core7.logger.debug(`Selected resource "${uri}" on server "${serverName}" because: ${reasoning}`);
1741
- const result = await mcpService.readResource(serverName, uri);
1742
- import_core7.logger.debug(`Read resource ${uri} from server ${serverName}`);
1743
- const { resourceContent, resourceMeta } = processResourceResult(result, uri);
1744
- await handleResourceAnalysis(runtime2, message, uri, serverName, resourceContent, resourceMeta, callback);
1745
- return {
1746
- text: `Successfully read resource: ${uri}`,
1747
- values: {
1748
- success: true,
1749
- resourceRead: true,
1750
- serverName,
1751
- uri
1752
- },
1753
- data: {
1754
- actionName: "READ_MCP_RESOURCE",
1755
- serverName,
1756
- uri,
1757
- reasoning,
1758
- resourceMeta,
1759
- contentLength: resourceContent?.length || 0
1760
- },
1761
- success: true
1762
- };
1763
- } catch (error) {
1764
- return await handleMcpError(composedState, mcpProvider, error, runtime2, message, "resource", callback);
645
+ const { serverName, uri, reasoning } = parsedSelection;
646
+ import_core5.logger.debug(`Selected resource "${uri}" on server "${serverName}" because: ${reasoning}`);
647
+ const result = await mcpService.readResource(serverName, uri);
648
+ import_core5.logger.debug(`Read resource ${uri} from server ${serverName}`);
649
+ const { resourceContent, resourceMeta } = processResourceResult(result, uri);
650
+ await handleResourceAnalysis(runtime, message, uri, serverName, resourceContent, resourceMeta, callback);
651
+ return {
652
+ text: `Successfully read resource: ${uri}`,
653
+ values: {
654
+ success: true,
655
+ resourceRead: true,
656
+ serverName,
657
+ uri
658
+ },
659
+ data: {
660
+ actionName: "READ_MCP_RESOURCE",
661
+ serverName,
662
+ uri,
663
+ reasoning,
664
+ resourceMeta,
665
+ contentLength: resourceContent?.length || 0
666
+ },
667
+ success: true
668
+ };
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);
1095
+ }
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;
1140
+ }
1765
1141
  }
1766
- },
1767
- examples: [
1768
- [
1769
- {
1770
- name: "{{user}}",
1771
- content: {
1772
- text: "Can you get the documentation about installing ElizaOS?"
1773
- }
1774
- },
1775
- {
1776
- name: "{{assistant}}",
1777
- content: {
1778
- text: `I'll retrieve that information for you. Let me access the resource...`,
1779
- actions: ["READ_MCP_RESOURCE"]
1780
- }
1781
- },
1782
- {
1783
- name: "{{assistant}}",
1784
- content: {
1785
- 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.`,
1786
- actions: ["READ_MCP_RESOURCE"]
1787
- }
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);
1788
1149
  }
1789
- ]
1790
- ]
1791
- };
1792
-
1793
- // src/provider.ts
1794
- var provider = {
1795
- name: "MCP",
1796
- description: "Information about connected MCP servers, tools, and resources",
1797
- get: async (runtime2, _message, _state) => {
1798
- const mcpService = runtime2.getService(MCP_SERVICE_NAME);
1799
- if (!mcpService) {
1800
- return {
1801
- values: { mcp: {} },
1802
- data: { mcp: {} },
1803
- text: "No MCP servers are available."
1804
- };
1805
1150
  }
1806
- return mcpService.getProviderData();
1151
+ return processed;
1807
1152
  }
1808
- };
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
+ }
1180
+
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;
1226
+ }
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;
1809
1349
 
1810
1350
  // src/service.ts
1811
- var import_core8 = require("@elizaos/core");
1812
- var import_client = require("@modelcontextprotocol/sdk/client/index.js");
1813
- var import_sse = require("@modelcontextprotocol/sdk/client/sse.js");
1814
- var import_stdio = require("@modelcontextprotocol/sdk/client/stdio.js");
1351
+ var err = (e) => e instanceof Error ? e.message : String(e);
1352
+
1815
1353
  class McpService extends import_core8.Service {
1816
1354
  static serviceType = MCP_SERVICE_NAME;
1817
- capabilityDescription = "Enables the agent to interact with MCP (Model Context Protocol) servers";
1355
+ capabilityDescription = "Enables the agent to interact with MCP servers";
1818
1356
  connections = new Map;
1819
1357
  connectionStates = new Map;
1820
- mcpProvider = {
1821
- values: { mcp: {}, mcpText: "" },
1822
- data: { mcp: {} },
1823
- text: ""
1824
- };
1358
+ mcpProvider = { values: { mcp: {}, mcpText: "" }, data: { mcp: {} }, text: "" };
1825
1359
  pingConfig = DEFAULT_PING_CONFIG;
1826
1360
  toolCompatibility = null;
1827
- compatibilityInitialized = false;
1828
- initializationPromise = null;
1829
- constructor(runtime2) {
1830
- super(runtime2);
1831
- import_core8.logger.info("[McpService] Constructor called, starting initialization...");
1832
- this.initializationPromise = this.initializeMcpServers();
1833
- }
1834
- static async start(runtime2) {
1835
- const service = new McpService(runtime2);
1836
- if (service.initializationPromise) {
1837
- await service.initializationPromise;
1838
- }
1839
- return service;
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;
1840
1373
  }
1841
1374
  async waitForInitialization() {
1842
- if (this.initializationPromise) {
1843
- await this.initializationPromise;
1844
- }
1375
+ await this.initPromise;
1845
1376
  }
1846
1377
  async stop() {
1847
- for (const [name] of this.connections) {
1848
- await this.deleteConnection(name);
1849
- }
1850
- this.connections.clear();
1851
- for (const state of this.connectionStates.values()) {
1852
- if (state.pingInterval)
1853
- clearInterval(state.pingInterval);
1854
- if (state.reconnectTimeout)
1855
- clearTimeout(state.reconnectTimeout);
1378
+ for (const name of this.connections.keys()) {
1379
+ await this.disconnect(name);
1856
1380
  }
1857
- this.connectionStates.clear();
1858
1381
  }
1859
- async initializeMcpServers() {
1860
- import_core8.logger.info("[McpService] Starting MCP server initialization...");
1382
+ async init() {
1861
1383
  try {
1862
- const mcpSettings = this.getMcpSettings();
1863
- const serverCount = mcpSettings?.servers ? Object.keys(mcpSettings.servers).length : 0;
1864
- const serverNames = mcpSettings?.servers ? Object.keys(mcpSettings.servers) : [];
1865
- import_core8.logger.info(`[McpService] Getting MCP settings... hasSettings=${!!mcpSettings} hasServers=${!!mcpSettings?.servers} serverCount=${serverCount} servers=${JSON.stringify(serverNames)}`);
1866
- if (!mcpSettings || !mcpSettings.servers) {
1867
- import_core8.logger.info("[McpService] No MCP servers configured.");
1868
- this.mcpProvider = buildMcpProviderData([]);
1869
- return;
1870
- }
1871
- if (Object.keys(mcpSettings.servers).length === 0) {
1872
- import_core8.logger.info("[McpService] MCP settings exist but no servers configured.");
1384
+ const settings = this.getSettings();
1385
+ if (!settings?.servers || Object.keys(settings.servers).length === 0) {
1873
1386
  this.mcpProvider = buildMcpProviderData([]);
1874
1387
  return;
1875
1388
  }
1876
- import_core8.logger.info(`[McpService] Connecting to ${Object.keys(mcpSettings.servers).length} MCP servers: ${JSON.stringify(Object.keys(mcpSettings.servers))}`);
1877
- const connectionStartTime = Date.now();
1878
- await this.updateServerConnections(mcpSettings.servers);
1879
- const connectionDuration = Date.now() - connectionStartTime;
1880
- const servers = this.getServers();
1881
- const connectedServers = servers.filter((s) => s.status === "connected");
1882
- const failedServers = servers.filter((s) => s.status !== "connected");
1883
- if (connectedServers.length > 0) {
1884
- const toolCounts = connectedServers.map((s) => `${s.name}:${s.tools?.length || 0}tools`).join(", ");
1885
- import_core8.logger.info(`[McpService] ✓ Successfully connected ${connectedServers.length}/${servers.length} servers in ${connectionDuration}ms: ${toolCounts}`);
1886
- }
1887
- if (failedServers.length > 0) {
1888
- const failedDetails = failedServers.map((s) => `${s.name}(${s.error || "unknown error"})`).join(", ");
1889
- import_core8.logger.warn(`[McpService] ⚠️ Failed to connect to ${failedServers.length}/${servers.length} servers: ${failedDetails}`);
1890
- }
1891
- if (connectedServers.length === 0 && servers.length > 0) {
1892
- import_core8.logger.error(`[McpService] ❌ ALL MCP servers failed to connect! MCP tools will NOT be available.`);
1893
- }
1894
- this.mcpProvider = buildMcpProviderData(servers);
1895
- const mcpDataKeys = Object.keys(this.mcpProvider.data?.mcp || {});
1896
- import_core8.logger.info(`[McpService] MCP provider data built: ${mcpDataKeys.length} server(s) available`);
1897
- } catch (error) {
1898
- import_core8.logger.error({ error: error instanceof Error ? error.message : String(error) }, "❌ Failed to initialize MCP servers - MCP tools will NOT be available");
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");
1899
1420
  this.mcpProvider = buildMcpProviderData([]);
1900
1421
  }
1901
1422
  }
1902
- getMcpSettings() {
1903
- const rawSettings = this.runtime.getSetting("mcp");
1904
- let settings = rawSettings;
1905
- import_core8.logger.info(`[McpService] getSetting("mcp") result: type=${typeof rawSettings} isNull=${rawSettings === null} hasServers=${!!settings?.servers}`);
1906
- if (!settings || !settings.servers) {
1907
- const characterSettings = this.runtime.character?.settings;
1908
- if (characterSettings?.mcp) {
1909
- import_core8.logger.info("[McpService] Found MCP settings in character.settings.mcp (fallback)");
1910
- settings = characterSettings.mcp;
1911
- }
1912
- }
1913
- if (!settings || !settings.servers) {
1914
- const runtimeSettings = this.runtime.settings;
1915
- if (runtimeSettings?.mcp) {
1916
- import_core8.logger.info("[McpService] Found MCP settings in runtime.settings.mcp (fallback)");
1917
- settings = runtimeSettings.mcp;
1918
- }
1919
- }
1920
- if (settings && typeof settings === "object" && settings.servers) {
1921
- import_core8.logger.info(`[McpService] MCP settings found with ${Object.keys(settings.servers).length} server(s)`);
1922
- return settings;
1923
- }
1924
- import_core8.logger.info("[McpService] No valid MCP settings found");
1925
- return;
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;
1926
1430
  }
1927
- async updateServerConnections(serverConfigs) {
1928
- const currentNames = new Set(this.connections.keys());
1929
- const newNames = new Set(Object.keys(serverConfigs));
1930
- for (const name of currentNames) {
1931
- if (!newNames.has(name)) {
1932
- await this.deleteConnection(name);
1933
- import_core8.logger.info(`Deleted MCP server: ${name}`);
1934
- }
1935
- }
1936
- const connectionPromises = Object.entries(serverConfigs).map(async ([name, config]) => {
1937
- const currentConnection = this.connections.get(name);
1938
- if (!currentConnection) {
1939
- try {
1940
- await this.initializeConnection(name, config);
1941
- import_core8.logger.info(`✓ Connected to MCP server: ${name}`);
1942
- } catch (error) {
1943
- import_core8.logger.error({ error: error instanceof Error ? error.message : String(error), serverName: name }, `✗ Failed to connect to new MCP server ${name}`);
1944
- }
1945
- } else if (JSON.stringify(config) !== currentConnection.server.config) {
1946
- try {
1947
- await this.deleteConnection(name);
1948
- await this.initializeConnection(name, config);
1949
- import_core8.logger.info(`✓ Reconnected MCP server with updated config: ${name}`);
1950
- } catch (error) {
1951
- import_core8.logger.error({ error: error instanceof Error ? error.message : String(error), serverName: name }, `✗ Failed to reconnect MCP server ${name}`);
1952
- }
1953
- }
1954
- });
1955
- await Promise.allSettled(connectionPromises);
1956
- import_core8.logger.info(`[McpService] All server connection attempts completed`);
1957
- }
1958
- async initializeConnection(name, config) {
1959
- await this.deleteConnection(name);
1960
- const state = {
1961
- status: "connecting",
1962
- reconnectAttempts: 0,
1963
- consecutivePingFailures: 0
1964
- };
1431
+ async connect(name, config) {
1432
+ await this.disconnect(name);
1433
+ const state = { status: "connecting", reconnectAttempts: 0, consecutivePingFailures: 0 };
1965
1434
  this.connectionStates.set(name, state);
1966
1435
  try {
1967
1436
  const client = new import_client.Client({ name: "ElizaOS", version: "1.0.0" }, { capabilities: {} });
1968
- const transport = config.type === "stdio" ? await this.buildStdioClientTransport(name, config) : await this.buildHttpClientTransport(name, config);
1969
- const connection = {
1970
- server: {
1971
- name,
1972
- config: JSON.stringify(config),
1973
- status: "connecting"
1974
- },
1437
+ const transport = config.type === "stdio" ? this.createStdioTransport(name, config) : this.createHttpTransport(name, config);
1438
+ const conn = {
1439
+ server: { name, config: JSON.stringify(config), status: "connecting" },
1975
1440
  client,
1976
1441
  transport
1977
1442
  };
1978
- this.connections.set(name, connection);
1979
- this.setupTransportHandlers(name, connection, state);
1980
- await client.connect(transport);
1981
- const capabilities = client.getServerCapabilities();
1982
- import_core8.logger.debug(`[${name}] Server capabilities:`, JSON.stringify(capabilities || {}));
1983
- const tools = await this.fetchToolsList(name);
1984
- const resources = capabilities?.resources ? await this.fetchResourcesList(name) : [];
1985
- const resourceTemplates = capabilities?.resources ? await this.fetchResourceTemplatesList(name) : [];
1986
- connection.server = {
1987
- status: "connected",
1988
- name,
1989
- config: JSON.stringify(config),
1990
- error: "",
1991
- tools,
1992
- resources,
1993
- resourceTemplates
1994
- };
1443
+ this.connections.set(name, conn);
1444
+ this.setupTransportHandlers(name, conn, state, config.type === "stdio");
1445
+ await Promise.race([
1446
+ client.connect(transport),
1447
+ new Promise((_, rej) => setTimeout(() => rej(new Error("Connection timeout")), 60000))
1448
+ ]);
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 };
1995
1454
  state.status = "connected";
1996
1455
  state.lastConnected = new Date;
1997
1456
  state.reconnectAttempts = 0;
1998
1457
  state.consecutivePingFailures = 0;
1999
- this.startPingMonitoring(name);
2000
- import_core8.logger.info(`Successfully connected to MCP server: ${name}`);
2001
- } catch (error) {
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) {
2002
1463
  state.status = "disconnected";
2003
- state.lastError = error instanceof Error ? error : new Error(String(error));
2004
- this.handleDisconnection(name, error);
2005
- throw error;
1464
+ state.lastError = e instanceof Error ? e : new Error(String(e));
1465
+ this.handleDisconnect(name, e);
1466
+ throw e;
2006
1467
  }
2007
1468
  }
2008
- setupTransportHandlers(name, connection, state) {
2009
- const config = JSON.parse(connection.server.config);
2010
- const isHttpTransport = config.type !== "stdio";
2011
- connection.transport.onerror = async (error) => {
2012
- const errorMessage = error?.message || String(error);
2013
- const isExpectedTimeout = isHttpTransport && (errorMessage === "undefined" || errorMessage === "" || errorMessage.includes("SSE error") || errorMessage.includes("timeout"));
2014
- if (isExpectedTimeout) {
2015
- import_core8.logger.debug({ serverName: name }, `SSE connection timeout for "${name}" (expected, will reconnect)`);
2016
- } else {
2017
- import_core8.logger.error({ error, serverName: name }, `Transport error for "${name}"`);
2018
- connection.server.status = "disconnected";
2019
- this.appendErrorMessage(connection, error.message);
2020
- }
2021
- if (!isHttpTransport) {
2022
- this.handleDisconnection(name, error);
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)`);
2023
1478
  }
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);
2024
1519
  };
2025
- connection.transport.onclose = async () => {
2026
- if (isHttpTransport) {
2027
- import_core8.logger.debug({ serverName: name }, `SSE connection closed for "${name}" (stateless, will reconnect on demand)`);
2028
- } else {
2029
- import_core8.logger.warn({ serverName: name }, `Transport closed for "${name}"`);
2030
- connection.server.status = "disconnected";
2031
- 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"));
2032
1524
  }
2033
1525
  };
2034
1526
  }
2035
- startPingMonitoring(name) {
2036
- const connection = this.connections.get(name);
2037
- if (!connection)
2038
- return;
2039
- const config = JSON.parse(connection.server.config);
2040
- const isHttpTransport = config.type !== "stdio";
2041
- if (isHttpTransport) {
2042
- import_core8.logger.debug(`[McpService] Skipping ping monitoring for HTTP server: ${name}`);
2043
- return;
2044
- }
1527
+ startPingMonitor(name) {
2045
1528
  const state = this.connectionStates.get(name);
2046
1529
  if (!state || !this.pingConfig.enabled)
2047
1530
  return;
2048
1531
  if (state.pingInterval)
2049
1532
  clearInterval(state.pingInterval);
2050
- state.pingInterval = setInterval(() => {
2051
- this.sendPing(name).catch((err) => {
2052
- import_core8.logger.warn({ error: err instanceof Error ? err.message : String(err), serverName: name }, `Ping failed for ${name}`);
2053
- this.handlePingFailure(name, err);
2054
- });
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
+ }
2055
1549
  }, this.pingConfig.intervalMs);
2056
1550
  }
2057
- async sendPing(name) {
2058
- const connection = this.connections.get(name);
2059
- if (!connection)
2060
- throw new Error(`No connection for ping: ${name}`);
2061
- await Promise.race([
2062
- connection.client.listTools(),
2063
- new Promise((_, reject) => setTimeout(() => reject(new Error("Ping timeout")), this.pingConfig.timeoutMs))
2064
- ]);
2065
- const state = this.connectionStates.get(name);
2066
- if (state)
2067
- state.consecutivePingFailures = 0;
2068
- }
2069
- handlePingFailure(name, error) {
2070
- const state = this.connectionStates.get(name);
2071
- if (!state)
2072
- return;
2073
- state.consecutivePingFailures++;
2074
- if (state.consecutivePingFailures >= this.pingConfig.failuresBeforeDisconnect) {
2075
- import_core8.logger.warn(`Ping failures exceeded for ${name}, disconnecting and attempting reconnect.`);
2076
- this.handleDisconnection(name, error);
2077
- }
2078
- }
2079
- handleDisconnection(name, error) {
1551
+ handleDisconnect(name, error) {
2080
1552
  const state = this.connectionStates.get(name);
2081
1553
  if (!state)
2082
1554
  return;
@@ -2087,212 +1559,145 @@ class McpService extends import_core8.Service {
2087
1559
  if (state.reconnectTimeout)
2088
1560
  clearTimeout(state.reconnectTimeout);
2089
1561
  if (state.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
2090
- import_core8.logger.error(`Max reconnect attempts reached for ${name}. Giving up.`);
1562
+ import_core8.logger.error(`[MCP] Max reconnect attempts for ${name}`);
2091
1563
  return;
2092
1564
  }
2093
1565
  const delay = INITIAL_RETRY_DELAY * Math.pow(BACKOFF_MULTIPLIER, state.reconnectAttempts);
2094
1566
  state.reconnectTimeout = setTimeout(async () => {
2095
1567
  state.reconnectAttempts++;
2096
- import_core8.logger.info(`Attempting to reconnect to ${name} (attempt ${state.reconnectAttempts})...`);
2097
1568
  const config = this.connections.get(name)?.server.config;
2098
1569
  if (config) {
2099
1570
  try {
2100
- await this.initializeConnection(name, JSON.parse(config));
2101
- } catch (err) {
2102
- import_core8.logger.error({ error: err instanceof Error ? err.message : String(err), serverName: name }, `Reconnect attempt failed for ${name}`);
2103
- this.handleDisconnection(name, err);
1571
+ await this.connect(name, JSON.parse(config));
1572
+ } catch (e) {
1573
+ this.handleDisconnect(name, e);
2104
1574
  }
2105
1575
  }
2106
1576
  }, delay);
2107
1577
  }
2108
- async deleteConnection(name) {
2109
- const connection = this.connections.get(name);
2110
- if (connection) {
2111
- try {
2112
- await connection.transport.close();
2113
- await connection.client.close();
2114
- } catch (error) {
2115
- import_core8.logger.error({ error: error instanceof Error ? error.message : String(error), serverName: name }, `Failed to close transport for ${name}`);
2116
- }
2117
- this.connections.delete(name);
2118
- }
2119
- const state = this.connectionStates.get(name);
2120
- if (state) {
2121
- if (state.pingInterval)
2122
- clearInterval(state.pingInterval);
2123
- if (state.reconnectTimeout)
2124
- clearTimeout(state.reconnectTimeout);
2125
- this.connectionStates.delete(name);
2126
- }
2127
- }
2128
- getServerConnection(serverName) {
2129
- return this.connections.get(serverName);
2130
- }
2131
- async buildStdioClientTransport(name, config) {
2132
- if (!config.command) {
2133
- throw new Error(`Missing command for stdio MCP server ${name}`);
2134
- }
2135
- return new import_stdio.StdioClientTransport({
2136
- command: config.command,
2137
- args: config.args,
2138
- env: {
2139
- ...config.env,
2140
- ...process.env.PATH ? { PATH: process.env.PATH } : {}
2141
- },
2142
- stderr: "pipe",
2143
- cwd: config.cwd
2144
- });
2145
- }
2146
- async buildHttpClientTransport(name, config) {
2147
- if (!config.url) {
2148
- throw new Error(`Missing URL for HTTP MCP server ${name}`);
2149
- }
2150
- if (config.type === "sse") {
2151
- import_core8.logger.warn(`Server "${name}": "sse" transport type is deprecated. Use "streamable-http" or "http" instead for the modern Streamable HTTP transport.`);
2152
- }
2153
- return new import_sse.SSEClientTransport(new URL(config.url));
2154
- }
2155
- appendErrorMessage(connection, error) {
2156
- const newError = connection.server.error ? `${connection.server.error}
2157
- ${error}` : error;
2158
- connection.server.error = newError;
2159
- }
2160
- async fetchToolsList(serverName) {
1578
+ async fetchTools(name) {
1579
+ const conn = this.connections.get(name);
1580
+ if (!conn)
1581
+ return [];
2161
1582
  try {
2162
- const connection = this.getServerConnection(serverName);
2163
- if (!connection) {
2164
- return [];
2165
- }
2166
- const response = await connection.client.listTools();
2167
- const tools = (response?.tools || []).map((tool) => {
2168
- let processedTool = { ...tool };
2169
- if (tool.inputSchema) {
2170
- try {
2171
- if (!this.compatibilityInitialized) {
2172
- this.initializeToolCompatibility();
2173
- }
2174
- processedTool.inputSchema = this.applyToolCompatibility(tool.inputSchema);
2175
- import_core8.logger.debug(`Applied tool compatibility for: ${tool.name} on server: ${serverName}`);
2176
- } catch (error) {
2177
- import_core8.logger.warn({ error, toolName: tool.name, serverName }, `Tool compatibility failed for ${tool.name} on ${serverName}`);
2178
- }
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);
1589
+ try {
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;
2179
1594
  }
2180
- return processedTool;
2181
1595
  });
2182
- import_core8.logger.info(`Fetched ${tools.length} tools for ${serverName}`);
2183
- for (const tool of tools) {
2184
- import_core8.logger.info(`[${serverName}] ${tool.name}: ${tool.description}`);
2185
- }
2186
- return tools;
2187
- } catch (error) {
2188
- import_core8.logger.error({ error: error instanceof Error ? error.message : String(error), serverName }, `Failed to fetch tools for ${serverName}`);
1596
+ } catch (e) {
1597
+ import_core8.logger.warn({ error: err(e), server: name }, `[MCP] Failed to fetch tools`);
2189
1598
  return [];
2190
1599
  }
2191
1600
  }
2192
- async fetchResourcesList(serverName) {
1601
+ async fetchResources(name) {
2193
1602
  try {
2194
- const connection = this.getServerConnection(serverName);
2195
- if (!connection) {
2196
- return [];
2197
- }
2198
- const response = await connection.client.listResources();
2199
- return response?.resources || [];
2200
- } catch (error) {
2201
- import_core8.logger.warn({ error: error instanceof Error ? error.message : String(error), serverName }, `No resources found for ${serverName}`);
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`);
2202
1606
  return [];
2203
1607
  }
2204
1608
  }
2205
- async fetchResourceTemplatesList(serverName) {
1609
+ async fetchResourceTemplates(name) {
2206
1610
  try {
2207
- const connection = this.getServerConnection(serverName);
2208
- if (!connection) {
2209
- return [];
2210
- }
2211
- const response = await connection.client.listResourceTemplates();
2212
- return response?.resourceTemplates || [];
2213
- } catch (error) {
2214
- import_core8.logger.warn({ error: error instanceof Error ? error.message : String(error), serverName }, `No resource templates found for ${serverName}`);
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`);
2215
1614
  return [];
2216
1615
  }
2217
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
+ }
2218
1643
  getServers() {
2219
- return Array.from(this.connections.values()).filter((conn) => !conn.server.disabled).map((conn) => conn.server);
1644
+ return Array.from(this.connections.values()).filter((c) => !c.server.disabled).map((c) => c.server);
2220
1645
  }
2221
1646
  getProviderData() {
2222
1647
  return this.mcpProvider;
2223
1648
  }
2224
- async callTool(serverName, toolName, toolArguments) {
2225
- const connection = this.connections.get(serverName);
2226
- if (!connection) {
2227
- throw new Error(`No connection found for server: ${serverName}`);
2228
- }
2229
- if (connection.server.disabled) {
2230
- throw new Error(`Server "${serverName}" is disabled`);
2231
- }
2232
- let timeout = DEFAULT_MCP_TIMEOUT_SECONDS;
2233
- try {
2234
- const config = JSON.parse(connection.server.config);
2235
- timeout = config.timeoutInMillis || DEFAULT_MCP_TIMEOUT_SECONDS;
2236
- } catch (error) {
2237
- import_core8.logger.error({ error: error instanceof Error ? error.message : String(error), serverName }, `Failed to parse timeout configuration for server ${serverName}`);
2238
- }
2239
- const result = await connection.client.callTool({ name: toolName, arguments: toolArguments }, undefined, { timeout });
2240
- if (!result.content) {
2241
- throw new Error("Invalid tool result: missing content array");
2242
- }
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
+ }
1671
+ async callTool(serverName, toolName, args) {
1672
+ await this.ensureConnected(serverName);
1673
+ const conn = this.connections.get(serverName);
1674
+ if (!conn)
1675
+ throw new Error(`No connection: ${serverName}`);
1676
+ if (conn.server.disabled)
1677
+ throw new Error(`Server disabled: ${serverName}`);
1678
+ const config = JSON.parse(conn.server.config);
1679
+ const timeout = config.timeoutInMillis || DEFAULT_MCP_TIMEOUT_SECONDS;
1680
+ const result = await conn.client.callTool({ name: toolName, arguments: args }, undefined, { timeout });
1681
+ if (!result.content)
1682
+ throw new Error("Invalid tool result");
2243
1683
  return result;
2244
1684
  }
2245
1685
  async readResource(serverName, uri) {
2246
- const connection = this.connections.get(serverName);
2247
- if (!connection) {
2248
- throw new Error(`No connection found for server: ${serverName}`);
2249
- }
2250
- if (connection.server.disabled) {
2251
- throw new Error(`Server "${serverName}" is disabled`);
2252
- }
2253
- return await connection.client.readResource({ uri });
1686
+ await this.ensureConnected(serverName);
1687
+ const conn = this.connections.get(serverName);
1688
+ if (!conn)
1689
+ throw new Error(`No connection: ${serverName}`);
1690
+ if (conn.server.disabled)
1691
+ throw new Error(`Server disabled: ${serverName}`);
1692
+ return conn.client.readResource({ uri });
2254
1693
  }
2255
1694
  async restartConnection(serverName) {
2256
- const connection = this.connections.get(serverName);
2257
- const config = connection?.server.config;
2258
- if (config) {
2259
- import_core8.logger.info(`Restarting ${serverName} MCP server...`);
2260
- connection.server.status = "connecting";
2261
- connection.server.error = "";
2262
- try {
2263
- await this.deleteConnection(serverName);
2264
- await this.initializeConnection(serverName, JSON.parse(config));
2265
- import_core8.logger.info(`${serverName} MCP server connected`);
2266
- } catch (error) {
2267
- import_core8.logger.error({ error: error instanceof Error ? error.message : String(error), serverName }, `Failed to restart connection for ${serverName}`);
2268
- throw new Error(`Failed to connect to ${serverName} MCP server`);
2269
- }
2270
- }
2271
- }
2272
- initializeToolCompatibility() {
2273
- if (this.compatibilityInitialized)
2274
- return;
2275
- this.toolCompatibility = createMcpToolCompatibilitySync(this.runtime);
2276
- this.compatibilityInitialized = true;
2277
- if (this.toolCompatibility) {
2278
- import_core8.logger.info(`Tool compatibility enabled`);
2279
- } else {
2280
- import_core8.logger.info(`No tool compatibility needed`);
2281
- }
2282
- }
2283
- applyToolCompatibility(toolSchema) {
2284
- if (!this.compatibilityInitialized) {
2285
- this.initializeToolCompatibility();
2286
- }
2287
- if (!this.toolCompatibility || !toolSchema) {
2288
- return toolSchema;
2289
- }
2290
- try {
2291
- return this.toolCompatibility.transformToolSchema(toolSchema);
2292
- } catch (error) {
2293
- import_core8.logger.warn({ error }, `Tool compatibility transformation failed`);
2294
- return toolSchema;
2295
- }
1695
+ const conn = this.connections.get(serverName);
1696
+ if (!conn)
1697
+ throw new Error(`No connection: ${serverName}`);
1698
+ const config = conn.server.config;
1699
+ await this.disconnect(serverName);
1700
+ await this.connect(serverName, JSON.parse(config));
2296
1701
  }
2297
1702
  }
2298
1703
 
@@ -2304,9 +1709,9 @@ var mcpPlugin = {
2304
1709
  import_core9.logger.info("Initializing MCP plugin...");
2305
1710
  },
2306
1711
  services: [McpService],
2307
- actions: [callToolAction, readResourceAction],
1712
+ actions: [readResourceAction],
2308
1713
  providers: [provider]
2309
1714
  };
2310
1715
  var src_default = mcpPlugin;
2311
1716
 
2312
- //# debugId=362D3DF72359421664756E2164756E21
1717
+ //# debugId=48F4A8AA4A8260E964756E2164756E21