@elizaos/plugin-mcp 1.3.5 → 1.7.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/dist/actions/callToolAction.d.ts +1 -1
- package/dist/actions/callToolAction.d.ts.map +1 -1
- package/dist/cjs/index.cjs +200 -308
- package/dist/cjs/index.js.map +12 -12
- package/dist/index.js +200 -308
- package/dist/index.js.map +12 -12
- package/dist/provider.d.ts.map +1 -1
- package/dist/service.d.ts +4 -8
- package/dist/service.d.ts.map +1 -1
- package/dist/types.d.ts +3 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/mcp.d.ts.map +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -667,6 +667,33 @@ async function handleMcpError(state, mcpProvider, error, runtime2, message, type
|
|
|
667
667
|
};
|
|
668
668
|
}
|
|
669
669
|
|
|
670
|
+
// src/utils/handler.ts
|
|
671
|
+
async function handleNoToolAvailable(callback, toolSelection) {
|
|
672
|
+
const responseText = "I don't have a specific tool that can help with that request. Let me try to assist you directly instead.";
|
|
673
|
+
const thoughtText = "No appropriate MCP tool available for this request. Falling back to direct assistance.";
|
|
674
|
+
if (callback && toolSelection?.noToolAvailable) {
|
|
675
|
+
await callback({
|
|
676
|
+
text: responseText,
|
|
677
|
+
thought: thoughtText,
|
|
678
|
+
actions: ["REPLY"]
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
return {
|
|
682
|
+
text: responseText,
|
|
683
|
+
values: {
|
|
684
|
+
success: true,
|
|
685
|
+
noToolAvailable: true,
|
|
686
|
+
fallbackToDirectAssistance: true
|
|
687
|
+
},
|
|
688
|
+
data: {
|
|
689
|
+
actionName: "CALL_MCP_TOOL",
|
|
690
|
+
noToolAvailable: true,
|
|
691
|
+
reason: toolSelection?.reasoning || "No appropriate tool available"
|
|
692
|
+
},
|
|
693
|
+
success: true
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
|
|
670
697
|
// src/utils/processing.ts
|
|
671
698
|
import {
|
|
672
699
|
ContentType,
|
|
@@ -736,82 +763,54 @@ Your response (written as if directly to the user):
|
|
|
736
763
|
`;
|
|
737
764
|
|
|
738
765
|
// src/utils/mcp.ts
|
|
766
|
+
var NO_DESC = "No description";
|
|
739
767
|
async function createMcpMemory(runtime2, message, type, serverName, content, metadata) {
|
|
740
768
|
const memory = await runtime2.addEmbeddingToMemory({
|
|
741
769
|
entityId: message.entityId,
|
|
742
770
|
agentId: runtime2.agentId,
|
|
743
771
|
roomId: message.roomId,
|
|
744
772
|
content: {
|
|
745
|
-
text: `Used
|
|
746
|
-
|
|
747
|
-
metadata: {
|
|
748
|
-
...metadata,
|
|
749
|
-
serverName
|
|
750
|
-
}
|
|
773
|
+
text: `Used "${type}" from "${serverName}". Content: ${content}`,
|
|
774
|
+
metadata: { ...metadata, serverName }
|
|
751
775
|
}
|
|
752
776
|
});
|
|
753
777
|
await runtime2.createMemory(memory, type === "resource" ? "resources" : "tools", true);
|
|
754
778
|
}
|
|
755
779
|
function buildMcpProviderData(servers) {
|
|
756
|
-
const mcpData = {};
|
|
757
|
-
let textContent = "";
|
|
758
780
|
if (servers.length === 0) {
|
|
759
|
-
return {
|
|
760
|
-
values: { mcp: {} },
|
|
761
|
-
data: { mcp: {} },
|
|
762
|
-
text: "No MCP servers are currently connected."
|
|
763
|
-
};
|
|
781
|
+
return { values: { mcp: {} }, data: { mcp: {} }, text: "No MCP servers connected." };
|
|
764
782
|
}
|
|
783
|
+
const mcpData = {};
|
|
784
|
+
const lines = [`# MCP Configuration
|
|
785
|
+
`];
|
|
765
786
|
for (const server of servers) {
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
for (const resource of server.resources) {
|
|
794
|
-
mcpData[server.name].resources[resource.uri] = {
|
|
795
|
-
name: resource.name,
|
|
796
|
-
description: resource.description || "No description available",
|
|
797
|
-
mimeType: resource.mimeType
|
|
798
|
-
};
|
|
799
|
-
textContent += `- **${resource.name}** (${resource.uri}): ${resource.description || "No description available"}
|
|
800
|
-
`;
|
|
801
|
-
}
|
|
802
|
-
textContent += `
|
|
803
|
-
`;
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
return {
|
|
807
|
-
values: { mcp: mcpData, mcpText: `# MCP Configuration
|
|
808
|
-
|
|
809
|
-
${textContent}` },
|
|
810
|
-
data: { mcp: mcpData },
|
|
811
|
-
text: `# MCP Configuration
|
|
812
|
-
|
|
813
|
-
${textContent}`
|
|
814
|
-
};
|
|
787
|
+
const tools = {};
|
|
788
|
+
const resources = {};
|
|
789
|
+
lines.push(`## ${server.name} (${server.status})
|
|
790
|
+
`);
|
|
791
|
+
if (server.tools?.length) {
|
|
792
|
+
lines.push(`### Tools
|
|
793
|
+
`);
|
|
794
|
+
for (const t of server.tools) {
|
|
795
|
+
tools[t.name] = { description: t.description || NO_DESC, inputSchema: t.inputSchema || {} };
|
|
796
|
+
lines.push(`- **${t.name}**: ${t.description || NO_DESC}`);
|
|
797
|
+
}
|
|
798
|
+
lines.push("");
|
|
799
|
+
}
|
|
800
|
+
if (server.resources?.length) {
|
|
801
|
+
lines.push(`### Resources
|
|
802
|
+
`);
|
|
803
|
+
for (const r of server.resources) {
|
|
804
|
+
resources[r.uri] = { name: r.name, description: r.description || NO_DESC, mimeType: r.mimeType };
|
|
805
|
+
lines.push(`- **${r.name}** (${r.uri}): ${r.description || NO_DESC}`);
|
|
806
|
+
}
|
|
807
|
+
lines.push("");
|
|
808
|
+
}
|
|
809
|
+
mcpData[server.name] = { status: server.status, tools, resources };
|
|
810
|
+
}
|
|
811
|
+
const text = lines.join(`
|
|
812
|
+
`);
|
|
813
|
+
return { values: { mcp: mcpData, mcpText: text }, data: { mcp: mcpData }, text };
|
|
815
814
|
}
|
|
816
815
|
|
|
817
816
|
// src/utils/processing.ts
|
|
@@ -1080,9 +1079,10 @@ ${JSON.stringify(parsedJson, null, 2)}`);
|
|
|
1080
1079
|
}
|
|
1081
1080
|
function getMaxRetries(runtime2) {
|
|
1082
1081
|
try {
|
|
1083
|
-
const
|
|
1084
|
-
|
|
1085
|
-
|
|
1082
|
+
const rawSettings = runtime2.getSetting("mcp");
|
|
1083
|
+
const settings = rawSettings;
|
|
1084
|
+
if (settings && typeof settings.maxRetries === "number") {
|
|
1085
|
+
const configValue = settings.maxRetries;
|
|
1086
1086
|
if (!Number.isNaN(configValue) && configValue >= 0) {
|
|
1087
1087
|
logger3.debug(`[WITH-MODEL-RETRY] Using configured selection retries: ${configValue}`);
|
|
1088
1088
|
return configValue;
|
|
@@ -1412,33 +1412,6 @@ function createFeedbackPrompt2(originalResponse, errorMessage, itemType, itemsDe
|
|
|
1412
1412
|
User request: ${userMessage}`;
|
|
1413
1413
|
}
|
|
1414
1414
|
|
|
1415
|
-
// src/utils/handler.ts
|
|
1416
|
-
async function handleNoToolAvailable(callback, toolSelection) {
|
|
1417
|
-
const responseText = "I don't have a specific tool that can help with that request. Let me try to assist you directly instead.";
|
|
1418
|
-
const thoughtText = "No appropriate MCP tool available for this request. Falling back to direct assistance.";
|
|
1419
|
-
if (callback && toolSelection?.noToolAvailable) {
|
|
1420
|
-
await callback({
|
|
1421
|
-
text: responseText,
|
|
1422
|
-
thought: thoughtText,
|
|
1423
|
-
actions: ["REPLY"]
|
|
1424
|
-
});
|
|
1425
|
-
}
|
|
1426
|
-
return {
|
|
1427
|
-
text: responseText,
|
|
1428
|
-
values: {
|
|
1429
|
-
success: true,
|
|
1430
|
-
noToolAvailable: true,
|
|
1431
|
-
fallbackToDirectAssistance: true
|
|
1432
|
-
},
|
|
1433
|
-
data: {
|
|
1434
|
-
actionName: "CALL_MCP_TOOL",
|
|
1435
|
-
noToolAvailable: true,
|
|
1436
|
-
reason: toolSelection?.reasoning || "No appropriate tool available"
|
|
1437
|
-
},
|
|
1438
|
-
success: true
|
|
1439
|
-
};
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
1415
|
// src/actions/callToolAction.ts
|
|
1443
1416
|
var callToolAction = {
|
|
1444
1417
|
name: "CALL_MCP_TOOL",
|
|
@@ -1500,7 +1473,7 @@ ${JSON.stringify(toolSelectionArgument, null, 2)}`);
|
|
|
1500
1473
|
const result = await mcpService.callTool(serverName, toolName, toolSelectionArgument.toolArguments);
|
|
1501
1474
|
const { toolOutput, hasAttachments, attachments } = processToolResult(result, serverName, toolName, runtime2, message.entityId);
|
|
1502
1475
|
const replyMemory = await handleToolResponse(runtime2, message, serverName, toolName, toolSelectionArgument.toolArguments, toolOutput, hasAttachments, attachments, composedState, mcpProvider, callback);
|
|
1503
|
-
|
|
1476
|
+
const actionResult = {
|
|
1504
1477
|
text: `Successfully called tool: ${serverName}/${toolName}. Reasoned response: ${replyMemory.content.text}`,
|
|
1505
1478
|
values: {
|
|
1506
1479
|
success: true,
|
|
@@ -1521,6 +1494,15 @@ ${JSON.stringify(toolSelectionArgument, null, 2)}`);
|
|
|
1521
1494
|
},
|
|
1522
1495
|
success: true
|
|
1523
1496
|
};
|
|
1497
|
+
logger5.info({
|
|
1498
|
+
serverName,
|
|
1499
|
+
toolName,
|
|
1500
|
+
hasOutput: !!toolOutput,
|
|
1501
|
+
outputLength: toolOutput?.length || 0,
|
|
1502
|
+
hasAttachments,
|
|
1503
|
+
reasoning: toolSelectionName.reasoning
|
|
1504
|
+
}, `[CALL_MCP_TOOL] Action result`);
|
|
1505
|
+
return actionResult;
|
|
1524
1506
|
} catch (error) {
|
|
1525
1507
|
return await handleMcpError(composedState, mcpProvider, error, runtime2, message, "tool", callback);
|
|
1526
1508
|
}
|
|
@@ -1762,19 +1744,20 @@ var readResourceAction = {
|
|
|
1762
1744
|
};
|
|
1763
1745
|
|
|
1764
1746
|
// src/provider.ts
|
|
1747
|
+
var createEmptyProvider = () => ({
|
|
1748
|
+
values: { mcp: {} },
|
|
1749
|
+
data: { mcp: {} },
|
|
1750
|
+
text: "No MCP servers available."
|
|
1751
|
+
});
|
|
1765
1752
|
var provider = {
|
|
1766
1753
|
name: "MCP",
|
|
1767
|
-
description: "
|
|
1754
|
+
description: "Connected MCP servers, tools, and resources",
|
|
1768
1755
|
get: async (runtime2, _message, _state) => {
|
|
1769
|
-
const
|
|
1770
|
-
if (!
|
|
1771
|
-
return
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
text: "No MCP servers are available."
|
|
1775
|
-
};
|
|
1776
|
-
}
|
|
1777
|
-
return mcpService.getProviderData();
|
|
1756
|
+
const svc = runtime2.getService(MCP_SERVICE_NAME);
|
|
1757
|
+
if (!svc)
|
|
1758
|
+
return createEmptyProvider();
|
|
1759
|
+
await svc.waitForInitialization();
|
|
1760
|
+
return svc.getProviderData();
|
|
1778
1761
|
}
|
|
1779
1762
|
};
|
|
1780
1763
|
|
|
@@ -1783,6 +1766,9 @@ import { Service, logger as logger7 } from "@elizaos/core";
|
|
|
1783
1766
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
1784
1767
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
1785
1768
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
1769
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
1770
|
+
var errMsg = (e) => e instanceof Error ? e.message : String(e);
|
|
1771
|
+
|
|
1786
1772
|
class McpService extends Service {
|
|
1787
1773
|
static serviceType = MCP_SERVICE_NAME;
|
|
1788
1774
|
capabilityDescription = "Enables the agent to interact with MCP (Model Context Protocol) servers";
|
|
@@ -1799,7 +1785,6 @@ class McpService extends Service {
|
|
|
1799
1785
|
initializationPromise = null;
|
|
1800
1786
|
constructor(runtime2) {
|
|
1801
1787
|
super(runtime2);
|
|
1802
|
-
logger7.info("[McpService] Constructor called, starting initialization...");
|
|
1803
1788
|
this.initializationPromise = this.initializeMcpServers();
|
|
1804
1789
|
}
|
|
1805
1790
|
static async start(runtime2) {
|
|
@@ -1815,115 +1800,70 @@ class McpService extends Service {
|
|
|
1815
1800
|
}
|
|
1816
1801
|
}
|
|
1817
1802
|
async stop() {
|
|
1818
|
-
for (const
|
|
1803
|
+
for (const name of this.connections.keys()) {
|
|
1819
1804
|
await this.deleteConnection(name);
|
|
1820
1805
|
}
|
|
1821
|
-
this.
|
|
1822
|
-
for (const state of this.connectionStates.values()) {
|
|
1806
|
+
for (const [name, state] of this.connectionStates.entries()) {
|
|
1823
1807
|
if (state.pingInterval)
|
|
1824
1808
|
clearInterval(state.pingInterval);
|
|
1825
1809
|
if (state.reconnectTimeout)
|
|
1826
1810
|
clearTimeout(state.reconnectTimeout);
|
|
1811
|
+
this.connectionStates.delete(name);
|
|
1827
1812
|
}
|
|
1828
|
-
this.connectionStates.clear();
|
|
1829
1813
|
}
|
|
1830
1814
|
async initializeMcpServers() {
|
|
1831
|
-
logger7.info("[McpService] Starting MCP server initialization...");
|
|
1832
1815
|
try {
|
|
1833
1816
|
const mcpSettings = this.getMcpSettings();
|
|
1834
|
-
|
|
1835
|
-
const serverNames = mcpSettings?.servers ? Object.keys(mcpSettings.servers) : [];
|
|
1836
|
-
logger7.info(`[McpService] Getting MCP settings... hasSettings=${!!mcpSettings} hasServers=${!!mcpSettings?.servers} serverCount=${serverCount} servers=${JSON.stringify(serverNames)}`);
|
|
1837
|
-
if (!mcpSettings || !mcpSettings.servers) {
|
|
1838
|
-
logger7.info("[McpService] No MCP servers configured.");
|
|
1839
|
-
this.mcpProvider = buildMcpProviderData([]);
|
|
1840
|
-
return;
|
|
1841
|
-
}
|
|
1842
|
-
if (Object.keys(mcpSettings.servers).length === 0) {
|
|
1843
|
-
logger7.info("[McpService] MCP settings exist but no servers configured.");
|
|
1817
|
+
if (!mcpSettings?.servers || Object.keys(mcpSettings.servers).length === 0) {
|
|
1844
1818
|
this.mcpProvider = buildMcpProviderData([]);
|
|
1845
1819
|
return;
|
|
1846
1820
|
}
|
|
1847
|
-
logger7.info(`[McpService] Connecting to ${Object.keys(mcpSettings.servers).length} MCP servers: ${JSON.stringify(Object.keys(mcpSettings.servers))}`);
|
|
1848
1821
|
const connectionStartTime = Date.now();
|
|
1849
1822
|
await this.updateServerConnections(mcpSettings.servers);
|
|
1850
1823
|
const connectionDuration = Date.now() - connectionStartTime;
|
|
1851
1824
|
const servers = this.getServers();
|
|
1852
|
-
const
|
|
1853
|
-
const
|
|
1854
|
-
if (
|
|
1855
|
-
|
|
1856
|
-
logger7.info(`[McpService] ✓ Successfully connected ${connectedServers.length}/${servers.length} servers in ${connectionDuration}ms: ${toolCounts}`);
|
|
1825
|
+
const connected = servers.filter((s) => s.status === "connected");
|
|
1826
|
+
const failed = servers.filter((s) => s.status !== "connected");
|
|
1827
|
+
if (connected.length > 0) {
|
|
1828
|
+
logger7.info(`[MCP] Connected ${connected.length}/${servers.length} in ${connectionDuration}ms (${connected.map((s) => `${s.name}:${s.tools?.length || 0}`).join(", ")})`);
|
|
1857
1829
|
}
|
|
1858
|
-
if (
|
|
1859
|
-
|
|
1860
|
-
logger7.warn(`[McpService] ⚠️ Failed to connect to ${failedServers.length}/${servers.length} servers: ${failedDetails}`);
|
|
1830
|
+
if (failed.length > 0) {
|
|
1831
|
+
logger7.warn(`[MCP] Failed: ${failed.map((s) => s.name).join(", ")}`);
|
|
1861
1832
|
}
|
|
1862
|
-
if (
|
|
1863
|
-
logger7.error(`[
|
|
1833
|
+
if (connected.length === 0 && servers.length > 0) {
|
|
1834
|
+
logger7.error(`[MCP] All servers failed`);
|
|
1864
1835
|
}
|
|
1865
1836
|
this.mcpProvider = buildMcpProviderData(servers);
|
|
1866
|
-
const mcpDataKeys = Object.keys(this.mcpProvider.data?.mcp || {});
|
|
1867
|
-
logger7.info(`[McpService] MCP provider data built: ${mcpDataKeys.length} server(s) available`);
|
|
1868
1837
|
} catch (error) {
|
|
1869
|
-
logger7.error({ error:
|
|
1838
|
+
logger7.error({ error: errMsg(error) }, "[MCP] Initialization failed");
|
|
1870
1839
|
this.mcpProvider = buildMcpProviderData([]);
|
|
1871
1840
|
}
|
|
1872
1841
|
}
|
|
1873
1842
|
getMcpSettings() {
|
|
1874
1843
|
let settings = this.runtime.getSetting("mcp");
|
|
1875
|
-
logger7.info(`[McpService] getSetting("mcp") result: type=${typeof settings} isNull=${settings === null} hasServers=${!!settings?.servers}`);
|
|
1876
1844
|
if (!settings || typeof settings === "object" && !settings.servers) {
|
|
1877
|
-
|
|
1878
|
-
if (characterSettings?.mcp) {
|
|
1879
|
-
logger7.info("[McpService] Found MCP settings in character.settings.mcp (fallback)");
|
|
1880
|
-
settings = characterSettings.mcp;
|
|
1881
|
-
}
|
|
1845
|
+
settings = this.runtime.character?.settings?.mcp;
|
|
1882
1846
|
}
|
|
1883
1847
|
if (!settings || typeof settings === "object" && !settings.servers) {
|
|
1884
|
-
|
|
1885
|
-
if (runtimeSettings?.mcp) {
|
|
1886
|
-
logger7.info("[McpService] Found MCP settings in runtime.settings.mcp (fallback)");
|
|
1887
|
-
settings = runtimeSettings.mcp;
|
|
1888
|
-
}
|
|
1848
|
+
settings = this.runtime.settings?.mcp;
|
|
1889
1849
|
}
|
|
1890
|
-
|
|
1891
|
-
logger7.info(`[McpService] MCP settings found with ${Object.keys(settings.servers).length} server(s)`);
|
|
1892
|
-
return settings;
|
|
1893
|
-
}
|
|
1894
|
-
logger7.info("[McpService] No valid MCP settings found");
|
|
1895
|
-
return;
|
|
1850
|
+
return settings && typeof settings === "object" && settings.servers ? settings : undefined;
|
|
1896
1851
|
}
|
|
1897
1852
|
async updateServerConnections(serverConfigs) {
|
|
1898
|
-
const currentNames = new Set(this.connections.keys());
|
|
1899
1853
|
const newNames = new Set(Object.keys(serverConfigs));
|
|
1900
|
-
for (const name of
|
|
1901
|
-
if (!newNames.has(name))
|
|
1854
|
+
for (const name of this.connections.keys()) {
|
|
1855
|
+
if (!newNames.has(name))
|
|
1902
1856
|
await this.deleteConnection(name);
|
|
1903
|
-
logger7.info(`Deleted MCP server: ${name}`);
|
|
1904
|
-
}
|
|
1905
1857
|
}
|
|
1906
|
-
|
|
1907
|
-
const
|
|
1908
|
-
if (!
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
logger7.error({ error: error instanceof Error ? error.message : String(error), serverName: name }, `✗ Failed to connect to new MCP server ${name}`);
|
|
1914
|
-
}
|
|
1915
|
-
} else if (JSON.stringify(config) !== currentConnection.server.config) {
|
|
1916
|
-
try {
|
|
1917
|
-
await this.deleteConnection(name);
|
|
1918
|
-
await this.initializeConnection(name, config);
|
|
1919
|
-
logger7.info(`✓ Reconnected MCP server with updated config: ${name}`);
|
|
1920
|
-
} catch (error) {
|
|
1921
|
-
logger7.error({ error: error instanceof Error ? error.message : String(error), serverName: name }, `✗ Failed to reconnect MCP server ${name}`);
|
|
1922
|
-
}
|
|
1858
|
+
await Promise.allSettled(Object.entries(serverConfigs).map(async ([name, config]) => {
|
|
1859
|
+
const current = this.connections.get(name);
|
|
1860
|
+
if (!current) {
|
|
1861
|
+
await this.initializeConnection(name, config).catch((e) => logger7.error({ error: errMsg(e), serverName: name }, `[MCP] Failed: ${name}`));
|
|
1862
|
+
} else if (JSON.stringify(config) !== current.server.config) {
|
|
1863
|
+
await this.deleteConnection(name);
|
|
1864
|
+
await this.initializeConnection(name, config).catch((e) => logger7.error({ error: errMsg(e), serverName: name }, `[MCP] Failed: ${name}`));
|
|
1923
1865
|
}
|
|
1924
|
-
});
|
|
1925
|
-
await Promise.allSettled(connectionPromises);
|
|
1926
|
-
logger7.info(`[McpService] All server connection attempts completed`);
|
|
1866
|
+
}));
|
|
1927
1867
|
}
|
|
1928
1868
|
async initializeConnection(name, config) {
|
|
1929
1869
|
await this.deleteConnection(name);
|
|
@@ -1937,19 +1877,19 @@ class McpService extends Service {
|
|
|
1937
1877
|
const client = new Client({ name: "ElizaOS", version: "1.0.0" }, { capabilities: {} });
|
|
1938
1878
|
const transport = config.type === "stdio" ? await this.buildStdioClientTransport(name, config) : await this.buildHttpClientTransport(name, config);
|
|
1939
1879
|
const connection = {
|
|
1940
|
-
server: {
|
|
1941
|
-
name,
|
|
1942
|
-
config: JSON.stringify(config),
|
|
1943
|
-
status: "connecting"
|
|
1944
|
-
},
|
|
1880
|
+
server: { name, config: JSON.stringify(config), status: "connecting" },
|
|
1945
1881
|
client,
|
|
1946
1882
|
transport
|
|
1947
1883
|
};
|
|
1948
1884
|
this.connections.set(name, connection);
|
|
1949
1885
|
this.setupTransportHandlers(name, connection, state);
|
|
1950
|
-
|
|
1886
|
+
const connectPromise = client.connect(transport);
|
|
1887
|
+
connectPromise.catch(() => {});
|
|
1888
|
+
await Promise.race([
|
|
1889
|
+
connectPromise,
|
|
1890
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`Timeout connecting to ${name}`)), 60000))
|
|
1891
|
+
]);
|
|
1951
1892
|
const capabilities = client.getServerCapabilities();
|
|
1952
|
-
logger7.debug(`[${name}] Server capabilities:`, JSON.stringify(capabilities || {}));
|
|
1953
1893
|
const tools = await this.fetchToolsList(name);
|
|
1954
1894
|
const resources = capabilities?.resources ? await this.fetchResourcesList(name) : [];
|
|
1955
1895
|
const resourceTemplates = capabilities?.resources ? await this.fetchResourceTemplatesList(name) : [];
|
|
@@ -1967,7 +1907,6 @@ class McpService extends Service {
|
|
|
1967
1907
|
state.reconnectAttempts = 0;
|
|
1968
1908
|
state.consecutivePingFailures = 0;
|
|
1969
1909
|
this.startPingMonitoring(name);
|
|
1970
|
-
logger7.info(`Successfully connected to MCP server: ${name}`);
|
|
1971
1910
|
} catch (error) {
|
|
1972
1911
|
state.status = "disconnected";
|
|
1973
1912
|
state.lastError = error instanceof Error ? error : new Error(String(error));
|
|
@@ -1977,26 +1916,21 @@ class McpService extends Service {
|
|
|
1977
1916
|
}
|
|
1978
1917
|
setupTransportHandlers(name, connection, state) {
|
|
1979
1918
|
const config = JSON.parse(connection.server.config);
|
|
1980
|
-
const
|
|
1919
|
+
const isHttp = config.type !== "stdio";
|
|
1981
1920
|
connection.transport.onerror = async (error) => {
|
|
1982
|
-
const
|
|
1983
|
-
const isExpectedTimeout =
|
|
1984
|
-
if (isExpectedTimeout) {
|
|
1985
|
-
logger7.
|
|
1986
|
-
} else {
|
|
1987
|
-
logger7.error({ error, serverName: name }, `Transport error for "${name}"`);
|
|
1921
|
+
const msg = error?.message || String(error);
|
|
1922
|
+
const isExpectedTimeout = isHttp && (!msg || msg === "undefined" || msg.includes("SSE") || msg.includes("timeout"));
|
|
1923
|
+
if (!isExpectedTimeout) {
|
|
1924
|
+
logger7.error({ error, serverName: name }, `Transport error: ${name}`);
|
|
1988
1925
|
connection.server.status = "disconnected";
|
|
1989
|
-
this.appendErrorMessage(connection,
|
|
1926
|
+
this.appendErrorMessage(connection, msg);
|
|
1990
1927
|
}
|
|
1991
|
-
if (!
|
|
1928
|
+
if (!isHttp)
|
|
1992
1929
|
this.handleDisconnection(name, error);
|
|
1993
|
-
}
|
|
1994
1930
|
};
|
|
1995
1931
|
connection.transport.onclose = async () => {
|
|
1996
|
-
if (
|
|
1997
|
-
logger7.
|
|
1998
|
-
} else {
|
|
1999
|
-
logger7.warn({ serverName: name }, `Transport closed for "${name}"`);
|
|
1932
|
+
if (!isHttp) {
|
|
1933
|
+
logger7.warn({ serverName: name }, `Transport closed: ${name}`);
|
|
2000
1934
|
connection.server.status = "disconnected";
|
|
2001
1935
|
this.handleDisconnection(name, new Error("Transport closed"));
|
|
2002
1936
|
}
|
|
@@ -2007,11 +1941,8 @@ class McpService extends Service {
|
|
|
2007
1941
|
if (!connection)
|
|
2008
1942
|
return;
|
|
2009
1943
|
const config = JSON.parse(connection.server.config);
|
|
2010
|
-
|
|
2011
|
-
if (isHttpTransport) {
|
|
2012
|
-
logger7.debug(`[McpService] Skipping ping monitoring for HTTP server: ${name}`);
|
|
1944
|
+
if (config.type !== "stdio")
|
|
2013
1945
|
return;
|
|
2014
|
-
}
|
|
2015
1946
|
const state = this.connectionStates.get(name);
|
|
2016
1947
|
if (!state || !this.pingConfig.enabled)
|
|
2017
1948
|
return;
|
|
@@ -2019,7 +1950,7 @@ class McpService extends Service {
|
|
|
2019
1950
|
clearInterval(state.pingInterval);
|
|
2020
1951
|
state.pingInterval = setInterval(() => {
|
|
2021
1952
|
this.sendPing(name).catch((err) => {
|
|
2022
|
-
logger7.warn({ error:
|
|
1953
|
+
logger7.warn({ error: errMsg(err), serverName: name }, `Ping failed: ${name}`);
|
|
2023
1954
|
this.handlePingFailure(name, err);
|
|
2024
1955
|
});
|
|
2025
1956
|
}, this.pingConfig.intervalMs);
|
|
@@ -2027,7 +1958,7 @@ class McpService extends Service {
|
|
|
2027
1958
|
async sendPing(name) {
|
|
2028
1959
|
const connection = this.connections.get(name);
|
|
2029
1960
|
if (!connection)
|
|
2030
|
-
throw new Error(`No connection
|
|
1961
|
+
throw new Error(`No connection: ${name}`);
|
|
2031
1962
|
await Promise.race([
|
|
2032
1963
|
connection.client.listTools(),
|
|
2033
1964
|
new Promise((_, reject) => setTimeout(() => reject(new Error("Ping timeout")), this.pingConfig.timeoutMs))
|
|
@@ -2069,7 +2000,7 @@ class McpService extends Service {
|
|
|
2069
2000
|
try {
|
|
2070
2001
|
await this.initializeConnection(name, JSON.parse(config));
|
|
2071
2002
|
} catch (err) {
|
|
2072
|
-
logger7.error({ error:
|
|
2003
|
+
logger7.error({ error: errMsg(err), serverName: name }, `Reconnect attempt failed for ${name}`);
|
|
2073
2004
|
this.handleDisconnection(name, err);
|
|
2074
2005
|
}
|
|
2075
2006
|
}
|
|
@@ -2082,7 +2013,7 @@ class McpService extends Service {
|
|
|
2082
2013
|
await connection.transport.close();
|
|
2083
2014
|
await connection.client.close();
|
|
2084
2015
|
} catch (error) {
|
|
2085
|
-
logger7.error({ error:
|
|
2016
|
+
logger7.error({ error: errMsg(error), serverName: name }, `Failed to close transport for ${name}`);
|
|
2086
2017
|
}
|
|
2087
2018
|
this.connections.delete(name);
|
|
2088
2019
|
}
|
|
@@ -2095,9 +2026,6 @@ class McpService extends Service {
|
|
|
2095
2026
|
this.connectionStates.delete(name);
|
|
2096
2027
|
}
|
|
2097
2028
|
}
|
|
2098
|
-
getServerConnection(serverName) {
|
|
2099
|
-
return this.connections.get(serverName);
|
|
2100
|
-
}
|
|
2101
2029
|
async buildStdioClientTransport(name, config) {
|
|
2102
2030
|
if (!config.command) {
|
|
2103
2031
|
throw new Error(`Missing command for stdio MCP server ${name}`);
|
|
@@ -2114,74 +2042,61 @@ class McpService extends Service {
|
|
|
2114
2042
|
});
|
|
2115
2043
|
}
|
|
2116
2044
|
async buildHttpClientTransport(name, config) {
|
|
2117
|
-
if (!config.url)
|
|
2118
|
-
throw new Error(`Missing URL for
|
|
2119
|
-
|
|
2045
|
+
if (!config.url)
|
|
2046
|
+
throw new Error(`Missing URL for MCP server ${name}`);
|
|
2047
|
+
const url = new URL(config.url);
|
|
2048
|
+
const opts = config.headers ? { requestInit: { headers: config.headers } } : undefined;
|
|
2120
2049
|
if (config.type === "sse") {
|
|
2121
|
-
logger7.warn(`
|
|
2050
|
+
logger7.warn(`[MCP] "${name}": SSE requires Redis. Use "streamable-http" instead.`);
|
|
2051
|
+
return new SSEClientTransport(url, opts);
|
|
2122
2052
|
}
|
|
2123
|
-
return new
|
|
2053
|
+
return new StreamableHTTPClientTransport(url, opts);
|
|
2124
2054
|
}
|
|
2125
2055
|
appendErrorMessage(connection, error) {
|
|
2126
|
-
|
|
2056
|
+
connection.server.error = connection.server.error ? `${connection.server.error}
|
|
2127
2057
|
${error}` : error;
|
|
2128
|
-
connection.server.error = newError;
|
|
2129
2058
|
}
|
|
2130
2059
|
async fetchToolsList(serverName) {
|
|
2060
|
+
const connection = this.connections.get(serverName);
|
|
2061
|
+
if (!connection)
|
|
2062
|
+
return [];
|
|
2131
2063
|
try {
|
|
2132
|
-
const connection = this.getServerConnection(serverName);
|
|
2133
|
-
if (!connection) {
|
|
2134
|
-
return [];
|
|
2135
|
-
}
|
|
2136
2064
|
const response = await connection.client.listTools();
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
} catch (error) {
|
|
2147
|
-
logger7.warn({ error, toolName: tool.name, serverName }, `Tool compatibility failed for ${tool.name} on ${serverName}`);
|
|
2148
|
-
}
|
|
2065
|
+
return (response?.tools || []).map((tool) => {
|
|
2066
|
+
if (!tool.inputSchema)
|
|
2067
|
+
return tool;
|
|
2068
|
+
if (!this.compatibilityInitialized)
|
|
2069
|
+
this.initializeToolCompatibility();
|
|
2070
|
+
try {
|
|
2071
|
+
return { ...tool, inputSchema: this.applyToolCompatibility(tool.inputSchema) };
|
|
2072
|
+
} catch {
|
|
2073
|
+
return tool;
|
|
2149
2074
|
}
|
|
2150
|
-
return processedTool;
|
|
2151
2075
|
});
|
|
2152
|
-
logger7.info(`Fetched ${tools.length} tools for ${serverName}`);
|
|
2153
|
-
for (const tool of tools) {
|
|
2154
|
-
logger7.info(`[${serverName}] ${tool.name}: ${tool.description}`);
|
|
2155
|
-
}
|
|
2156
|
-
return tools;
|
|
2157
2076
|
} catch (error) {
|
|
2158
|
-
logger7.error({ error:
|
|
2077
|
+
logger7.error({ error: errMsg(error), serverName }, `Failed to fetch tools: ${serverName}`);
|
|
2159
2078
|
return [];
|
|
2160
2079
|
}
|
|
2161
2080
|
}
|
|
2162
|
-
async fetchResourcesList(
|
|
2081
|
+
async fetchResourcesList(name) {
|
|
2082
|
+
const conn = this.connections.get(name);
|
|
2083
|
+
if (!conn)
|
|
2084
|
+
return [];
|
|
2163
2085
|
try {
|
|
2164
|
-
|
|
2165
|
-
if (!connection) {
|
|
2166
|
-
return [];
|
|
2167
|
-
}
|
|
2168
|
-
const response = await connection.client.listResources();
|
|
2169
|
-
return response?.resources || [];
|
|
2086
|
+
return (await conn.client.listResources())?.resources || [];
|
|
2170
2087
|
} catch (error) {
|
|
2171
|
-
logger7.
|
|
2088
|
+
logger7.debug({ error: errMsg(error), serverName: name }, `No resources for ${name}`);
|
|
2172
2089
|
return [];
|
|
2173
2090
|
}
|
|
2174
2091
|
}
|
|
2175
|
-
async fetchResourceTemplatesList(
|
|
2092
|
+
async fetchResourceTemplatesList(name) {
|
|
2093
|
+
const conn = this.connections.get(name);
|
|
2094
|
+
if (!conn)
|
|
2095
|
+
return [];
|
|
2176
2096
|
try {
|
|
2177
|
-
|
|
2178
|
-
if (!connection) {
|
|
2179
|
-
return [];
|
|
2180
|
-
}
|
|
2181
|
-
const response = await connection.client.listResourceTemplates();
|
|
2182
|
-
return response?.resourceTemplates || [];
|
|
2097
|
+
return (await conn.client.listResourceTemplates())?.resourceTemplates || [];
|
|
2183
2098
|
} catch (error) {
|
|
2184
|
-
logger7.
|
|
2099
|
+
logger7.debug({ error: errMsg(error), serverName: name }, `No resource templates for ${name}`);
|
|
2185
2100
|
return [];
|
|
2186
2101
|
}
|
|
2187
2102
|
}
|
|
@@ -2191,76 +2106,53 @@ ${error}` : error;
|
|
|
2191
2106
|
getProviderData() {
|
|
2192
2107
|
return this.mcpProvider;
|
|
2193
2108
|
}
|
|
2194
|
-
async callTool(serverName, toolName,
|
|
2195
|
-
const
|
|
2196
|
-
if (!
|
|
2197
|
-
throw new Error(`No connection
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
logger7.error({ error: error instanceof Error ? error.message : String(error), serverName }, `Failed to parse timeout configuration for server ${serverName}`);
|
|
2208
|
-
}
|
|
2209
|
-
const result = await connection.client.callTool({ name: toolName, arguments: toolArguments }, undefined, { timeout });
|
|
2210
|
-
if (!result.content) {
|
|
2211
|
-
throw new Error("Invalid tool result: missing content array");
|
|
2212
|
-
}
|
|
2109
|
+
async callTool(serverName, toolName, args) {
|
|
2110
|
+
const conn = this.connections.get(serverName);
|
|
2111
|
+
if (!conn)
|
|
2112
|
+
throw new Error(`No connection: ${serverName}`);
|
|
2113
|
+
if (conn.server.disabled)
|
|
2114
|
+
throw new Error(`Server disabled: ${serverName}`);
|
|
2115
|
+
const config = JSON.parse(conn.server.config);
|
|
2116
|
+
const timeout = (config.type === "stdio" ? config.timeoutInMillis : config.timeout) ?? DEFAULT_MCP_TIMEOUT_SECONDS;
|
|
2117
|
+
const result = await conn.client.callTool({ name: toolName, arguments: args }, undefined, {
|
|
2118
|
+
timeout
|
|
2119
|
+
});
|
|
2120
|
+
if (!result.content)
|
|
2121
|
+
throw new Error("Invalid tool result: missing content");
|
|
2213
2122
|
return result;
|
|
2214
2123
|
}
|
|
2215
2124
|
async readResource(serverName, uri) {
|
|
2216
|
-
const
|
|
2217
|
-
if (!
|
|
2218
|
-
throw new Error(`No connection
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
}
|
|
2223
|
-
return await connection.client.readResource({ uri });
|
|
2125
|
+
const conn = this.connections.get(serverName);
|
|
2126
|
+
if (!conn)
|
|
2127
|
+
throw new Error(`No connection: ${serverName}`);
|
|
2128
|
+
if (conn.server.disabled)
|
|
2129
|
+
throw new Error(`Server disabled: ${serverName}`);
|
|
2130
|
+
return conn.client.readResource({ uri });
|
|
2224
2131
|
}
|
|
2225
2132
|
async restartConnection(serverName) {
|
|
2226
|
-
const
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
await this.initializeConnection(serverName, JSON.parse(config));
|
|
2235
|
-
logger7.info(`${serverName} MCP server connected`);
|
|
2236
|
-
} catch (error) {
|
|
2237
|
-
logger7.error({ error: error instanceof Error ? error.message : String(error), serverName }, `Failed to restart connection for ${serverName}`);
|
|
2238
|
-
throw new Error(`Failed to connect to ${serverName} MCP server`);
|
|
2239
|
-
}
|
|
2240
|
-
}
|
|
2133
|
+
const conn = this.connections.get(serverName);
|
|
2134
|
+
if (!conn)
|
|
2135
|
+
throw new Error(`No connection: ${serverName}`);
|
|
2136
|
+
const config = conn.server.config;
|
|
2137
|
+
conn.server.status = "connecting";
|
|
2138
|
+
conn.server.error = "";
|
|
2139
|
+
await this.deleteConnection(serverName);
|
|
2140
|
+
await this.initializeConnection(serverName, JSON.parse(config));
|
|
2241
2141
|
}
|
|
2242
2142
|
initializeToolCompatibility() {
|
|
2243
2143
|
if (this.compatibilityInitialized)
|
|
2244
2144
|
return;
|
|
2245
2145
|
this.toolCompatibility = createMcpToolCompatibilitySync(this.runtime);
|
|
2246
2146
|
this.compatibilityInitialized = true;
|
|
2247
|
-
if (this.toolCompatibility) {
|
|
2248
|
-
logger7.info(`Tool compatibility enabled`);
|
|
2249
|
-
} else {
|
|
2250
|
-
logger7.info(`No tool compatibility needed`);
|
|
2251
|
-
}
|
|
2252
2147
|
}
|
|
2253
2148
|
applyToolCompatibility(toolSchema) {
|
|
2254
|
-
if (!this.compatibilityInitialized)
|
|
2149
|
+
if (!this.compatibilityInitialized)
|
|
2255
2150
|
this.initializeToolCompatibility();
|
|
2256
|
-
|
|
2257
|
-
if (!this.toolCompatibility || !toolSchema) {
|
|
2151
|
+
if (!this.toolCompatibility || !toolSchema)
|
|
2258
2152
|
return toolSchema;
|
|
2259
|
-
}
|
|
2260
2153
|
try {
|
|
2261
2154
|
return this.toolCompatibility.transformToolSchema(toolSchema);
|
|
2262
|
-
} catch
|
|
2263
|
-
logger7.warn({ error }, `Tool compatibility transformation failed`);
|
|
2155
|
+
} catch {
|
|
2264
2156
|
return toolSchema;
|
|
2265
2157
|
}
|
|
2266
2158
|
}
|
|
@@ -2297,4 +2189,4 @@ export {
|
|
|
2297
2189
|
BACKOFF_MULTIPLIER
|
|
2298
2190
|
};
|
|
2299
2191
|
|
|
2300
|
-
//# debugId=
|
|
2192
|
+
//# debugId=D6293B10B9AC06C464756E2164756E21
|