@elizaos/plugin-mcp 2.0.0-alpha.7 → 2.0.0-beta.1

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 (48) hide show
  1. package/README.md +269 -0
  2. package/dist/cjs/index.cjs +822 -358
  3. package/dist/cjs/index.js.map +19 -18
  4. package/dist/node/actions/mcp.d.ts +6 -0
  5. package/dist/node/actions/mcp.d.ts.map +1 -0
  6. package/dist/node/index.d.ts +1 -0
  7. package/dist/node/index.d.ts.map +1 -1
  8. package/dist/node/index.js +818 -362
  9. package/dist/node/index.js.map +19 -18
  10. package/dist/node/mcp-marketplace.d.ts +69 -0
  11. package/dist/node/mcp-marketplace.d.ts.map +1 -0
  12. package/dist/node/prompts.d.ts +23 -0
  13. package/dist/node/prompts.d.ts.map +1 -0
  14. package/dist/node/provider.d.ts.map +1 -1
  15. package/dist/node/routes-mcp.d.ts +48 -0
  16. package/dist/node/routes-mcp.d.ts.map +1 -0
  17. package/dist/node/service.d.ts +1 -1
  18. package/dist/node/service.d.ts.map +1 -1
  19. package/dist/node/templates/errorAnalysisPrompt.d.ts.map +1 -1
  20. package/dist/node/templates/feedbackTemplate.d.ts +1 -6
  21. package/dist/node/templates/feedbackTemplate.d.ts.map +1 -1
  22. package/dist/node/templates/resourceAnalysisTemplate.d.ts +1 -6
  23. package/dist/node/templates/resourceAnalysisTemplate.d.ts.map +1 -1
  24. package/dist/node/templates/resourceSelectionTemplate.d.ts +1 -6
  25. package/dist/node/templates/resourceSelectionTemplate.d.ts.map +1 -1
  26. package/dist/node/templates/toolReasoningTemplate.d.ts +1 -6
  27. package/dist/node/templates/toolReasoningTemplate.d.ts.map +1 -1
  28. package/dist/node/templates/toolSelectionTemplate.d.ts +2 -7
  29. package/dist/node/templates/toolSelectionTemplate.d.ts.map +1 -1
  30. package/dist/node/tool-compatibility/integration-test.d.ts.map +1 -1
  31. package/dist/node/tool-compatibility/providers/openai.d.ts.map +1 -1
  32. package/dist/node/tool-compatibility/test-example.d.ts.map +1 -1
  33. package/dist/node/types.d.ts +1 -0
  34. package/dist/node/types.d.ts.map +1 -1
  35. package/dist/node/utils/error.d.ts.map +1 -1
  36. package/dist/node/utils/handler.d.ts.map +1 -1
  37. package/dist/node/utils/json.d.ts +1 -0
  38. package/dist/node/utils/json.d.ts.map +1 -1
  39. package/dist/node/utils/validation.d.ts.map +1 -1
  40. package/dist/node/utils/wrapper.d.ts.map +1 -1
  41. package/package.json +9 -8
  42. package/dist/node/actions/callToolAction.d.ts +0 -3
  43. package/dist/node/actions/callToolAction.d.ts.map +0 -1
  44. package/dist/node/actions/readResourceAction.d.ts +0 -3
  45. package/dist/node/actions/readResourceAction.d.ts.map +0 -1
  46. package/dist/node/generated/prompts/typescript/prompts.d.ts +0 -24
  47. package/dist/node/generated/prompts/typescript/prompts.d.ts.map +0 -1
  48. package/dist/tsconfig.build.tsbuildinfo +0 -1
@@ -2,27 +2,37 @@ var __defProp = Object.defineProperty;
2
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __hasOwnProp = Object.prototype.hasOwnProperty;
5
- var __moduleCache = /* @__PURE__ */ new WeakMap;
5
+ function __accessProp(key) {
6
+ return this[key];
7
+ }
6
8
  var __toCommonJS = (from) => {
7
- var entry = __moduleCache.get(from), desc;
9
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
8
10
  if (entry)
9
11
  return entry;
10
12
  entry = __defProp({}, "__esModule", { value: true });
11
- if (from && typeof from === "object" || typeof from === "function")
12
- __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
13
- get: () => from[key],
14
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
- }));
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (var key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(entry, key))
16
+ __defProp(entry, key, {
17
+ get: __accessProp.bind(from, key),
18
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
19
+ });
20
+ }
16
21
  __moduleCache.set(from, entry);
17
22
  return entry;
18
23
  };
24
+ var __moduleCache;
25
+ var __returnValue = (v) => v;
26
+ function __exportSetter(name, newValue) {
27
+ this[name] = __returnValue.bind(null, newValue);
28
+ }
19
29
  var __export = (target, all) => {
20
30
  for (var name in all)
21
31
  __defProp(target, name, {
22
32
  get: all[name],
23
33
  enumerable: true,
24
34
  configurable: true,
25
- set: (newValue) => all[name] = () => newValue
35
+ set: __exportSetter.bind(all, name)
26
36
  });
27
37
  };
28
38
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -336,7 +346,7 @@ IMPORTANT: ${constraintText}`;
336
346
  if (constraints.maxItems) {
337
347
  rules.push(`array must have at most ${constraints.maxItems} items`);
338
348
  }
339
- return rules.length > 0 ? rules.join(", ") : JSON.stringify(constraints);
349
+ return rules.length > 0 ? rules.join(", ") : Object.entries(constraints).map(([key, value]) => `${key}: ${String(value)}`).join(", ");
340
350
  }
341
351
  };
342
352
  });
@@ -488,52 +498,18 @@ Constraints: ${constraintText}`;
488
498
  });
489
499
 
490
500
  // src/index.ts
491
- import { logger as logger3 } from "@elizaos/core";
492
-
493
- // src/types.ts
494
- var MCP_SERVICE_NAME = "mcp";
495
- var DEFAULT_MCP_TIMEOUT_SECONDS = 60000;
496
- var DEFAULT_MAX_RETRIES = 2;
497
- var ResourceSelectionSchema = {
498
- type: "object",
499
- required: ["serverName", "uri"],
500
- properties: {
501
- serverName: {
502
- type: "string",
503
- minLength: 1,
504
- errorMessage: "serverName must not be empty"
505
- },
506
- uri: {
507
- type: "string",
508
- minLength: 1,
509
- errorMessage: "uri must not be empty"
510
- },
511
- reasoning: {
512
- type: "string"
513
- },
514
- noResourceAvailable: {
515
- type: "boolean"
516
- }
517
- }
518
- };
519
- var DEFAULT_PING_CONFIG = {
520
- enabled: true,
521
- intervalMs: 1e4,
522
- timeoutMs: 5000,
523
- failuresBeforeDisconnect: 3
524
- };
525
- var MAX_RECONNECT_ATTEMPTS = 5;
526
- var BACKOFF_MULTIPLIER = 2;
527
- var INITIAL_RETRY_DELAY = 2000;
501
+ import {
502
+ logger as logger4,
503
+ promoteSubactionsToActions
504
+ } from "@elizaos/core";
528
505
 
529
- // src/utils/error.ts
506
+ // src/actions/mcp.ts
530
507
  import {
531
- composePromptFromState,
532
- logger,
533
- ModelType
508
+ composePromptFromState as composePromptFromState4,
509
+ ModelType as ModelType5
534
510
  } from "@elizaos/core";
535
511
 
536
- // src/generated/prompts/typescript/prompts.ts
512
+ // src/prompts.ts
537
513
  var errorAnalysisTemplate = `{{{mcpProvider.text}}}
538
514
 
539
515
  {{{recentMessages}}}
@@ -562,10 +538,10 @@ You are a helpful assistant responding to a user's request. You've just accessed
562
538
 
563
539
  Original user request: "{{{userMessage}}}"
564
540
 
565
- Resource metadata:
541
+ Resource metadata:
566
542
  {{{resourceMeta}}
567
543
 
568
- Resource content:
544
+ Resource content:
569
545
  {{{resourceContent}}
570
546
 
571
547
  Instructions:
@@ -588,33 +564,38 @@ You are an intelligent assistant helping select the right resource to address a
588
564
  CRITICAL INSTRUCTIONS:
589
565
  1. You MUST specify both a valid serverName AND uri from the list above
590
566
  2. The serverName value should match EXACTLY the server name shown in parentheses (Server: X)
591
- CORRECT: "serverName": "github" (if the server is called "github")
592
- WRONG: "serverName": "GitHub" or "Github" or any other variation
567
+ CORRECT: serverName: github (if the server is called "github")
568
+ WRONG: serverName: GitHub, serverName: Github, or any other variation
593
569
  3. The uri value should match EXACTLY the resource uri listed
594
- CORRECT: "uri": "weather://San Francisco/current" (if that's the exact uri)
595
- WRONG: "uri": "weather://sanfrancisco/current" or any variation
570
+ CORRECT: uri: weather://San Francisco/current (if that's the exact uri)
571
+ WRONG: uri: weather://sanfrancisco/current or any variation
596
572
  4. Identify the user's information need from the conversation context
597
573
  5. Select the most appropriate resource based on its description and the request
598
- 6. If no resource seems appropriate, output {"noResourceAvailable": true}
574
+ 6. If no resource seems appropriate, set noResourceAvailable: true
599
575
 
600
- !!! YOUR RESPONSE MUST BE A VALID JSON OBJECT ONLY !!!
576
+ Respond with compact JSON only.
601
577
 
602
578
  STRICT FORMAT REQUIREMENTS:
603
- - NO code block formatting (NO backticks or \`\`\`)
604
- - NO comments (NO // or /* */)
579
+ - Include "noResourceAvailable": false when selecting a resource
580
+ - NO code block formatting (NO backticks)
581
+ - NO comments
605
582
  - NO placeholders like "replace with...", "example", "your...", "actual", etc.
606
583
  - Every parameter value must be a concrete, usable value (not instructions to replace)
607
- - Use proper JSON syntax with double quotes for strings
608
584
  - NO explanatory text before or after the JSON object
609
585
 
610
586
  EXAMPLE RESPONSE:
611
587
  {
612
588
  "serverName": "weather-server",
613
589
  "uri": "weather://San Francisco/current",
614
- "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."
590
+ "reasoning": "The user is asking about current weather in San Francisco. This resource provides up-to-date weather information for that city.",
591
+ "noResourceAvailable": false
615
592
  }
616
593
 
617
- REMEMBER: Your response will be parsed directly as JSON. If it fails to parse, the operation will fail completely!`;
594
+ NO RESOURCE EXAMPLE:
595
+ {
596
+ "reasoning": "None of the available resources match the user's request.",
597
+ "noResourceAvailable": true
598
+ }`;
618
599
  var toolReasoningTemplate = `{{{mcpProvider.text}}}
