@elizaos/plugin-mcp 2.0.0-alpha.7 → 2.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +269 -0
- package/dist/cjs/index.cjs +822 -358
- package/dist/cjs/index.js.map +19 -18
- package/dist/node/actions/mcp.d.ts +6 -0
- package/dist/node/actions/mcp.d.ts.map +1 -0
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +818 -362
- package/dist/node/index.js.map +19 -18
- package/dist/node/mcp-marketplace.d.ts +69 -0
- package/dist/node/mcp-marketplace.d.ts.map +1 -0
- package/dist/node/prompts.d.ts +23 -0
- package/dist/node/prompts.d.ts.map +1 -0
- package/dist/node/provider.d.ts.map +1 -1
- package/dist/node/routes-mcp.d.ts +48 -0
- package/dist/node/routes-mcp.d.ts.map +1 -0
- package/dist/node/service.d.ts +1 -1
- package/dist/node/service.d.ts.map +1 -1
- package/dist/node/templates/errorAnalysisPrompt.d.ts.map +1 -1
- package/dist/node/templates/feedbackTemplate.d.ts +1 -6
- package/dist/node/templates/feedbackTemplate.d.ts.map +1 -1
- package/dist/node/templates/resourceAnalysisTemplate.d.ts +1 -6
- package/dist/node/templates/resourceAnalysisTemplate.d.ts.map +1 -1
- package/dist/node/templates/resourceSelectionTemplate.d.ts +1 -6
- package/dist/node/templates/resourceSelectionTemplate.d.ts.map +1 -1
- package/dist/node/templates/toolReasoningTemplate.d.ts +1 -6
- package/dist/node/templates/toolReasoningTemplate.d.ts.map +1 -1
- package/dist/node/templates/toolSelectionTemplate.d.ts +2 -7
- package/dist/node/templates/toolSelectionTemplate.d.ts.map +1 -1
- package/dist/node/tool-compatibility/integration-test.d.ts.map +1 -1
- package/dist/node/tool-compatibility/providers/openai.d.ts.map +1 -1
- package/dist/node/tool-compatibility/test-example.d.ts.map +1 -1
- package/dist/node/types.d.ts +1 -0
- package/dist/node/types.d.ts.map +1 -1
- package/dist/node/utils/error.d.ts.map +1 -1
- package/dist/node/utils/handler.d.ts.map +1 -1
- package/dist/node/utils/json.d.ts +1 -0
- package/dist/node/utils/json.d.ts.map +1 -1
- package/dist/node/utils/validation.d.ts.map +1 -1
- package/dist/node/utils/wrapper.d.ts.map +1 -1
- package/package.json +9 -8
- package/dist/node/actions/callToolAction.d.ts +0 -3
- package/dist/node/actions/callToolAction.d.ts.map +0 -1
- package/dist/node/actions/readResourceAction.d.ts +0 -3
- package/dist/node/actions/readResourceAction.d.ts.map +0 -1
- package/dist/node/generated/prompts/typescript/prompts.d.ts +0 -24
- package/dist/node/generated/prompts/typescript/prompts.d.ts.map +0 -1
- package/dist/tsconfig.build.tsbuildinfo +0 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -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: (
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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: (
|
|
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(", ") :
|
|
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
|
|
510
|
-
|
|
511
|
-
// src/types.ts
|
|
512
|
-
var MCP_SERVICE_NAME = "mcp";
|
|
513
|
-
var DEFAULT_MCP_TIMEOUT_SECONDS = 60000;
|
|
514
|
-
var DEFAULT_MAX_RETRIES = 2;
|
|
515
|
-
var ResourceSelectionSchema = {
|
|
516
|
-
type: "object",
|
|
517
|
-
required: ["serverName", "uri"],
|
|
518
|
-
properties: {
|
|
519
|
-
serverName: {
|
|
520
|
-
type: "string",
|
|
521
|
-
minLength: 1,
|
|
522
|
-
errorMessage: "serverName must not be empty"
|
|
523
|
-
},
|
|
524
|
-
uri: {
|
|
525
|
-
type: "string",
|
|
526
|
-
minLength: 1,
|
|
527
|
-
errorMessage: "uri must not be empty"
|
|
528
|
-
},
|
|
529
|
-
reasoning: {
|
|
530
|
-
type: "string"
|
|
531
|
-
},
|
|
532
|
-
noResourceAvailable: {
|
|
533
|
-
type: "boolean"
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
};
|
|
537
|
-
var DEFAULT_PING_CONFIG = {
|
|
538
|
-
enabled: true,
|
|
539
|
-
intervalMs: 1e4,
|
|
540
|
-
timeoutMs: 5000,
|
|
541
|
-
failuresBeforeDisconnect: 3
|
|
542
|
-
};
|
|
543
|
-
var MAX_RECONNECT_ATTEMPTS = 5;
|
|
544
|
-
var BACKOFF_MULTIPLIER = 2;
|
|
545
|
-
var INITIAL_RETRY_DELAY = 2000;
|
|
531
|
+
var import_core8 = require("@elizaos/core");
|
|
546
532
|
|
|
547
|
-
// src/
|
|
548
|
-
var
|
|
533
|
+
// src/actions/mcp.ts
|
|
534
|
+
var import_core5 = require("@elizaos/core");
|
|
549
535
|
|
|
550
|
-
// src/
|
|
536
|
+
// src/prompts.ts
|
|
551
537
|
var errorAnalysisTemplate = `{{{mcpProvider.text}}}
|
|
552
538
|
|
|
553
539
|
{{{recentMessages}}}
|
|
@@ -576,10 +562,10 @@ You are a helpful assistant responding to a user's request. You've just accessed
|
|
|
576
562
|
|
|
577
563
|
Original user request: "{{{userMessage}}}"
|
|
578
564
|
|
|
579
|
-
Resource metadata:
|
|
565
|
+
Resource metadata:
|
|
580
566
|
{{{resourceMeta}}
|
|
581
567
|
|
|
582
|
-
Resource content:
|
|
568
|
+
Resource content:
|
|
583
569
|
{{{resourceContent}}
|
|
584
570
|
|
|
585
571
|
Instructions:
|
|
@@ -602,33 +588,38 @@ You are an intelligent assistant helping select the right resource to address a
|
|
|
602
588
|
CRITICAL INSTRUCTIONS:
|
|
603
589
|
1. You MUST specify both a valid serverName AND uri from the list above
|
|
604
590
|
2. The serverName value should match EXACTLY the server name shown in parentheses (Server: X)
|
|
605
|
-
CORRECT:
|
|
606
|
-
WRONG:
|
|
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:
|
|
609
|
-
WRONG:
|
|
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,
|
|
598
|
+
6. If no resource seems appropriate, set noResourceAvailable: true
|
|
613
599
|
|
|
614
|
-
|
|
600
|
+
Respond with compact JSON only.
|
|
615
601
|
|
|
616
602
|
STRICT FORMAT REQUIREMENTS:
|
|
617
|
-
-
|
|
618
|
-
- NO
|
|
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": "
|
|
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
|
-
|
|
618
|
+
NO RESOURCE EXAMPLE:
|
|
619
|
+
{
|
|
620
|
+
"reasoning": "None of the available resources match the user's request.",
|
|
621
|
+
"noResourceAvailable": true
|
|
622
|
+
}`;
|
|
632
623
|
var toolReasoningTemplate = `{{{mcpProvider.text}}}
|
|
633
624
|
|
|
634
625
|
{{{recentMessages}}}
|
|
@@ -657,34 +648,34 @@ Instructions:
|
|
|
657
648
|
Your response (written as if directly to the user):`;
|
|
658
649
|
var toolSelectionArgumentTemplate = `{{recentMessages}}
|
|
659
650
|
|
|
660
|
-
# TASK: Generate
|
|
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
|
|
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
|
-
|
|
661
|
+
Respond with compact JSON only.
|
|
671
662
|
|
|
672
663
|
## STRICT FORMAT REQUIREMENTS
|
|
673
|
-
- The response MUST be
|
|
674
|
-
- DO NOT wrap
|
|
675
|
-
- DO NOT include comments
|
|
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
|
-
##
|
|
675
|
+
## RESPONSE STRUCTURE
|
|
685
676
|
Your response MUST contain ONLY these two top-level keys:
|
|
686
|
-
1.
|
|
687
|
-
2.
|
|
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
|
-
|
|
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.
|
|
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
|
|
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):
|
|
713
|
-
- Example (incorrect):
|
|
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
|
|
717
|
-
5. If no tool is appropriate,
|
|
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
|
|
728
|
-
- DO NOT wrap
|
|
729
|
-
- DO NOT include comments
|
|
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
|
-
##
|
|
725
|
+
## RESPONSE STRUCTURE
|
|
739
726
|
Your response MUST contain ONLY these top-level keys:
|
|
740
|
-
1.
|
|
741
|
-
2.
|
|
742
|
-
3.
|
|
743
|
-
4.
|
|
727
|
+
1. serverName - The name of the server (e.g., github, notion)
|
|
728
|
+
2. toolName - The name of the tool (e.g., get_file_contents, search)
|
|
729
|
+
3. reasoning - A short explanation of how the values were inferred from the conversation
|
|
730
|
+
4. noToolAvailable - true or false
|
|
744
731
|
|
|
745
732
|
## EXAMPLE RESPONSE
|
|
746
733
|
{
|
|
@@ -750,12 +737,70 @@ Your response MUST contain ONLY these top-level keys:
|
|
|
750
737
|
"noToolAvailable": false
|
|
751
738
|
}
|
|
752
739
|
|
|
740
|
+
## NO TOOL EXAMPLE
|
|
741
|
+
{
|
|
742
|
+
"reasoning": "None of the available tools match the user's request.",
|
|
743
|
+
"noToolAvailable": true
|
|
744
|
+
}
|
|
745
|
+
|
|
753
746
|
## REMINDERS
|
|
754
747
|
- Use "github" as serverName for GitHub tools.
|
|
755
748
|
- Use "notion" as serverName for Notion tools.
|
|
756
|
-
- For search and knowledge-based tasks, MCP tools are often appropriate
|
|
749
|
+
- For search and knowledge-based tasks, MCP tools are often appropriate.`;
|
|
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
|
+
errorMessage: "serverName must not be empty"
|
|
778
|
+
},
|
|
779
|
+
uri: {
|
|
780
|
+
type: "string",
|
|
781
|
+
minLength: 1,
|
|
782
|
+
errorMessage: "uri must not be empty"
|
|
783
|
+
},
|
|
784
|
+
reasoning: {
|
|
785
|
+
type: "string"
|
|
786
|
+
},
|
|
787
|
+
noResourceAvailable: {
|
|
788
|
+
type: "boolean"
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
var DEFAULT_PING_CONFIG = {
|
|
793
|
+
enabled: true,
|
|
794
|
+
intervalMs: 1e4,
|
|
795
|
+
timeoutMs: 5000,
|
|
796
|
+
failuresBeforeDisconnect: 3
|
|
797
|
+
};
|
|
798
|
+
var MAX_RECONNECT_ATTEMPTS = 5;
|
|
799
|
+
var BACKOFF_MULTIPLIER = 2;
|
|
800
|
+
var INITIAL_RETRY_DELAY = 2000;
|
|
757
801
|
|
|
758
|
-
|
|
802
|
+
// src/utils/error.ts
|
|
803
|
+
var import_core = require("@elizaos/core");
|
|
759
804
|
|
|
760
805
|
// src/templates/errorAnalysisPrompt.ts
|
|
761
806
|
var errorAnalysisPrompt = errorAnalysisTemplate;
|
|
@@ -796,7 +841,8 @@ async function handleMcpError(state, mcpProvider, error, runtime, message, type,
|
|
|
796
841
|
errorType: type
|
|
797
842
|
},
|
|
798
843
|
data: {
|
|
799
|
-
actionName:
|
|
844
|
+
actionName: "MCP",
|
|
845
|
+
op: type === "tool" ? "call_tool" : "read_resource",
|
|
800
846
|
error: errorMessage,
|
|
801
847
|
mcpType: type
|
|
802
848
|
},
|
|
@@ -822,7 +868,8 @@ async function handleNoToolAvailable(callback, toolSelection) {
|
|
|
822
868
|
fallbackToDirectAssistance: true
|
|
823
869
|
},
|
|
824
870
|
data: {
|
|
825
|
-
actionName: "
|
|
871
|
+
actionName: "MCP",
|
|
872
|
+
op: "call_tool",
|
|
826
873
|
noToolAvailable: true,
|
|
827
874
|
reason: toolSelection?.reasoning ?? "No appropriate tool available"
|
|
828
875
|
},
|
|
@@ -1088,12 +1135,12 @@ function parseJSON(input) {
|
|
|
1088
1135
|
return import_json5.default.parse(cleanedInput);
|
|
1089
1136
|
}
|
|
1090
1137
|
var ajv = new import_ajv.default({
|
|
1091
|
-
allErrors: true
|
|
1092
|
-
strict: false
|
|
1138
|
+
allErrors: true
|
|
1093
1139
|
});
|
|
1094
1140
|
function formatAjvErrors(errors) {
|
|
1095
1141
|
return errors.map((err) => {
|
|
1096
|
-
const
|
|
1142
|
+
const errorPath = err.instancePath ?? err.dataPath ?? "";
|
|
1143
|
+
const path = errorPath ? errorPath.replace(/^\//, "") : "value";
|
|
1097
1144
|
return `${path}: ${err.message ?? "validation failed"}`;
|
|
1098
1145
|
}).join(", ");
|
|
1099
1146
|
}
|
|
@@ -1107,6 +1154,15 @@ function validateJsonSchema(data, schema) {
|
|
|
1107
1154
|
}
|
|
1108
1155
|
return { success: true, data };
|
|
1109
1156
|
}
|
|
1157
|
+
function parseStructuredModelOutput(input) {
|
|
1158
|
+
const errors = [];
|
|
1159
|
+
try {
|
|
1160
|
+
return parseJSON(input);
|
|
1161
|
+
} catch {
|
|
1162
|
+
errors.push("JSON object parse failed");
|
|
1163
|
+
}
|
|
1164
|
+
throw new Error(`No valid JSON object found: ${errors.join("; ")}`);
|
|
1165
|
+
}
|
|
1110
1166
|
|
|
1111
1167
|
// src/utils/schemas.ts
|
|
1112
1168
|
var toolSelectionNameSchema = {
|
|
@@ -1142,7 +1198,24 @@ var toolSelectionArgumentSchema = {
|
|
|
1142
1198
|
};
|
|
1143
1199
|
|
|
1144
1200
|
// src/utils/validation.ts
|
|
1201
|
+
function isRecord2(value) {
|
|
1202
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1203
|
+
}
|
|
1204
|
+
function optionalReasoning(parsed) {
|
|
1205
|
+
return typeof parsed.reasoning === "string" ? { reasoning: parsed.reasoning } : {};
|
|
1206
|
+
}
|
|
1145
1207
|
function validateToolSelectionName(parsed, state) {
|
|
1208
|
+
if (isRecord2(parsed) && parsed.noToolAvailable === true) {
|
|
1209
|
+
return {
|
|
1210
|
+
success: true,
|
|
1211
|
+
data: {
|
|
1212
|
+
serverName: "",
|
|
1213
|
+
toolName: "",
|
|
1214
|
+
noToolAvailable: true,
|
|
1215
|
+
...optionalReasoning(parsed)
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1146
1219
|
const basicResult = validateJsonSchema(parsed, toolSelectionNameSchema);
|
|
1147
1220
|
if (basicResult.success === false) {
|
|
1148
1221
|
return { success: false, error: basicResult.error };
|
|
@@ -1166,7 +1239,8 @@ function validateToolSelectionName(parsed, state) {
|
|
|
1166
1239
|
return { success: true, data };
|
|
1167
1240
|
}
|
|
1168
1241
|
function validateToolSelectionArgument(parsed, toolInputSchema) {
|
|
1169
|
-
const
|
|
1242
|
+
const normalizedParsed = isRecord2(parsed) && typeof parsed.toolArguments === "string" && ["", "{}"].includes(parsed.toolArguments.trim()) ? { ...parsed, toolArguments: {} } : parsed;
|
|
1243
|
+
const basicResult = validateJsonSchema(normalizedParsed, toolSelectionArgumentSchema);
|
|
1170
1244
|
if (basicResult.success === false) {
|
|
1171
1245
|
return { success: false, error: basicResult.error };
|
|
1172
1246
|
}
|
|
@@ -1181,6 +1255,17 @@ function validateToolSelectionArgument(parsed, toolInputSchema) {
|
|
|
1181
1255
|
return { success: true, data };
|
|
1182
1256
|
}
|
|
1183
1257
|
function validateResourceSelection(selection) {
|
|
1258
|
+
if (isRecord2(selection) && selection.noResourceAvailable === true) {
|
|
1259
|
+
return {
|
|
1260
|
+
success: true,
|
|
1261
|
+
data: {
|
|
1262
|
+
serverName: "",
|
|
1263
|
+
uri: "",
|
|
1264
|
+
noResourceAvailable: true,
|
|
1265
|
+
...optionalReasoning(selection)
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1184
1269
|
return validateJsonSchema(selection, ResourceSelectionSchema);
|
|
1185
1270
|
}
|
|
1186
1271
|
function createToolSelectionFeedbackPrompt(originalResponse, errorMessage, composedState, userMessage) {
|
|
@@ -1228,16 +1313,18 @@ function createResourceSelectionFeedbackPrompt(originalResponse, errorMessage, c
|
|
|
1228
1313
|
return createFeedbackPrompt(originalResponse, errorMessage, "resource", resourcesDescription, userMessage);
|
|
1229
1314
|
}
|
|
1230
1315
|
function createFeedbackPrompt(originalResponse, errorMessage, itemType, itemsDescription, userMessage) {
|
|
1231
|
-
return `
|
|
1316
|
+
return `The previous ${itemType} selection could not be parsed or validated: ${errorMessage}
|
|
1232
1317
|
|
|
1233
1318
|
Your original response:
|
|
1234
1319
|
${originalResponse}
|
|
1235
1320
|
|
|
1236
|
-
|
|
1321
|
+
Reply again as compact JSON for ${itemType} selection.
|
|
1237
1322
|
Available ${itemType}s:
|
|
1238
1323
|
${itemsDescription}
|
|
1239
1324
|
|
|
1240
|
-
User request: ${userMessage}
|
|
1325
|
+
User request: ${userMessage}
|
|
1326
|
+
|
|
1327
|
+
Use exact names from the list. For tools, return a JSON object with serverName, toolName, reasoning, and noToolAvailable. For resources, return a JSON object with serverName, uri, reasoning, and noResourceAvailable.`;
|
|
1241
1328
|
}
|
|
1242
1329
|
|
|
1243
1330
|
// src/utils/wrapper.ts
|
|
@@ -1254,15 +1341,21 @@ async function withModelRetry({
|
|
|
1254
1341
|
retryCount = 0
|
|
1255
1342
|
}) {
|
|
1256
1343
|
const maxRetries = getMaxRetries(runtime);
|
|
1257
|
-
|
|
1258
|
-
|
|
1344
|
+
let validationResult;
|
|
1345
|
+
try {
|
|
1346
|
+
const parsedInput = typeof input === "string" ? parseStructuredModelOutput(input) : input;
|
|
1347
|
+
validationResult = validationFn(parsedInput);
|
|
1348
|
+
} catch (error) {
|
|
1349
|
+
const errorMessage2 = error instanceof Error ? error.message : String(error);
|
|
1350
|
+
validationResult = { success: false, error: errorMessage2 };
|
|
1351
|
+
}
|
|
1259
1352
|
if (validationResult.success) {
|
|
1260
1353
|
return validationResult.data;
|
|
1261
1354
|
}
|
|
1262
1355
|
const errorMessage = validationResult.error;
|
|
1263
1356
|
if (retryCount < maxRetries) {
|
|
1264
1357
|
const feedbackPrompt = createFeedbackPromptFn(input, errorMessage, state, message.content.text ?? "");
|
|
1265
|
-
const retrySelection = await runtime.useModel(import_core3.ModelType.
|
|
1358
|
+
const retrySelection = await runtime.useModel(import_core3.ModelType.TEXT_LARGE, {
|
|
1266
1359
|
prompt: feedbackPrompt
|
|
1267
1360
|
});
|
|
1268
1361
|
return withModelRetry({
|
|
@@ -1287,11 +1380,8 @@ async function withModelRetry({
|
|
|
1287
1380
|
}
|
|
1288
1381
|
function getMaxRetries(runtime) {
|
|
1289
1382
|
const rawSettings = runtime.getSetting("mcp");
|
|
1290
|
-
if (rawSettings && typeof rawSettings === "
|
|
1291
|
-
|
|
1292
|
-
if (typeof settings.maxRetries === "number" && settings.maxRetries >= 0) {
|
|
1293
|
-
return settings.maxRetries;
|
|
1294
|
-
}
|
|
1383
|
+
if (isMcpSettings(rawSettings) && typeof rawSettings.maxRetries === "number" && rawSettings.maxRetries >= 0) {
|
|
1384
|
+
return rawSettings.maxRetries;
|
|
1295
1385
|
}
|
|
1296
1386
|
return DEFAULT_MAX_RETRIES;
|
|
1297
1387
|
}
|
|
@@ -1369,138 +1459,48 @@ async function createToolSelectionArgument({
|
|
|
1369
1459
|
});
|
|
1370
1460
|
}
|
|
1371
1461
|
|
|
1372
|
-
// src/actions/
|
|
1373
|
-
var
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
"
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
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");
|
|
1462
|
+
// src/actions/mcp.ts
|
|
1463
|
+
var MCP_ACTION_CONTEXT = "mcp";
|
|
1464
|
+
function readOptions(options) {
|
|
1465
|
+
const direct = options ?? {};
|
|
1466
|
+
const parameters = direct.parameters && typeof direct.parameters === "object" ? direct.parameters : {};
|
|
1467
|
+
return { ...direct, ...parameters };
|
|
1468
|
+
}
|
|
1469
|
+
function normalizeOp(value) {
|
|
1470
|
+
if (typeof value !== "string")
|
|
1471
|
+
return null;
|
|
1472
|
+
const v = value.trim().toLowerCase();
|
|
1473
|
+
if (v === "call_tool" || v === "tool" || v === "call")
|
|
1474
|
+
return "call_tool";
|
|
1475
|
+
if (v === "read_resource" || v === "resource" || v === "read")
|
|
1476
|
+
return "read_resource";
|
|
1477
|
+
if (v === "search_actions" || v === "search" || v === "discover")
|
|
1478
|
+
return "search_actions";
|
|
1479
|
+
if (v === "list_connections" || v === "list" || v === "connections")
|
|
1480
|
+
return "list_connections";
|
|
1481
|
+
return null;
|
|
1482
|
+
}
|
|
1483
|
+
function inferOpFromText(text) {
|
|
1484
|
+
if (/\b(read|get|fetch|access|open|list)\b.*\b(resource|resources|document|docs?|file)\b/i.test(text)) {
|
|
1485
|
+
return "read_resource";
|
|
1486
|
+
}
|
|
1487
|
+
if (/\b(call|use|run|execute|invoke|search|query)\b.*\b(tool|tools|mcp)\b/i.test(text)) {
|
|
1488
|
+
return "call_tool";
|
|
1489
|
+
}
|
|
1490
|
+
return null;
|
|
1491
|
+
}
|
|
1492
|
+
function getDirectResourceSelection(options) {
|
|
1493
|
+
const params = readOptions(options);
|
|
1494
|
+
const serverName = typeof params.serverName === "string" ? params.serverName.trim() : "";
|
|
1495
|
+
const uri = typeof params.uri === "string" ? params.uri.trim() : "";
|
|
1496
|
+
if (!serverName || !uri)
|
|
1497
|
+
return null;
|
|
1498
|
+
return {
|
|
1499
|
+
serverName,
|
|
1500
|
+
uri,
|
|
1501
|
+
reasoning: typeof params.reasoning === "string" && params.reasoning.trim() ? params.reasoning.trim() : "Selected from structured MCP read_resource parameters."
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
1504
|
function createResourceSelectionPrompt(composedState, userMessage) {
|
|
1505
1505
|
const mcpData = composedState.values.mcp ?? {};
|
|
1506
1506
|
const serverNames = Object.keys(mcpData);
|
|
@@ -1536,61 +1536,80 @@ function createResourceSelectionPrompt(composedState, userMessage) {
|
|
|
1536
1536
|
template: resourceSelectionTemplate
|
|
1537
1537
|
});
|
|
1538
1538
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
"
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
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;
|
|
1539
|
+
async function handleCallTool(runtime, message, callback) {
|
|
1540
|
+
const composedState = await runtime.composeState(message, ["RECENT_MESSAGES", "MCP"]);
|
|
1541
|
+
const mcpService = runtime.getService(MCP_SERVICE_NAME);
|
|
1542
|
+
if (!mcpService) {
|
|
1543
|
+
throw new Error("MCP service not available");
|
|
1544
|
+
}
|
|
1545
|
+
const mcpProvider = mcpService.getProviderData();
|
|
1546
|
+
try {
|
|
1547
|
+
const toolSelectionName = await createToolSelectionName({
|
|
1548
|
+
runtime,
|
|
1549
|
+
state: composedState,
|
|
1550
|
+
message,
|
|
1551
|
+
callback,
|
|
1552
|
+
mcpProvider
|
|
1553
|
+
});
|
|
1554
|
+
if (!toolSelectionName || toolSelectionName.noToolAvailable) {
|
|
1555
|
+
return await handleNoToolAvailable(callback, toolSelectionName);
|
|
1578
1556
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1557
|
+
const { serverName, toolName } = toolSelectionName;
|
|
1558
|
+
const toolSelectionArgument = await createToolSelectionArgument({
|
|
1559
|
+
runtime,
|
|
1560
|
+
state: composedState,
|
|
1561
|
+
message,
|
|
1562
|
+
callback,
|
|
1563
|
+
mcpProvider,
|
|
1564
|
+
toolSelectionName
|
|
1565
|
+
});
|
|
1566
|
+
if (!toolSelectionArgument) {
|
|
1567
|
+
return await handleNoToolAvailable(callback, toolSelectionName);
|
|
1585
1568
|
}
|
|
1586
|
-
const
|
|
1587
|
-
|
|
1588
|
-
|
|
1569
|
+
const result = await mcpService.callTool(serverName, toolName, toolSelectionArgument.toolArguments);
|
|
1570
|
+
const { toolOutput, hasAttachments, attachments } = processToolResult(result, serverName, toolName, runtime, message.entityId);
|
|
1571
|
+
const replyMemory = await handleToolResponse(runtime, message, serverName, toolName, toolSelectionArgument.toolArguments, toolOutput, hasAttachments, attachments, composedState, mcpProvider, callback);
|
|
1572
|
+
return {
|
|
1573
|
+
text: `Successfully called tool: ${serverName}/${toolName}. Reasoned response: ${replyMemory.content.text}`,
|
|
1574
|
+
values: {
|
|
1575
|
+
success: true,
|
|
1576
|
+
toolExecuted: true,
|
|
1577
|
+
serverName,
|
|
1578
|
+
toolName,
|
|
1579
|
+
hasAttachments,
|
|
1580
|
+
output: toolOutput
|
|
1581
|
+
},
|
|
1582
|
+
data: {
|
|
1583
|
+
actionName: "MCP",
|
|
1584
|
+
op: "call_tool",
|
|
1585
|
+
serverName,
|
|
1586
|
+
toolName,
|
|
1587
|
+
toolArgumentsJson: JSON.stringify(toolSelectionArgument.toolArguments),
|
|
1588
|
+
reasoning: toolSelectionName.reasoning,
|
|
1589
|
+
output: toolOutput,
|
|
1590
|
+
attachmentCount: attachments?.length ?? 0
|
|
1591
|
+
},
|
|
1592
|
+
success: true
|
|
1593
|
+
};
|
|
1594
|
+
} catch (error) {
|
|
1595
|
+
return await handleMcpError(composedState, mcpProvider, error, runtime, message, "tool", callback);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
async function handleReadResource(runtime, message, options, callback) {
|
|
1599
|
+
const composedState = await runtime.composeState(message, ["RECENT_MESSAGES", "MCP"]);
|
|
1600
|
+
const mcpService = runtime.getService(MCP_SERVICE_NAME);
|
|
1601
|
+
if (!mcpService) {
|
|
1602
|
+
throw new Error("MCP service not available");
|
|
1603
|
+
}
|
|
1604
|
+
const mcpProvider = mcpService.getProviderData();
|
|
1605
|
+
try {
|
|
1606
|
+
await sendInitialResponse(callback);
|
|
1607
|
+
const parsedSelection = getDirectResourceSelection(options) ?? await (async () => {
|
|
1589
1608
|
const resourceSelectionPrompt = createResourceSelectionPrompt(composedState, message.content.text ?? "");
|
|
1590
1609
|
const resourceSelection = await runtime.useModel(import_core5.ModelType.TEXT_SMALL, {
|
|
1591
1610
|
prompt: resourceSelectionPrompt
|
|
1592
1611
|
});
|
|
1593
|
-
|
|
1612
|
+
return withModelRetry({
|
|
1594
1613
|
runtime,
|
|
1595
1614
|
state: composedState,
|
|
1596
1615
|
message,
|
|
@@ -1601,75 +1620,210 @@ var readResourceAction = {
|
|
|
1601
1620
|
failureMsg: `I'm having trouble finding the resource you're looking for. Could you provide more details about what you need?`,
|
|
1602
1621
|
retryCount: 0
|
|
1603
1622
|
});
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
actions: ["REPLY"]
|
|
1610
|
-
});
|
|
1611
|
-
}
|
|
1612
|
-
return {
|
|
1623
|
+
})();
|
|
1624
|
+
if (!parsedSelection || parsedSelection.noResourceAvailable) {
|
|
1625
|
+
const responseText = "I don't have a specific resource that contains the information you're looking for. Let me try to assist you directly instead.";
|
|
1626
|
+
if (callback && parsedSelection?.noResourceAvailable) {
|
|
1627
|
+
await callback({
|
|
1613
1628
|
text: responseText,
|
|
1614
|
-
|
|
1615
|
-
|
|
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
|
-
};
|
|
1629
|
+
actions: ["REPLY"]
|
|
1630
|
+
});
|
|
1626
1631
|
}
|
|
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
1632
|
return {
|
|
1632
|
-
text:
|
|
1633
|
+
text: responseText,
|
|
1633
1634
|
values: {
|
|
1634
1635
|
success: true,
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
uri
|
|
1636
|
+
noResourceAvailable: true,
|
|
1637
|
+
fallbackToDirectAssistance: true
|
|
1638
1638
|
},
|
|
1639
1639
|
data: {
|
|
1640
|
-
actionName: "
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
resourceMeta,
|
|
1645
|
-
contentLength: resourceContent?.length ?? 0
|
|
1640
|
+
actionName: "MCP",
|
|
1641
|
+
op: "read_resource",
|
|
1642
|
+
noResourceAvailable: true,
|
|
1643
|
+
reason: parsedSelection?.reasoning ?? "No appropriate resource available"
|
|
1646
1644
|
},
|
|
1647
1645
|
success: true
|
|
1648
1646
|
};
|
|
1649
|
-
} catch (error) {
|
|
1650
|
-
return await handleMcpError(composedState, mcpProvider, error, runtime, message, "resource", callback);
|
|
1651
1647
|
}
|
|
1648
|
+
const { serverName, uri } = parsedSelection;
|
|
1649
|
+
const result = await mcpService.readResource(serverName, uri);
|
|
1650
|
+
const { resourceContent, resourceMeta } = processResourceResult(result, uri);
|
|
1651
|
+
await handleResourceAnalysis(runtime, message, uri, serverName, resourceContent, resourceMeta, callback);
|
|
1652
|
+
return {
|
|
1653
|
+
text: `Successfully read resource: ${uri}`,
|
|
1654
|
+
values: {
|
|
1655
|
+
success: true,
|
|
1656
|
+
resourceRead: true,
|
|
1657
|
+
serverName,
|
|
1658
|
+
uri
|
|
1659
|
+
},
|
|
1660
|
+
data: {
|
|
1661
|
+
actionName: "MCP",
|
|
1662
|
+
op: "read_resource",
|
|
1663
|
+
serverName,
|
|
1664
|
+
uri,
|
|
1665
|
+
reasoning: parsedSelection?.reasoning,
|
|
1666
|
+
resourceMeta,
|
|
1667
|
+
contentLength: resourceContent?.length ?? 0
|
|
1668
|
+
},
|
|
1669
|
+
success: true
|
|
1670
|
+
};
|
|
1671
|
+
} catch (error) {
|
|
1672
|
+
return await handleMcpError(composedState, mcpProvider, error, runtime, message, "resource", callback);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
function textOf(message) {
|
|
1676
|
+
return typeof message.content?.text === "string" ? message.content.text : "";
|
|
1677
|
+
}
|
|
1678
|
+
function hasConnectedCapability(runtime) {
|
|
1679
|
+
const mcpService = runtime.getService(MCP_SERVICE_NAME);
|
|
1680
|
+
if (!mcpService)
|
|
1681
|
+
return false;
|
|
1682
|
+
return mcpService.getServers().some((server) => {
|
|
1683
|
+
if (server.status !== "connected")
|
|
1684
|
+
return false;
|
|
1685
|
+
return (server.tools?.length ?? 0) > 0 || (server.resources?.length ?? 0) > 0;
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
var mcpAction = {
|
|
1689
|
+
name: "MCP",
|
|
1690
|
+
contexts: ["general", "automation", "knowledge", "connectors", MCP_ACTION_CONTEXT, "files"],
|
|
1691
|
+
contextGate: {
|
|
1692
|
+
anyOf: ["general", "automation", "knowledge", "connectors", MCP_ACTION_CONTEXT, "files"]
|
|
1693
|
+
},
|
|
1694
|
+
roleGate: { minRole: "USER" },
|
|
1695
|
+
similes: [
|
|
1696
|
+
"MCP_ACTION",
|
|
1697
|
+
"MCP_ROUTER",
|
|
1698
|
+
"USE_MCP",
|
|
1699
|
+
"CALL_MCP_TOOL",
|
|
1700
|
+
"CALL_TOOL",
|
|
1701
|
+
"USE_TOOL",
|
|
1702
|
+
"USE_MCP_TOOL",
|
|
1703
|
+
"EXECUTE_TOOL",
|
|
1704
|
+
"EXECUTE_MCP_TOOL",
|
|
1705
|
+
"RUN_TOOL",
|
|
1706
|
+
"RUN_MCP_TOOL",
|
|
1707
|
+
"INVOKE_TOOL",
|
|
1708
|
+
"INVOKE_MCP_TOOL",
|
|
1709
|
+
"READ_MCP_RESOURCE",
|
|
1710
|
+
"READ_RESOURCE",
|
|
1711
|
+
"GET_RESOURCE",
|
|
1712
|
+
"GET_MCP_RESOURCE",
|
|
1713
|
+
"FETCH_RESOURCE",
|
|
1714
|
+
"FETCH_MCP_RESOURCE",
|
|
1715
|
+
"ACCESS_RESOURCE",
|
|
1716
|
+
"ACCESS_MCP_RESOURCE"
|
|
1717
|
+
],
|
|
1718
|
+
description: "Single MCP entry point. Use action=call_tool to invoke an MCP tool, action=read_resource to read an MCP resource. Cloud runtimes also accept action=search_actions and action=list_connections.",
|
|
1719
|
+
descriptionCompressed: "MCP call_tool read_resource search_actions list_connections",
|
|
1720
|
+
parameters: [
|
|
1721
|
+
{
|
|
1722
|
+
name: "action",
|
|
1723
|
+
description: "MCP operation: call_tool | read_resource | search_actions | list_connections",
|
|
1724
|
+
required: false,
|
|
1725
|
+
schema: {
|
|
1726
|
+
type: "string",
|
|
1727
|
+
enum: ["call_tool", "read_resource", "search_actions", "list_connections"]
|
|
1728
|
+
}
|
|
1729
|
+
},
|
|
1730
|
+
{
|
|
1731
|
+
name: "serverName",
|
|
1732
|
+
description: "Optional MCP server name that owns the tool or resource.",
|
|
1733
|
+
required: false,
|
|
1734
|
+
schema: { type: "string" }
|
|
1735
|
+
},
|
|
1736
|
+
{
|
|
1737
|
+
name: "toolName",
|
|
1738
|
+
description: "For action=call_tool: optional exact MCP tool name to call.",
|
|
1739
|
+
required: false,
|
|
1740
|
+
schema: { type: "string" }
|
|
1741
|
+
},
|
|
1742
|
+
{
|
|
1743
|
+
name: "arguments",
|
|
1744
|
+
description: "For action=call_tool: optional JSON arguments to pass to the selected MCP tool.",
|
|
1745
|
+
required: false,
|
|
1746
|
+
schema: { type: "object" }
|
|
1747
|
+
},
|
|
1748
|
+
{
|
|
1749
|
+
name: "uri",
|
|
1750
|
+
description: "For action=read_resource: exact MCP resource URI to read.",
|
|
1751
|
+
required: false,
|
|
1752
|
+
schema: { type: "string" }
|
|
1753
|
+
},
|
|
1754
|
+
{
|
|
1755
|
+
name: "query",
|
|
1756
|
+
description: "Natural-language description of the tool call or resource to select; for action=search_actions, the keyword query.",
|
|
1757
|
+
required: false,
|
|
1758
|
+
schema: { type: "string" }
|
|
1759
|
+
},
|
|
1760
|
+
{
|
|
1761
|
+
name: "platform",
|
|
1762
|
+
description: "For action=search_actions: filter results to a single connected platform.",
|
|
1763
|
+
required: false,
|
|
1764
|
+
schema: { type: "string" }
|
|
1765
|
+
},
|
|
1766
|
+
{
|
|
1767
|
+
name: "limit",
|
|
1768
|
+
description: "For action=search_actions: maximum results to return.",
|
|
1769
|
+
required: false,
|
|
1770
|
+
schema: { type: "number" }
|
|
1771
|
+
},
|
|
1772
|
+
{
|
|
1773
|
+
name: "offset",
|
|
1774
|
+
description: "For action=search_actions: skip first N results for pagination.",
|
|
1775
|
+
required: false,
|
|
1776
|
+
schema: { type: "number" }
|
|
1777
|
+
}
|
|
1778
|
+
],
|
|
1779
|
+
validate: async (runtime) => {
|
|
1780
|
+
if (!hasConnectedCapability(runtime))
|
|
1781
|
+
return false;
|
|
1782
|
+
return true;
|
|
1783
|
+
},
|
|
1784
|
+
handler: async (runtime, message, _state, options, callback) => {
|
|
1785
|
+
const opts = readOptions(options);
|
|
1786
|
+
const requested = normalizeOp(opts.action ?? opts.subaction ?? opts.op ?? opts.operation);
|
|
1787
|
+
const op = requested ?? inferOpFromText(textOf(message)) ?? "call_tool";
|
|
1788
|
+
if (op === "read_resource") {
|
|
1789
|
+
return handleReadResource(runtime, message, options, callback);
|
|
1790
|
+
}
|
|
1791
|
+
if (op === "search_actions" || op === "list_connections") {
|
|
1792
|
+
const text = `MCP op=${op} is only available in the cloud runtime.`;
|
|
1793
|
+
await callback?.({ text, source: message.content?.source });
|
|
1794
|
+
return {
|
|
1795
|
+
success: false,
|
|
1796
|
+
text,
|
|
1797
|
+
values: { error: "OP_NOT_SUPPORTED" },
|
|
1798
|
+
data: { actionName: "MCP", op }
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
return handleCallTool(runtime, message, callback);
|
|
1652
1802
|
},
|
|
1653
1803
|
examples: [
|
|
1654
1804
|
[
|
|
1655
1805
|
{
|
|
1656
1806
|
name: "{{user}}",
|
|
1657
|
-
content: {
|
|
1658
|
-
text: "Can you get the documentation about installing elizaOS?"
|
|
1659
|
-
}
|
|
1807
|
+
content: { text: "Use the MCP GitHub tool to read the repository README" }
|
|
1660
1808
|
},
|
|
1661
1809
|
{
|
|
1662
1810
|
name: "{{assistant}}",
|
|
1663
1811
|
content: {
|
|
1664
|
-
text:
|
|
1665
|
-
actions: ["
|
|
1812
|
+
text: "I'll route that through MCP.",
|
|
1813
|
+
actions: ["MCP"]
|
|
1666
1814
|
}
|
|
1815
|
+
}
|
|
1816
|
+
],
|
|
1817
|
+
[
|
|
1818
|
+
{
|
|
1819
|
+
name: "{{user}}",
|
|
1820
|
+
content: { text: "Can you get the documentation about installing elizaOS?" }
|
|
1667
1821
|
},
|
|
1668
1822
|
{
|
|
1669
1823
|
name: "{{assistant}}",
|
|
1670
1824
|
content: {
|
|
1671
|
-
text:
|
|
1672
|
-
actions: ["
|
|
1825
|
+
text: "I'll read the MCP resource for that.",
|
|
1826
|
+
actions: ["MCP"]
|
|
1673
1827
|
}
|
|
1674
1828
|
}
|
|
1675
1829
|
]
|
|
@@ -1677,10 +1831,36 @@ var readResourceAction = {
|
|
|
1677
1831
|
};
|
|
1678
1832
|
|
|
1679
1833
|
// src/provider.ts
|
|
1834
|
+
var MAX_MCP_SERVERS_IN_STATE = 20;
|
|
1835
|
+
var MAX_MCP_TOOLS_PER_SERVER = 30;
|
|
1836
|
+
var MAX_MCP_RESOURCES_PER_SERVER = 30;
|
|
1837
|
+
function formatMcpServersForPrompt(mcp) {
|
|
1838
|
+
const entries = Object.entries(mcp).slice(0, MAX_MCP_SERVERS_IN_STATE);
|
|
1839
|
+
if (entries.length === 0)
|
|
1840
|
+
return "No MCP servers are available.";
|
|
1841
|
+
return [
|
|
1842
|
+
`mcpServers[${Object.keys(mcp).length}, showing ${entries.length}]:`,
|
|
1843
|
+
...entries.flatMap(([serverName, server]) => {
|
|
1844
|
+
const tools = Object.keys(server.tools ?? {}).slice(0, MAX_MCP_TOOLS_PER_SERVER);
|
|
1845
|
+
const resources = Object.keys(server.resources ?? {}).slice(0, MAX_MCP_RESOURCES_PER_SERVER);
|
|
1846
|
+
return [
|
|
1847
|
+
` - name: ${serverName}`,
|
|
1848
|
+
` status: ${server.status}`,
|
|
1849
|
+
` tools: ${tools.length > 0 ? tools.join(", ") : "none"}`,
|
|
1850
|
+
` resources: ${resources.length > 0 ? resources.join(", ") : "none"}`
|
|
1851
|
+
];
|
|
1852
|
+
})
|
|
1853
|
+
].join(`
|
|
1854
|
+
`);
|
|
1855
|
+
}
|
|
1680
1856
|
var provider = {
|
|
1681
1857
|
name: "MCP",
|
|
1682
1858
|
description: "Information about connected MCP servers, tools, and resources",
|
|
1683
1859
|
dynamic: true,
|
|
1860
|
+
contexts: ["connectors", "settings"],
|
|
1861
|
+
contextGate: { anyOf: ["connectors", "settings"] },
|
|
1862
|
+
cacheStable: false,
|
|
1863
|
+
cacheScope: "turn",
|
|
1684
1864
|
get: async (runtime, _message, _state) => {
|
|
1685
1865
|
const mcpService = runtime.getService(MCP_SERVICE_NAME);
|
|
1686
1866
|
if (!mcpService) {
|
|
@@ -1690,12 +1870,25 @@ var provider = {
|
|
|
1690
1870
|
text: "No MCP servers are available."
|
|
1691
1871
|
};
|
|
1692
1872
|
}
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1873
|
+
try {
|
|
1874
|
+
const providerData = mcpService.getProviderData();
|
|
1875
|
+
const mcp = providerData.values.mcp;
|
|
1876
|
+
const serverEntries = Object.entries(providerData.data.mcp).slice(0, MAX_MCP_SERVERS_IN_STATE);
|
|
1877
|
+
return {
|
|
1878
|
+
values: { mcpServers: formatMcpServersForPrompt(mcp) },
|
|
1879
|
+
data: {
|
|
1880
|
+
mcpServerCount: Object.keys(providerData.data.mcp).length,
|
|
1881
|
+
shownMcpServerCount: serverEntries.length
|
|
1882
|
+
},
|
|
1883
|
+
text: formatMcpServersForPrompt(mcp)
|
|
1884
|
+
};
|
|
1885
|
+
} catch (error) {
|
|
1886
|
+
return {
|
|
1887
|
+
values: {},
|
|
1888
|
+
data: { error: error instanceof Error ? error.message : String(error) },
|
|
1889
|
+
text: "No MCP servers are available."
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1699
1892
|
}
|
|
1700
1893
|
};
|
|
1701
1894
|
|
|
@@ -1742,7 +1935,9 @@ class McpService extends import_core6.Service {
|
|
|
1742
1935
|
initializationPromise = null;
|
|
1743
1936
|
constructor(runtime) {
|
|
1744
1937
|
super(runtime);
|
|
1745
|
-
|
|
1938
|
+
if (runtime) {
|
|
1939
|
+
this.initializationPromise = this.initializeMcpServers();
|
|
1940
|
+
}
|
|
1746
1941
|
}
|
|
1747
1942
|
static async start(runtime) {
|
|
1748
1943
|
const service = new McpService(runtime);
|
|
@@ -1771,7 +1966,7 @@ class McpService extends import_core6.Service {
|
|
|
1771
1966
|
}
|
|
1772
1967
|
async initializeMcpServers() {
|
|
1773
1968
|
const mcpSettings = this.getMcpSettings();
|
|
1774
|
-
if (!mcpSettings
|
|
1969
|
+
if (!mcpSettings?.servers || Object.keys(mcpSettings.servers).length === 0) {
|
|
1775
1970
|
this.mcpProvider = buildMcpProviderData([]);
|
|
1776
1971
|
return;
|
|
1777
1972
|
}
|
|
@@ -1782,17 +1977,14 @@ class McpService extends import_core6.Service {
|
|
|
1782
1977
|
getMcpSettings() {
|
|
1783
1978
|
const rawSettings = this.runtime.getSetting("mcp");
|
|
1784
1979
|
let settings = null;
|
|
1785
|
-
if (
|
|
1786
|
-
|
|
1787
|
-
if ("servers" in parsed && typeof parsed.servers === "object" && parsed.servers !== null) {
|
|
1788
|
-
settings = parsed;
|
|
1789
|
-
}
|
|
1980
|
+
if (isMcpSettings(rawSettings)) {
|
|
1981
|
+
settings = rawSettings;
|
|
1790
1982
|
}
|
|
1791
|
-
if (!settings
|
|
1983
|
+
if (!settings?.servers) {
|
|
1792
1984
|
const characterSettings = this.runtime.character.settings;
|
|
1793
1985
|
if (characterSettings && typeof characterSettings === "object" && "mcp" in characterSettings) {
|
|
1794
1986
|
const characterMcpSettings = characterSettings.mcp;
|
|
1795
|
-
if (characterMcpSettings
|
|
1987
|
+
if (isMcpSettings(characterMcpSettings)) {
|
|
1796
1988
|
settings = characterMcpSettings;
|
|
1797
1989
|
}
|
|
1798
1990
|
}
|
|
@@ -2097,17 +2289,289 @@ ${error}` : error;
|
|
|
2097
2289
|
}
|
|
2098
2290
|
}
|
|
2099
2291
|
|
|
2292
|
+
// src/routes-mcp.ts
|
|
2293
|
+
var import_core7 = require("@elizaos/core");
|
|
2294
|
+
|
|
2295
|
+
// src/mcp-marketplace.ts
|
|
2296
|
+
var MCP_REGISTRY_BASE_URL = "https://registry.modelcontextprotocol.io";
|
|
2297
|
+
async function searchMcpMarketplace(query, limit = 30) {
|
|
2298
|
+
const resp = await fetch(`${MCP_REGISTRY_BASE_URL}/v0/servers`, {
|
|
2299
|
+
headers: {
|
|
2300
|
+
Accept: "application/json"
|
|
2301
|
+
}
|
|
2302
|
+
});
|
|
2303
|
+
if (!resp.ok) {
|
|
2304
|
+
throw new Error(`Registry API error: ${resp.status} ${resp.statusText}`);
|
|
2305
|
+
}
|
|
2306
|
+
const data = await resp.json();
|
|
2307
|
+
const results = [];
|
|
2308
|
+
const seenNames = new Set;
|
|
2309
|
+
for (const entry of data.servers) {
|
|
2310
|
+
const server = entry.server;
|
|
2311
|
+
const meta = entry._meta?.["io.modelcontextprotocol.registry/official"];
|
|
2312
|
+
if (!meta?.isLatest)
|
|
2313
|
+
continue;
|
|
2314
|
+
if (seenNames.has(server.name))
|
|
2315
|
+
continue;
|
|
2316
|
+
seenNames.add(server.name);
|
|
2317
|
+
if (query) {
|
|
2318
|
+
const q = query.toLowerCase();
|
|
2319
|
+
const matchName = server.name.toLowerCase().includes(q);
|
|
2320
|
+
const matchTitle = server.title?.toLowerCase().includes(q);
|
|
2321
|
+
const matchDesc = server.description?.toLowerCase().includes(q);
|
|
2322
|
+
if (!matchName && !matchTitle && !matchDesc)
|
|
2323
|
+
continue;
|
|
2324
|
+
}
|
|
2325
|
+
let connectionType = "remote";
|
|
2326
|
+
let connectionUrl;
|
|
2327
|
+
let npmPackage;
|
|
2328
|
+
let dockerImage;
|
|
2329
|
+
if (server.remotes && server.remotes.length > 0) {
|
|
2330
|
+
connectionType = "remote";
|
|
2331
|
+
connectionUrl = server.remotes[0].url;
|
|
2332
|
+
} else if (server.packages && server.packages.length > 0) {
|
|
2333
|
+
const pkg = server.packages[0];
|
|
2334
|
+
connectionType = "stdio";
|
|
2335
|
+
if (pkg.registryType === "npm") {
|
|
2336
|
+
npmPackage = pkg.identifier;
|
|
2337
|
+
} else if (pkg.registryType === "oci") {
|
|
2338
|
+
dockerImage = pkg.identifier;
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
results.push({
|
|
2342
|
+
id: `${server.name}@${server.version}`,
|
|
2343
|
+
name: server.name,
|
|
2344
|
+
title: server.title || server.name.split("/").pop() || server.name,
|
|
2345
|
+
description: server.description || "No description",
|
|
2346
|
+
version: server.version,
|
|
2347
|
+
connectionType,
|
|
2348
|
+
connectionUrl,
|
|
2349
|
+
npmPackage,
|
|
2350
|
+
dockerImage,
|
|
2351
|
+
repositoryUrl: server.repository?.url,
|
|
2352
|
+
websiteUrl: server.websiteUrl,
|
|
2353
|
+
iconUrl: server.icons?.[0]?.src,
|
|
2354
|
+
publishedAt: meta?.publishedAt,
|
|
2355
|
+
isLatest: true
|
|
2356
|
+
});
|
|
2357
|
+
if (results.length >= limit)
|
|
2358
|
+
break;
|
|
2359
|
+
}
|
|
2360
|
+
return { results };
|
|
2361
|
+
}
|
|
2362
|
+
async function getMcpServerDetails(name) {
|
|
2363
|
+
const resp = await fetch(`${MCP_REGISTRY_BASE_URL}/v0/servers/${encodeURIComponent(name)}`, {
|
|
2364
|
+
headers: { Accept: "application/json" }
|
|
2365
|
+
});
|
|
2366
|
+
if (!resp.ok) {
|
|
2367
|
+
if (resp.status === 404) {
|
|
2368
|
+
return null;
|
|
2369
|
+
}
|
|
2370
|
+
throw new Error(`Registry API error: ${resp.status}`);
|
|
2371
|
+
}
|
|
2372
|
+
const data = await resp.json();
|
|
2373
|
+
return data.server;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
// src/routes-mcp.ts
|
|
2377
|
+
function parseClampedInteger(value, options = {}) {
|
|
2378
|
+
const raw = value == null ? "" : value.trim();
|
|
2379
|
+
if (!raw)
|
|
2380
|
+
return Number.isFinite(options.fallback) ? options.fallback : undefined;
|
|
2381
|
+
const parsed = Number.parseInt(raw, 10);
|
|
2382
|
+
if (!Number.isFinite(parsed)) {
|
|
2383
|
+
return Number.isFinite(options.fallback) ? options.fallback : undefined;
|
|
2384
|
+
}
|
|
2385
|
+
if (options.min !== undefined && parsed < options.min)
|
|
2386
|
+
return options.min;
|
|
2387
|
+
if (options.max !== undefined && parsed > options.max)
|
|
2388
|
+
return options.max;
|
|
2389
|
+
return parsed;
|
|
2390
|
+
}
|
|
2391
|
+
async function handleMcpRoutes(ctx) {
|
|
2392
|
+
const { req, res, method, pathname, url, state, json, error, readJsonBody } = ctx;
|
|
2393
|
+
if (method === "GET" && pathname === "/api/mcp/marketplace/search") {
|
|
2394
|
+
const query = url.searchParams.get("q") ?? "";
|
|
2395
|
+
const limitStr = url.searchParams.get("limit");
|
|
2396
|
+
const limit = limitStr ? parseClampedInteger(limitStr, { min: 1, max: 50, fallback: 30 }) : 30;
|
|
2397
|
+
try {
|
|
2398
|
+
const result = await searchMcpMarketplace(query || undefined, limit);
|
|
2399
|
+
json(res, { ok: true, results: result.results });
|
|
2400
|
+
} catch (err) {
|
|
2401
|
+
error(res, `MCP marketplace search failed: ${err instanceof Error ? err.message : err}`, 502);
|
|
2402
|
+
}
|
|
2403
|
+
return true;
|
|
2404
|
+
}
|
|
2405
|
+
if (method === "GET" && pathname.startsWith("/api/mcp/marketplace/details/")) {
|
|
2406
|
+
const serverName = ctx.decodePathComponent(pathname.slice("/api/mcp/marketplace/details/".length), res, "server name");
|
|
2407
|
+
if (serverName === null)
|
|
2408
|
+
return true;
|
|
2409
|
+
if (!serverName.trim()) {
|
|
2410
|
+
error(res, "Server name is required", 400);
|
|
2411
|
+
return true;
|
|
2412
|
+
}
|
|
2413
|
+
try {
|
|
2414
|
+
const details = await getMcpServerDetails(serverName);
|
|
2415
|
+
if (!details) {
|
|
2416
|
+
error(res, `MCP server "${serverName}" not found`, 404);
|
|
2417
|
+
return true;
|
|
2418
|
+
}
|
|
2419
|
+
json(res, { ok: true, server: details });
|
|
2420
|
+
} catch (err) {
|
|
2421
|
+
error(res, `Failed to fetch server details: ${err instanceof Error ? err.message : err}`, 502);
|
|
2422
|
+
}
|
|
2423
|
+
return true;
|
|
2424
|
+
}
|
|
2425
|
+
if (method === "GET" && pathname === "/api/mcp/config") {
|
|
2426
|
+
const servers = state.config.mcp?.servers ?? {};
|
|
2427
|
+
json(res, { ok: true, servers: ctx.redactDeep(servers) });
|
|
2428
|
+
return true;
|
|
2429
|
+
}
|
|
2430
|
+
if (method === "POST" && pathname === "/api/mcp/config/server") {
|
|
2431
|
+
const body = await readJsonBody(req, res);
|
|
2432
|
+
if (!body)
|
|
2433
|
+
return true;
|
|
2434
|
+
const serverName = body.name?.trim();
|
|
2435
|
+
if (!serverName) {
|
|
2436
|
+
error(res, "Server name is required", 400);
|
|
2437
|
+
return true;
|
|
2438
|
+
}
|
|
2439
|
+
if (ctx.isBlockedObjectKey(serverName)) {
|
|
2440
|
+
error(res, 'Invalid server name: "__proto__", "constructor", and "prototype" are reserved', 400);
|
|
2441
|
+
return true;
|
|
2442
|
+
}
|
|
2443
|
+
const config = body.config;
|
|
2444
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
2445
|
+
error(res, "Server config object is required", 400);
|
|
2446
|
+
return true;
|
|
2447
|
+
}
|
|
2448
|
+
const mcpRejection = await ctx.resolveMcpServersRejection({
|
|
2449
|
+
[serverName]: config
|
|
2450
|
+
});
|
|
2451
|
+
if (mcpRejection) {
|
|
2452
|
+
error(res, mcpRejection, 400);
|
|
2453
|
+
return true;
|
|
2454
|
+
}
|
|
2455
|
+
const mcpTerminalRejection = ctx.resolveMcpTerminalAuthorizationRejection(req, { [serverName]: config }, body);
|
|
2456
|
+
if (mcpTerminalRejection) {
|
|
2457
|
+
error(res, `Configuring stdio MCP servers requires terminal authorization. ${mcpTerminalRejection.reason}`, mcpTerminalRejection.status);
|
|
2458
|
+
return true;
|
|
2459
|
+
}
|
|
2460
|
+
if (!state.config.mcp)
|
|
2461
|
+
state.config.mcp = {};
|
|
2462
|
+
if (!state.config.mcp.servers)
|
|
2463
|
+
state.config.mcp.servers = {};
|
|
2464
|
+
const sanitized = ctx.cloneWithoutBlockedObjectKeys(config);
|
|
2465
|
+
state.config.mcp.servers[serverName] = sanitized;
|
|
2466
|
+
try {
|
|
2467
|
+
ctx.saveElizaConfig(state.config);
|
|
2468
|
+
} catch (err) {
|
|
2469
|
+
import_core7.logger.warn(`[api] Config save failed: ${err instanceof Error ? err.message : err}`);
|
|
2470
|
+
}
|
|
2471
|
+
json(res, { ok: true, name: serverName, requiresRestart: true });
|
|
2472
|
+
return true;
|
|
2473
|
+
}
|
|
2474
|
+
if (method === "DELETE" && pathname.startsWith("/api/mcp/config/server/")) {
|
|
2475
|
+
const serverName = ctx.decodePathComponent(pathname.slice("/api/mcp/config/server/".length), res, "server name");
|
|
2476
|
+
if (serverName === null)
|
|
2477
|
+
return true;
|
|
2478
|
+
if (ctx.isBlockedObjectKey(serverName)) {
|
|
2479
|
+
error(res, 'Invalid server name: "__proto__", "constructor", and "prototype" are reserved', 400);
|
|
2480
|
+
return true;
|
|
2481
|
+
}
|
|
2482
|
+
if (state.config.mcp?.servers?.[serverName]) {
|
|
2483
|
+
delete state.config.mcp.servers[serverName];
|
|
2484
|
+
try {
|
|
2485
|
+
ctx.saveElizaConfig(state.config);
|
|
2486
|
+
} catch (err) {
|
|
2487
|
+
import_core7.logger.warn(`[api] Config save failed: ${err instanceof Error ? err.message : err}`);
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
json(res, { ok: true, requiresRestart: true });
|
|
2491
|
+
return true;
|
|
2492
|
+
}
|
|
2493
|
+
if (method === "PUT" && pathname === "/api/mcp/config") {
|
|
2494
|
+
const body = await readJsonBody(req, res);
|
|
2495
|
+
if (!body)
|
|
2496
|
+
return true;
|
|
2497
|
+
if (!state.config.mcp)
|
|
2498
|
+
state.config.mcp = {};
|
|
2499
|
+
if (body.servers !== undefined) {
|
|
2500
|
+
if (!body.servers || typeof body.servers !== "object" || Array.isArray(body.servers)) {
|
|
2501
|
+
error(res, "servers must be a JSON object", 400);
|
|
2502
|
+
return true;
|
|
2503
|
+
}
|
|
2504
|
+
const mcpRejection = await ctx.resolveMcpServersRejection(body.servers);
|
|
2505
|
+
if (mcpRejection) {
|
|
2506
|
+
error(res, mcpRejection, 400);
|
|
2507
|
+
return true;
|
|
2508
|
+
}
|
|
2509
|
+
const mcpTerminalRejection = ctx.resolveMcpTerminalAuthorizationRejection(req, body.servers, body);
|
|
2510
|
+
if (mcpTerminalRejection) {
|
|
2511
|
+
error(res, `Configuring stdio MCP servers requires terminal authorization. ${mcpTerminalRejection.reason}`, mcpTerminalRejection.status);
|
|
2512
|
+
return true;
|
|
2513
|
+
}
|
|
2514
|
+
const sanitized = ctx.cloneWithoutBlockedObjectKeys(body.servers);
|
|
2515
|
+
state.config.mcp.servers = sanitized;
|
|
2516
|
+
}
|
|
2517
|
+
try {
|
|
2518
|
+
ctx.saveElizaConfig(state.config);
|
|
2519
|
+
} catch (err) {
|
|
2520
|
+
import_core7.logger.warn(`[api] Config save failed: ${err instanceof Error ? err.message : err}`);
|
|
2521
|
+
}
|
|
2522
|
+
json(res, { ok: true });
|
|
2523
|
+
return true;
|
|
2524
|
+
}
|
|
2525
|
+
if (method === "GET" && pathname === "/api/mcp/status") {
|
|
2526
|
+
const servers = [];
|
|
2527
|
+
if (state.runtime) {
|
|
2528
|
+
try {
|
|
2529
|
+
const mcpService = state.runtime.getService("MCP");
|
|
2530
|
+
if (mcpService && typeof mcpService.getServers === "function") {
|
|
2531
|
+
for (const s of mcpService.getServers()) {
|
|
2532
|
+
servers.push({
|
|
2533
|
+
name: s.name,
|
|
2534
|
+
status: s.status,
|
|
2535
|
+
toolCount: Array.isArray(s.tools) ? s.tools.length : 0,
|
|
2536
|
+
resourceCount: Array.isArray(s.resources) ? s.resources.length : 0
|
|
2537
|
+
});
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
} catch (err) {
|
|
2541
|
+
import_core7.logger.debug(`[api] Service not available: ${err instanceof Error ? err.message : err}`);
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
json(res, { ok: true, servers });
|
|
2545
|
+
return true;
|
|
2546
|
+
}
|
|
2547
|
+
return false;
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2100
2550
|
// src/index.ts
|
|
2551
|
+
function withMcpContext(action) {
|
|
2552
|
+
return {
|
|
2553
|
+
...action,
|
|
2554
|
+
contexts: [
|
|
2555
|
+
...new Set([
|
|
2556
|
+
...action.contexts ?? [],
|
|
2557
|
+
"general",
|
|
2558
|
+
"automation",
|
|
2559
|
+
"knowledge",
|
|
2560
|
+
MCP_ACTION_CONTEXT
|
|
2561
|
+
])
|
|
2562
|
+
]
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2101
2565
|
var mcpPlugin = {
|
|
2102
2566
|
name: "mcp",
|
|
2103
2567
|
description: "Plugin for connecting to MCP (Model Context Protocol) servers",
|
|
2104
2568
|
init: async (_config, _runtime) => {
|
|
2105
|
-
|
|
2569
|
+
import_core8.logger.info("Initializing MCP plugin...");
|
|
2106
2570
|
},
|
|
2107
2571
|
services: [McpService],
|
|
2108
|
-
actions: [
|
|
2572
|
+
actions: [...import_core8.promoteSubactionsToActions(withMcpContext(mcpAction))],
|
|
2109
2573
|
providers: [provider]
|
|
2110
2574
|
};
|
|
2111
2575
|
var src_default = mcpPlugin;
|
|
2112
2576
|
|
|
2113
|
-
//# debugId=
|
|
2577
|
+
//# debugId=2D70561DBB87DFA464756E2164756E21
|