@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.
- package/README.md +269 -0
- package/dist/cjs/index.cjs +824 -317
- 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 +820 -321
- 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/node/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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: (
|
|
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(", ") :
|
|
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 {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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/
|
|
506
|
+
// src/actions/mcp.ts
|
|
530
507
|
import {
|
|
531
|
-
composePromptFromState,
|
|
532
|
-
|
|
533
|
-
ModelType
|
|
508
|
+
composePromptFromState as composePromptFromState4,
|
|
509
|
+
ModelType as ModelType5
|
|
534
510
|
} from "@elizaos/core";
|
|
535
511
|
|
|
536
|
-
// src/
|
|
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:
|
|
592
|
-
WRONG:
|
|
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:
|
|
595
|
-
WRONG:
|
|
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,
|
|
574
|
+
6. If no resource seems appropriate, set noResourceAvailable: true
|
|
599
575
|
|
|
600
|
-
|
|
576
|
+
Respond with compact JSON only.
|
|
601
577
|
|
|
602
578
|
STRICT FORMAT REQUIREMENTS:
|
|
603
|
-
-
|
|
604
|
-
- NO
|
|
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": "
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
637
|
+
Respond with compact JSON only.
|
|
657
638
|
|
|
658
639
|
## STRICT FORMAT REQUIREMENTS
|
|
659
|
-
- The response MUST be
|
|
660
|
-
- DO NOT wrap
|
|
661
|
-
- DO NOT include comments
|
|
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
|
-
##
|
|
651
|
+
## RESPONSE STRUCTURE
|
|
671
652
|
Your response MUST contain ONLY these two top-level keys:
|
|
672
|
-
1.
|
|
673
|
-
2.
|
|
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
|
-
|
|
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.
|
|
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
|
|
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):
|
|
699
|
-
- Example (incorrect):
|
|
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
|
|
703
|
-
5. If no tool is appropriate,
|
|
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
|
|
714
|
-
- DO NOT wrap
|
|
715
|
-
- DO NOT include comments
|
|
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
|
-
##
|
|
701
|
+
## RESPONSE STRUCTURE
|
|
725
702
|
Your response MUST contain ONLY these top-level keys:
|
|
726
|
-
1.
|
|
727
|
-
2.
|
|
728
|
-
3.
|
|
729
|
-
4.
|
|
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
|
-
|
|
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:
|
|
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: "
|
|
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
|
|
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
|
|
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 `
|
|
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
|
-
|
|
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
|
-
|
|
1254
|
-
|
|
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.
|
|
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 === "
|
|
1287
|
-
|
|
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/
|
|
1369
|
-
var
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
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
|
-
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
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
"
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
if (!
|
|
1533
|
-
return
|
|
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
|
|
1544
|
-
|
|
1545
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
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
|
-
|
|
1572
|
-
|
|
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:
|
|
1623
|
+
text: responseText,
|
|
1590
1624
|
values: {
|
|
1591
1625
|
success: true,
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
uri
|
|
1626
|
+
noResourceAvailable: true,
|
|
1627
|
+
fallbackToDirectAssistance: true
|
|
1595
1628
|
},
|
|
1596
1629
|
data: {
|
|
1597
|
-
actionName: "
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
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:
|
|
1622
|
-
actions: ["
|
|
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:
|
|
1629
|
-
actions: ["
|
|
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
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
1742
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
2559
|
+
logger4.info("Initializing MCP plugin...");
|
|
2062
2560
|
},
|
|
2063
2561
|
services: [McpService],
|
|
2064
|
-
actions: [
|
|
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=
|
|
2571
|
+
//# debugId=BAE4A5B3EC26EDA264756E2164756E21
|