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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +98 -0
  2. package/dist/cjs/index.cjs +917 -375
  3. package/dist/cjs/index.js.map +21 -20
  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 +913 -379
  9. package/dist/node/index.js.map +21 -20
  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 +2 -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 -4
  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/schemas.d.ts +0 -4
  40. package/dist/node/utils/schemas.d.ts.map +1 -1
  41. package/dist/node/utils/selection.d.ts.map +1 -1
  42. package/dist/node/utils/validation.d.ts.map +1 -1
  43. package/dist/node/utils/wrapper.d.ts.map +1 -1
  44. package/package.json +24 -10
  45. package/dist/node/actions/callToolAction.d.ts +0 -3
  46. package/dist/node/actions/callToolAction.d.ts.map +0 -1
  47. package/dist/node/actions/readResourceAction.d.ts +0 -3
  48. package/dist/node/actions/readResourceAction.d.ts.map +0 -1
  49. package/dist/node/generated/prompts/typescript/prompts.d.ts +0 -24
  50. package/dist/node/generated/prompts/typescript/prompts.d.ts.map +0 -1
  51. 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");
531
+ var import_core8 = require("@elizaos/core");
510
532
 
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;
546
-
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}}}
@@ -572,15 +558,15 @@ var resourceAnalysisTemplate = `{{{mcpProvider.text}}}
572
558
 
573
559
  # Prompt
574
560
 
575
- You are a helpful assistant responding to a user's request. You've just accessed the resource "{{{uri}}}" to help answer this request.
561
+ Respond to the user's request using the resource "{{{uri}}}".
576
562
 
577
563
  Original user request: "{{{userMessage}}}"
578
564
 
579
- Resource metadata:
580
- {{{resourceMeta}}
565
+ Resource metadata:
566
+ {{{resourceMeta}}}
581
567
 
582
- Resource content:
583
- {{{resourceContent}}
568
+ Resource content:
569
+ {{{resourceContent}}}
584
570
 
585
571
  Instructions:
586
572
  1. Analyze how well the resource's content addresses the user's specific question or need
@@ -597,45 +583,50 @@ var resourceSelectionTemplate = `{{{mcpProvider.text}}}
597
583
 
598
584
  # Prompt
599
585
 
600
- You are an intelligent assistant helping select the right resource to address a user's request.
586
+ Select the right resource to address the user's request.
601
587
 
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}}}
635
626
 
636
627
  # Prompt
637
628
 
638
- You are a helpful assistant responding to a user's request. You've just used the "{{{toolName}}}" tool from the "{{{serverName}}}" server to help answer this request.
629
+ Synthesize the result from the "{{{toolName}}}" tool into a response to the user's request.
639
630
 
640
631
  Original user request: "{{{userMessage}}}"
641
632
 
@@ -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,68 @@ 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.`;
750
+
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
+ },
778
+ uri: {
779
+ type: "string",
780
+ minLength: 1
781
+ },
782
+ reasoning: {
783
+ type: "string"
784
+ },
785
+ noResourceAvailable: {
786
+ type: "boolean"
787
+ }
788
+ }
789
+ };
790
+ var DEFAULT_PING_CONFIG = {
791
+ enabled: true,
792
+ intervalMs: 1e4,
793
+ timeoutMs: 5000,
794
+ failuresBeforeDisconnect: 3
795
+ };
796
+ var MAX_RECONNECT_ATTEMPTS = 5;
797
+ var BACKOFF_MULTIPLIER = 2;
798
+ var INITIAL_RETRY_DELAY = 2000;
757
799
 
758
- REMEMBER: This output will be parsed directly as JSON. If the format is incorrect, the operation will fail.`;
800
+ // src/utils/error.ts
801
+ var import_core = require("@elizaos/core");
759
802
 
760
803
  // src/templates/errorAnalysisPrompt.ts
761
804
  var errorAnalysisPrompt = errorAnalysisTemplate;
@@ -796,7 +839,8 @@ async function handleMcpError(state, mcpProvider, error, runtime, message, type,
796
839
  errorType: type
797
840
  },
798
841
  data: {
799
- actionName: type === "tool" ? "CALL_MCP_TOOL" : "READ_MCP_RESOURCE",
842
+ actionName: "MCP",
843
+ op: type === "tool" ? "call_tool" : "read_resource",
800
844
  error: errorMessage,
801
845
  mcpType: type
802
846
  },
@@ -822,7 +866,8 @@ async function handleNoToolAvailable(callback, toolSelection) {
822
866
  fallbackToDirectAssistance: true
823
867
  },
824
868
  data: {
825
- actionName: "CALL_MCP_TOOL",
869
+ actionName: "MCP",
870
+ op: "call_tool",
826
871
  noToolAvailable: true,
827
872
  reason: toolSelection?.reasoning ?? "No appropriate tool available"
828
873
  },
@@ -1088,12 +1133,12 @@ function parseJSON(input) {
1088
1133
  return import_json5.default.parse(cleanedInput);
1089
1134
  }
1090
1135
  var ajv = new import_ajv.default({
1091
- allErrors: true,
1092
- strict: false
1136
+ allErrors: true
1093
1137
  });
1094
1138
  function formatAjvErrors(errors) {
1095
1139
  return errors.map((err) => {
1096
- const path = err.instancePath ? `${err.instancePath.replace(/^\//, "")}` : "value";
1140
+ const errorPath = err.instancePath ?? err.dataPath ?? "";
1141
+ const path = errorPath ? errorPath.replace(/^\//, "") : "value";
1097
1142
  return `${path}: ${err.message ?? "validation failed"}`;
1098
1143
  }).join(", ");
1099
1144
  }
@@ -1107,6 +1152,15 @@ function validateJsonSchema(data, schema) {
1107
1152
  }
1108
1153
  return { success: true, data };
1109
1154
  }
1155
+ function parseStructuredModelOutput(input) {
1156
+ const errors = [];
1157
+ try {
1158
+ return parseJSON(input);
1159
+ } catch {
1160
+ errors.push("JSON object parse failed");
1161
+ }
1162
+ throw new Error(`No valid JSON object found: ${errors.join("; ")}`);
1163
+ }
1110
1164
 
1111
1165
  // src/utils/schemas.ts
