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