@elizaos/plugin-mcp 1.7.0 → 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 +199 -309
- package/dist/cjs/index.js.map +9 -9
- package/dist/index.js +199 -309
- package/dist/index.js.map +9 -9
- 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/package.json +1 -1
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
|
|
@@ -1413,33 +1412,6 @@ function createFeedbackPrompt2(originalResponse, errorMessage, itemType, itemsDe
|
|
|
1413
1412
|
User request: ${userMessage}`;
|
|
1414
1413
|
}
|
|
1415
1414
|
|
|
1416
|
-
// src/utils/handler.ts
|
|
1417
|
-
async function handleNoToolAvailable(callback, toolSelection) {
|
|
1418
|
-
const responseText = "I don't have a specific tool that can help with that request. Let me try to assist you directly instead.";
|
|
1419
|
-
const thoughtText = "No appropriate MCP tool available for this request. Falling back to direct assistance.";
|
|
1420
|
-
if (callback && toolSelection?.noToolAvailable) {
|
|
1421
|
-
await callback({
|
|
1422
|
-
text: responseText,
|
|
1423
|
-
thought: thoughtText,
|
|
1424
|
-
actions: ["REPLY"]
|
|
1425
|
-
});
|
|
1426
|
-
}
|
|
1427
|
-
return {
|
|
1428
|
-
text: responseText,
|
|
1429
|
-
values: {
|
|
1430
|
-
success: true,
|
|
1431
|
-
noToolAvailable: true,
|
|
1432
|
-
fallbackToDirectAssistance: true
|
|
1433
|
-
},
|
|
1434
|
-
data: {
|
|
1435
|
-
actionName: "CALL_MCP_TOOL",
|
|
1436
|
-
noToolAvailable: true,
|
|
1437
|
-
reason: toolSelection?.reasoning || "No appropriate tool available"
|
|
1438
|
-
},
|
|
1439
|
-
success: true
|
|
1440
|
-
};
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
1415
|
// src/actions/callToolAction.ts
|
|
1444
1416
|
var callToolAction = {
|
|
1445
1417
|
name: "CALL_MCP_TOOL",
|
|
@@ -1501,7 +1473,7 @@ ${JSON.stringify(toolSelectionArgument, null, 2)}`);
|
|
|
1501
1473
|
const result = await mcpService.callTool(serverName, toolName, toolSelectionArgument.toolArguments);
|
|
1502
1474
|
const { toolOutput, hasAttachments, attachments } = processToolResult(result, serverName, toolName, runtime2, message.entityId);
|
|
1503
1475
|
const replyMemory = await handleToolResponse(runtime2, message, serverName, toolName, toolSelectionArgument.toolArguments, toolOutput, hasAttachments, attachments, composedState, mcpProvider, callback);
|
|
1504
|
-
|
|
1476
|
+
const actionResult = {
|
|
1505
1477
|
text: `Successfully called tool: ${serverName}/${toolName}. Reasoned response: ${replyMemory.content.text}`,
|
|
1506
1478
|
values: {
|
|
1507
1479
|
success: true,
|
|
@@ -1522,6 +1494,15 @@ ${JSON.stringify(toolSelectionArgument, null, 2)}`);
|
|
|
1522
1494
|
},
|
|
1523
1495
|
success: true
|
|
1524
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;
|
|
1525
1506
|
} catch (error) {
|
|
1526
1507
|
return await handleMcpError(composedState, mcpProvider, error, runtime2, message, "tool", callback);
|
|
1527
1508
|
}
|
|
@@ -1763,19 +1744,20 @@ var readResourceAction = {
|
|
|
1763
1744
|
};
|
|
1764
1745
|
|
|
1765
1746
|
// src/provider.ts
|
|
1747
|
+
var createEmptyProvider = () => ({
|
|
1748
|
+
values: { mcp: {} },
|
|
1749
|
+
data: { mcp: {} },
|
|
1750
|
+
text: "No MCP servers available."
|
|
1751
|
+
});
|
|
1766
1752
|
var provider = {
|
|
1767
1753
|
name: "MCP",
|
|
1768
|
-
description: "
|
|
1754
|
+
description: "Connected MCP servers, tools, and resources",
|
|
1769
1755
|
get: async (runtime2, _message, _state) => {
|
|
1770
|
-
const
|
|
1771
|
-
if (!
|
|
1772
|
-
return
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
text: "No MCP servers are available."
|
|
1776
|
-
};
|
|
1777
|
-
}
|
|
1778
|
-
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();
|
|
1779
1761
|
}
|
|
1780
1762
|
};
|
|
1781
1763
|
|
|
@@ -1784,6 +1766,9 @@ import { Service, logger as logger7 } from "@elizaos/core";
|
|
|
1784
1766
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
1785
1767
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
1786
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
|
+
|
|
1787
1772
|
class McpService extends Service {
|
|
1788
1773
|
static serviceType = MCP_SERVICE_NAME;
|
|
1789
1774
|
capabilityDescription = "Enables the agent to interact with MCP (Model Context Protocol) servers";
|
|
@@ -1800,7 +1785,6 @@ class McpService extends Service {
|
|
|
1800
1785
|
initializationPromise = null;
|
|
1801
1786
|
constructor(runtime2) {
|
|
1802
1787
|
super(runtime2);
|
|
1803
|
-
logger7.info("[McpService] Constructor called, starting initialization...");
|
|
1804
1788
|
this.initializationPromise = this.initializeMcpServers();
|
|
1805
1789
|
}
|
|
1806
1790
|
static async start(runtime2) {
|
|
@@ -1816,116 +1800,70 @@ class McpService extends Service {
|
|
|
1816
1800
|
}
|
|
1817
1801
|
}
|
|
1818
1802
|
async stop() {
|
|
1819
|
-
for (const
|
|
1803
|
+
for (const name of this.connections.keys()) {
|
|
1820
1804
|
await this.deleteConnection(name);
|
|
1821
1805
|
}
|
|
1822
|
-
this.
|
|
1823
|
-
for (const state of this.connectionStates.values()) {
|
|
1806
|
+
for (const [name, state] of this.connectionStates.entries()) {
|
|
1824
1807
|
if (state.pingInterval)
|
|
1825
1808
|
clearInterval(state.pingInterval);
|
|
1826
1809
|
if (state.reconnectTimeout)
|
|
1827
1810
|
clearTimeout(state.reconnectTimeout);
|
|
1811
|
+
this.connectionStates.delete(name);
|
|
1828
1812
|
}
|
|
1829
|
-
this.connectionStates.clear();
|
|
1830
1813
|
}
|
|
1831
1814
|
async initializeMcpServers() {
|
|
1832
|
-
logger7.info("[McpService] Starting MCP server initialization...");
|
|
1833
1815
|
try {
|
|
1834
1816
|
const mcpSettings = this.getMcpSettings();
|
|
1835
|
-
|
|
1836
|
-
const serverNames = mcpSettings?.servers ? Object.keys(mcpSettings.servers) : [];
|
|
1837
|
-
logger7.info(`[McpService] Getting MCP settings... hasSettings=${!!mcpSettings} hasServers=${!!mcpSettings?.servers} serverCount=${serverCount} servers=${JSON.stringify(serverNames)}`);
|
|
1838
|
-
if (!mcpSettings || !mcpSettings.servers) {
|
|
1839
|
-
logger7.info("[McpService] No MCP servers configured.");
|
|
1840
|
-
this.mcpProvider = buildMcpProviderData([]);
|
|
1841
|
-
return;
|
|
1842
|
-
}
|
|
1843
|
-
if (Object.keys(mcpSettings.servers).length === 0) {
|
|
1844
|
-
logger7.info("[McpService] MCP settings exist but no servers configured.");
|
|
1817
|
+
if (!mcpSettings?.servers || Object.keys(mcpSettings.servers).length === 0) {
|
|
1845
1818
|
this.mcpProvider = buildMcpProviderData([]);
|
|
1846
1819
|
return;
|
|
1847
1820
|
}
|
|
1848
|
-
logger7.info(`[McpService] Connecting to ${Object.keys(mcpSettings.servers).length} MCP servers: ${JSON.stringify(Object.keys(mcpSettings.servers))}`);
|
|
1849
1821
|
const connectionStartTime = Date.now();
|
|
1850
1822
|
await this.updateServerConnections(mcpSettings.servers);
|
|
1851
1823
|
const connectionDuration = Date.now() - connectionStartTime;
|
|
1852
1824
|
const servers = this.getServers();
|
|
1853
|
-
const
|
|
1854
|
-
const
|
|
1855
|
-
if (
|
|
1856
|
-
|
|
1857
|
-
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(", ")})`);
|
|
1858
1829
|
}
|
|
1859
|
-
if (
|
|
1860
|
-
|
|
1861
|
-
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(", ")}`);
|
|
1862
1832
|
}
|
|
1863
|
-
if (
|
|
1864
|
-
logger7.error(`[
|
|
1833
|
+
if (connected.length === 0 && servers.length > 0) {
|
|
1834
|
+
logger7.error(`[MCP] All servers failed`);
|
|
1865
1835
|
}
|
|
1866
1836
|
this.mcpProvider = buildMcpProviderData(servers);
|
|
1867
|
-
const mcpDataKeys = Object.keys(this.mcpProvider.data?.mcp || {});
|
|
1868
|
-
logger7.info(`[McpService] MCP provider data built: ${mcpDataKeys.length} server(s) available`);
|
|
1869
1837
|
} catch (error) {
|
|
1870
|
-
logger7.error({ error:
|
|
1838
|
+
logger7.error({ error: errMsg(error) }, "[MCP] Initialization failed");
|
|
1871
1839
|
this.mcpProvider = buildMcpProviderData([]);
|
|
1872
1840
|
}
|
|
1873
1841
|
}
|
|
1874
1842
|
getMcpSettings() {
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
if (!settings || !settings.servers) {
|
|
1879
|
-
const characterSettings = this.runtime.character?.settings;
|
|
1880
|
-
if (characterSettings?.mcp) {
|
|
1881
|
-
logger7.info("[McpService] Found MCP settings in character.settings.mcp (fallback)");
|
|
1882
|
-
settings = characterSettings.mcp;
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
if (!settings || !settings.servers) {
|
|
1886
|
-
const runtimeSettings = this.runtime.settings;
|
|
1887
|
-
if (runtimeSettings?.mcp) {
|
|
1888
|
-
logger7.info("[McpService] Found MCP settings in runtime.settings.mcp (fallback)");
|
|
1889
|
-
settings = runtimeSettings.mcp;
|
|
1890
|
-
}
|
|
1843
|
+
let settings = this.runtime.getSetting("mcp");
|
|
1844
|
+
if (!settings || typeof settings === "object" && !settings.servers) {
|
|
1845
|
+
settings = this.runtime.character?.settings?.mcp;
|
|
1891
1846
|
}
|
|
1892
|
-
if (settings
|
|
1893
|
-
|
|
1894
|
-
return settings;
|
|
1847
|
+
if (!settings || typeof settings === "object" && !settings.servers) {
|
|
1848
|
+
settings = this.runtime.settings?.mcp;
|
|
1895
1849
|
}
|
|
1896
|
-
|
|
1897
|
-
return;
|
|
1850
|
+
return settings && typeof settings === "object" && settings.servers ? settings : undefined;
|
|
1898
1851
|
}
|
|
1899
1852
|
async updateServerConnections(serverConfigs) {
|
|
1900
|
-
const currentNames = new Set(this.connections.keys());
|
|
1901
1853
|
const newNames = new Set(Object.keys(serverConfigs));
|
|
1902
|
-
for (const name of
|
|
1903
|
-
if (!newNames.has(name))
|
|
1854
|
+
for (const name of this.connections.keys()) {
|
|
1855
|
+
if (!newNames.has(name))
|
|
1904
1856
|
await this.deleteConnection(name);
|
|
1905
|
-
logger7.info(`Deleted MCP server: ${name}`);
|
|
1906
|
-
}
|
|
1907
1857
|
}
|
|
1908
|
-
|
|
1909
|
-
const
|
|
1910
|
-
if (!
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
logger7.error({ error: error instanceof Error ? error.message : String(error), serverName: name }, `✗ Failed to connect to new MCP server ${name}`);
|
|
1916
|
-
}
|
|
1917
|
-
} else if (JSON.stringify(config) !== currentConnection.server.config) {
|
|
1918
|
-
try {
|
|
1919
|
-
await this.deleteConnection(name);
|
|
1920
|
-
await this.initializeConnection(name, config);
|
|
1921
|
-
logger7.info(`✓ Reconnected MCP server with updated config: ${name}`);
|
|
1922
|
-
} catch (error) {
|
|
1923
|
-
logger7.error({ error: error instanceof Error ? error.message : String(error), serverName: name }, `✗ Failed to reconnect MCP server ${name}`);
|
|
1924
|
-
}
|
|
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}`));
|
|
1925
1865
|
}
|
|
1926
|
-
});
|
|
1927
|
-
await Promise.allSettled(connectionPromises);
|
|
1928
|
-
logger7.info(`[McpService] All server connection attempts completed`);
|
|
1866
|
+
}));
|
|
1929
1867
|
}
|
|
1930
1868
|
async initializeConnection(name, config) {
|
|
1931
1869
|
await this.deleteConnection(name);
|
|
@@ -1939,19 +1877,19 @@ class McpService extends Service {
|
|
|
1939
1877
|
const client = new Client({ name: "ElizaOS", version: "1.0.0" }, { capabilities: {} });
|
|
1940
1878
|
const transport = config.type === "stdio" ? await this.buildStdioClientTransport(name, config) : await this.buildHttpClientTransport(name, config);
|
|
1941
1879
|
const connection = {
|
|
1942
|
-
server: {
|
|
1943
|
-
name,
|
|
1944
|
-
config: JSON.stringify(config),
|
|
1945
|
-
status: "connecting"
|
|
1946
|
-
},
|
|
1880
|
+
server: { name, config: JSON.stringify(config), status: "connecting" },
|
|
1947
1881
|
client,
|
|
1948
1882
|
transport
|
|
1949
1883
|
};
|
|
1950
1884
|
this.connections.set(name, connection);
|
|
1951
1885
|
this.setupTransportHandlers(name, connection, state);
|
|
1952
|
-
|
|
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
|
+
]);
|
|
1953
1892
|
const capabilities = client.getServerCapabilities();
|
|
1954
|
-
logger7.debug(`[${name}] Server capabilities:`, JSON.stringify(capabilities || {}));
|
|
1955
1893
|
const tools = await this.fetchToolsList(name);
|
|
1956
1894
|
const resources = capabilities?.resources ? await this.fetchResourcesList(name) : [];
|
|
1957
1895
|
const resourceTemplates = capabilities?.resources ? await this.fetchResourceTemplatesList(name) : [];
|
|
@@ -1969,7 +1907,6 @@ class McpService extends Service {
|
|
|
1969
1907
|
state.reconnectAttempts = 0;
|
|
1970
1908
|
state.consecutivePingFailures = 0;
|
|
1971
1909
|
this.startPingMonitoring(name);
|
|
1972
|
-
logger7.info(`Successfully connected to MCP server: ${name}`);
|
|
1973
1910
|
} catch (error) {
|
|
1974
1911
|
state.status = "disconnected";
|
|
1975
1912
|
state.lastError = error instanceof Error ? error : new Error(String(error));
|
|
@@ -1979,26 +1916,21 @@ class McpService extends Service {
|
|
|
1979
1916
|
}
|
|
1980
1917
|
setupTransportHandlers(name, connection, state) {
|
|
1981
1918
|
const config = JSON.parse(connection.server.config);
|
|
1982
|
-
const
|
|
1919
|
+
const isHttp = config.type !== "stdio";
|
|
1983
1920
|
connection.transport.onerror = async (error) => {
|
|
1984
|
-
const
|
|
1985
|
-
const isExpectedTimeout =
|
|
1986
|
-
if (isExpectedTimeout) {
|
|
1987
|
-
logger7.
|
|
1988
|
-
} else {
|
|
1989
|
-
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}`);
|
|
1990
1925
|
connection.server.status = "disconnected";
|
|
1991
|
-
this.appendErrorMessage(connection,
|
|
1926
|
+
this.appendErrorMessage(connection, msg);
|
|
1992
1927
|
}
|
|
1993
|
-
if (!
|
|
1928
|
+
if (!isHttp)
|
|
1994
1929
|
this.handleDisconnection(name, error);
|
|
1995
|
-
}
|
|
1996
1930
|
};
|
|
1997
1931
|
connection.transport.onclose = async () => {
|
|
1998
|
-
if (
|
|
1999
|
-
logger7.
|
|
2000
|
-
} else {
|
|
2001
|
-
logger7.warn({ serverName: name }, `Transport closed for "${name}"`);
|
|
1932
|
+
if (!isHttp) {
|
|
1933
|
+
logger7.warn({ serverName: name }, `Transport closed: ${name}`);
|
|
2002
1934
|
connection.server.status = "disconnected";
|
|
2003
1935
|
this.handleDisconnection(name, new Error("Transport closed"));
|
|
2004
1936
|
}
|
|
@@ -2009,11 +1941,8 @@ class McpService extends Service {
|
|
|
2009
1941
|
if (!connection)
|
|
2010
1942
|
return;
|
|
2011
1943
|
const config = JSON.parse(connection.server.config);
|
|
2012
|
-
|
|
2013
|
-
if (isHttpTransport) {
|
|
2014
|
-
logger7.debug(`[McpService] Skipping ping monitoring for HTTP server: ${name}`);
|
|
1944
|
+
if (config.type !== "stdio")
|
|
2015
1945
|
return;
|
|
2016
|
-
}
|
|
2017
1946
|
const state = this.connectionStates.get(name);
|
|
2018
1947
|
if (!state || !this.pingConfig.enabled)
|
|
2019
1948
|
return;
|
|
@@ -2021,7 +1950,7 @@ class McpService extends Service {
|
|
|
2021
1950
|
clearInterval(state.pingInterval);
|
|
2022
1951
|
state.pingInterval = setInterval(() => {
|
|
2023
1952
|
this.sendPing(name).catch((err) => {
|
|
2024
|
-
logger7.warn({ error:
|
|
1953
|
+
logger7.warn({ error: errMsg(err), serverName: name }, `Ping failed: ${name}`);
|
|
2025
1954
|
this.handlePingFailure(name, err);
|
|
2026
1955
|
});
|
|
2027
1956
|
}, this.pingConfig.intervalMs);
|
|
@@ -2029,7 +1958,7 @@ class McpService extends Service {
|
|
|
2029
1958
|
async sendPing(name) {
|
|
2030
1959
|
const connection = this.connections.get(name);
|
|
2031
1960
|
if (!connection)
|
|
2032
|
-
throw new Error(`No connection
|
|
1961
|
+
throw new Error(`No connection: ${name}`);
|
|
2033
1962
|
await Promise.race([
|
|
2034
1963
|
connection.client.listTools(),
|
|
2035
1964
|
new Promise((_, reject) => setTimeout(() => reject(new Error("Ping timeout")), this.pingConfig.timeoutMs))
|
|
@@ -2071,7 +2000,7 @@ class McpService extends Service {
|
|
|
2071
2000
|
try {
|
|
2072
2001
|
await this.initializeConnection(name, JSON.parse(config));
|
|
2073
2002
|
} catch (err) {
|
|
2074
|
-
logger7.error({ error:
|
|
2003
|
+
logger7.error({ error: errMsg(err), serverName: name }, `Reconnect attempt failed for ${name}`);
|
|
2075
2004
|
this.handleDisconnection(name, err);
|
|
2076
2005
|
}
|
|
2077
2006
|
}
|
|
@@ -2084,7 +2013,7 @@ class McpService extends Service {
|
|
|
2084
2013
|
await connection.transport.close();
|
|
2085
2014
|
await connection.client.close();
|
|
2086
2015
|
} catch (error) {
|
|
2087
|
-
logger7.error({ error:
|
|
2016
|
+
logger7.error({ error: errMsg(error), serverName: name }, `Failed to close transport for ${name}`);
|
|
2088
2017
|
}
|
|
2089
2018
|
this.connections.delete(name);
|
|
2090
2019
|
}
|
|
@@ -2097,9 +2026,6 @@ class McpService extends Service {
|
|
|
2097
2026
|
this.connectionStates.delete(name);
|
|
2098
2027
|
}
|
|
2099
2028
|
}
|
|
2100
|
-
getServerConnection(serverName) {
|
|
2101
|
-
return this.connections.get(serverName);
|
|
2102
|
-
}
|
|
2103
2029
|
async buildStdioClientTransport(name, config) {
|
|
2104
2030
|
if (!config.command) {
|
|
2105
2031
|
throw new Error(`Missing command for stdio MCP server ${name}`);
|
|
@@ -2116,74 +2042,61 @@ class McpService extends Service {
|
|
|
2116
2042
|
});
|
|
2117
2043
|
}
|
|
2118
2044
|
async buildHttpClientTransport(name, config) {
|
|
2119
|
-
if (!config.url)
|
|
2120
|
-
throw new Error(`Missing URL for
|
|
2121
|
-
|
|
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;
|
|
2122
2049
|
if (config.type === "sse") {
|
|
2123
|
-
logger7.warn(`
|
|
2050
|
+
logger7.warn(`[MCP] "${name}": SSE requires Redis. Use "streamable-http" instead.`);
|
|
2051
|
+
return new SSEClientTransport(url, opts);
|
|
2124
2052
|
}
|
|
2125
|
-
return new
|
|
2053
|
+
return new StreamableHTTPClientTransport(url, opts);
|
|
2126
2054
|
}
|
|
2127
2055
|
appendErrorMessage(connection, error) {
|
|
2128
|
-
|
|
2056
|
+
connection.server.error = connection.server.error ? `${connection.server.error}
|
|
2129
2057
|
${error}` : error;
|
|
2130
|
-
connection.server.error = newError;
|
|
2131
2058
|
}
|
|
2132
2059
|
async fetchToolsList(serverName) {
|
|
2060
|
+
const connection = this.connections.get(serverName);
|
|
2061
|
+
if (!connection)
|
|
2062
|
+
return [];
|
|
2133
2063
|
try {
|
|
2134
|
-
const connection = this.getServerConnection(serverName);
|
|
2135
|
-
if (!connection) {
|
|
2136
|
-
return [];
|
|
2137
|
-
}
|
|
2138
2064
|
const response = await connection.client.listTools();
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
} catch (error) {
|
|
2149
|
-
logger7.warn({ error, toolName: tool.name, serverName }, `Tool compatibility failed for ${tool.name} on ${serverName}`);
|
|
2150
|
-
}
|
|
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;
|
|
2151
2074
|
}
|
|
2152
|
-
return processedTool;
|
|
2153
2075
|
});
|
|
2154
|
-
logger7.info(`Fetched ${tools.length} tools for ${serverName}`);
|
|
2155
|
-
for (const tool of tools) {
|
|
2156
|
-
logger7.info(`[${serverName}] ${tool.name}: ${tool.description}`);
|
|
2157
|
-
}
|
|
2158
|
-
return tools;
|
|
2159
2076
|
} catch (error) {
|
|
2160
|
-
logger7.error({ error:
|
|
2077
|
+
logger7.error({ error: errMsg(error), serverName }, `Failed to fetch tools: ${serverName}`);
|
|
2161
2078
|
return [];
|
|
2162
2079
|
}
|
|
2163
2080
|
}
|
|
2164
|
-
async fetchResourcesList(
|
|
2081
|
+
async fetchResourcesList(name) {
|
|
2082
|
+
const conn = this.connections.get(name);
|
|
2083
|
+
if (!conn)
|
|
2084
|
+
return [];
|
|
2165
2085
|
try {
|
|
2166
|
-
|
|
2167
|
-
if (!connection) {
|
|
2168
|
-
return [];
|
|
2169
|
-
}
|
|
2170
|
-
const response = await connection.client.listResources();
|
|
2171
|
-
return response?.resources || [];
|
|
2086
|
+
return (await conn.client.listResources())?.resources || [];
|
|
2172
2087
|
} catch (error) {
|
|
2173
|
-
logger7.
|
|
2088
|
+
logger7.debug({ error: errMsg(error), serverName: name }, `No resources for ${name}`);
|
|
2174
2089
|
return [];
|
|
2175
2090
|
}
|
|
2176
2091
|
}
|
|
2177
|
-
async fetchResourceTemplatesList(
|
|
2092
|
+
async fetchResourceTemplatesList(name) {
|
|
2093
|
+
const conn = this.connections.get(name);
|
|
2094
|
+
if (!conn)
|
|
2095
|
+
return [];
|
|
2178
2096
|
try {
|
|
2179
|
-
|
|
2180
|
-
if (!connection) {
|
|
2181
|
-
return [];
|
|
2182
|
-
}
|
|
2183
|
-
const response = await connection.client.listResourceTemplates();
|
|
2184
|
-
return response?.resourceTemplates || [];
|
|
2097
|
+
return (await conn.client.listResourceTemplates())?.resourceTemplates || [];
|
|
2185
2098
|
} catch (error) {
|
|
2186
|
-
logger7.
|
|
2099
|
+
logger7.debug({ error: errMsg(error), serverName: name }, `No resource templates for ${name}`);
|
|
2187
2100
|
return [];
|
|
2188
2101
|
}
|
|
2189
2102
|
}
|
|
@@ -2193,76 +2106,53 @@ ${error}` : error;
|
|
|
2193
2106
|
getProviderData() {
|
|
2194
2107
|
return this.mcpProvider;
|
|
2195
2108
|
}
|
|
2196
|
-
async callTool(serverName, toolName,
|
|
2197
|
-
const
|
|
2198
|
-
if (!
|
|
2199
|
-
throw new Error(`No connection
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
logger7.error({ error: error instanceof Error ? error.message : String(error), serverName }, `Failed to parse timeout configuration for server ${serverName}`);
|
|
2210
|
-
}
|
|
2211
|
-
const result = await connection.client.callTool({ name: toolName, arguments: toolArguments }, undefined, { timeout });
|
|
2212
|
-
if (!result.content) {
|
|
2213
|
-
throw new Error("Invalid tool result: missing content array");
|
|
2214
|
-
}
|
|
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");
|
|
2215
2122
|
return result;
|
|
2216
2123
|
}
|
|
2217
2124
|
async readResource(serverName, uri) {
|
|
2218
|
-
const
|
|
2219
|
-
if (!
|
|
2220
|
-
throw new Error(`No connection
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
}
|
|
2225
|
-
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 });
|
|
2226
2131
|
}
|
|
2227
2132
|
async restartConnection(serverName) {
|
|
2228
|
-
const
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
await this.initializeConnection(serverName, JSON.parse(config));
|
|
2237
|
-
logger7.info(`${serverName} MCP server connected`);
|
|
2238
|
-
} catch (error) {
|
|
2239
|
-
logger7.error({ error: error instanceof Error ? error.message : String(error), serverName }, `Failed to restart connection for ${serverName}`);
|
|
2240
|
-
throw new Error(`Failed to connect to ${serverName} MCP server`);
|
|
2241
|
-
}
|
|
2242
|
-
}
|
|
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));
|
|
2243
2141
|
}
|
|
2244
2142
|
initializeToolCompatibility() {
|
|
2245
2143
|
if (this.compatibilityInitialized)
|
|
2246
2144
|
return;
|
|
2247
2145
|
this.toolCompatibility = createMcpToolCompatibilitySync(this.runtime);
|
|
2248
2146
|
this.compatibilityInitialized = true;
|
|
2249
|
-
if (this.toolCompatibility) {
|
|
2250
|
-
logger7.info(`Tool compatibility enabled`);
|
|
2251
|
-
} else {
|
|
2252
|
-
logger7.info(`No tool compatibility needed`);
|
|
2253
|
-
}
|
|
2254
2147
|
}
|
|
2255
2148
|
applyToolCompatibility(toolSchema) {
|
|
2256
|
-
if (!this.compatibilityInitialized)
|
|
2149
|
+
if (!this.compatibilityInitialized)
|
|
2257
2150
|
this.initializeToolCompatibility();
|
|
2258
|
-
|
|
2259
|
-
if (!this.toolCompatibility || !toolSchema) {
|
|
2151
|
+
if (!this.toolCompatibility || !toolSchema)
|
|
2260
2152
|
return toolSchema;
|
|
2261
|
-
}
|
|
2262
2153
|
try {
|
|
2263
2154
|
return this.toolCompatibility.transformToolSchema(toolSchema);
|
|
2264
|
-
} catch
|
|
2265
|
-
logger7.warn({ error }, `Tool compatibility transformation failed`);
|
|
2155
|
+
} catch {
|
|
2266
2156
|
return toolSchema;
|
|
2267
2157
|
}
|
|
2268
2158
|
}
|
|
@@ -2299,4 +2189,4 @@ export {
|
|
|
2299
2189
|
BACKOFF_MULTIPLIER
|
|
2300
2190
|
};
|
|
2301
2191
|
|
|
2302
|
-
//# debugId=
|
|
2192
|
+
//# debugId=D6293B10B9AC06C464756E2164756E21
|