619
600
 
620
601
  {{{recentMessages}}}
@@ -643,34 +624,34 @@ Instructions:
643
624
  Your response (written as if directly to the user):`;
644
625
  var toolSelectionArgumentTemplate = `{{recentMessages}}
645
626
 
646
- # TASK: Generate a Strictly Valid JSON Object for Tool Execution
627
+ # TASK: Generate Tool Arguments for Tool Execution
647
628
 
648
629
  You have chosen the "{{toolSelectionName.toolName}}" tool from the "{{toolSelectionName.serverName}}" server to address the user's request.
649
630
  The reasoning behind this selection is: "{{toolSelectionName.reasoning}}"
650
631
 
651
632
  ## CRITICAL INSTRUCTIONS
652
- 1. Ensure the "toolArguments" object strictly adheres to the structure and requirements defined in the schema.
633
+ 1. Ensure the toolArguments block strictly adheres to the structure and requirements defined in the schema.
653
634
  2. All parameter values must be extracted from the conversation context and must be concrete, usable values.
654
635
  3. Avoid placeholders or generic terms unless explicitly provided by the user.
655
636
 
656
- !!! YOUR RESPONSE MUST BE A VALID JSON OBJECT ONLY !!!
637
+ Respond with compact JSON only.
657
638
 
658
639
  ## STRICT FORMAT REQUIREMENTS