1112
1166
  var toolSelectionNameSchema = {
@@ -1115,13 +1169,11 @@ var toolSelectionNameSchema = {
1115
1169
  properties: {
1116
1170
  serverName: {
1117
1171
  type: "string",
1118
- minLength: 1,
1119
- errorMessage: "serverName must not be empty"
1172
+ minLength: 1
1120
1173
  },
1121
1174
  toolName: {
1122
1175
  type: "string",
1123
- minLength: 1,
1124
- errorMessage: "toolName must not be empty"
1176
+ minLength: 1
1125
1177
  },
1126
1178
  reasoning: {
1127
1179
  type: "string"
@@ -1142,7 +1194,24 @@ var toolSelectionArgumentSchema = {
1142
1194
  };
1143
1195
 
1144
1196
  // src/utils/validation.ts
1197
+ function isRecord2(value) {
1198
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1199
+ }
1200
+ function optionalReasoning(parsed) {
1201
+ return typeof parsed.reasoning === "string" ? { reasoning: parsed.reasoning } : {};
1202
+ }
1145
1203
  function validateToolSelectionName(parsed, state) {
1204
+ if (isRecord2(parsed) && parsed.noToolAvailable === true) {
1205
+ return {
1206
+ success: true,
1207
+ data: {
1208
+ serverName: "",
1209
+ toolName: "",
1210
+ noToolAvailable: true,
1211
+ ...optionalReasoning(parsed)
1212
+ }
1213
+ };
1214
+ }
1146
1215
  const basicResult = validateJsonSchema(parsed, toolSelectionNameSchema);
1147
1216
  if (basicResult.success === false) {
1148
1217
  return { success: false, error: basicResult.error };
@@ -1150,7 +1219,7 @@ function validateToolSelectionName(parsed, state) {
1150
1219
  const data = basicResult.data;
1151
1220
  const mcpData = state.values.mcp ?? {};
1152
1221
  const server = mcpData[data.serverName];
1153
- if (!server || server.status !== "connected") {
1222
+ if (server?.status !== "connected") {
1154
1223
  return {
1155
1224
  success: false,
1156
1225
  error: `Server "${data.serverName}" not found or not connected`
@@ -1166,7 +1235,8 @@ function validateToolSelectionName(parsed, state) {
1166
1235
  return { success: true, data };
1167
1236
  }
1168
1237
  function validateToolSelectionArgument(parsed, toolInputSchema) {
1169
- const basicResult = validateJsonSchema(parsed, toolSelectionArgumentSchema);
1238
+ const normalizedParsed = isRecord2(parsed) && typeof parsed.toolArguments === "string" && ["", "{}"].includes(parsed.toolArguments.trim()) ? { ...parsed, toolArguments: {} } : parsed;
1239
+ const basicResult = validateJsonSchema(normalizedParsed, toolSelectionArgumentSchema);
1170
1240
  if (basicResult.success === false) {
1171
1241
  return { success: false, error: basicResult.error };
1172
1242
  }
@@ -1181,6 +1251,17 @@ function validateToolSelectionArgument(parsed, toolInputSchema) {
1181
1251
  return { success: true, data };
1182
1252
  }
1183
1253
  function validateResourceSelection(selection) {
1254
+ if (isRecord2(selection) && selection.noResourceAvailable === true) {
1255
+ return {
1256
+ success: true,
1257
+ data: {
1258
+ serverName: "",
1259
+ uri: "",
1260
+ noResourceAvailable: true,
1261
+ ...optionalReasoning(selection)
1262
+ }
1263
+ };
1264
+ }
1184
1265
  return validateJsonSchema(selection, ResourceSelectionSchema);
1185
1266
  }
1186
1267
  function createToolSelectionFeedbackPrompt(originalResponse, errorMessage, composedState, userMessage) {
@@ -1228,16 +1309,18 @@ function createResourceSelectionFeedbackPrompt(originalResponse, errorMessage, c
1228
1309
  return createFeedbackPrompt(originalResponse, errorMessage, "resource", resourcesDescription, userMessage);
1229
1310
  }
1230
1311
  function createFeedbackPrompt(originalResponse, errorMessage, itemType, itemsDescription, userMessage) {
1231
- return `Error parsing JSON: ${errorMessage}
1312
+ return `The previous ${itemType} selection could not be parsed or validated: ${errorMessage}
1232
1313
 
1233
1314
  Your original response:
1234
1315
  ${originalResponse}
1235
1316
 
1236
- Please try again with valid JSON for ${itemType} selection.
1317
+ Reply again as compact JSON for ${itemType} selection.
1237
1318
  Available ${itemType}s:
1238
1319
  ${itemsDescription}
1239
1320
 
1240
- User request: ${userMessage}`;
1321
+ User request: ${userMessage}
1322
+
1323
+ 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
1324
  }
1242
1325
 
1243
1326
  // src/utils/wrapper.ts
@@ -1254,15 +1337,21 @@ async function withModelRetry({
1254
1337
  retryCount = 0
1255
1338
  }) {
1256
1339
  const maxRetries = getMaxRetries(runtime);
1257
- const parsedJson = typeof input === "string" ? parseJSON(input) : input;
1258
- const validationResult = validationFn(parsedJson);
1340
+ let validationResult;
1341
+ try {
1342
+ const parsedInput = typeof input === "string" ? parseStructuredModelOutput(input) : input;
1343
+ validationResult = validationFn(parsedInput);
1344
+ } catch (error) {
1345
+ const errorMessage2 = error instanceof Error ? error.message : String(error);
1346
+ validationResult = { success: false, error: errorMessage2 };
1347
+ }
1259
1348
  if (validationResult.success) {
1260
1349
  return validationResult.data;
1261
1350
  }
1262
1351
  const errorMessage = validationResult.error;
1263
1352
  if (retryCount < maxRetries) {
1264
1353
  const feedbackPrompt = createFeedbackPromptFn(input, errorMessage, state, message.content.text ?? "");
1265
- const retrySelection = await runtime.useModel(import_core3.ModelType.OBJECT_LARGE, {
1354
+ const retrySelection = await runtime.useModel(import_core3.ModelType.TEXT_LARGE, {
1266
1355
  prompt: feedbackPrompt
1267
1356
  });
1268
1357
  return withModelRetry({
@@ -1287,11 +1376,8 @@ async function withModelRetry({
1287
1376
  }
1288
1377
  function getMaxRetries(runtime) {
1289
1378
  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
- }
1379
+ if (isMcpSettings(rawSettings) && typeof rawSettings.maxRetries === "number" && rawSettings.maxRetries >= 0) {
1380
+ return rawSettings.maxRetries;
1295
1381
  }
1296
1382
  return DEFAULT_MAX_RETRIES;
1297
1383
  }
@@ -1304,8 +1390,16 @@ async function createToolSelectionName({
1304
1390
  callback,
1305
1391
  mcpProvider
1306
1392
  }) {
1393
+ const stateWithMcp = {
1394
+ ...state,
1395
+ values: {
1396
+ ...state.values,
1397
+ mcp: state.values.mcp ?? mcpProvider.data.mcp,
1398
+ mcpProvider
1399
+ }
1400
+ };
1307
1401
  const toolSelectionPrompt = import_core4.composePromptFromState({
1308
- state: { ...state, values: { ...state.values, mcpProvider } },
1402
+ state: stateWithMcp,
1309
1403
  template: toolSelectionNameTemplate
1310
1404
  });
1311
1405
  const toolSelectionName = await runtime.useModel(import_core4.ModelType.TEXT_LARGE, {
@@ -1314,10 +1408,10 @@ async function createToolSelectionName({
1314
1408
  return await withModelRetry({
1315
1409
  runtime,
1316
1410
  message,
1317
- state,
1411
+ state: stateWithMcp,
1318
1412
  callback,
1319
1413
  input: toolSelectionName,
1320
- validationFn: (parsed) => validateToolSelectionName(parsed, state),
1414
+ validationFn: (parsed) => validateToolSelectionName(parsed, stateWithMcp),
1321
1415
  createFeedbackPromptFn: (originalResponse, errorMessage, composedState, userMessage) => createToolSelectionFeedbackPrompt(typeof originalResponse === "string" ? originalResponse : JSON.stringify(originalResponse), errorMessage, composedState, userMessage),
1322
1416
  failureMsg: "I'm having trouble figuring out the best way to help with your request."
1323
1417
  });
@@ -1369,138 +1463,48 @@ async function createToolSelectionArgument({
1369
1463
  });
1370
1464
  }
1371
1465
 
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, options) => {
1389
- const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
1390
- const __avText = __avTextRaw.toLowerCase();
1391
- const __avKeywords = ["call", "mcp", "tool"];
1392
- const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
1393
- const __avRegex = /\b(?:call|mcp|tool)\b/i;
1394
- const __avRegexOk = __avRegex.test(__avText);
1395
- const __avSource = String(message?.content?.source ?? message?.source ?? "");
1396
- const __avExpectedSource = "";
1397
- const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
1398
- const __avOptions = options && typeof options === "object" ? options : {};
1399
- const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object");
1400
- if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
1401
- return false;
1402
- }
1403
- const __avLegacyValidate = async (runtime2, _message, _state) => {
1404
- const mcpService = runtime2.getService(MCP_SERVICE_NAME);
1405
- if (!mcpService)
1406
- return false;
1407
- const servers = mcpService.getServers();
1408
- return servers.length > 0 && servers.some((server) => server.status === "connected" && server.tools && server.tools.length > 0);
1409
- };
1410
- try {
1411
- return Boolean(await __avLegacyValidate(runtime, message, state, options));
1412
- } catch {
1413
- return false;
1414
- }
1415
- },
1416
- handler: async (runtime, message, _state, _options, callback) => {
1417
- const composedState = await runtime.composeState(message, ["RECENT_MESSAGES", "MCP"]);
1418
- const mcpService = runtime.getService(MCP_SERVICE_NAME);
1419
- if (!mcpService) {
1420
- throw new Error("MCP service not available");
1421
- }
1422
- const mcpProvider = mcpService.getProviderData();
1423
- try {
1424
- const toolSelectionName = await createToolSelectionName({
1425
- runtime,
1426
- state: composedState,
1427
- message,
1428
- callback,
1429
- mcpProvider
1430
- });
1431
- if (!toolSelectionName || toolSelectionName.noToolAvailable) {
1432
- return await handleNoToolAvailable(callback, toolSelectionName);
1433
- }
1434
- const { serverName, toolName } = toolSelectionName;
1435
- const toolSelectionArgument = await createToolSelectionArgument({
1436
- runtime,
1437
- state: composedState,
1438
- message,
1439
- callback,
1440
- mcpProvider,
1441
- toolSelectionName
1442
- });
1443
- if (!toolSelectionArgument) {
1444
- return await handleNoToolAvailable(callback, toolSelectionName);
1445
- }
1446
- const result = await mcpService.callTool(serverName, toolName, toolSelectionArgument.toolArguments);
1447
- const { toolOutput, hasAttachments, attachments } = processToolResult(result, serverName, toolName, runtime, message.entityId);
1448
- const replyMemory = await handleToolResponse(runtime, message, serverName, toolName, toolSelectionArgument.toolArguments, toolOutput, hasAttachments, attachments, composedState, mcpProvider, callback);
1449
- return {
1450
- text: `Successfully called tool: ${serverName}/${toolName}. Reasoned response: ${replyMemory.content.text}`,
1451
- values: {
1452
- success: true,
1453
- toolExecuted: true,
1454
- serverName,
1455
- toolName,
1456
- hasAttachments,
1457
- output: toolOutput
1458
- },
1459
- data: {
1460
- actionName: "CALL_MCP_TOOL",
1461
- serverName,
1462
- toolName,
1463
- toolArgumentsJson: JSON.stringify(toolSelectionArgument.toolArguments),
1464
- reasoning: toolSelectionName.reasoning,
1465
- output: toolOutput,
1466
- attachmentCount: attachments?.length ?? 0
1467
- },
1468
- success: true
1469
- };
1470
- } catch (error) {
1471
- return await handleMcpError(composedState, mcpProvider, error, runtime, message, "tool", callback);
1472
- }
1473
- },
1474
- examples: [
1475
- [
1476
- {
1477
- name: "{{user}}",
1478
- content: {
1479
- text: "Can you search for information about climate change?"
1480
- }
1481
- },
1482
- {
1483
- name: "{{assistant}}",
1484
- content: {
1485
- text: "I'll help you with that request. Let me access the right tool...",
1486
- actions: ["CALL_MCP_TOOL"]
1487
- }
1488
- },
1489
- {
1490
- name: "{{assistant}}",
1491
- content: {
1492
- text: `I found the following information about climate change:
1493
-
1494
- 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.`,
1495
- actions: ["CALL_MCP_TOOL"]
1496
- }
1497
- }
1498
- ]
1499
- ]
1500
- };
1501
-
1502
- // src/actions/readResourceAction.ts
1503
- var import_core5 = require("@elizaos/core");
1466
+ // src/actions/mcp.ts
1467
+ var MCP_ACTION_CONTEXT = "mcp";
1468
+ function readOptions(options) {
1469
+ const direct = options ?? {};
1470
+ const parameters = direct.parameters && typeof direct.parameters === "object" ? direct.parameters : {};
1471
+ return { ...direct, ...parameters };
1472
+ }
1473
+ function normalizeOp(value) {
1474
+ if (typeof value !== "string")
1475
+ return null;
1476
+ const v = value.trim().toLowerCase();
1477
+ if (v === "call_tool" || v === "tool" || v === "call")
1478
+ return "call_tool";
1479
+ if (v === "read_resource" || v === "resource" || v === "read")
1480
+ return "read_resource";
1481
+ if (v === "search_actions" || v === "search" || v === "discover")
1482
+ return "search_actions";
1483
+ if (v === "list_connections" || v === "list" || v === "connections")
1484
+ return "list_connections";
1485
+ return null;
1486
+ }
1487
+ function inferOpFromText(text) {
1488
+ if (/\b(read|get|fetch|access|open|list)\b.*\b(resource|resources|document|docs?|file)\b/i.test(text)) {
1489
+ return "read_resource";
1490
+ }
1491
+ if (/\b(call|use|run|execute|invoke|search|query)\b.*\b(tool|tools|mcp)\b/i.test(text)) {
1492
+ return "call_tool";
1493
+ }
1494
+ return null;
1495
+ }
1496
+ function getDirectResourceSelection(options) {
1497
+ const params = readOptions(options);
1498
+ const serverName = typeof params.serverName === "string" ? params.serverName.trim() : "";
1499
+ const uri = typeof params.uri === "string" ? params.uri.trim() : "";
1500
+ if (!serverName || !uri)
1501
+ return null;
1502
+ return {
1503
+ serverName,
1504
+ uri,
1505
+ reasoning: typeof params.reasoning === "string" && params.reasoning.trim() ? params.reasoning.trim() : "Selected from structured MCP read_resource parameters."
1506
+ };
1507
+ }
1504
1508
  function createResourceSelectionPrompt(composedState, userMessage) {
1505
1509
  const mcpData = composedState.values.mcp ?? {};
1506
1510
  const serverNames = Object.keys(mcpData);
@@ -1536,61 +1540,80 @@ function createResourceSelectionPrompt(composedState, userMessage) {
1536
1540
  template: resourceSelectionTemplate
1537
1541
  });
1538
1542
  }
1539
- var readResourceAction = {
1540
- name: "READ_MCP_RESOURCE",
1541
- similes: [
1542
- "READ_RESOURCE",
1543
- "READ_MCP_RESOURCE",
1544
- "GET_RESOURCE",
1545
- "GET_MCP_RESOURCE",
1546
- "FETCH_RESOURCE",
1547
- "FETCH_MCP_RESOURCE",
1548
- "ACCESS_RESOURCE",
1549
- "ACCESS_MCP_RESOURCE"
1550
- ],
1551
- description: "Reads a resource from an MCP server",
1552
- validate: async (runtime, message, state, options) => {
1553
- const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
1554
- const __avText = __avTextRaw.toLowerCase();
1555
- const __avKeywords = ["read", "mcp", "resource"];
1556
- const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
1557
- const __avRegex = /\b(?:read|mcp|resource)\b/i;
1558
- const __avRegexOk = __avRegex.test(__avText);
1559
- const __avSource = String(message?.content?.source ?? message?.source ?? "");
1560
- const __avExpectedSource = "";
1561
- const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
1562
- const __avOptions = options && typeof options === "object" ? options : {};
1563
- const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object");
1564
- if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
1565
- return false;
1566
- }
1567
- const __avLegacyValidate = async (runtime2, _message, _state) => {
1568
- const mcpService = runtime2.getService(MCP_SERVICE_NAME);
1569
- if (!mcpService)
1570
- return false;
1571
- const servers = mcpService.getServers();
1572
- return servers.length > 0 && servers.some((server) => server.status === "connected" && server.resources && server.resources.length > 0);
1573
- };
1574
- try {
1575
- return Boolean(await __avLegacyValidate(runtime, message, state, options));
1576
- } catch {
1577
- return false;
1543
+ async function handleCallTool(runtime, message, callback) {
1544
+ const composedState = await runtime.composeState(message, ["RECENT_MESSAGES", "MCP"]);
1545
+ const mcpService = runtime.getService(MCP_SERVICE_NAME);
1546
+ if (!mcpService) {
1547
+ throw new Error("MCP service not available");
1548
+ }
1549
+ const mcpProvider = mcpService.getProviderData();
1550
+ try {
1551
+ const toolSelectionName = await createToolSelectionName({
1552
+ runtime,
1553
+ state: composedState,
1554
+ message,
1555
+ callback,
1556
+ mcpProvider
1557
+ });
1558
+ if (!toolSelectionName || toolSelectionName.noToolAvailable) {
1559
+ return await handleNoToolAvailable(callback, toolSelectionName);
1578
1560
  }
1579
- },
1580
- handler: async (runtime, message, _state, _options, callback) => {
1581
- const composedState = await runtime.composeState(message, ["RECENT_MESSAGES", "MCP"]);
1582
- const mcpService = runtime.getService(MCP_SERVICE_NAME);
1583
- if (!mcpService) {
1584
- throw new Error("MCP service not available");
1561
+ const { serverName, toolName } = toolSelectionName;
1562
+ const toolSelectionArgument = await createToolSelectionArgument({
1563
+ runtime,
1564
+ state: composedState,
1565
+ message,
1566
+ callback,
1567
+ mcpProvider,
1568
+ toolSelectionName
1569
+ });
1570
+ if (!toolSelectionArgument) {
1571
+ return await handleNoToolAvailable(callback, toolSelectionName);
1585
1572
  }
1586
- const mcpProvider = mcpService.getProviderData();
1587
- try {
1588
- await sendInitialResponse(callback);
1573
+ const result = await mcpService.callTool(serverName, toolName, toolSelectionArgument.toolArguments);
1574
+ const { toolOutput, hasAttachments, attachments } = processToolResult(result, serverName, toolName, runtime, message.entityId);
1575
+ const replyMemory = await handleToolResponse(runtime, message, serverName, toolName, toolSelectionArgument.toolArguments, toolOutput, hasAttachments, attachments, composedState, mcpProvider, callback);
1576
+ return {
1577
+ text: `Successfully called tool: ${serverName}/${toolName}. Reasoned response: ${replyMemory.content.text}`,
1578
+ values: {
1579
+ success: true,
1580
+ toolExecuted: true,
1581
+ serverName,
1582
+ toolName,
1583
+ hasAttachments,
1584
+ output: toolOutput
1585
+ },
1586
+ data: {
1587
+ actionName: "MCP",
1588
+ op: "call_tool",
1589
+ serverName,
1590
+ toolName,
1591
+ toolArgumentsJson: JSON.stringify(toolSelectionArgument.toolArguments),
1592
+ reasoning: toolSelectionName.reasoning,
1593
+ output: toolOutput,
1594
+ attachmentCount: attachments?.length ?? 0
1595
+ },
1596
+ success: true
1597
+ };
1598
+ } catch (error) {
1599
+ return await handleMcpError(composedState, mcpProvider, error, runtime, message, "tool", callback);
1600
+ }
1601
+ }
1602
+ async function handleReadResource(runtime, message, options, callback) {
1603
+ const composedState = await runtime.composeState(message, ["RECENT_MESSAGES", "MCP"]);
1604
+ const mcpService = runtime.getService(MCP_SERVICE_NAME);
1605
+ if (!mcpService) {
1606
+ throw new Error("MCP service not available");
1607
+ }
1608
+ const mcpProvider = mcpService.getProviderData();
1609
+ try {
1610
+ await sendInitialResponse(callback);
1611
+ const parsedSelection = getDirectResourceSelection(options) ?? await (async () => {
1589
1612
  const resourceSelectionPrompt = createResourceSelectionPrompt(composedState, message.content.text ?? "");
1590
1613
  const resourceSelection = await runtime.useModel(import_core5.ModelType.TEXT_SMALL, {
1591
1614
  prompt: resourceSelectionPrompt
1592
1615
  });
1593
- const parsedSelection = await withModelRetry({
1616
+ return withModelRetry({
1594
1617
  runtime,
1595
1618
  state: composedState,
1596
1619
  message,
@@ -1601,75 +1624,210 @@ var readResourceAction = {
1601
1624
  failureMsg: `I'm having trouble finding the resource you're looking for. Could you provide more details about what you need?`,
1602
1625
  retryCount: 0
1603
1626
  });
1604
- if (!parsedSelection || parsedSelection.noResourceAvailable) {
1605
- 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.";
1606
- if (callback && parsedSelection?.noResourceAvailable) {
1607
- await callback({
1608
- text: responseText,
1609
- actions: ["REPLY"]
1610
- });
1611
- }
1612
- return {
1627
+ })();
1628
+ if (!parsedSelection || parsedSelection.noResourceAvailable) {
1629
+ 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.";
1630
+ if (callback && parsedSelection?.noResourceAvailable) {
1631
+ await callback({
1613
1632
  text: responseText,
1614
- values: {
1615
- success: true,
1616
- noResourceAvailable: true,
1617
- fallbackToDirectAssistance: true
1618
- },
1619
- data: {
1620
- actionName: "READ_MCP_RESOURCE",
1621
- noResourceAvailable: true,
1622
- reason: parsedSelection?.reasoning ?? "No appropriate resource available"
1623
- },
1624
- success: true
1625
- };
1633
+ actions: ["REPLY"]
1634
+ });
1626
1635
  }
1627
- const { serverName, uri } = parsedSelection;
1628
- const result = await mcpService.readResource(serverName, uri);
1629
- const { resourceContent, resourceMeta } = processResourceResult(result, uri);
1630
- await handleResourceAnalysis(runtime, message, uri, serverName, resourceContent, resourceMeta, callback);
1631
1636
  return {
1632
- text: `Successfully read resource: ${uri}`,
1637
+ text: responseText,
1633
1638
  values: {
1634
1639
  success: true,
1635
- resourceRead: true,
1636
- serverName,
1637
- uri
1640
+ noResourceAvailable: true,
1641
+ fallbackToDirectAssistance: true
1638
1642
  },
1639
1643
  data: {
1640
- actionName: "READ_MCP_RESOURCE",
1641
- serverName,
1642
- uri,
1643
- reasoning: parsedSelection?.reasoning,
1644
- resourceMeta,
1645
- contentLength: resourceContent?.length ?? 0
1644
+ actionName: "MCP",
1645
+ op: "read_resource",
1646
+ noResourceAvailable: true,
1647
+ reason: parsedSelection?.reasoning ?? "No appropriate resource available"
1646
1648
  },
1647
1649
  success: true
1648
1650
  };
1649
- } catch (error) {
1650
- return await handleMcpError(composedState, mcpProvider, error, runtime, message, "resource", callback);
1651
1651
  }
1652
+ const { serverName, uri } = parsedSelection;
1653
+ const result = await mcpService.readResource(serverName, uri);
1654
+ const { resourceContent, resourceMeta } = processResourceResult(result, uri);
1655
+ await handleResourceAnalysis(runtime, message, uri, serverName, resourceContent, resourceMeta, callback);
1656
+ return {
1657
+ text: `Successfully read resource: ${uri}`,
1658
+ values: {
1659
+ success: true,
1660
+ resourceRead: true,
1661
+ serverName,
1662
+ uri
1663
+ },
1664
+ data: {
1665
+ actionName: "MCP",
1666
+ op: "read_resource",
1667
+ serverName,
1668
+ uri,
1669
+ reasoning: parsedSelection?.reasoning,
1670
+ resourceMeta,
1671
+ contentLength: resourceContent?.length ?? 0
1672
+ },
1673
+ success: true
1674
+ };
1675
+ } catch (error) {
1676
+ return await handleMcpError(composedState, mcpProvider, error, runtime, message, "resource", callback);
1677
+ }
1678
+ }
1679
+ function textOf(message) {
1680
+ return typeof message.content?.text === "string" ? message.content.text : "";
1681
+ }
1682
+ function hasConnectedCapability(runtime) {
1683
+ const mcpService = runtime.getService(MCP_SERVICE_NAME);
1684
+ if (!mcpService)
1685
+ return false;
1686
+ return mcpService.getServers().some((server) => {
1687
+ if (server.status !== "connected")
1688
+ return false;
1689
+ return (server.tools?.length ?? 0) > 0 || (server.resources?.length ?? 0) > 0;
1690
+ });
1691
+ }
1692
+ var mcpAction = {
1693
+ name: "MCP",
1694
+ contexts: ["general", "automation", "knowledge", "connectors", MCP_ACTION_CONTEXT, "files"],
1695
+ contextGate: {
1696
+ anyOf: ["general", "automation", "knowledge", "connectors", MCP_ACTION_CONTEXT, "files"]
1697
+ },
1698
+ roleGate: { minRole: "USER" },
1699
+ similes: [
1700
+ "MCP_ACTION",
1701
+ "MCP_ROUTER",
1702
+ "USE_MCP",
1703
+ "CALL_MCP_TOOL",
1704
+ "CALL_TOOL",
1705
+ "USE_TOOL",
1706
+ "USE_MCP_TOOL",
1707
+ "EXECUTE_TOOL",
1708
+ "EXECUTE_MCP_TOOL",
1709
+ "RUN_TOOL",
1710
+ "RUN_MCP_TOOL",
1711
+ "INVOKE_TOOL",
1712
+ "INVOKE_MCP_TOOL",
1713
+ "READ_MCP_RESOURCE",
1714
+ "READ_RESOURCE",
1715
+ "GET_RESOURCE",
1716
+ "GET_MCP_RESOURCE",
1717
+ "FETCH_RESOURCE",
1718
+ "FETCH_MCP_RESOURCE",
1719
+ "ACCESS_RESOURCE",
1720
+ "ACCESS_MCP_RESOURCE"
1721
+ ],
1722
+ 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.",
1723
+ descriptionCompressed: "MCP call_tool read_resource search_actions list_connections",
1724
+ parameters: [
1725
+ {
1726
+ name: "action",
1727
+ description: "MCP operation: call_tool | read_resource | search_actions | list_connections",
1728
+ required: false,
1729
+ schema: {
1730
+ type: "string",
1731
+ enum: ["call_tool", "read_resource", "search_actions", "list_connections"]
1732
+ }
1733
+ },
1734
+ {
1735
+ name: "serverName",
1736
+ description: "Optional MCP server name that owns the tool or resource.",
1737
+ required: false,
1738
+ schema: { type: "string" }
1739
+ },
1740
+ {
1741
+ name: "toolName",
1742
+ description: "For action=call_tool: optional exact MCP tool name to call.",
1743
+ required: false,
1744
+ schema: { type: "string" }
1745
+ },
1746
+ {
1747
+ name: "arguments",
1748
+ description: "For action=call_tool: optional JSON arguments to pass to the selected MCP tool.",
1749
+ required: false,
1750
+ schema: { type: "object" }
1751
+ },
1752
+ {
1753
+ name: "uri",
1754
+ description: "For action=read_resource: exact MCP resource URI to read.",
1755
+ required: false,
1756
+ schema: { type: "string" }
1757
+ },
1758
+ {
1759
+ name: "query",
1760
+ description: "Natural-language description of the tool call or resource to select; for action=search_actions, the keyword query.",
1761
+ required: false,
1762
+ schema: { type: "string" }
1763
+ },
1764
+ {
1765
+ name: "platform",
1766
+ description: "For action=search_actions: filter results to a single connected platform.",
1767
+ required: false,
1768
+ schema: { type: "string" }
1769
+ },
1770
+ {
1771
+ name: "limit",
1772
+ description: "For action=search_actions: maximum results to return.",
1773
+ required: false,
1774
+ schema: { type: "number" }
1775
+ },
1776
+ {
1777
+ name: "offset",
1778
+ description: "For action=search_actions: skip first N results for pagination.",
1779
+ required: false,
1780
+ schema: { type: "number" }
1781
+ }
1782
+ ],
1783
+ validate: async (runtime) => {
1784
+ if (!hasConnectedCapability(runtime))
1785
+ return false;
1786
+ return true;
1787
+ },
1788
+ handler: async (runtime, message, _state, options, callback) => {
1789
+ const opts = readOptions(options);
1790
+ const requested = normalizeOp(opts.action ?? opts.subaction ?? opts.op ?? opts.operation);
1791
+ const op = requested ?? inferOpFromText(textOf(message)) ?? "call_tool";
1792
+ if (op === "read_resource") {
1793
+ return handleReadResource(runtime, message, options, callback);
1794
+ }
1795
+ if (op === "search_actions" || op === "list_connections") {
1796
+ const text = `MCP op=${op} is only available in the cloud runtime.`;
1797
+ await callback?.({ text, source: message.content?.source });
1798
+ return {
1799
+ success: false,
1800
+ text,
1801
+ values: { error: "OP_NOT_SUPPORTED" },
1802
+ data: { actionName: "MCP", op }
1803
+ };
1804
+ }
1805
+ return handleCallTool(runtime, message, callback);
1652
1806
  },
1653
1807
  examples: [
1654
1808
  [
1655
1809
  {
1656
1810
  name: "{{user}}",
1657
- content: {
1658
- text: "Can you get the documentation about installing elizaOS?"
1659
- }
1811
+ content: { text: "Use the MCP GitHub tool to read the repository README" }
1660
1812
  },
1661
1813
  {
1662
1814
  name: "{{assistant}}",
1663
1815
  content: {
1664
- text: `I'll retrieve that information for you. Let me access the resource...`,
1665
- actions: ["READ_MCP_RESOURCE"]
1816
+ text: "I'll route that through MCP.",
1817
+ actions: ["MCP"]
1666
1818
  }
1819
+ }
1820
+ ],
1821
+ [
1822
+ {
1823
+ name: "{{user}}",
1824
+ content: { text: "Can you get the documentation about installing elizaOS?" }
1667
1825
  },
1668
1826
  {
1669
1827
  name: "{{assistant}}",
1670
1828
  content: {
1671
- 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.`,
1672
- actions: ["READ_MCP_RESOURCE"]
1829
+ text: "I'll read the MCP resource for that.",
1830
+ actions: ["MCP"]
1673
1831
  }
1674
1832
  }
1675
1833
  ]
@@ -1677,10 +1835,36 @@ var readResourceAction = {
1677
1835
  };
1678
1836
 
1679
1837
  // src/provider.ts
1838
+ var MAX_MCP_SERVERS_IN_STATE = 20;
1839
+ var MAX_MCP_TOOLS_PER_SERVER = 30;
1840
+ var MAX_MCP_RESOURCES_PER_SERVER = 30;
1841
+ function formatMcpServersForPrompt(mcp) {
1842
+ const entries = Object.entries(mcp).slice(0, MAX_MCP_SERVERS_IN_STATE);
1843
+ if (entries.length === 0)
1844
+ return "No MCP servers are available.";
1845
+ return [
1846
+ `mcpServers[${Object.keys(mcp).length}, showing ${entries.length}]:`,
1847
+ ...entries.flatMap(([serverName, server]) => {
1848
+ const tools = Object.keys(server.tools ?? {}).slice(0, MAX_MCP_TOOLS_PER_SERVER);
1849
+ const resources = Object.keys(server.resources ?? {}).slice(0, MAX_MCP_RESOURCES_PER_SERVER);
1850
+ return [
1851
+ ` - name: ${serverName}`,
1852
+ ` status: ${server.status}`,
1853
+ ` tools: ${tools.length > 0 ? tools.join(", ") : "none"}`,
1854
+ ` resources: ${resources.length > 0 ? resources.join(", ") : "none"}`
1855
+ ];
1856
+ })
1857
+ ].join(`
1858
+ `);
1859
+ }
1680
1860
  var provider = {
1681
1861
  name: "MCP",
1682
1862
  description: "Information about connected MCP servers, tools, and resources",
1683
1863
  dynamic: true,
1864
+ contexts: ["connectors", "settings"],
1865
+ contextGate: { anyOf: ["connectors", "settings"] },
1866
+ cacheStable: false,
1867
+ cacheScope: "turn",
1684
1868
  get: async (runtime, _message, _state) => {
1685
1869
  const mcpService = runtime.getService(MCP_SERVICE_NAME);
1686
1870
  if (!mcpService) {
@@ -1690,17 +1874,31 @@ var provider = {
1690
1874
  text: "No MCP servers are available."
1691
1875
  };
1692
1876
  }
1693
- const providerData = mcpService.getProviderData();
1694
- return {
1695
- values: { mcpServers: JSON.stringify(providerData.values.mcp) },
1696
- data: { mcpServerCount: Object.keys(providerData.data.mcp).length },
1697
- text: providerData.text
1698
- };
1877
+ try {
1878
+ const providerData = mcpService.getProviderData();
1879
+ const mcp = providerData.values.mcp;
1880
+ const serverEntries = Object.entries(providerData.data.mcp).slice(0, MAX_MCP_SERVERS_IN_STATE);
1881
+ return {
1882
+ values: { mcpServers: formatMcpServersForPrompt(mcp) },
1883
+ data: {
1884
+ mcpServerCount: Object.keys(providerData.data.mcp).length,
1885
+ shownMcpServerCount: serverEntries.length
1886
+ },
1887
+ text: formatMcpServersForPrompt(mcp)
1888
+ };
1889
+ } catch (error) {
1890
+ return {
1891
+ values: {},
1892
+ data: { error: error instanceof Error ? error.message : String(error) },
1893
+ text: "No MCP servers are available."
1894
+ };
1895
+ }
1699
1896
  }
1700
1897
  };
1701
1898
 
1702
1899
  // src/service.ts
1703
1900
  var import_core6 = require("@elizaos/core");
1901
+ var import_mcp_server_config = require("@elizaos/security/mcp-server-config");
1704
1902
  var import_client = require("@modelcontextprotocol/sdk/client/index.js");
1705
1903
  var import_sse = require("@modelcontextprotocol/sdk/client/sse.js");
1706
1904
  var import_stdio = require("@modelcontextprotocol/sdk/client/stdio.js");
@@ -1742,7 +1940,9 @@ class McpService extends import_core6.Service {
1742
1940
  initializationPromise = null;
1743
1941
  constructor(runtime) {
1744
1942
  super(runtime);
1745
- this.initializationPromise = this.initializeMcpServers();
1943
+ if (runtime) {
1944
+ this.initializationPromise = this.initializeMcpServers();
1945
+ }
1746
1946
  }
1747
1947
  static async start(runtime) {
1748
1948
  const service = new McpService(runtime);
@@ -1771,7 +1971,7 @@ class McpService extends import_core6.Service {
1771
1971
  }
1772
1972
  async initializeMcpServers() {
1773
1973
  const mcpSettings = this.getMcpSettings();
1774
- if (!mcpSettings || !mcpSettings.servers || Object.keys(mcpSettings.servers).length === 0) {
1974
+ if (!mcpSettings?.servers || Object.keys(mcpSettings.servers).length === 0) {
1775
1975
  this.mcpProvider = buildMcpProviderData([]);
1776
1976
  return;
1777
1977
  }
@@ -1782,17 +1982,14 @@ class McpService extends import_core6.Service {
1782
1982
  getMcpSettings() {
1783
1983
  const rawSettings = this.runtime.getSetting("mcp");
1784
1984
  let settings = null;
1785
- if (rawSettings && typeof rawSettings === "object" && !Array.isArray(rawSettings)) {
1786
- const parsed = rawSettings;
1787
- if ("servers" in parsed && typeof parsed.servers === "object" && parsed.servers !== null) {
1788
- settings = parsed;
1789
- }
1985
+ if (isMcpSettings(rawSettings)) {
1986
+ settings = rawSettings;
1790
1987
  }
1791
- if (!settings || !settings.servers) {
1988
+ if (!settings?.servers) {
1792
1989
  const characterSettings = this.runtime.character.settings;
1793
1990
  if (characterSettings && typeof characterSettings === "object" && "mcp" in characterSettings) {
1794
1991
  const characterMcpSettings = characterSettings.mcp;
1795
- if (characterMcpSettings && typeof characterMcpSettings === "object" && "servers" in characterMcpSettings) {
1992
+ if (isMcpSettings(characterMcpSettings)) {
1796
1993
  settings = characterMcpSettings;
1797
1994
  }
1798
1995
  }
@@ -1802,15 +1999,28 @@ class McpService extends import_core6.Service {
1802
1999
  }
1803
2000
  return;
1804
2001
  }
2002
+ async filterValidatedServerConfigs(serverConfigs) {
2003
+ const validated = {};
2004
+ for (const [name, config] of Object.entries(serverConfigs)) {
2005
+ const rejection = await import_mcp_server_config.validateMcpServerConfig(config);
2006
+ if (rejection) {
2007
+ import_core6.logger.error({ server: name, rejection }, "Skipping MCP server with invalid or unsafe config");
2008
+ continue;
2009
+ }
2010
+ validated[name] = config;
2011
+ }
2012
+ return validated;
2013
+ }
1805
2014
  async updateServerConnections(serverConfigs) {
2015
+ const safeConfigs = await this.filterValidatedServerConfigs(serverConfigs);
1806
2016
  const currentNames = new Set(this.connections.keys());
1807
- const newNames = new Set(Object.keys(serverConfigs));
2017
+ const newNames = new Set(Object.keys(safeConfigs));
1808
2018
  for (const name of currentNames) {
1809
2019
  if (!newNames.has(name)) {
1810
2020
  await this.deleteConnection(name);
1811
2021
  }
1812
2022
  }
1813
- const connectionPromises = Object.entries(serverConfigs).map(async ([name, config]) => {
2023
+ const connectionPromises = Object.entries(safeConfigs).map(async ([name, config]) => {
1814
2024
  const currentConnection = this.connections.get(name);
1815
2025
  if (!currentConnection) {
1816
2026
  await this.initializeConnection(name, config);
@@ -1956,9 +2166,16 @@ class McpService extends import_core6.Service {
1956
2166
  async deleteConnection(name) {
1957
2167
  const connection = this.connections.get(name);
1958
2168
  if (connection) {
1959
- await connection.transport.close();
1960
- await connection.client.close();
2169
+ const closeResults = await Promise.allSettled([
2170
+ connection.transport.close(),
2171
+ connection.client.close()
2172
+ ]);
1961
2173
  this.connections.delete(name);
2174
+ for (const result of closeResults) {
2175
+ if (result.status === "rejected") {
2176
+ import_core6.logger.warn({ error: result.reason, serverName: name }, `Failed to close MCP connection resource for "${name}"`);
2177
+ }
2178
+ }
1962
2179
  }
1963
2180
  const state = this.connectionStates.get(name);
1964
2181
  if (state) {
@@ -1976,6 +2193,17 @@ class McpService extends import_core6.Service {
1976
2193
  if (!config.command) {
1977
2194
  throw new Error(`Missing command for stdio MCP server ${name}`);
1978
2195
  }
2196
+ const rejection = await import_mcp_server_config.validateMcpServerConfig({
2197
+ type: "stdio",
2198
+ command: config.command,
2199
+ args: config.args,
2200
+ env: config.env,
2201
+ cwd: config.cwd,
2202
+ timeoutInMillis: config.timeoutInMillis
2203
+ });
2204
+ if (rejection) {
2205
+ throw new Error(`MCP stdio server "${name}" rejected at spawn: ${rejection}`);
2206
+ }
1979
2207
  return new import_stdio.StdioClientTransport({
1980
2208
  command: config.command,
1981
2209
  args: config.args ? [...config.args] : undefined,
@@ -1991,6 +2219,13 @@ class McpService extends import_core6.Service {
1991
2219
  if (!config.url) {
1992
2220
  throw new Error(`Missing URL for HTTP MCP server ${name}`);
1993
2221
  }
2222
+ const rejection = await import_mcp_server_config.validateMcpServerConfig({
2223
+ type: config.type,
2224
+ url: config.url
2225
+ });
2226
+ if (rejection) {
2227
+ throw new Error(`MCP remote server "${name}" rejected at connect: ${rejection}`);
2228
+ }
1994
2229
  return new import_sse.SSEClientTransport(new URL(config.url));
1995
2230
  }
1996
2231
  appendErrorMessage(connection, error) {
@@ -2097,17 +2332,324 @@ ${error}` : error;
2097
2332
  }
2098
2333
  }
2099
2334
 
2335
+ // src/routes-mcp.ts
2336
+ var import_core7 = require("@elizaos/core");
2337
+
2338
+ // src/mcp-marketplace.ts
2339
+ var MCP_REGISTRY_BASE_URL = "https://registry.modelcontextprotocol.io";
2340
+ async function searchMcpMarketplace(query, limit = 30) {
2341
+ const resp = await fetch(`${MCP_REGISTRY_BASE_URL}/v0/servers`, {
2342
+ headers: {
2343
+ Accept: "application/json"
2344
+ }
2345
+ });
2346
+ if (!resp.ok) {
2347
+ throw new Error(`Registry API error: ${resp.status} ${resp.statusText}`);
2348
+ }
2349
+ const data = await resp.json();
2350
+ const results = [];
2351
+ const seenNames = new Set;
2352
+ for (const entry of data.servers) {
2353
+ const server = entry.server;
2354
+ const meta = entry._meta?.["io.modelcontextprotocol.registry/official"];
2355
+ if (!meta?.isLatest)
2356
+ continue;
2357
+ if (seenNames.has(server.name))
2358
+ continue;
2359
+ seenNames.add(server.name);
2360
+ if (query) {
2361
+ const q = query.toLowerCase();
2362
+ const matchName = server.name.toLowerCase().includes(q);
2363
+ const matchTitle = server.title?.toLowerCase().includes(q);
2364
+ const matchDesc = server.description?.toLowerCase().includes(q);
2365
+ if (!matchName && !matchTitle && !matchDesc)
2366
+ continue;
2367
+ }
2368
+ let connectionType = "remote";
2369
+ let connectionUrl;
2370
+ let npmPackage;
2371
+ let dockerImage;
2372
+ if (server.remotes && server.remotes.length > 0) {
2373
+ connectionType = "remote";
2374
+ connectionUrl = server.remotes[0].url;
2375
+ } else if (server.packages && server.packages.length > 0) {
2376
+ const pkg = server.packages[0];
2377
+ connectionType = "stdio";
2378
+ if (pkg.registryType === "npm") {
2379
+ npmPackage = pkg.identifier;
2380
+ } else if (pkg.registryType === "oci") {
2381
+ dockerImage = pkg.identifier;
2382
+ }
2383
+ }
2384
+ results.push({
2385
+ id: `${server.name}@${server.version}`,
2386
+ name: server.name,
2387
+ title: server.title || server.name.split("/").pop() || server.name,
2388
+ description: server.description || "No description",
2389
+ version: server.version,
2390
+ connectionType,
2391
+ connectionUrl,
2392
+ npmPackage,
2393
+ dockerImage,
2394
+ repositoryUrl: server.repository?.url,
2395
+ websiteUrl: server.websiteUrl,
2396
+ iconUrl: server.icons?.[0]?.src,
2397
+ publishedAt: meta?.publishedAt,
2398
+ isLatest: true
2399
+ });
2400
+ if (results.length >= limit)
2401
+ break;
2402
+ }
2403
+ return { results };
2404
+ }
2405
+ async function getMcpServerDetails(name) {
2406
+ const resp = await fetch(`${MCP_REGISTRY_BASE_URL}/v0/servers/${encodeURIComponent(name)}`, {
2407
+ headers: { Accept: "application/json" }
2408
+ });
2409
+ if (!resp.ok) {
2410
+ if (resp.status === 404) {
2411
+ return null;
2412
+ }
2413
+ throw new Error(`Registry API error: ${resp.status}`);
2414
+ }
2415
+ const data = await resp.json();
2416
+ return data.server;
2417
+ }
2418
+
2419
+ // src/routes-mcp.ts
2420
+ var MCP_MARKETPLACE_QUERY_MAX_LENGTH = 200;
2421
+ var MCP_MARKETPLACE_SERVER_NAME_MAX_LENGTH = 200;
2422
+ function parseClampedInteger(value, options = {}) {
2423
+ const raw = value == null ? "" : value.trim();
2424
+ if (!raw)
2425
+ return Number.isFinite(options.fallback) ? options.fallback : undefined;
2426
+ if (!/^[+-]?\d+$/.test(raw)) {
2427
+ return Number.isFinite(options.fallback) ? options.fallback : undefined;
2428
+ }
2429
+ const parsed = Number(raw);
2430
+ if (!Number.isSafeInteger(parsed)) {
2431
+ return Number.isFinite(options.fallback) ? options.fallback : undefined;
2432
+ }
2433
+ if (options.min !== undefined && parsed < options.min)
2434
+ return options.min;
2435
+ if (options.max !== undefined && parsed > options.max)
2436
+ return options.max;
2437
+ return parsed;
2438
+ }
2439
+ function normalizeBoundedString(value, maxLength, label) {
2440
+ const normalized = value.trim();
2441
+ if (normalized.length > maxLength) {
2442
+ throw new RangeError(`${label} must be ${maxLength} characters or fewer`);
2443
+ }
2444
+ return normalized;
2445
+ }
2446
+ async function handleMcpRoutes(ctx) {
2447
+ const { req, res, method, pathname, url, state, json, error, readJsonBody } = ctx;
2448
+ if (method === "GET" && pathname === "/api/mcp/marketplace/search") {
2449
+ let query;
2450
+ try {
2451
+ query = normalizeBoundedString(url.searchParams.get("q") ?? "", MCP_MARKETPLACE_QUERY_MAX_LENGTH, "Marketplace search query");
2452
+ } catch (err) {
2453
+ error(res, err instanceof Error ? err.message : String(err), 400);
2454
+ return true;
2455
+ }
2456
+ const limitStr = url.searchParams.get("limit");
2457
+ const limit = limitStr ? parseClampedInteger(limitStr, { min: 1, max: 50, fallback: 30 }) : 30;
2458
+ try {
2459
+ const result = await searchMcpMarketplace(query || undefined, limit);
2460
+ json(res, { ok: true, results: result.results });
2461
+ } catch (err) {
2462
+ error(res, `MCP marketplace search failed: ${err instanceof Error ? err.message : err}`, 502);
2463
+ }
2464
+ return true;
2465
+ }
2466
+ if (method === "GET" && pathname.startsWith("/api/mcp/marketplace/details/")) {
2467
+ const serverName = ctx.decodePathComponent(pathname.slice("/api/mcp/marketplace/details/".length), res, "server name");
2468
+ if (serverName === null)
2469
+ return true;
2470
+ let normalizedServerName;
2471
+ try {
2472
+ normalizedServerName = normalizeBoundedString(serverName, MCP_MARKETPLACE_SERVER_NAME_MAX_LENGTH, "Server name");
2473
+ } catch (err) {
2474
+ error(res, err instanceof Error ? err.message : String(err), 400);
2475
+ return true;
2476
+ }
2477
+ if (!normalizedServerName) {
2478
+ error(res, "Server name is required", 400);
2479
+ return true;
2480
+ }
2481
+ try {
2482
+ const details = await getMcpServerDetails(normalizedServerName);
2483
+ if (!details) {
2484
+ error(res, `MCP server "${normalizedServerName}" not found`, 404);
2485
+ return true;
2486
+ }
2487
+ json(res, { ok: true, server: details });
2488
+ } catch (err) {
2489
+ error(res, `Failed to fetch server details: ${err instanceof Error ? err.message : err}`, 502);
2490
+ }
2491
+ return true;
2492
+ }
2493
+ if (method === "GET" && pathname === "/api/mcp/config") {
2494
+ const servers = state.config.mcp?.servers ?? {};
2495
+ json(res, { ok: true, servers: ctx.redactDeep(servers) });
2496
+ return true;
2497
+ }
2498
+ if (method === "POST" && pathname === "/api/mcp/config/server") {
2499
+ const body = await readJsonBody(req, res);
2500
+ if (!body)
2501
+ return true;
2502
+ const serverName = body.name?.trim();
2503
+ if (!serverName) {
2504
+ error(res, "Server name is required", 400);
2505
+ return true;
2506
+ }
2507
+ if (ctx.isBlockedObjectKey(serverName)) {
2508
+ error(res, 'Invalid server name: "__proto__", "constructor", and "prototype" are reserved', 400);
2509
+ return true;
2510
+ }
2511
+ const config = body.config;
2512
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
2513
+ error(res, "Server config object is required", 400);
2514
+ return true;
2515
+ }
2516
+ const mcpRejection = await ctx.resolveMcpServersRejection({
2517
+ [serverName]: config
2518
+ });
2519
+ if (mcpRejection) {
2520
+ error(res, mcpRejection, 400);
2521
+ return true;
2522
+ }
2523
+ const mcpTerminalRejection = ctx.resolveMcpTerminalAuthorizationRejection(req, { [serverName]: config }, body);
2524
+ if (mcpTerminalRejection) {
2525
+ error(res, `Configuring stdio MCP servers requires terminal authorization. ${mcpTerminalRejection.reason}`, mcpTerminalRejection.status);
2526
+ return true;
2527
+ }
2528
+ if (!state.config.mcp)
2529
+ state.config.mcp = {};
2530
+ if (!state.config.mcp.servers)
2531
+ state.config.mcp.servers = {};
2532
+ const sanitized = ctx.cloneWithoutBlockedObjectKeys(config);
2533
+ state.config.mcp.servers[serverName] = sanitized;
2534
+ try {
2535
+ ctx.saveElizaConfig(state.config);
2536
+ } catch (err) {
2537
+ import_core7.logger.warn(`[api] Config save failed: ${err instanceof Error ? err.message : err}`);
2538
+ }
2539
+ json(res, { ok: true, name: serverName, requiresRestart: true });
2540
+ return true;
2541
+ }
2542
+ if (method === "DELETE" && pathname.startsWith("/api/mcp/config/server/")) {
2543
+ const serverName = ctx.decodePathComponent(pathname.slice("/api/mcp/config/server/".length), res, "server name");
2544
+ if (serverName === null)
2545
+ return true;
2546
+ if (ctx.isBlockedObjectKey(serverName)) {
2547
+ error(res, 'Invalid server name: "__proto__", "constructor", and "prototype" are reserved', 400);
2548
+ return true;
2549
+ }
2550
+ if (state.config.mcp?.servers?.[serverName]) {
2551
+ delete state.config.mcp.servers[serverName];
2552
+ try {
2553
+ ctx.saveElizaConfig(state.config);
2554
+ } catch (err) {
2555
+ import_core7.logger.warn(`[api] Config save failed: ${err instanceof Error ? err.message : err}`);
2556
+ }
2557
+ }
2558
+ json(res, { ok: true, requiresRestart: true });
2559
+ return true;
2560
+ }
2561
+ if (method === "PUT" && pathname === "/api/mcp/config") {
2562
+ const body = await readJsonBody(req, res);
2563
+ if (!body)
2564
+ return true;
2565
+ if (!state.config.mcp)
2566
+ state.config.mcp = {};
2567
+ if (body.servers !== undefined) {
2568
+ if (!body.servers || typeof body.servers !== "object" || Array.isArray(body.servers)) {
2569
+ error(res, "servers must be a JSON object", 400);
2570
+ return true;
2571
+ }
2572
+ for (const serverName of Object.keys(body.servers)) {
2573
+ if (ctx.isBlockedObjectKey(serverName)) {
2574
+ error(res, 'Invalid server name: "__proto__", "constructor", and "prototype" are reserved', 400);
2575
+ return true;
2576
+ }
2577
+ }
2578
+ const mcpRejection = await ctx.resolveMcpServersRejection(body.servers);
2579
+ if (mcpRejection) {
2580
+ error(res, mcpRejection, 400);
2581
+ return true;
2582
+ }
2583
+ const mcpTerminalRejection = ctx.resolveMcpTerminalAuthorizationRejection(req, body.servers, body);
2584
+ if (mcpTerminalRejection) {
2585
+ error(res, `Configuring stdio MCP servers requires terminal authorization. ${mcpTerminalRejection.reason}`, mcpTerminalRejection.status);
2586
+ return true;
2587
+ }
2588
+ const sanitized = ctx.cloneWithoutBlockedObjectKeys(body.servers);
2589
+ state.config.mcp.servers = sanitized;
2590
+ }
2591
+ try {
2592
+ ctx.saveElizaConfig(state.config);
2593
+ } catch (err) {
2594
+ import_core7.logger.warn(`[api] Config save failed: ${err instanceof Error ? err.message : err}`);
2595
+ }
2596
+ json(res, { ok: true });
2597
+ return true;
2598
+ }
2599
+ if (method === "GET" && pathname === "/api/mcp/status") {
2600
+ const servers = [];
2601
+ if (state.runtime) {
2602
+ try {
2603
+ const mcpService = state.runtime.getService("MCP");
2604
+ if (mcpService && typeof mcpService.getServers === "function") {
2605
+ for (const s of mcpService.getServers()) {
2606
+ servers.push({
2607
+ name: s.name,
2608
+ status: s.status,
2609
+ toolCount: Array.isArray(s.tools) ? s.tools.length : 0,
2610
+ resourceCount: Array.isArray(s.resources) ? s.resources.length : 0
2611
+ });
2612
+ }
2613
+ }
2614
+ } catch (err) {
2615
+ import_core7.logger.debug(`[api] Service not available: ${err instanceof Error ? err.message : err}`);
2616
+ }
2617
+ }
2618
+ json(res, { ok: true, servers });
2619
+ return true;
2620
+ }
2621
+ return false;
2622
+ }
2623
+
2100
2624
  // src/index.ts
2625
+ function withMcpContext(action) {
2626
+ return {
2627
+ ...action,
2628
+ contexts: [
2629
+ ...new Set([
2630
+ ...action.contexts ?? [],
2631
+ "general",
2632
+ "automation",
2633
+ "knowledge",
2634
+ MCP_ACTION_CONTEXT
2635
+ ])
2636
+ ]
2637
+ };
2638
+ }
2101
2639
  var mcpPlugin = {
2102
2640
  name: "mcp",
2103
2641
  description: "Plugin for connecting to MCP (Model Context Protocol) servers",
2104
2642
  init: async (_config, _runtime) => {
2105
- import_core7.logger.info("Initializing MCP plugin...");
2643
+ import_core8.logger.info("Initializing MCP plugin...");
2644
+ },
2645
+ async dispose(runtime) {
2646
+ const svc = runtime.getService(McpService.serviceType);
2647
+ await svc?.stop();
2106
2648
  },
2107
2649
  services: [McpService],
2108
- actions: [callToolAction, readResourceAction],
2650
+ actions: [...import_core8.promoteSubactionsToActions(withMcpContext(mcpAction))],
2109
2651
  providers: [provider]
2110
2652
  };
2111
2653
  var src_default = mcpPlugin;
2112
2654
 
2113
- //# debugId=1F147A30DFB3603364756E2164756E21
2655
+ //# debugId=E8D9359237465E8664756E2164756E21