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