659
- - The response MUST be a single valid JSON object.
660
- - DO NOT wrap the JSON in triple backticks (\`\`\`), code blocks, or include any explanatory text.
661
- - DO NOT include comments (// or /* */) anywhere.
640
+ - The response MUST be one JSON object.
641
+ - DO NOT wrap it in triple backticks, code blocks, or include any explanatory text.
642
+ - DO NOT include comments anywhere.
662
643
  - DO NOT use placeholders (e.g., "replace with...", "example", "your...", etc.)
663
- - ALL strings must use double quotes
664
644
 
665
645
  ## CRITICAL NOTES
666
646
  - All values must be fully grounded in user input or inferred contextually.
667
647
  - No missing fields unless they are explicitly optional in the schema.
668
648
  - All types must match the schema (strings, numbers, booleans).
649
+ - Put all executable parameters under the toolArguments object.
669
650
 
670
- ## JSON OBJECT STRUCTURE
651
+ ## RESPONSE STRUCTURE
671
652
  Your response MUST contain ONLY these two top-level keys:
672
- 1. "toolArguments" An object matching the input schema: {{toolInputSchema}}
673
- 2. "reasoning" A string explaining how the values were inferred from the conversation.
653
+ 1. toolArguments - object with fields matching the input schema: {{toolInputSchema}}
654
+ 2. reasoning - a short explanation of how the values were inferred from the conversation.
674
655
 
675
656
  ## EXAMPLE RESPONSE
676
657
  {
@@ -683,50 +664,46 @@ Your response MUST contain ONLY these two top-level keys:
683
664
  "reasoning": "The user wants to see the README from the facebook/react repository based on our conversation."
684
665
  }
685
666
 
686
- REMEMBER: Your response will be parsed directly as JSON. If it fails to parse, the operation will fail completely.`;
667
+ If the tool takes no arguments, use an empty toolArguments object:
668
+ {
669
+ "toolArguments": {},
670
+ "reasoning": "The selected tool does not require arguments for this request."
671
+ }`;
687
672
  var toolSelectionNameTemplate = `{{mcpProvider.text}}
688
673
 
689
674
  {{recentMessages}}
690
675
 
691
676
  # TASK: Select the Most Appropriate Tool and Server
692
677
 
693
- 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.
678
+ You must select the most appropriate tool from the list above to fulfill the user's request. Respond with compact JSON.
694
679
 
695
680
  ## CRITICAL INSTRUCTIONS
696
- 1. Provide both "serverName" and "toolName" from the options listed above.
681
+ 1. Provide both serverName and toolName from the options listed above.
697
682
  2. Each name must match EXACTLY as shown in the list:
698
- - Example (correct): "serverName": "github"
699
- - Example (incorrect): "serverName": "GitHub", "Github", or variations
683
+ - Example (correct): serverName: github
684
+ - Example (incorrect): serverName: GitHub, serverName: Github, or variations
700
685
  3. Extract ACTUAL parameter values from the conversation context.
701
686
  - Do not invent or use placeholders like "octocat" or "Hello-World" unless the user said so.
702
- 4. Include a "reasoning" field explaining why the selected tool fits the request.
703
- 5. If no tool is appropriate, respond with:
704
- {
705
- "noToolAvailable": true
706
- }
707
-
708
- !!! YOUR RESPONSE MUST BE A VALID JSON OBJECT ONLY !!!
709
-
710
- CRITICAL: Your response must START with { and END with }. DO NOT include ANY text before or after the JSON.
687
+ 4. Include a reasoning field explaining why the selected tool fits the request.
688
+ 5. If no tool is appropriate, set noToolAvailable: true.
711
689
 
712
690
  ## STRICT FORMAT REQUIREMENTS
713
- - The response MUST be a single valid JSON object.
714
- - DO NOT wrap the JSON in triple backticks (\`\`\`), code blocks, or include any explanatory text.
715
- - DO NOT include comments (// or /* */) anywhere.
691
+ - The response MUST be one JSON object.
692
+ - DO NOT wrap it in triple backticks, code blocks, or include any explanatory text.
693
+ - DO NOT include comments anywhere.
716
694
  - DO NOT use placeholders (e.g., "replace with...", "example", "your...", etc.)
717
- - ALL strings must use double quotes.
718
695
 
719
696
  ## CRITICAL NOTES
720
697
  - All values must be fully grounded in user input or inferred contextually.
721
698
  - No missing fields unless they are explicitly optional in the schema.
722
699
  - All types must match the schema (strings, numbers, booleans).
723
700
 
724
- ## JSON OBJECT STRUCTURE
701
+ ## RESPONSE STRUCTURE
725
702
  Your response MUST contain ONLY these top-level keys:
726
- 1. "serverName" The name of the server (e.g., "github", "notion")
727
- 2. "toolName" The name of the tool (e.g., "get_file_contents", "search")
728
- 3. "reasoning" A string explaining how the values were inferred from the conversation.
729
- 4. "noToolAvailable" A boolean indicating if no tool is available (true/false)
703
+ 1. serverName - The name of the server (e.g., github, notion)
704
+ 2. toolName - The name of the tool (e.g., get_file_contents, search)
705
+ 3. reasoning - A short explanation of how the values were inferred from the conversation
706
+ 4. noToolAvailable - true or false
730
707
 
731
708
  ## EXAMPLE RESPONSE
732
709
  {
@@ -736,12 +713,74 @@ Your response MUST contain ONLY these top-level keys:
736
713
  "noToolAvailable": false
737
714
  }
738
715
 
716
+ ## NO TOOL EXAMPLE
717
+ {
718
+ "reasoning": "None of the available tools match the user's request.",
719
+ "noToolAvailable": true
720
+ }
721
+
739
722
  ## REMINDERS
740
723
  - Use "github" as serverName for GitHub tools.
741
724
  - Use "notion" as serverName for Notion tools.
742
- - For search and knowledge-based tasks, MCP tools are often appropriate.
725
+ - For search and knowledge-based tasks, MCP tools are often appropriate.`;
726
+
727
+ // src/types.ts
728
+ var MCP_SERVICE_NAME = "mcp";
729
+ var DEFAULT_MCP_TIMEOUT_SECONDS = 60000;
730
+ var DEFAULT_MAX_RETRIES = 2;
731
+ function isRecord(value) {
732
+ return typeof value === "object" && value !== null && !Array.isArray(value);
733
+ }
734
+ function isStdioMcpServerConfig(value) {
735
+ return isRecord(value) && value.type === "stdio" && typeof value.command === "string" && (value.args === undefined || Array.isArray(value.args) && value.args.every((arg) => typeof arg === "string")) && (value.env === undefined || isRecord(value.env) && Object.values(value.env).every((entry) => typeof entry === "string")) && (value.cwd === undefined || typeof value.cwd === "string") && (value.timeoutInMillis === undefined || typeof value.timeoutInMillis === "number");
736
+ }
737
+ function isHttpMcpServerConfig(value) {
738
+ return isRecord(value) && (value.type === "http" || value.type === "streamable-http" || value.type === "sse") && typeof value.url === "string" && (value.timeout === undefined || typeof value.timeout === "number");
739
+ }
740
+ function isMcpSettings(value) {
741
+ if (!isRecord(value) || !isRecord(value.servers)) {
742
+ return false;
743
+ }
744
+ return Object.values(value.servers).every((server) => isStdioMcpServerConfig(server) || isHttpMcpServerConfig(server)) && (value.maxRetries === undefined || typeof value.maxRetries === "number");
745
+ }
746
+ var ResourceSelectionSchema = {
747
+ type: "object",
748
+ required: ["serverName", "uri"],
749
+ properties: {
750
+ serverName: {
751
+ type: "string",
752
+ minLength: 1,
753
+ errorMessage: "serverName must not be empty"
754
+ },
755
+ uri: {
756
+ type: "string",
757
+ minLength: 1,
758
+ errorMessage: "uri must not be empty"
759
+ },
760
+ reasoning: {
761
+ type: "string"
762
+ },
763
+ noResourceAvailable: {
764
+ type: "boolean"
765
+ }
766
+ }
767
+ };
768
+ var DEFAULT_PING_CONFIG = {
769
+ enabled: true,
770
+ intervalMs: 1e4,
771
+ timeoutMs: 5000,
772
+ failuresBeforeDisconnect: 3
773
+ };
774
+ var MAX_RECONNECT_ATTEMPTS = 5;
775
+ var BACKOFF_MULTIPLIER = 2;
776
+ var INITIAL_RETRY_DELAY = 2000;
743
777
 
744
- REMEMBER: This output will be parsed directly as JSON. If the format is incorrect, the operation will fail.`;
778
+ // src/utils/error.ts
779
+ import {
780
+ composePromptFromState,
781
+ logger,
782
+ ModelType
783
+ } from "@elizaos/core";
745
784
 
746
785
  // src/templates/errorAnalysisPrompt.ts
747
786
  var errorAnalysisPrompt = errorAnalysisTemplate;
@@ -782,7 +821,8 @@ async function handleMcpError(state, mcpProvider, error, runtime, message, type,
782
821
  errorType: type
783
822
  },
784
823
  data: {
785
- actionName: type === "tool" ? "CALL_MCP_TOOL" : "READ_MCP_RESOURCE",
824
+ actionName: "MCP",
825
+ op: type === "tool" ? "call_tool" : "read_resource",
786
826
  error: errorMessage,
787
827
  mcpType: type
788
828
  },
@@ -808,7 +848,8 @@ async function handleNoToolAvailable(callback, toolSelection) {
808
848
  fallbackToDirectAssistance: true
809
849
  },
810
850
  data: {
811
- actionName: "CALL_MCP_TOOL",
851
+ actionName: "MCP",
852
+ op: "call_tool",
812
853
  noToolAvailable: true,
813
854
  reason: toolSelection?.reasoning ?? "No appropriate tool available"
814
855
  },
@@ -1082,12 +1123,12 @@ function parseJSON(input) {
1082
1123
  return JSON5.parse(cleanedInput);
1083
1124
  }
1084
1125
  var ajv = new Ajv({
1085
- allErrors: true,
1086
- strict: false
1126
+ allErrors: true
1087
1127
  });
1088
1128
  function formatAjvErrors(errors) {
1089
1129
  return errors.map((err) => {
1090
- const path = err.instancePath ? `${err.instancePath.replace(/^\//, "")}` : "value";
1130
+ const errorPath = err.instancePath ?? err.dataPath ?? "";
1131
+ const path = errorPath ? errorPath.replace(/^\//, "") : "value";
1091
1132
  return `${path}: ${err.message ?? "validation failed"}`;
1092
1133
  }).join(", ");
1093
1134
  }
@@ -1101,6 +1142,15 @@ function validateJsonSchema(data, schema) {
1101
1142
  }
1102
1143
  return { success: true, data };
1103
1144
  }
1145
+ function parseStructuredModelOutput(input) {
1146
+ const errors = [];
1147
+ try {
1148
+ return parseJSON(input);
1149
+ } catch {
1150
+ errors.push("JSON object parse failed");
1151
+ }
1152
+ throw new Error(`No valid JSON object found: ${errors.join("; ")}`);
1153
+ }
1104
1154
 
1105
1155
  // src/utils/schemas.ts
1106
1156
  var toolSelectionNameSchema = {
@@ -1136,7 +1186,24 @@ var toolSelectionArgumentSchema = {
1136
1186
  };
1137
1187
 
1138
1188
  // src/utils/validation.ts
1189
+ function isRecord2(value) {
1190
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1191
+ }
1192
+ function optionalReasoning(parsed) {
1193
+ return typeof parsed.reasoning === "string" ? { reasoning: parsed.reasoning } : {};
1194
+ }
1139
1195
  function validateToolSelectionName(parsed, state) {
1196
+ if (isRecord2(parsed) && parsed.noToolAvailable === true) {
1197
+ return {
1198
+ success: true,
1199
+ data: {
1200
+ serverName: "",
1201
+ toolName: "",
1202
+ noToolAvailable: true,
1203
+ ...optionalReasoning(parsed)
1204
+ }
1205
+ };
1206
+ }
1140
1207
  const basicResult = validateJsonSchema(parsed, toolSelectionNameSchema);
1141
1208
  if (basicResult.success === false) {
1142
1209
  return { success: false, error: basicResult.error };
@@ -1160,7 +1227,8 @@ function validateToolSelectionName(parsed, state) {
1160
1227
  return { success: true, data };
1161
1228
  }
1162
1229
  function validateToolSelectionArgument(parsed, toolInputSchema) {
1163
- const basicResult = validateJsonSchema(parsed, toolSelectionArgumentSchema);
1230
+ const normalizedParsed = isRecord2(parsed) && typeof parsed.toolArguments === "string" && ["", "{}"].includes(parsed.toolArguments.trim()) ? { ...parsed, toolArguments: {} } : parsed;
1231
+ const basicResult = validateJsonSchema(normalizedParsed, toolSelectionArgumentSchema);
1164
1232
  if (basicResult.success === false) {
1165
1233
  return { success: false, error: basicResult.error };
1166
1234
  }
@@ -1175,6 +1243,17 @@ function validateToolSelectionArgument(parsed, toolInputSchema) {
1175
1243
  return { success: true, data };
1176
1244
  }
1177
1245
  function validateResourceSelection(selection) {
1246
+ if (isRecord2(selection) && selection.noResourceAvailable === true) {
1247
+ return {
1248
+ success: true,
1249
+ data: {
1250
+ serverName: "",
1251
+ uri: "",
1252
+ noResourceAvailable: true,
1253
+ ...optionalReasoning(selection)
1254
+ }
1255
+ };
1256
+ }
1178
1257
  return validateJsonSchema(selection, ResourceSelectionSchema);
1179
1258
  }
1180
1259
  function createToolSelectionFeedbackPrompt(originalResponse, errorMessage, composedState, userMessage) {
@@ -1222,16 +1301,18 @@ function createResourceSelectionFeedbackPrompt(originalResponse, errorMessage, c
1222
1301
  return createFeedbackPrompt(originalResponse, errorMessage, "resource", resourcesDescription, userMessage);
1223
1302
  }
1224
1303
  function createFeedbackPrompt(originalResponse, errorMessage, itemType, itemsDescription, userMessage) {
1225
- return `Error parsing JSON: ${errorMessage}
1304
+ return `The previous ${itemType} selection could not be parsed or validated: ${errorMessage}
1226
1305
 
1227
1306
  Your original response:
1228
1307
  ${originalResponse}
1229
1308
 
1230
- Please try again with valid JSON for ${itemType} selection.
1309
+ Reply again as compact JSON for ${itemType} selection.
1231
1310
  Available ${itemType}s:
1232
1311
  ${itemsDescription}
1233
1312
 
1234
- User request: ${userMessage}`;
1313
+ User request: ${userMessage}
1314
+
1315
+ Use exact names from the list. For tools, return a JSON object with serverName, toolName, reasoning, and noToolAvailable. For resources, return a JSON object with serverName, uri, reasoning, and noResourceAvailable.`;
1235
1316
  }
1236
1317
 
1237
1318
  // src/utils/wrapper.ts
@@ -1250,15 +1331,21 @@ async function withModelRetry({
1250
1331
  retryCount = 0
1251
1332
  }) {
1252
1333
  const maxRetries = getMaxRetries(runtime);
1253
- const parsedJson = typeof input === "string" ? parseJSON(input) : input;
1254
- const validationResult = validationFn(parsedJson);
1334
+ let validationResult;
1335
+ try {
1336
+ const parsedInput = typeof input === "string" ? parseStructuredModelOutput(input) : input;
1337
+ validationResult = validationFn(parsedInput);
1338
+ } catch (error) {
1339
+ const errorMessage2 = error instanceof Error ? error.message : String(error);
1340
+ validationResult = { success: false, error: errorMessage2 };
1341
+ }
1255
1342
  if (validationResult.success) {
1256
1343
  return validationResult.data;
1257
1344
  }
1258
1345
  const errorMessage = validationResult.error;
1259
1346
  if (retryCount < maxRetries) {
1260
1347
  const feedbackPrompt = createFeedbackPromptFn(input, errorMessage, state, message.content.text ?? "");
1261
- const retrySelection = await runtime.useModel(ModelType3.OBJECT_LARGE, {
1348
+ const retrySelection = await runtime.useModel(ModelType3.TEXT_LARGE, {
1262
1349
  prompt: feedbackPrompt
1263
1350
  });
1264
1351
  return withModelRetry({
@@ -1283,11 +1370,8 @@ async function withModelRetry({
1283
1370
  }
1284
1371
  function getMaxRetries(runtime) {
1285
1372
  const rawSettings = runtime.getSetting("mcp");
1286
- if (rawSettings && typeof rawSettings === "object") {
1287
- const settings = rawSettings;
1288
- if (typeof settings.maxRetries === "number" && settings.maxRetries >= 0) {
1289
- return settings.maxRetries;
1290
- }
1373
+ if (isMcpSettings(rawSettings) && typeof rawSettings.maxRetries === "number" && rawSettings.maxRetries >= 0) {
1374
+ return rawSettings.maxRetries;
1291
1375
  }
1292
1376
  return DEFAULT_MAX_RETRIES;
1293
1377
  }
@@ -1365,141 +1449,48 @@ async function createToolSelectionArgument({
1365
1449
  });
1366
1450
  }
1367
1451
 
1368
- // src/actions/callToolAction.ts
1369
- var callToolAction = {
1370
- name: "CALL_MCP_TOOL",
1371
- similes: [
1372
- "CALL_TOOL",
1373
- "CALL_MCP_TOOL",
1374
- "USE_TOOL",
1375
- "USE_MCP_TOOL",
1376
- "EXECUTE_TOOL",
1377
- "EXECUTE_MCP_TOOL",
1378
- "RUN_TOOL",
1379
- "RUN_MCP_TOOL",
1380
- "INVOKE_TOOL",
1381
- "INVOKE_MCP_TOOL"
1382
- ],
1383
- description: "Calls a tool from an MCP server to perform a specific task",
1384
- validate: async (runtime, message, state, options) => {
1385
- const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
1386
- const __avText = __avTextRaw.toLowerCase();
1387
- const __avKeywords = ["call", "mcp", "tool"];
1388
- const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
1389
- const __avRegex = /\b(?:call|mcp|tool)\b/i;
1390
- const __avRegexOk = __avRegex.test(__avText);
1391
- const __avSource = String(message?.content?.source ?? message?.source ?? "");
1392
- const __avExpectedSource = "";
1393
- const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
1394
- const __avOptions = options && typeof options === "object" ? options : {};
1395
- const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object");
1396
- if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
1397
- return false;
1398
- }
1399
- const __avLegacyValidate = async (runtime2, _message, _state) => {
1400
- const mcpService = runtime2.getService(MCP_SERVICE_NAME);
1401
- if (!mcpService)
1402
- return false;
1403
- const servers = mcpService.getServers();
1404
- return servers.length > 0 && servers.some((server) => server.status === "connected" && server.tools && server.tools.length > 0);
1405
- };
1406
- try {
1407
- return Boolean(await __avLegacyValidate(runtime, message, state, options));
1408
- } catch {
1409
- return false;
1410
- }
1411
- },
1412
- handler: async (runtime, message, _state, _options, callback) => {
1413
- const composedState = await runtime.composeState(message, ["RECENT_MESSAGES", "MCP"]);
1414
- const mcpService = runtime.getService(MCP_SERVICE_NAME);
1415
- if (!mcpService) {
1416
- throw new Error("MCP service not available");
1417
- }
1418
- const mcpProvider = mcpService.getProviderData();
1419
- try {
1420
- const toolSelectionName = await createToolSelectionName({
1421
- runtime,
1422
- state: composedState,
1423
- message,
1424
- callback,
1425
- mcpProvider
1426
- });
1427
- if (!toolSelectionName || toolSelectionName.noToolAvailable) {
1428
- return await handleNoToolAvailable(callback, toolSelectionName);
1429
- }
1430
- const { serverName, toolName } = toolSelectionName;
1431
- const toolSelectionArgument = await createToolSelectionArgument({
1432
- runtime,
1433
- state: composedState,
1434
- message,
1435
- callback,
1436
- mcpProvider,
1437
- toolSelectionName
1438
- });
1439
- if (!toolSelectionArgument) {
1440
- return await handleNoToolAvailable(callback, toolSelectionName);
1441
- }
1442
- const result = await mcpService.callTool(serverName, toolName, toolSelectionArgument.toolArguments);
1443
- const { toolOutput, hasAttachments, attachments } = processToolResult(result, serverName, toolName, runtime, message.entityId);
1444
- const replyMemory = await handleToolResponse(runtime, message, serverName, toolName, toolSelectionArgument.toolArguments, toolOutput, hasAttachments, attachments, composedState, mcpProvider, callback);
1445
- return {
1446
- text: `Successfully called tool: ${serverName}/${toolName}. Reasoned response: ${replyMemory.content.text}`,
1447
- values: {
1448
- success: true,
1449
- toolExecuted: true,
1450
- serverName,
1451
- toolName,
1452
- hasAttachments,
1453
- output: toolOutput
1454
- },
1455
- data: {
1456
- actionName: "CALL_MCP_TOOL",
1457
- serverName,
1458
- toolName,
1459
- toolArgumentsJson: JSON.stringify(toolSelectionArgument.toolArguments),
1460
- reasoning: toolSelectionName.reasoning,
1461
- output: toolOutput,
1462
- attachmentCount: attachments?.length ?? 0
1463
- },
1464
- success: true
1465
- };
1466
- } catch (error) {
1467
- return await handleMcpError(composedState, mcpProvider, error, runtime, message, "tool", callback);
1468
- }
1469
- },
1470
- examples: [
1471
- [
1472
- {
1473
- name: "{{user}}",
1474
- content: {
1475
- text: "Can you search for information about climate change?"
1476
- }
1477
- },
1478
- {
1479
- name: "{{assistant}}",
1480
- content: {
1481
- text: "I'll help you with that request. Let me access the right tool...",
1482
- actions: ["CALL_MCP_TOOL"]
1483
- }
1484
- },
1485
- {
1486
- name: "{{assistant}}",
1487
- content: {
1488
- text: `I found the following information about climate change:
1489
-
1490
- 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.`,
1491
- actions: ["CALL_MCP_TOOL"]
1492
- }
1493
- }
1494
- ]
1495
- ]
1496
- };
1497
-
1498
- // src/actions/readResourceAction.ts
1499
- import {
1500
- composePromptFromState as composePromptFromState4,
1501
- ModelType as ModelType5
1502
- } from "@elizaos/core";
1452
+ // src/actions/mcp.ts
1453
+ var MCP_ACTION_CONTEXT = "mcp";
1454
+ function readOptions(options) {
1455
+ const direct = options ?? {};
1456
+ const parameters = direct.parameters && typeof direct.parameters === "object" ? direct.parameters : {};
1457
+ return { ...direct, ...parameters };
1458
+ }
1459
+ function normalizeOp(value) {
1460
+ if (typeof value !== "string")
1461
+ return null;
1462
+ const v = value.trim().toLowerCase();
1463
+ if (v === "call_tool" || v === "tool" || v === "call")
1464
+ return "call_tool";
1465
+ if (v === "read_resource" || v === "resource" || v === "read")
1466
+ return "read_resource";
1467
+ if (v === "search_actions" || v === "search" || v === "discover")
1468
+ return "search_actions";
1469
+ if (v === "list_connections" || v === "list" || v === "connections")
1470
+ return "list_connections";
1471
+ return null;
1472
+ }
1473
+ function inferOpFromText(text) {
1474
+ if (/\b(read|get|fetch|access|open|list)\b.*\b(resource|resources|document|docs?|file)\b/i.test(text)) {
1475
+ return "read_resource";
1476
+ }
1477
+ if (/\b(call|use|run|execute|invoke|search|query)\b.*\b(tool|tools|mcp)\b/i.test(text)) {
1478
+ return "call_tool";
1479
+ }
1480
+ return null;
1481
+ }
1482
+ function getDirectResourceSelection(options) {
1483
+ const params = readOptions(options);
1484
+ const serverName = typeof params.serverName === "string" ? params.serverName.trim() : "";
1485
+ const uri = typeof params.uri === "string" ? params.uri.trim() : "";
1486
+ if (!serverName || !uri)
1487
+ return null;
1488
+ return {
1489
+ serverName,
1490
+ uri,
1491
+ reasoning: typeof params.reasoning === "string" && params.reasoning.trim() ? params.reasoning.trim() : "Selected from structured MCP read_resource parameters."
1492
+ };
1493
+ }
1503
1494
  function createResourceSelectionPrompt(composedState, userMessage) {
1504
1495
  const mcpData = composedState.values.mcp ?? {};
1505
1496
  const serverNames = Object.keys(mcpData);
@@ -1535,61 +1526,80 @@ function createResourceSelectionPrompt(composedState, userMessage) {
1535
1526
  template: resourceSelectionTemplate
1536
1527
  });
1537
1528
  }
1538
- var readResourceAction = {
1539
- name: "READ_MCP_RESOURCE",
1540
- similes: [
1541
- "READ_RESOURCE",
1542
- "READ_MCP_RESOURCE",
1543
- "GET_RESOURCE",
1544
- "GET_MCP_RESOURCE",
1545
- "FETCH_RESOURCE",
1546
- "FETCH_MCP_RESOURCE",
1547
- "ACCESS_RESOURCE",
1548
- "ACCESS_MCP_RESOURCE"
1549
- ],
1550
- description: "Reads a resource from an MCP server",
1551
- validate: async (runtime, message, state, options) => {
1552
- const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
1553
- const __avText = __avTextRaw.toLowerCase();
1554
- const __avKeywords = ["read", "mcp", "resource"];
1555
- const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
1556
- const __avRegex = /\b(?:read|mcp|resource)\b/i;
1557
- const __avRegexOk = __avRegex.test(__avText);
1558
- const __avSource = String(message?.content?.source ?? message?.source ?? "");
1559
- const __avExpectedSource = "";
1560
- const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
1561
- const __avOptions = options && typeof options === "object" ? options : {};
1562
- const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object");
1563
- if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
1564
- return false;
1565
- }
1566
- const __avLegacyValidate = async (runtime2, _message, _state) => {
1567
- const mcpService = runtime2.getService(MCP_SERVICE_NAME);
1568
- if (!mcpService)
1569
- return false;
1570
- const servers = mcpService.getServers();
1571
- return servers.length > 0 && servers.some((server) => server.status === "connected" && server.resources && server.resources.length > 0);
1572
- };
1573
- try {
1574
- return Boolean(await __avLegacyValidate(runtime, message, state, options));
1575
- } catch {
1576
- return false;
1529
+ async function handleCallTool(runtime, message, callback) {
1530
+ const composedState = await runtime.composeState(message, ["RECENT_MESSAGES", "MCP"]);
1531
+ const mcpService = runtime.getService(MCP_SERVICE_NAME);
1532
+ if (!mcpService) {
1533
+ throw new Error("MCP service not available");
1534
+ }
1535
+ const mcpProvider = mcpService.getProviderData();
1536
+ try {
1537
+ const toolSelectionName = await createToolSelectionName({
1538
+ runtime,
1539
+ state: composedState,
1540
+ message,
1541
+ callback,
1542
+ mcpProvider
1543
+ });
1544
+ if (!toolSelectionName || toolSelectionName.noToolAvailable) {
1545
+ return await handleNoToolAvailable(callback, toolSelectionName);
1577
1546
  }
1578
- },
1579
- handler: async (runtime, message, _state, _options, callback) => {
1580
- const composedState = await runtime.composeState(message, ["RECENT_MESSAGES", "MCP"]);
1581
- const mcpService = runtime.getService(MCP_SERVICE_NAME);
1582
- if (!mcpService) {
1583
- throw new Error("MCP service not available");
1547
+ const { serverName, toolName } = toolSelectionName;
1548
+ const toolSelectionArgument = await createToolSelectionArgument({
1549
+ runtime,
1550
+ state: composedState,
1551
+ message,
1552
+ callback,
1553
+ mcpProvider,
1554
+ toolSelectionName
1555
+ });
1556
+ if (!toolSelectionArgument) {
1557
+ return await handleNoToolAvailable(callback, toolSelectionName);
1584
1558
  }
1585
- const mcpProvider = mcpService.getProviderData();
1586
- try {
1587
- await sendInitialResponse(callback);
1559
+ const result = await mcpService.callTool(serverName, toolName, toolSelectionArgument.toolArguments);
1560
+ const { toolOutput, hasAttachments, attachments } = processToolResult(result, serverName, toolName, runtime, message.entityId);
1561
+ const replyMemory = await handleToolResponse(runtime, message, serverName, toolName, toolSelectionArgument.toolArguments, toolOutput, hasAttachments, attachments, composedState, mcpProvider, callback);
1562
+ return {
1563
+ text: `Successfully called tool: ${serverName}/${toolName}. Reasoned response: ${replyMemory.content.text}`,
1564
+ values: {
1565
+ success: true,
1566
+ toolExecuted: true,
1567
+ serverName,
1568
+ toolName,
1569
+ hasAttachments,
1570
+ output: toolOutput
1571
+ },
1572
+ data: {
1573
+ actionName: "MCP",
1574
+ op: "call_tool",
1575
+ serverName,
1576
+ toolName,
1577
+ toolArgumentsJson: JSON.stringify(toolSelectionArgument.toolArguments),
1578
+ reasoning: toolSelectionName.reasoning,
1579
+ output: toolOutput,
1580
+ attachmentCount: attachments?.length ?? 0
1581
+ },
1582
+ success: true
1583
+ };
1584
+ } catch (error) {
1585
+ return await handleMcpError(composedState, mcpProvider, error, runtime, message, "tool", callback);
1586
+ }
1587
+ }
1588
+ async function handleReadResource(runtime, message, options, callback) {
1589
+ const composedState = await runtime.composeState(message, ["RECENT_MESSAGES", "MCP"]);
1590
+ const mcpService = runtime.getService(MCP_SERVICE_NAME);
1591
+ if (!mcpService) {
1592
+ throw new Error("MCP service not available");
1593
+ }
1594
+ const mcpProvider = mcpService.getProviderData();
1595
+ try {
1596
+ await sendInitialResponse(callback);
1597
+ const parsedSelection = getDirectResourceSelection(options) ?? await (async () => {
1588
1598
  const resourceSelectionPrompt = createResourceSelectionPrompt(composedState, message.content.text ?? "");
1589
1599
  const resourceSelection = await runtime.useModel(ModelType5.TEXT_SMALL, {
1590
1600
  prompt: resourceSelectionPrompt
1591
1601
  });
1592
- const parsedSelection = await withModelRetry({
1602
+ return withModelRetry({
1593
1603
  runtime,
1594
1604
  state: composedState,
1595
1605
  message,
@@ -1600,75 +1610,210 @@ var readResourceAction = {
1600
1610
  failureMsg: `I'm having trouble finding the resource you're looking for. Could you provide more details about what you need?`,
1601
1611
  retryCount: 0
1602
1612
  });
1603
- if (!parsedSelection || parsedSelection.noResourceAvailable) {
1604
- const responseText = "I don't have a specific resource that contains the information you're looking for. Let me try to assist you directly instead.";
1605
- if (callback && parsedSelection?.noResourceAvailable) {
1606
- await callback({
1607
- text: responseText,
1608
- actions: ["REPLY"]
1609
- });
1610
- }
1611
- return {
1613
+ })();
1614
+ if (!parsedSelection || parsedSelection.noResourceAvailable) {
1615
+ const responseText = "I don't have a specific resource that contains the information you're looking for. Let me try to assist you directly instead.";
1616
+ if (callback && parsedSelection?.noResourceAvailable) {
1617
+ await callback({
1612
1618
  text: responseText,
1613
- values: {
1614
- success: true,
1615
- noResourceAvailable: true,
1616
- fallbackToDirectAssistance: true
1617
- },
1618
- data: {
1619
- actionName: "READ_MCP_RESOURCE",
1620
- noResourceAvailable: true,
1621
- reason: parsedSelection?.reasoning ?? "No appropriate resource available"
1622
- },
1623
- success: true
1624
- };
1619
+ actions: ["REPLY"]
1620
+ });
1625
1621
  }
1626
- const { serverName, uri } = parsedSelection;
1627
- const result = await mcpService.readResource(serverName, uri);
1628
- const { resourceContent, resourceMeta } = processResourceResult(result, uri);
1629
- await handleResourceAnalysis(runtime, message, uri, serverName, resourceContent, resourceMeta, callback);
1630
1622
  return {
1631
- text: `Successfully read resource: ${uri}`,
1623
+ text: responseText,
1632
1624
  values: {
1633
1625
  success: true,
1634
- resourceRead: true,
1635
- serverName,
1636
- uri
1626
+ noResourceAvailable: true,
1627
+ fallbackToDirectAssistance: true
1637
1628
  },
1638
1629
  data: {
1639
- actionName: "READ_MCP_RESOURCE",
1640
- serverName,
1641
- uri,
1642
- reasoning: parsedSelection?.reasoning,
1643
- resourceMeta,
1644
- contentLength: resourceContent?.length ?? 0
1630
+ actionName: "MCP",
1631
+ op: "read_resource",
1632
+ noResourceAvailable: true,
1633
+ reason: parsedSelection?.reasoning ?? "No appropriate resource available"
1645
1634
  },
1646
1635
  success: true
1647
1636
  };
1648
- } catch (error) {
1649
- return await handleMcpError(composedState, mcpProvider, error, runtime, message, "resource", callback);
1650
1637
  }
1638
+ const { serverName, uri } = parsedSelection;
1639
+ const result = await mcpService.readResource(serverName, uri);
1640
+ const { resourceContent, resourceMeta } = processResourceResult(result, uri);
1641
+ await handleResourceAnalysis(runtime, message, uri, serverName, resourceContent, resourceMeta, callback);
1642
+ return {
1643
+ text: `Successfully read resource: ${uri}`,
1644
+ values: {
1645
+ success: true,
1646
+ resourceRead: true,
1647
+ serverName,
1648
+ uri
1649
+ },
1650
+ data: {
1651
+ actionName: "MCP",
1652
+ op: "read_resource",
1653
+ serverName,
1654
+ uri,
1655
+ reasoning: parsedSelection?.reasoning,
1656
+ resourceMeta,
1657
+ contentLength: resourceContent?.length ?? 0
1658
+ },
1659
+ success: true
1660
+ };
1661
+ } catch (error) {
1662
+ return await handleMcpError(composedState, mcpProvider, error, runtime, message, "resource", callback);
1663
+ }
1664
+ }
1665
+ function textOf(message) {
1666
+ return typeof message.content?.text === "string" ? message.content.text : "";
1667
+ }
1668
+ function hasConnectedCapability(runtime) {
1669
+ const mcpService = runtime.getService(MCP_SERVICE_NAME);
1670
+ if (!mcpService)
1671
+ return false;
1672
+ return mcpService.getServers().some((server) => {
1673
+ if (server.status !== "connected")
1674
+ return false;
1675
+ return (server.tools?.length ?? 0) > 0 || (server.resources?.length ?? 0) > 0;
1676
+ });
1677
+ }
1678
+ var mcpAction = {
1679
+ name: "MCP",
1680
+ contexts: ["general", "automation", "knowledge", "connectors", MCP_ACTION_CONTEXT, "files"],
1681
+ contextGate: {
1682
+ anyOf: ["general", "automation", "knowledge", "connectors", MCP_ACTION_CONTEXT, "files"]
1683
+ },
1684
+ roleGate: { minRole: "USER" },
1685
+ similes: [
1686
+ "MCP_ACTION",
1687
+ "MCP_ROUTER",
1688
+ "USE_MCP",
1689
+ "CALL_MCP_TOOL",
1690
+ "CALL_TOOL",
1691
+ "USE_TOOL",
1692
+ "USE_MCP_TOOL",
1693
+ "EXECUTE_TOOL",
1694
+ "EXECUTE_MCP_TOOL",
1695
+ "RUN_TOOL",
1696
+ "RUN_MCP_TOOL",
1697
+ "INVOKE_TOOL",
1698
+ "INVOKE_MCP_TOOL",
1699
+ "READ_MCP_RESOURCE",
1700
+ "READ_RESOURCE",
1701
+ "GET_RESOURCE",
1702
+ "GET_MCP_RESOURCE",
1703
+ "FETCH_RESOURCE",
1704
+ "FETCH_MCP_RESOURCE",
1705
+ "ACCESS_RESOURCE",
1706
+ "ACCESS_MCP_RESOURCE"
1707
+ ],
1708
+ description: "Single MCP entry point. Use action=call_tool to invoke an MCP tool, action=read_resource to read an MCP resource. Cloud runtimes also accept action=search_actions and action=list_connections.",
1709
+ descriptionCompressed: "MCP call_tool read_resource search_actions list_connections",
1710
+ parameters: [
1711
+ {
1712
+ name: "action",
1713
+ description: "MCP operation: call_tool | read_resource | search_actions | list_connections",
1714
+ required: false,
1715
+ schema: {
1716
+ type: "string",
1717
+ enum: ["call_tool", "read_resource", "search_actions", "list_connections"]
1718
+ }
1719
+ },
1720
+ {
1721
+ name: "serverName",
1722
+ description: "Optional MCP server name that owns the tool or resource.",
1723
+ required: false,
1724
+ schema: { type: "string" }
1725
+ },
1726
+ {
1727
+ name: "toolName",
1728
+ description: "For action=call_tool: optional exact MCP tool name to call.",
1729
+ required: false,
1730
+ schema: { type: "string" }
1731
+ },
1732
+ {
1733
+ name: "arguments",
1734
+ description: "For action=call_tool: optional JSON arguments to pass to the selected MCP tool.",
1735
+ required: false,
1736
+ schema: { type: "object" }
1737
+ },
1738
+ {
1739
+ name: "uri",
1740
+ description: "For action=read_resource: exact MCP resource URI to read.",
1741
+ required: false,
1742
+ schema: { type: "string" }
1743
+ },
1744
+ {
1745
+ name: "query",
1746
+ description: "Natural-language description of the tool call or resource to select; for action=search_actions, the keyword query.",
1747
+ required: false,
1748
+ schema: { type: "string" }
1749
+ },
1750
+ {
1751
+ name: "platform",
1752
+ description: "For action=search_actions: filter results to a single connected platform.",
1753
+ required: false,
1754
+ schema: { type: "string" }
1755
+ },
1756
+ {
1757
+ name: "limit",
1758
+ description: "For action=search_actions: maximum results to return.",
1759
+ required: false,
1760
+ schema: { type: "number" }
1761
+ },
1762
+ {
1763
+ name: "offset",
1764
+ description: "For action=search_actions: skip first N results for pagination.",
1765
+ required: false,
1766
+ schema: { type: "number" }
1767
+ }
1768
+ ],
1769
+ validate: async (runtime) => {
1770
+ if (!hasConnectedCapability(runtime))
1771
+ return false;
1772
+ return true;
1773
+ },
1774
+ handler: async (runtime, message, _state, options, callback) => {
1775
+ const opts = readOptions(options);
1776
+ const requested = normalizeOp(opts.action ?? opts.subaction ?? opts.op ?? opts.operation);
1777
+ const op = requested ?? inferOpFromText(textOf(message)) ?? "call_tool";
1778
+ if (op === "read_resource") {
1779
+ return handleReadResource(runtime, message, options, callback);
1780
+ }
1781
+ if (op === "search_actions" || op === "list_connections") {
1782
+ const text = `MCP op=${op} is only available in the cloud runtime.`;
1783
+ await callback?.({ text, source: message.content?.source });
1784
+ return {
1785
+ success: false,
1786
+ text,
1787
+ values: { error: "OP_NOT_SUPPORTED" },
1788
+ data: { actionName: "MCP", op }
1789
+ };
1790
+ }
1791
+ return handleCallTool(runtime, message, callback);
1651
1792
  },
1652
1793
  examples: [
1653
1794
  [
1654
1795
  {
1655
1796
  name: "{{user}}",
1656
- content: {
1657
- text: "Can you get the documentation about installing elizaOS?"
1658
- }
1797
+ content: { text: "Use the MCP GitHub tool to read the repository README" }
1659
1798
  },
1660
1799
  {
1661
1800
  name: "{{assistant}}",
1662
1801
  content: {
1663
- text: `I'll retrieve that information for you. Let me access the resource...`,
1664
- actions: ["READ_MCP_RESOURCE"]
1802
+ text: "I'll route that through MCP.",
1803
+ actions: ["MCP"]
1665
1804
  }
1805
+ }
1806
+ ],
1807
+ [
1808
+ {
1809
+ name: "{{user}}",
1810
+ content: { text: "Can you get the documentation about installing elizaOS?" }
1666
1811
  },
1667
1812
  {
1668
1813
  name: "{{assistant}}",
1669
1814
  content: {
1670
- 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.`,
1671
- actions: ["READ_MCP_RESOURCE"]
1815
+ text: "I'll read the MCP resource for that.",
1816
+ actions: ["MCP"]
1672
1817
  }
1673
1818
  }
1674
1819
  ]
@@ -1676,10 +1821,36 @@ var readResourceAction = {
1676
1821
  };
1677
1822
 
1678
1823
  // src/provider.ts
1824
+ var MAX_MCP_SERVERS_IN_STATE = 20;
1825
+ var MAX_MCP_TOOLS_PER_SERVER = 30;
1826
+ var MAX_MCP_RESOURCES_PER_SERVER = 30;
1827
+ function formatMcpServersForPrompt(mcp) {
1828
+ const entries = Object.entries(mcp).slice(0, MAX_MCP_SERVERS_IN_STATE);
1829
+ if (entries.length === 0)
1830
+ return "No MCP servers are available.";
1831
+ return [
1832
+ `mcpServers[${Object.keys(mcp).length}, showing ${entries.length}]:`,
1833
+ ...entries.flatMap(([serverName, server]) => {
1834
+ const tools = Object.keys(server.tools ?? {}).slice(0, MAX_MCP_TOOLS_PER_SERVER);
1835
+ const resources = Object.keys(server.resources ?? {}).slice(0, MAX_MCP_RESOURCES_PER_SERVER);
1836
+ return [
1837
+ ` - name: ${serverName}`,
1838
+ ` status: ${server.status}`,
1839
+ ` tools: ${tools.length > 0 ? tools.join(", ") : "none"}`,
1840
+ ` resources: ${resources.length > 0 ? resources.join(", ") : "none"}`
1841
+ ];
1842
+ })
1843
+ ].join(`
1844
+ `);
1845
+ }
1679
1846
  var provider = {
1680
1847
  name: "MCP",
1681
1848
  description: "Information about connected MCP servers, tools, and resources",
1682
1849
  dynamic: true,
1850
+ contexts: ["connectors", "settings"],
1851
+ contextGate: { anyOf: ["connectors", "settings"] },
1852
+ cacheStable: false,
1853
+ cacheScope: "turn",
1683
1854
  get: async (runtime, _message, _state) => {
1684
1855
  const mcpService = runtime.getService(MCP_SERVICE_NAME);
1685
1856
  if (!mcpService) {
@@ -1689,12 +1860,25 @@ var provider = {
1689
1860
  text: "No MCP servers are available."
1690
1861
  };
1691
1862
  }
1692
- const providerData = mcpService.getProviderData();
1693
- return {
1694
- values: { mcpServers: JSON.stringify(providerData.values.mcp) },
1695
- data: { mcpServerCount: Object.keys(providerData.data.mcp).length },
1696
- text: providerData.text
1697
- };
1863
+ try {
1864
+ const providerData = mcpService.getProviderData();
1865
+ const mcp = providerData.values.mcp;
1866
+ const serverEntries = Object.entries(providerData.data.mcp).slice(0, MAX_MCP_SERVERS_IN_STATE);
1867
+ return {
1868
+ values: { mcpServers: formatMcpServersForPrompt(mcp) },
1869
+ data: {
1870
+ mcpServerCount: Object.keys(providerData.data.mcp).length,
1871
+ shownMcpServerCount: serverEntries.length
1872
+ },
1873
+ text: formatMcpServersForPrompt(mcp)
1874
+ };
1875
+ } catch (error) {
1876
+ return {
1877
+ values: {},
1878
+ data: { error: error instanceof Error ? error.message : String(error) },
1879
+ text: "No MCP servers are available."
1880
+ };
1881
+ }
1698
1882
  }
1699
1883
  };
1700
1884
 
@@ -1741,7 +1925,9 @@ class McpService extends Service {
1741
1925
  initializationPromise = null;
1742
1926
  constructor(runtime) {
1743
1927
  super(runtime);
1744
- this.initializationPromise = this.initializeMcpServers();
1928
+ if (runtime) {
1929
+ this.initializationPromise = this.initializeMcpServers();
1930
+ }
1745
1931
  }
1746
1932
  static async start(runtime) {
1747
1933
  const service = new McpService(runtime);
@@ -1770,7 +1956,7 @@ class McpService extends Service {
1770
1956
  }
1771
1957
  async initializeMcpServers() {
1772
1958
  const mcpSettings = this.getMcpSettings();
1773
- if (!mcpSettings || !mcpSettings.servers || Object.keys(mcpSettings.servers).length === 0) {
1959
+ if (!mcpSettings?.servers || Object.keys(mcpSettings.servers).length === 0) {
1774
1960
  this.mcpProvider = buildMcpProviderData([]);
1775
1961
  return;
1776
1962
  }
@@ -1781,17 +1967,14 @@ class McpService extends Service {
1781
1967
  getMcpSettings() {
1782
1968
  const rawSettings = this.runtime.getSetting("mcp");
1783
1969
  let settings = null;
1784
- if (rawSettings && typeof rawSettings === "object" && !Array.isArray(rawSettings)) {
1785
- const parsed = rawSettings;
1786
- if ("servers" in parsed && typeof parsed.servers === "object" && parsed.servers !== null) {
1787
- settings = parsed;
1788
- }
1970
+ if (isMcpSettings(rawSettings)) {
1971
+ settings = rawSettings;
1789
1972
  }
1790
- if (!settings || !settings.servers) {
1973
+ if (!settings?.servers) {
1791
1974
  const characterSettings = this.runtime.character.settings;
1792
1975
  if (characterSettings && typeof characterSettings === "object" && "mcp" in characterSettings) {
1793
1976
  const characterMcpSettings = characterSettings.mcp;
1794
- if (characterMcpSettings && typeof characterMcpSettings === "object" && "servers" in characterMcpSettings) {
1977
+ if (isMcpSettings(characterMcpSettings)) {
1795
1978
  settings = characterMcpSettings;
1796
1979
  }
1797
1980
  }
@@ -2096,20 +2279,293 @@ ${error}` : error;
2096
2279
  }
2097
2280
  }
2098
2281
 
2282
+ // src/routes-mcp.ts
2283
+ import { logger as logger3 } from "@elizaos/core";
2284
+
2285
+ // src/mcp-marketplace.ts
2286
+ var MCP_REGISTRY_BASE_URL = "https://registry.modelcontextprotocol.io";
2287
+ async function searchMcpMarketplace(query, limit = 30) {
2288
+ const resp = await fetch(`${MCP_REGISTRY_BASE_URL}/v0/servers`, {
2289
+ headers: {
2290
+ Accept: "application/json"
2291
+ }
2292
+ });
2293
+ if (!resp.ok) {
2294
+ throw new Error(`Registry API error: ${resp.status} ${resp.statusText}`);
2295
+ }
2296
+ const data = await resp.json();
2297
+ const results = [];
2298
+ const seenNames = new Set;
2299
+ for (const entry of data.servers) {
2300
+ const server = entry.server;
2301
+ const meta = entry._meta?.["io.modelcontextprotocol.registry/official"];
2302
+ if (!meta?.isLatest)
2303
+ continue;
2304
+ if (seenNames.has(server.name))
2305
+ continue;
2306
+ seenNames.add(server.name);
2307
+ if (query) {
2308
+ const q = query.toLowerCase();
2309
+ const matchName = server.name.toLowerCase().includes(q);
2310
+ const matchTitle = server.title?.toLowerCase().includes(q);
2311
+ const matchDesc = server.description?.toLowerCase().includes(q);
2312
+ if (!matchName && !matchTitle && !matchDesc)
2313
+ continue;
2314
+ }
2315
+ let connectionType = "remote";
2316
+ let connectionUrl;
2317
+ let npmPackage;
2318
+ let dockerImage;
2319
+ if (server.remotes && server.remotes.length > 0) {
2320
+ connectionType = "remote";
2321
+ connectionUrl = server.remotes[0].url;
2322
+ } else if (server.packages && server.packages.length > 0) {
2323
+ const pkg = server.packages[0];
2324
+ connectionType = "stdio";
2325
+ if (pkg.registryType === "npm") {
2326
+ npmPackage = pkg.identifier;
2327
+ } else if (pkg.registryType === "oci") {
2328
+ dockerImage = pkg.identifier;
2329
+ }
2330
+ }
2331
+ results.push({
2332
+ id: `${server.name}@${server.version}`,
2333
+ name: server.name,
2334
+ title: server.title || server.name.split("/").pop() || server.name,
2335
+ description: server.description || "No description",
2336
+ version: server.version,
2337
+ connectionType,
2338
+ connectionUrl,
2339
+ npmPackage,
2340
+ dockerImage,
2341
+ repositoryUrl: server.repository?.url,
2342
+ websiteUrl: server.websiteUrl,
2343
+ iconUrl: server.icons?.[0]?.src,
2344
+ publishedAt: meta?.publishedAt,
2345
+ isLatest: true
2346
+ });
2347
+ if (results.length >= limit)
2348
+ break;
2349
+ }
2350
+ return { results };
2351
+ }
2352
+ async function getMcpServerDetails(name) {
2353
+ const resp = await fetch(`${MCP_REGISTRY_BASE_URL}/v0/servers/${encodeURIComponent(name)}`, {
2354
+ headers: { Accept: "application/json" }
2355
+ });
2356
+ if (!resp.ok) {
2357
+ if (resp.status === 404) {
2358
+ return null;
2359
+ }
2360
+ throw new Error(`Registry API error: ${resp.status}`);
2361
+ }
2362
+ const data = await resp.json();
2363
+ return data.server;
2364
+ }
2365
+
2366
+ // src/routes-mcp.ts
2367
+ function parseClampedInteger(value, options = {}) {
2368
+ const raw = value == null ? "" : value.trim();
2369
+ if (!raw)
2370
+ return Number.isFinite(options.fallback) ? options.fallback : undefined;
2371
+ const parsed = Number.parseInt(raw, 10);
2372
+ if (!Number.isFinite(parsed)) {
2373
+ return Number.isFinite(options.fallback) ? options.fallback : undefined;
2374
+ }
2375
+ if (options.min !== undefined && parsed < options.min)
2376
+ return options.min;
2377
+ if (options.max !== undefined && parsed > options.max)
2378
+ return options.max;
2379
+ return parsed;
2380
+ }
2381
+ async function handleMcpRoutes(ctx) {
2382
+ const { req, res, method, pathname, url, state, json, error, readJsonBody } = ctx;
2383
+ if (method === "GET" && pathname === "/api/mcp/marketplace/search") {
2384
+ const query = url.searchParams.get("q") ?? "";
2385
+ const limitStr = url.searchParams.get("limit");
2386
+ const limit = limitStr ? parseClampedInteger(limitStr, { min: 1, max: 50, fallback: 30 }) : 30;
2387
+ try {
2388
+ const result = await searchMcpMarketplace(query || undefined, limit);
2389
+ json(res, { ok: true, results: result.results });
2390
+ } catch (err) {
2391
+ error(res, `MCP marketplace search failed: ${err instanceof Error ? err.message : err}`, 502);
2392
+ }
2393
+ return true;
2394
+ }
2395
+ if (method === "GET" && pathname.startsWith("/api/mcp/marketplace/details/")) {
2396
+ const serverName = ctx.decodePathComponent(pathname.slice("/api/mcp/marketplace/details/".length), res, "server name");
2397
+ if (serverName === null)
2398
+ return true;
2399
+ if (!serverName.trim()) {
2400
+ error(res, "Server name is required", 400);
2401
+ return true;
2402
+ }
2403
+ try {
2404
+ const details = await getMcpServerDetails(serverName);
2405
+ if (!details) {
2406
+ error(res, `MCP server "${serverName}" not found`, 404);
2407
+ return true;
2408
+ }
2409
+ json(res, { ok: true, server: details });
2410
+ } catch (err) {
2411
+ error(res, `Failed to fetch server details: ${err instanceof Error ? err.message : err}`, 502);
2412
+ }
2413
+ return true;
2414
+ }
2415
+ if (method === "GET" && pathname === "/api/mcp/config") {
2416
+ const servers = state.config.mcp?.servers ?? {};
2417
+ json(res, { ok: true, servers: ctx.redactDeep(servers) });
2418
+ return true;
2419
+ }
2420
+ if (method === "POST" && pathname === "/api/mcp/config/server") {
2421
+ const body = await readJsonBody(req, res);
2422
+ if (!body)
2423
+ return true;
2424
+ const serverName = body.name?.trim();
2425
+ if (!serverName) {
2426
+ error(res, "Server name is required", 400);
2427
+ return true;
2428
+ }
2429
+ if (ctx.isBlockedObjectKey(serverName)) {
2430
+ error(res, 'Invalid server name: "__proto__", "constructor", and "prototype" are reserved', 400);
2431
+ return true;
2432
+ }
2433
+ const config = body.config;
2434
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
2435
+ error(res, "Server config object is required", 400);
2436
+ return true;
2437
+ }
2438
+ const mcpRejection = await ctx.resolveMcpServersRejection({
2439
+ [serverName]: config
2440
+ });
2441
+ if (mcpRejection) {
2442
+ error(res, mcpRejection, 400);
2443
+ return true;
2444
+ }
2445
+ const mcpTerminalRejection = ctx.resolveMcpTerminalAuthorizationRejection(req, { [serverName]: config }, body);
2446
+ if (mcpTerminalRejection) {
2447
+ error(res, `Configuring stdio MCP servers requires terminal authorization. ${mcpTerminalRejection.reason}`, mcpTerminalRejection.status);
2448
+ return true;
2449
+ }
2450
+ if (!state.config.mcp)
2451
+ state.config.mcp = {};
2452
+ if (!state.config.mcp.servers)
2453
+ state.config.mcp.servers = {};
2454
+ const sanitized = ctx.cloneWithoutBlockedObjectKeys(config);
2455
+ state.config.mcp.servers[serverName] = sanitized;
2456
+ try {
2457
+ ctx.saveElizaConfig(state.config);
2458
+ } catch (err) {
2459
+ logger3.warn(`[api] Config save failed: ${err instanceof Error ? err.message : err}`);
2460
+ }
2461
+ json(res, { ok: true, name: serverName, requiresRestart: true });
2462
+ return true;
2463
+ }
2464
+ if (method === "DELETE" && pathname.startsWith("/api/mcp/config/server/")) {
2465
+ const serverName = ctx.decodePathComponent(pathname.slice("/api/mcp/config/server/".length), res, "server name");
2466
+ if (serverName === null)
2467
+ return true;
2468
+ if (ctx.isBlockedObjectKey(serverName)) {
2469
+ error(res, 'Invalid server name: "__proto__", "constructor", and "prototype" are reserved', 400);
2470
+ return true;
2471
+ }
2472
+ if (state.config.mcp?.servers?.[serverName]) {
2473
+ delete state.config.mcp.servers[serverName];
2474
+ try {
2475
+ ctx.saveElizaConfig(state.config);
2476
+ } catch (err) {
2477
+ logger3.warn(`[api] Config save failed: ${err instanceof Error ? err.message : err}`);
2478
+ }
2479
+ }
2480
+ json(res, { ok: true, requiresRestart: true });
2481
+ return true;
2482
+ }
2483
+ if (method === "PUT" && pathname === "/api/mcp/config") {
2484
+ const body = await readJsonBody(req, res);
2485
+ if (!body)
2486
+ return true;
2487
+ if (!state.config.mcp)
2488
+ state.config.mcp = {};
2489
+ if (body.servers !== undefined) {
2490
+ if (!body.servers || typeof body.servers !== "object" || Array.isArray(body.servers)) {
2491
+ error(res, "servers must be a JSON object", 400);
2492
+ return true;
2493
+ }
2494
+ const mcpRejection = await ctx.resolveMcpServersRejection(body.servers);
2495
+ if (mcpRejection) {
2496
+ error(res, mcpRejection, 400);
2497
+ return true;
2498
+ }
2499
+ const mcpTerminalRejection = ctx.resolveMcpTerminalAuthorizationRejection(req, body.servers, body);
2500
+ if (mcpTerminalRejection) {
2501
+ error(res, `Configuring stdio MCP servers requires terminal authorization. ${mcpTerminalRejection.reason}`, mcpTerminalRejection.status);
2502
+ return true;
2503
+ }
2504
+ const sanitized = ctx.cloneWithoutBlockedObjectKeys(body.servers);
2505
+ state.config.mcp.servers = sanitized;
2506
+ }
2507
+ try {
2508
+ ctx.saveElizaConfig(state.config);
2509
+ } catch (err) {
2510
+ logger3.warn(`[api] Config save failed: ${err instanceof Error ? err.message : err}`);
2511
+ }
2512
+ json(res, { ok: true });
2513
+ return true;
2514
+ }
2515
+ if (method === "GET" && pathname === "/api/mcp/status") {
2516
+ const servers = [];
2517
+ if (state.runtime) {
2518
+ try {
2519
+ const mcpService = state.runtime.getService("MCP");
2520
+ if (mcpService && typeof mcpService.getServers === "function") {
2521
+ for (const s of mcpService.getServers()) {
2522
+ servers.push({
2523
+ name: s.name,
2524
+ status: s.status,
2525
+ toolCount: Array.isArray(s.tools) ? s.tools.length : 0,
2526
+ resourceCount: Array.isArray(s.resources) ? s.resources.length : 0
2527
+ });
2528
+ }
2529
+ }
2530
+ } catch (err) {
2531
+ logger3.debug(`[api] Service not available: ${err instanceof Error ? err.message : err}`);
2532
+ }
2533
+ }
2534
+ json(res, { ok: true, servers });
2535
+ return true;
2536
+ }
2537
+ return false;
2538
+ }
2539
+
2099
2540
  // src/index.ts
2541
+ function withMcpContext(action) {
2542
+ return {
2543
+ ...action,
2544
+ contexts: [
2545
+ ...new Set([
2546
+ ...action.contexts ?? [],
2547
+ "general",
2548
+ "automation",
2549
+ "knowledge",
2550
+ MCP_ACTION_CONTEXT
2551
+ ])
2552
+ ]
2553
+ };
2554
+ }
2100
2555
  var mcpPlugin = {
2101
2556
  name: "mcp",
2102
2557
  description: "Plugin for connecting to MCP (Model Context Protocol) servers",
2103
2558
  init: async (_config, _runtime) => {
2104
- logger3.info("Initializing MCP plugin...");
2559
+ logger4.info("Initializing MCP plugin...");
2105
2560
  },
2106
2561
  services: [McpService],
2107
- actions: [callToolAction, readResourceAction],
2562
+ actions: [...promoteSubactionsToActions(withMcpContext(mcpAction))],
2108
2563
  providers: [provider]
2109
2564
  };
2110
2565
  var src_default = mcpPlugin;
2111
2566
  export {
2567
+ handleMcpRoutes,
2112
2568
  src_default as default
2113
2569
  };
2114
2570
 
2115
- //# debugId=3F220FC71C03FFDC64756E2164756E21
2571
+ //# debugId=BAE4A5B3EC26EDA264756E2164756E21