@agiflowai/one-mcp 0.3.11 → 0.3.12
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 +136 -200
- package/dist/cli.cjs +251 -98
- package/dist/cli.mjs +250 -97
- package/dist/{http-pwzeCOpM.cjs → http-B4_JfY6t.cjs} +1079 -450
- package/dist/{http-BDeLFFzK.mjs → http-ClRbCldm.mjs} +1049 -444
- package/dist/index.cjs +3 -1
- package/dist/index.d.cts +156 -22
- package/dist/index.d.mts +156 -22
- package/dist/index.mjs +2 -2
- package/package.json +2 -2
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
-
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListToolsRequestSchema, isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
-
import { access, mkdir, readFile, readdir, stat, unlink, watch, writeFile } from "node:fs/promises";
|
|
2
|
+
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { access, mkdir, readFile, readdir, rm, stat, unlink, watch, writeFile } from "node:fs/promises";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import yaml from "js-yaml";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
8
8
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
9
|
-
import { tmpdir } from "node:os";
|
|
9
|
+
import { homedir, tmpdir } from "node:os";
|
|
10
10
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
11
11
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
12
12
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
@@ -760,14 +760,22 @@ var ConfigFetcherService = class {
|
|
|
760
760
|
*/
|
|
761
761
|
mergeConfigurations(localConfig, remoteConfig, mergeStrategy) {
|
|
762
762
|
switch (mergeStrategy) {
|
|
763
|
-
case "local-priority": return {
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
}
|
|
763
|
+
case "local-priority": return {
|
|
764
|
+
id: localConfig.id ?? remoteConfig.id,
|
|
765
|
+
mcpServers: {
|
|
766
|
+
...remoteConfig.mcpServers,
|
|
767
|
+
...localConfig.mcpServers
|
|
768
|
+
},
|
|
769
|
+
skills: localConfig.skills ?? remoteConfig.skills
|
|
770
|
+
};
|
|
771
|
+
case "remote-priority": return {
|
|
772
|
+
id: remoteConfig.id ?? localConfig.id,
|
|
773
|
+
mcpServers: {
|
|
774
|
+
...localConfig.mcpServers,
|
|
775
|
+
...remoteConfig.mcpServers
|
|
776
|
+
},
|
|
777
|
+
skills: remoteConfig.skills ?? localConfig.skills
|
|
778
|
+
};
|
|
771
779
|
case "merge-deep": {
|
|
772
780
|
const merged = { ...remoteConfig.mcpServers };
|
|
773
781
|
for (const [serverName, localServerConfig] of Object.entries(localConfig.mcpServers)) if (merged[serverName]) {
|
|
@@ -794,7 +802,11 @@ var ConfigFetcherService = class {
|
|
|
794
802
|
config: mergedConfig
|
|
795
803
|
};
|
|
796
804
|
} else merged[serverName] = localServerConfig;
|
|
797
|
-
return {
|
|
805
|
+
return {
|
|
806
|
+
id: localConfig.id ?? remoteConfig.id,
|
|
807
|
+
mcpServers: merged,
|
|
808
|
+
skills: localConfig.skills ?? remoteConfig.skills
|
|
809
|
+
};
|
|
798
810
|
}
|
|
799
811
|
default: throw new Error(`Unknown merge strategy: ${mergeStrategy}`);
|
|
800
812
|
}
|
|
@@ -815,213 +827,6 @@ var ConfigFetcherService = class {
|
|
|
815
827
|
}
|
|
816
828
|
};
|
|
817
829
|
|
|
818
|
-
//#endregion
|
|
819
|
-
//#region src/services/McpClientManagerService.ts
|
|
820
|
-
/** Default connection timeout in milliseconds (30 seconds) */
|
|
821
|
-
const DEFAULT_CONNECTION_TIMEOUT_MS = 3e4;
|
|
822
|
-
/**
|
|
823
|
-
* MCP Client wrapper for managing individual server connections
|
|
824
|
-
* This is an internal class used by McpClientManagerService
|
|
825
|
-
*/
|
|
826
|
-
var McpClient = class {
|
|
827
|
-
serverName;
|
|
828
|
-
serverInstruction;
|
|
829
|
-
toolBlacklist;
|
|
830
|
-
omitToolDescription;
|
|
831
|
-
prompts;
|
|
832
|
-
transport;
|
|
833
|
-
client;
|
|
834
|
-
childProcess;
|
|
835
|
-
connected = false;
|
|
836
|
-
constructor(serverName, transport, client, config) {
|
|
837
|
-
this.serverName = serverName;
|
|
838
|
-
this.serverInstruction = config.instruction;
|
|
839
|
-
this.toolBlacklist = config.toolBlacklist;
|
|
840
|
-
this.omitToolDescription = config.omitToolDescription;
|
|
841
|
-
this.prompts = config.prompts;
|
|
842
|
-
this.transport = transport;
|
|
843
|
-
this.client = client;
|
|
844
|
-
}
|
|
845
|
-
setChildProcess(process$1) {
|
|
846
|
-
this.childProcess = process$1;
|
|
847
|
-
}
|
|
848
|
-
setConnected(connected) {
|
|
849
|
-
this.connected = connected;
|
|
850
|
-
}
|
|
851
|
-
async listTools() {
|
|
852
|
-
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
853
|
-
return (await this.client.listTools()).tools;
|
|
854
|
-
}
|
|
855
|
-
async listResources() {
|
|
856
|
-
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
857
|
-
return (await this.client.listResources()).resources;
|
|
858
|
-
}
|
|
859
|
-
async listPrompts() {
|
|
860
|
-
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
861
|
-
return (await this.client.listPrompts()).prompts;
|
|
862
|
-
}
|
|
863
|
-
async callTool(name, args) {
|
|
864
|
-
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
865
|
-
return await this.client.callTool({
|
|
866
|
-
name,
|
|
867
|
-
arguments: args
|
|
868
|
-
});
|
|
869
|
-
}
|
|
870
|
-
async readResource(uri) {
|
|
871
|
-
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
872
|
-
return await this.client.readResource({ uri });
|
|
873
|
-
}
|
|
874
|
-
async getPrompt(name, args) {
|
|
875
|
-
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
876
|
-
return await this.client.getPrompt({
|
|
877
|
-
name,
|
|
878
|
-
arguments: args
|
|
879
|
-
});
|
|
880
|
-
}
|
|
881
|
-
async close() {
|
|
882
|
-
if (this.childProcess) this.childProcess.kill();
|
|
883
|
-
await this.client.close();
|
|
884
|
-
this.connected = false;
|
|
885
|
-
}
|
|
886
|
-
};
|
|
887
|
-
/**
|
|
888
|
-
* Service for managing MCP client connections to remote servers
|
|
889
|
-
*/
|
|
890
|
-
var McpClientManagerService = class {
|
|
891
|
-
clients = /* @__PURE__ */ new Map();
|
|
892
|
-
constructor() {
|
|
893
|
-
process.on("exit", () => {
|
|
894
|
-
this.cleanupOnExit();
|
|
895
|
-
});
|
|
896
|
-
process.on("SIGINT", () => {
|
|
897
|
-
this.cleanupOnExit();
|
|
898
|
-
process.exit(0);
|
|
899
|
-
});
|
|
900
|
-
process.on("SIGTERM", () => {
|
|
901
|
-
this.cleanupOnExit();
|
|
902
|
-
process.exit(0);
|
|
903
|
-
});
|
|
904
|
-
}
|
|
905
|
-
/**
|
|
906
|
-
* Cleanup all resources on exit (child processes)
|
|
907
|
-
*/
|
|
908
|
-
cleanupOnExit() {
|
|
909
|
-
for (const [serverName, client] of this.clients) try {
|
|
910
|
-
const childProcess = client["childProcess"];
|
|
911
|
-
if (childProcess && !childProcess.killed) {
|
|
912
|
-
console.error(`Killing stdio MCP server: ${serverName} (PID: ${childProcess.pid})`);
|
|
913
|
-
childProcess.kill("SIGTERM");
|
|
914
|
-
setTimeout(() => {
|
|
915
|
-
if (!childProcess.killed) {
|
|
916
|
-
console.error(`Force killing stdio MCP server: ${serverName} (PID: ${childProcess.pid})`);
|
|
917
|
-
childProcess.kill("SIGKILL");
|
|
918
|
-
}
|
|
919
|
-
}, 1e3);
|
|
920
|
-
}
|
|
921
|
-
} catch (error) {
|
|
922
|
-
console.error(`Failed to kill child process for ${serverName}:`, error);
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
/**
|
|
926
|
-
* Connect to an MCP server based on its configuration with timeout
|
|
927
|
-
* Uses the timeout from server config, falling back to default (30s)
|
|
928
|
-
*/
|
|
929
|
-
async connectToServer(serverName, config) {
|
|
930
|
-
const timeoutMs = config.timeout ?? DEFAULT_CONNECTION_TIMEOUT_MS;
|
|
931
|
-
if (this.clients.has(serverName)) throw new Error(`Client for ${serverName} is already connected`);
|
|
932
|
-
const client = new Client({
|
|
933
|
-
name: `@agiflowai/one-mcp-client`,
|
|
934
|
-
version: "0.1.0"
|
|
935
|
-
}, { capabilities: {} });
|
|
936
|
-
const mcpClient = new McpClient(serverName, config.transport, client, {
|
|
937
|
-
instruction: config.instruction,
|
|
938
|
-
toolBlacklist: config.toolBlacklist,
|
|
939
|
-
omitToolDescription: config.omitToolDescription,
|
|
940
|
-
prompts: config.prompts
|
|
941
|
-
});
|
|
942
|
-
try {
|
|
943
|
-
await Promise.race([this.performConnection(mcpClient, config), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error(`Connection timeout after ${timeoutMs}ms`)), timeoutMs))]);
|
|
944
|
-
mcpClient.setConnected(true);
|
|
945
|
-
if (!mcpClient.serverInstruction) try {
|
|
946
|
-
const serverInstruction = mcpClient["client"].getInstructions();
|
|
947
|
-
if (serverInstruction) mcpClient.serverInstruction = serverInstruction;
|
|
948
|
-
} catch (error) {
|
|
949
|
-
console.error(`Failed to get server instruction from ${serverName}:`, error);
|
|
950
|
-
}
|
|
951
|
-
this.clients.set(serverName, mcpClient);
|
|
952
|
-
} catch (error) {
|
|
953
|
-
await mcpClient.close();
|
|
954
|
-
throw error;
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
/**
|
|
958
|
-
* Perform the actual connection to MCP server
|
|
959
|
-
*/
|
|
960
|
-
async performConnection(mcpClient, config) {
|
|
961
|
-
if (config.transport === "stdio") await this.connectStdioClient(mcpClient, config.config);
|
|
962
|
-
else if (config.transport === "http") await this.connectHttpClient(mcpClient, config.config);
|
|
963
|
-
else if (config.transport === "sse") await this.connectSseClient(mcpClient, config.config);
|
|
964
|
-
else throw new Error(`Unsupported transport type: ${config.transport}`);
|
|
965
|
-
}
|
|
966
|
-
async connectStdioClient(mcpClient, config) {
|
|
967
|
-
const transport = new StdioClientTransport({
|
|
968
|
-
command: config.command,
|
|
969
|
-
args: config.args,
|
|
970
|
-
env: {
|
|
971
|
-
...process.env,
|
|
972
|
-
...config.env ?? {}
|
|
973
|
-
}
|
|
974
|
-
});
|
|
975
|
-
await mcpClient["client"].connect(transport);
|
|
976
|
-
const childProcess = transport["_process"];
|
|
977
|
-
if (childProcess) mcpClient.setChildProcess(childProcess);
|
|
978
|
-
}
|
|
979
|
-
async connectHttpClient(mcpClient, config) {
|
|
980
|
-
const transport = new StreamableHTTPClientTransport(new URL(config.url), { requestInit: config.headers ? { headers: config.headers } : void 0 });
|
|
981
|
-
await mcpClient["client"].connect(transport);
|
|
982
|
-
}
|
|
983
|
-
async connectSseClient(mcpClient, config) {
|
|
984
|
-
const transport = new SSEClientTransport(new URL(config.url));
|
|
985
|
-
await mcpClient["client"].connect(transport);
|
|
986
|
-
}
|
|
987
|
-
/**
|
|
988
|
-
* Get a connected client by server name
|
|
989
|
-
*/
|
|
990
|
-
getClient(serverName) {
|
|
991
|
-
return this.clients.get(serverName);
|
|
992
|
-
}
|
|
993
|
-
/**
|
|
994
|
-
* Get all connected clients
|
|
995
|
-
*/
|
|
996
|
-
getAllClients() {
|
|
997
|
-
return Array.from(this.clients.values());
|
|
998
|
-
}
|
|
999
|
-
/**
|
|
1000
|
-
* Disconnect from a specific server
|
|
1001
|
-
*/
|
|
1002
|
-
async disconnectServer(serverName) {
|
|
1003
|
-
const client = this.clients.get(serverName);
|
|
1004
|
-
if (client) {
|
|
1005
|
-
await client.close();
|
|
1006
|
-
this.clients.delete(serverName);
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
/**
|
|
1010
|
-
* Disconnect from all servers
|
|
1011
|
-
*/
|
|
1012
|
-
async disconnectAll() {
|
|
1013
|
-
const disconnectPromises = Array.from(this.clients.values()).map((client) => client.close());
|
|
1014
|
-
await Promise.all(disconnectPromises);
|
|
1015
|
-
this.clients.clear();
|
|
1016
|
-
}
|
|
1017
|
-
/**
|
|
1018
|
-
* Check if a server is connected
|
|
1019
|
-
*/
|
|
1020
|
-
isConnected(serverName) {
|
|
1021
|
-
return this.clients.has(serverName);
|
|
1022
|
-
}
|
|
1023
|
-
};
|
|
1024
|
-
|
|
1025
830
|
//#endregion
|
|
1026
831
|
//#region src/utils/findConfigFile.ts
|
|
1027
832
|
/**
|
|
@@ -1287,8 +1092,565 @@ function generateServerId(length = DEFAULT_ID_LENGTH) {
|
|
|
1287
1092
|
remaining--;
|
|
1288
1093
|
}
|
|
1289
1094
|
}
|
|
1290
|
-
return result;
|
|
1291
|
-
}
|
|
1095
|
+
return result;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
//#endregion
|
|
1099
|
+
//#region src/constants/index.ts
|
|
1100
|
+
/**
|
|
1101
|
+
* Shared constants for one-mcp package
|
|
1102
|
+
*/
|
|
1103
|
+
/**
|
|
1104
|
+
* Prefix added to skill names when they clash with MCP tool names.
|
|
1105
|
+
* This ensures skills can be uniquely identified even when a tool has the same name.
|
|
1106
|
+
*/
|
|
1107
|
+
const SKILL_PREFIX = "skill__";
|
|
1108
|
+
/**
|
|
1109
|
+
* Log prefix for skill detection messages.
|
|
1110
|
+
* Used to easily filter skill detection logs in stderr output.
|
|
1111
|
+
*/
|
|
1112
|
+
const LOG_PREFIX_SKILL_DETECTION = "[skill-detection]";
|
|
1113
|
+
/**
|
|
1114
|
+
* Log prefix for general MCP capability discovery messages.
|
|
1115
|
+
*/
|
|
1116
|
+
const LOG_PREFIX_CAPABILITY_DISCOVERY = "[capability-discovery]";
|
|
1117
|
+
/**
|
|
1118
|
+
* Prefix for prompt-based skill locations.
|
|
1119
|
+
* Format: "prompt:{serverName}:{promptName}"
|
|
1120
|
+
*/
|
|
1121
|
+
const PROMPT_LOCATION_PREFIX = "prompt:";
|
|
1122
|
+
/**
|
|
1123
|
+
* Default server ID used when no ID is provided via CLI or config.
|
|
1124
|
+
* This fallback is used when auto-generation also fails.
|
|
1125
|
+
*/
|
|
1126
|
+
const DEFAULT_SERVER_ID = "unknown";
|
|
1127
|
+
|
|
1128
|
+
//#endregion
|
|
1129
|
+
//#region src/services/DefinitionsCacheService.ts
|
|
1130
|
+
/**
|
|
1131
|
+
* DefinitionsCacheService
|
|
1132
|
+
*
|
|
1133
|
+
* Provides shared discovery, caching, and serialization for startup-time MCP
|
|
1134
|
+
* capability metadata. This avoids repeated remote enumeration during
|
|
1135
|
+
* mcp-serve startup and describe_tools generation.
|
|
1136
|
+
*/
|
|
1137
|
+
function isYamlPath(filePath) {
|
|
1138
|
+
return filePath.endsWith(".yaml") || filePath.endsWith(".yml");
|
|
1139
|
+
}
|
|
1140
|
+
function toErrorMessage(error) {
|
|
1141
|
+
return error instanceof Error ? error.message : String(error);
|
|
1142
|
+
}
|
|
1143
|
+
function sanitizeConfigPathForFilename(configFilePath) {
|
|
1144
|
+
const absoluteConfigPath = resolve(configFilePath);
|
|
1145
|
+
const normalizedPath = absoluteConfigPath.length >= 2 && absoluteConfigPath[1] === ":" && (absoluteConfigPath[0] >= "A" && absoluteConfigPath[0] <= "Z" || absoluteConfigPath[0] >= "a" && absoluteConfigPath[0] <= "z") ? `${absoluteConfigPath[0].toLowerCase()}${absoluteConfigPath.slice(1)}` : absoluteConfigPath;
|
|
1146
|
+
let result = "";
|
|
1147
|
+
let previousWasUnderscore = false;
|
|
1148
|
+
for (const char of normalizedPath) {
|
|
1149
|
+
if (char >= "a" && char <= "z" || char >= "A" && char <= "Z" || char >= "0" && char <= "9" || char === "." || char === "_" || char === "-") {
|
|
1150
|
+
result += char;
|
|
1151
|
+
previousWasUnderscore = false;
|
|
1152
|
+
continue;
|
|
1153
|
+
}
|
|
1154
|
+
if (!previousWasUnderscore) {
|
|
1155
|
+
result += "_";
|
|
1156
|
+
previousWasUnderscore = true;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
let start = 0;
|
|
1160
|
+
let end = result.length;
|
|
1161
|
+
while (start < end && result[start] === "_") start += 1;
|
|
1162
|
+
while (end > start && result[end - 1] === "_") end -= 1;
|
|
1163
|
+
return result.slice(start, end);
|
|
1164
|
+
}
|
|
1165
|
+
function cloneCache(cache) {
|
|
1166
|
+
return {
|
|
1167
|
+
...cache,
|
|
1168
|
+
failures: [...cache.failures ?? []],
|
|
1169
|
+
skills: (cache.skills ?? []).map((skill) => ({ ...skill })),
|
|
1170
|
+
servers: Object.fromEntries(Object.entries(cache.servers).map(([serverName, server]) => [serverName, {
|
|
1171
|
+
...server,
|
|
1172
|
+
tools: (server.tools ?? []).map((tool) => ({ ...tool })),
|
|
1173
|
+
resources: (server.resources ?? []).map((resource) => ({ ...resource })),
|
|
1174
|
+
prompts: (server.prompts ?? []).map((prompt) => ({
|
|
1175
|
+
...prompt,
|
|
1176
|
+
arguments: prompt.arguments?.map((arg) => ({ ...arg }))
|
|
1177
|
+
})),
|
|
1178
|
+
promptSkills: (server.promptSkills ?? []).map((promptSkill) => ({
|
|
1179
|
+
...promptSkill,
|
|
1180
|
+
skill: { ...promptSkill.skill }
|
|
1181
|
+
}))
|
|
1182
|
+
}]))
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
var DefinitionsCacheService = class {
|
|
1186
|
+
clientManager;
|
|
1187
|
+
skillService;
|
|
1188
|
+
cacheData;
|
|
1189
|
+
liveDefinitionsPromise = null;
|
|
1190
|
+
mergedDefinitionsPromise = null;
|
|
1191
|
+
constructor(clientManager, skillService, options) {
|
|
1192
|
+
this.clientManager = clientManager;
|
|
1193
|
+
this.skillService = skillService;
|
|
1194
|
+
this.cacheData = options?.cacheData;
|
|
1195
|
+
}
|
|
1196
|
+
static async readFromFile(filePath) {
|
|
1197
|
+
const content = await readFile(filePath, "utf-8");
|
|
1198
|
+
const parsed = isYamlPath(filePath) ? yaml.load(content) : JSON.parse(content);
|
|
1199
|
+
if (!parsed || typeof parsed !== "object") throw new Error("Definitions cache must be an object");
|
|
1200
|
+
const cache = parsed;
|
|
1201
|
+
if (cache.version !== 1 || !cache.servers) throw new Error("Definitions cache is missing required fields");
|
|
1202
|
+
return {
|
|
1203
|
+
...cache,
|
|
1204
|
+
failures: Array.isArray(cache.failures) ? cache.failures : [],
|
|
1205
|
+
skills: Array.isArray(cache.skills) ? cache.skills : [],
|
|
1206
|
+
servers: Object.fromEntries(Object.entries(cache.servers).map(([serverName, server]) => [serverName, {
|
|
1207
|
+
...server,
|
|
1208
|
+
tools: Array.isArray(server.tools) ? server.tools : [],
|
|
1209
|
+
resources: Array.isArray(server.resources) ? server.resources : [],
|
|
1210
|
+
prompts: Array.isArray(server.prompts) ? server.prompts : [],
|
|
1211
|
+
promptSkills: Array.isArray(server.promptSkills) ? server.promptSkills : []
|
|
1212
|
+
}]))
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
static async writeToFile(filePath, cache) {
|
|
1216
|
+
const serialized = isYamlPath(filePath) ? yaml.dump(cache, { noRefs: true }) : JSON.stringify(cache, null, 2);
|
|
1217
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
1218
|
+
await writeFile(filePath, serialized, "utf-8");
|
|
1219
|
+
}
|
|
1220
|
+
static getDefaultCachePath(configFilePath) {
|
|
1221
|
+
const sanitizedPath = sanitizeConfigPathForFilename(configFilePath);
|
|
1222
|
+
return join(homedir(), ".aicode-toolkit", `${sanitizedPath}.definitions-cache.json`);
|
|
1223
|
+
}
|
|
1224
|
+
static generateConfigHash(config) {
|
|
1225
|
+
return createHash("sha256").update(JSON.stringify(config)).digest("hex");
|
|
1226
|
+
}
|
|
1227
|
+
static isCacheValid(cache, options) {
|
|
1228
|
+
if (options.configHash && cache.configHash && cache.configHash !== options.configHash) return false;
|
|
1229
|
+
if (options.oneMcpVersion && cache.oneMcpVersion && cache.oneMcpVersion !== options.oneMcpVersion) return false;
|
|
1230
|
+
return true;
|
|
1231
|
+
}
|
|
1232
|
+
static async clearFile(filePath) {
|
|
1233
|
+
await rm(filePath, { force: true });
|
|
1234
|
+
}
|
|
1235
|
+
clearLiveCache() {
|
|
1236
|
+
this.liveDefinitionsPromise = null;
|
|
1237
|
+
this.mergedDefinitionsPromise = null;
|
|
1238
|
+
}
|
|
1239
|
+
setCacheData(cacheData) {
|
|
1240
|
+
this.cacheData = cacheData;
|
|
1241
|
+
this.mergedDefinitionsPromise = null;
|
|
1242
|
+
}
|
|
1243
|
+
async collectForCache(options) {
|
|
1244
|
+
const liveDefinitions = await this.collectLiveDefinitions(options);
|
|
1245
|
+
this.setCacheData(liveDefinitions);
|
|
1246
|
+
this.liveDefinitionsPromise = Promise.resolve(cloneCache(liveDefinitions));
|
|
1247
|
+
return cloneCache(liveDefinitions);
|
|
1248
|
+
}
|
|
1249
|
+
async getDefinitions() {
|
|
1250
|
+
if (this.mergedDefinitionsPromise) return this.mergedDefinitionsPromise;
|
|
1251
|
+
this.mergedDefinitionsPromise = (async () => {
|
|
1252
|
+
const clients = this.clientManager.getAllClients();
|
|
1253
|
+
if (!this.cacheData) return this.getLiveDefinitions();
|
|
1254
|
+
const missingServers = clients.map((client) => client.serverName).filter((serverName) => !this.cacheData?.servers[serverName]);
|
|
1255
|
+
if (missingServers.length === 0) return cloneCache(this.cacheData);
|
|
1256
|
+
const liveDefinitions = await this.getLiveDefinitions();
|
|
1257
|
+
const merged = cloneCache(this.cacheData);
|
|
1258
|
+
for (const serverName of missingServers) {
|
|
1259
|
+
const serverDefinition = liveDefinitions.servers[serverName];
|
|
1260
|
+
if (serverDefinition) merged.servers[serverName] = serverDefinition;
|
|
1261
|
+
}
|
|
1262
|
+
const failureMap = /* @__PURE__ */ new Map();
|
|
1263
|
+
for (const failure of [...merged.failures, ...liveDefinitions.failures]) failureMap.set(failure.serverName, failure.error);
|
|
1264
|
+
merged.failures = Array.from(failureMap.entries()).map(([serverName, error]) => ({
|
|
1265
|
+
serverName,
|
|
1266
|
+
error
|
|
1267
|
+
}));
|
|
1268
|
+
if (merged.skills.length === 0 && liveDefinitions.skills.length > 0) merged.skills = liveDefinitions.skills.map((skill) => ({ ...skill }));
|
|
1269
|
+
return merged;
|
|
1270
|
+
})();
|
|
1271
|
+
return this.mergedDefinitionsPromise;
|
|
1272
|
+
}
|
|
1273
|
+
async getServerDefinitions() {
|
|
1274
|
+
const definitions = await this.getDefinitions();
|
|
1275
|
+
const serverOrder = this.clientManager.getKnownServerNames();
|
|
1276
|
+
if (serverOrder.length === 0) return Object.values(definitions.servers);
|
|
1277
|
+
return serverOrder.map((serverName) => definitions.servers[serverName]).filter((server) => server !== void 0);
|
|
1278
|
+
}
|
|
1279
|
+
async getServersForTool(toolName) {
|
|
1280
|
+
return (await this.getServerDefinitions()).filter((serverDefinition) => serverDefinition.tools.some((tool) => tool.name === toolName)).map((serverDefinition) => serverDefinition.serverName);
|
|
1281
|
+
}
|
|
1282
|
+
async getServersForResource(uri) {
|
|
1283
|
+
return (await this.getServerDefinitions()).filter((serverDefinition) => serverDefinition.resources.some((resource) => resource.uri === uri)).map((serverDefinition) => serverDefinition.serverName);
|
|
1284
|
+
}
|
|
1285
|
+
async getPromptSkillByName(skillName) {
|
|
1286
|
+
const definitions = await this.getDefinitions();
|
|
1287
|
+
for (const [serverName, server] of Object.entries(definitions.servers)) for (const promptSkill of server.promptSkills) if (promptSkill.skill.name === skillName) return {
|
|
1288
|
+
serverName,
|
|
1289
|
+
promptName: promptSkill.promptName,
|
|
1290
|
+
skill: promptSkill.skill,
|
|
1291
|
+
autoDetected: promptSkill.autoDetected
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
async getCachedFileSkills() {
|
|
1295
|
+
return (await this.getDefinitions()).skills.map((skill) => ({ ...skill }));
|
|
1296
|
+
}
|
|
1297
|
+
async getLiveDefinitions() {
|
|
1298
|
+
if (!this.liveDefinitionsPromise) this.liveDefinitionsPromise = this.collectLiveDefinitions();
|
|
1299
|
+
return this.liveDefinitionsPromise;
|
|
1300
|
+
}
|
|
1301
|
+
async collectLiveDefinitions(options) {
|
|
1302
|
+
const clients = this.clientManager.getAllClients();
|
|
1303
|
+
const failures = [];
|
|
1304
|
+
const servers = {};
|
|
1305
|
+
const serverResults = await Promise.all(clients.map(async (client) => {
|
|
1306
|
+
try {
|
|
1307
|
+
const tools = await client.listTools();
|
|
1308
|
+
const resources = await this.listResourcesSafe(client);
|
|
1309
|
+
const prompts = await this.listPromptsSafe(client);
|
|
1310
|
+
const blacklist = new Set(client.toolBlacklist || []);
|
|
1311
|
+
const filteredTools = tools.filter((tool) => !blacklist.has(tool.name));
|
|
1312
|
+
const promptSkills = await this.collectPromptSkillsForClient(client, prompts);
|
|
1313
|
+
return {
|
|
1314
|
+
serverName: client.serverName,
|
|
1315
|
+
serverInstruction: client.serverInstruction,
|
|
1316
|
+
omitToolDescription: client.omitToolDescription,
|
|
1317
|
+
toolBlacklist: client.toolBlacklist,
|
|
1318
|
+
tools: filteredTools.map((tool) => ({
|
|
1319
|
+
name: tool.name,
|
|
1320
|
+
description: tool.description,
|
|
1321
|
+
inputSchema: tool.inputSchema,
|
|
1322
|
+
_meta: tool._meta
|
|
1323
|
+
})),
|
|
1324
|
+
resources,
|
|
1325
|
+
prompts,
|
|
1326
|
+
promptSkills
|
|
1327
|
+
};
|
|
1328
|
+
} catch (error) {
|
|
1329
|
+
failures.push({
|
|
1330
|
+
serverName: client.serverName,
|
|
1331
|
+
error: toErrorMessage(error)
|
|
1332
|
+
});
|
|
1333
|
+
return null;
|
|
1334
|
+
}
|
|
1335
|
+
}));
|
|
1336
|
+
for (const serverDefinition of serverResults) if (serverDefinition) servers[serverDefinition.serverName] = serverDefinition;
|
|
1337
|
+
return {
|
|
1338
|
+
version: 1,
|
|
1339
|
+
oneMcpVersion: options?.oneMcpVersion,
|
|
1340
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1341
|
+
configPath: options?.configPath,
|
|
1342
|
+
configHash: options?.configHash,
|
|
1343
|
+
serverId: options?.serverId,
|
|
1344
|
+
servers,
|
|
1345
|
+
skills: await this.collectFileSkills(),
|
|
1346
|
+
failures
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
async collectFileSkills() {
|
|
1350
|
+
if (!this.skillService) return [];
|
|
1351
|
+
return (await this.skillService.getSkills()).map((skill) => this.toCachedFileSkill(skill));
|
|
1352
|
+
}
|
|
1353
|
+
toCachedFileSkill(skill) {
|
|
1354
|
+
return {
|
|
1355
|
+
name: skill.name,
|
|
1356
|
+
description: skill.description,
|
|
1357
|
+
location: skill.location,
|
|
1358
|
+
basePath: skill.basePath
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
async listPromptsSafe(client) {
|
|
1362
|
+
try {
|
|
1363
|
+
return (await client.listPrompts()).map((prompt) => ({
|
|
1364
|
+
name: prompt.name,
|
|
1365
|
+
description: prompt.description,
|
|
1366
|
+
arguments: prompt.arguments?.map((arg) => ({ ...arg }))
|
|
1367
|
+
}));
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
console.error(`${LOG_PREFIX_SKILL_DETECTION} Failed to list prompts from ${client.serverName}: ${toErrorMessage(error)}`);
|
|
1370
|
+
return [];
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
async listResourcesSafe(client) {
|
|
1374
|
+
try {
|
|
1375
|
+
return (await client.listResources()).map((resource) => ({
|
|
1376
|
+
uri: resource.uri,
|
|
1377
|
+
name: resource.name,
|
|
1378
|
+
description: resource.description,
|
|
1379
|
+
mimeType: resource.mimeType
|
|
1380
|
+
}));
|
|
1381
|
+
} catch (error) {
|
|
1382
|
+
console.error(`${LOG_PREFIX_CAPABILITY_DISCOVERY} Failed to list resources from ${client.serverName}: ${toErrorMessage(error)}`);
|
|
1383
|
+
return [];
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
async collectPromptSkillsForClient(client, prompts) {
|
|
1387
|
+
const configuredPromptNames = new Set(client.prompts ? Object.keys(client.prompts) : []);
|
|
1388
|
+
const promptSkills = [];
|
|
1389
|
+
if (client.prompts) {
|
|
1390
|
+
for (const [promptName, promptConfig] of Object.entries(client.prompts)) if (promptConfig.skill) promptSkills.push({
|
|
1391
|
+
promptName,
|
|
1392
|
+
skill: { ...promptConfig.skill }
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
const autoDetectedSkills = await Promise.all(prompts.map(async (prompt) => {
|
|
1396
|
+
if (configuredPromptNames.has(prompt.name)) return null;
|
|
1397
|
+
try {
|
|
1398
|
+
const skillExtraction = extractSkillFrontMatter((await client.getPrompt(prompt.name)).messages?.map((message) => {
|
|
1399
|
+
const content = message.content;
|
|
1400
|
+
if (typeof content === "string") return content;
|
|
1401
|
+
if (content && typeof content === "object" && "text" in content) return String(content.text);
|
|
1402
|
+
return "";
|
|
1403
|
+
}).join("\n") || "");
|
|
1404
|
+
if (!skillExtraction) return null;
|
|
1405
|
+
return {
|
|
1406
|
+
promptName: prompt.name,
|
|
1407
|
+
skill: skillExtraction.skill,
|
|
1408
|
+
autoDetected: true
|
|
1409
|
+
};
|
|
1410
|
+
} catch (error) {
|
|
1411
|
+
console.error(`${LOG_PREFIX_SKILL_DETECTION} Failed to fetch prompt '${prompt.name}' from ${client.serverName}: ${toErrorMessage(error)}`);
|
|
1412
|
+
return null;
|
|
1413
|
+
}
|
|
1414
|
+
}));
|
|
1415
|
+
for (const autoDetectedSkill of autoDetectedSkills) if (autoDetectedSkill) promptSkills.push(autoDetectedSkill);
|
|
1416
|
+
return promptSkills;
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
|
|
1420
|
+
//#endregion
|
|
1421
|
+
//#region src/services/McpClientManagerService.ts
|
|
1422
|
+
/** Default connection timeout in milliseconds (30 seconds) */
|
|
1423
|
+
const DEFAULT_CONNECTION_TIMEOUT_MS = 3e4;
|
|
1424
|
+
/**
|
|
1425
|
+
* MCP Client wrapper for managing individual server connections
|
|
1426
|
+
* This is an internal class used by McpClientManagerService
|
|
1427
|
+
*/
|
|
1428
|
+
var McpClient = class {
|
|
1429
|
+
serverName;
|
|
1430
|
+
serverInstruction;
|
|
1431
|
+
toolBlacklist;
|
|
1432
|
+
omitToolDescription;
|
|
1433
|
+
prompts;
|
|
1434
|
+
transport;
|
|
1435
|
+
client;
|
|
1436
|
+
childProcess;
|
|
1437
|
+
connected = false;
|
|
1438
|
+
constructor(serverName, transport, client, config) {
|
|
1439
|
+
this.serverName = serverName;
|
|
1440
|
+
this.serverInstruction = config.instruction;
|
|
1441
|
+
this.toolBlacklist = config.toolBlacklist;
|
|
1442
|
+
this.omitToolDescription = config.omitToolDescription;
|
|
1443
|
+
this.prompts = config.prompts;
|
|
1444
|
+
this.transport = transport;
|
|
1445
|
+
this.client = client;
|
|
1446
|
+
}
|
|
1447
|
+
setChildProcess(process$1) {
|
|
1448
|
+
this.childProcess = process$1;
|
|
1449
|
+
}
|
|
1450
|
+
setConnected(connected) {
|
|
1451
|
+
this.connected = connected;
|
|
1452
|
+
}
|
|
1453
|
+
async listTools() {
|
|
1454
|
+
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
1455
|
+
return (await this.client.listTools()).tools;
|
|
1456
|
+
}
|
|
1457
|
+
async listResources() {
|
|
1458
|
+
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
1459
|
+
return (await this.client.listResources()).resources;
|
|
1460
|
+
}
|
|
1461
|
+
async listPrompts() {
|
|
1462
|
+
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
1463
|
+
return (await this.client.listPrompts()).prompts;
|
|
1464
|
+
}
|
|
1465
|
+
async callTool(name, args) {
|
|
1466
|
+
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
1467
|
+
return await this.client.callTool({
|
|
1468
|
+
name,
|
|
1469
|
+
arguments: args
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
async readResource(uri) {
|
|
1473
|
+
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
1474
|
+
return await this.client.readResource({ uri });
|
|
1475
|
+
}
|
|
1476
|
+
async getPrompt(name, args) {
|
|
1477
|
+
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
1478
|
+
return await this.client.getPrompt({
|
|
1479
|
+
name,
|
|
1480
|
+
arguments: args
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
async close() {
|
|
1484
|
+
if (this.childProcess) this.childProcess.kill();
|
|
1485
|
+
await this.client.close();
|
|
1486
|
+
this.connected = false;
|
|
1487
|
+
}
|
|
1488
|
+
};
|
|
1489
|
+
/**
|
|
1490
|
+
* Service for managing MCP client connections to remote servers
|
|
1491
|
+
*/
|
|
1492
|
+
var McpClientManagerService = class {
|
|
1493
|
+
clients = /* @__PURE__ */ new Map();
|
|
1494
|
+
serverConfigs = /* @__PURE__ */ new Map();
|
|
1495
|
+
connectionPromises = /* @__PURE__ */ new Map();
|
|
1496
|
+
constructor() {
|
|
1497
|
+
process.on("exit", () => {
|
|
1498
|
+
this.cleanupOnExit();
|
|
1499
|
+
});
|
|
1500
|
+
process.on("SIGINT", () => {
|
|
1501
|
+
this.cleanupOnExit();
|
|
1502
|
+
process.exit(0);
|
|
1503
|
+
});
|
|
1504
|
+
process.on("SIGTERM", () => {
|
|
1505
|
+
this.cleanupOnExit();
|
|
1506
|
+
process.exit(0);
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Cleanup all resources on exit (child processes)
|
|
1511
|
+
*/
|
|
1512
|
+
cleanupOnExit() {
|
|
1513
|
+
for (const [serverName, client] of this.clients) try {
|
|
1514
|
+
const childProcess = client["childProcess"];
|
|
1515
|
+
if (childProcess && !childProcess.killed) {
|
|
1516
|
+
console.error(`Killing stdio MCP server: ${serverName} (PID: ${childProcess.pid})`);
|
|
1517
|
+
childProcess.kill("SIGTERM");
|
|
1518
|
+
setTimeout(() => {
|
|
1519
|
+
if (!childProcess.killed) {
|
|
1520
|
+
console.error(`Force killing stdio MCP server: ${serverName} (PID: ${childProcess.pid})`);
|
|
1521
|
+
childProcess.kill("SIGKILL");
|
|
1522
|
+
}
|
|
1523
|
+
}, 1e3);
|
|
1524
|
+
}
|
|
1525
|
+
} catch (error) {
|
|
1526
|
+
console.error(`Failed to kill child process for ${serverName}:`, error);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* Connect to an MCP server based on its configuration with timeout
|
|
1531
|
+
* Uses the timeout from server config, falling back to default (30s)
|
|
1532
|
+
*/
|
|
1533
|
+
async connectToServer(serverName, config) {
|
|
1534
|
+
this.serverConfigs.set(serverName, config);
|
|
1535
|
+
await this.ensureConnected(serverName);
|
|
1536
|
+
}
|
|
1537
|
+
registerServerConfigs(configs) {
|
|
1538
|
+
for (const [serverName, config] of Object.entries(configs)) this.serverConfigs.set(serverName, config);
|
|
1539
|
+
}
|
|
1540
|
+
getKnownServerNames() {
|
|
1541
|
+
return Array.from(this.serverConfigs.keys());
|
|
1542
|
+
}
|
|
1543
|
+
async ensureConnected(serverName) {
|
|
1544
|
+
const existingClient = this.clients.get(serverName);
|
|
1545
|
+
if (existingClient) return existingClient;
|
|
1546
|
+
const inflightConnection = this.connectionPromises.get(serverName);
|
|
1547
|
+
if (inflightConnection) return await inflightConnection;
|
|
1548
|
+
const config = this.serverConfigs.get(serverName);
|
|
1549
|
+
if (!config) throw new Error(`No configuration found for server "${serverName}"`);
|
|
1550
|
+
const connectionPromise = this.createConnection(serverName, config);
|
|
1551
|
+
this.connectionPromises.set(serverName, connectionPromise);
|
|
1552
|
+
try {
|
|
1553
|
+
return await connectionPromise;
|
|
1554
|
+
} finally {
|
|
1555
|
+
this.connectionPromises.delete(serverName);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
async createConnection(serverName, config) {
|
|
1559
|
+
const timeoutMs = config.timeout ?? DEFAULT_CONNECTION_TIMEOUT_MS;
|
|
1560
|
+
const client = new Client({
|
|
1561
|
+
name: `@agiflowai/one-mcp-client`,
|
|
1562
|
+
version: "0.1.0"
|
|
1563
|
+
}, { capabilities: {} });
|
|
1564
|
+
const mcpClient = new McpClient(serverName, config.transport, client, {
|
|
1565
|
+
instruction: config.instruction,
|
|
1566
|
+
toolBlacklist: config.toolBlacklist,
|
|
1567
|
+
omitToolDescription: config.omitToolDescription,
|
|
1568
|
+
prompts: config.prompts
|
|
1569
|
+
});
|
|
1570
|
+
try {
|
|
1571
|
+
await Promise.race([this.performConnection(mcpClient, config), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error(`Connection timeout after ${timeoutMs}ms`)), timeoutMs))]);
|
|
1572
|
+
mcpClient.setConnected(true);
|
|
1573
|
+
if (!mcpClient.serverInstruction) try {
|
|
1574
|
+
const serverInstruction = mcpClient["client"].getInstructions();
|
|
1575
|
+
if (serverInstruction) mcpClient.serverInstruction = serverInstruction;
|
|
1576
|
+
} catch (error) {
|
|
1577
|
+
console.error(`Failed to get server instruction from ${serverName}:`, error);
|
|
1578
|
+
}
|
|
1579
|
+
this.clients.set(serverName, mcpClient);
|
|
1580
|
+
return mcpClient;
|
|
1581
|
+
} catch (error) {
|
|
1582
|
+
await mcpClient.close();
|
|
1583
|
+
throw error;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
/**
|
|
1587
|
+
* Perform the actual connection to MCP server
|
|
1588
|
+
*/
|
|
1589
|
+
async performConnection(mcpClient, config) {
|
|
1590
|
+
if (config.transport === "stdio") await this.connectStdioClient(mcpClient, config.config);
|
|
1591
|
+
else if (config.transport === "http") await this.connectHttpClient(mcpClient, config.config);
|
|
1592
|
+
else if (config.transport === "sse") await this.connectSseClient(mcpClient, config.config);
|
|
1593
|
+
else throw new Error(`Unsupported transport type: ${config.transport}`);
|
|
1594
|
+
}
|
|
1595
|
+
async connectStdioClient(mcpClient, config) {
|
|
1596
|
+
const transport = new StdioClientTransport({
|
|
1597
|
+
command: config.command,
|
|
1598
|
+
args: config.args,
|
|
1599
|
+
env: {
|
|
1600
|
+
...process.env,
|
|
1601
|
+
...config.env ?? {}
|
|
1602
|
+
}
|
|
1603
|
+
});
|
|
1604
|
+
await mcpClient["client"].connect(transport);
|
|
1605
|
+
const childProcess = transport["_process"];
|
|
1606
|
+
if (childProcess) mcpClient.setChildProcess(childProcess);
|
|
1607
|
+
}
|
|
1608
|
+
async connectHttpClient(mcpClient, config) {
|
|
1609
|
+
const transport = new StreamableHTTPClientTransport(new URL(config.url), { requestInit: config.headers ? { headers: config.headers } : void 0 });
|
|
1610
|
+
await mcpClient["client"].connect(transport);
|
|
1611
|
+
}
|
|
1612
|
+
async connectSseClient(mcpClient, config) {
|
|
1613
|
+
const transport = new SSEClientTransport(new URL(config.url));
|
|
1614
|
+
await mcpClient["client"].connect(transport);
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Get a connected client by server name
|
|
1618
|
+
*/
|
|
1619
|
+
getClient(serverName) {
|
|
1620
|
+
return this.clients.get(serverName);
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Get all connected clients
|
|
1624
|
+
*/
|
|
1625
|
+
getAllClients() {
|
|
1626
|
+
return Array.from(this.clients.values());
|
|
1627
|
+
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Disconnect from a specific server
|
|
1630
|
+
*/
|
|
1631
|
+
async disconnectServer(serverName) {
|
|
1632
|
+
const client = this.clients.get(serverName);
|
|
1633
|
+
if (client) {
|
|
1634
|
+
await client.close();
|
|
1635
|
+
this.clients.delete(serverName);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* Disconnect from all servers
|
|
1640
|
+
*/
|
|
1641
|
+
async disconnectAll() {
|
|
1642
|
+
const disconnectPromises = Array.from(this.clients.values()).map((client) => client.close());
|
|
1643
|
+
await Promise.all(disconnectPromises);
|
|
1644
|
+
this.clients.clear();
|
|
1645
|
+
this.connectionPromises.clear();
|
|
1646
|
+
}
|
|
1647
|
+
/**
|
|
1648
|
+
* Check if a server is connected
|
|
1649
|
+
*/
|
|
1650
|
+
isConnected(serverName) {
|
|
1651
|
+
return this.clients.has(serverName);
|
|
1652
|
+
}
|
|
1653
|
+
};
|
|
1292
1654
|
|
|
1293
1655
|
//#endregion
|
|
1294
1656
|
//#region src/services/SkillService.ts
|
|
@@ -1362,6 +1724,8 @@ var SkillService = class {
|
|
|
1362
1724
|
skillsByName = null;
|
|
1363
1725
|
/** Active file watchers for skill directories */
|
|
1364
1726
|
watchers = [];
|
|
1727
|
+
/** Polling timers used when native file watching is unavailable */
|
|
1728
|
+
pollingTimers = [];
|
|
1365
1729
|
/** Callback invoked when cache is invalidated due to file changes */
|
|
1366
1730
|
onCacheInvalidated;
|
|
1367
1731
|
/**
|
|
@@ -1446,7 +1810,13 @@ var SkillService = class {
|
|
|
1446
1810
|
const abortController = new AbortController();
|
|
1447
1811
|
this.watchers.push(abortController);
|
|
1448
1812
|
this.watchDirectory(skillsDir, abortController.signal).catch((error) => {
|
|
1449
|
-
if (error?.name !== "AbortError")
|
|
1813
|
+
if (error?.name !== "AbortError") {
|
|
1814
|
+
if (this.isWatchResourceLimitError(error)) {
|
|
1815
|
+
this.startPollingDirectory(skillsDir, abortController.signal);
|
|
1816
|
+
return;
|
|
1817
|
+
}
|
|
1818
|
+
console.error(`[skill-watcher] Error watching ${skillsDir}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1819
|
+
}
|
|
1450
1820
|
});
|
|
1451
1821
|
}
|
|
1452
1822
|
}
|
|
@@ -1457,6 +1827,8 @@ var SkillService = class {
|
|
|
1457
1827
|
stopWatching() {
|
|
1458
1828
|
for (const controller of this.watchers) controller.abort();
|
|
1459
1829
|
this.watchers = [];
|
|
1830
|
+
for (const timer of this.pollingTimers) clearInterval(timer);
|
|
1831
|
+
this.pollingTimers = [];
|
|
1460
1832
|
}
|
|
1461
1833
|
/**
|
|
1462
1834
|
* Watches a directory for changes to SKILL.md files.
|
|
@@ -1468,10 +1840,66 @@ var SkillService = class {
|
|
|
1468
1840
|
recursive: true,
|
|
1469
1841
|
signal
|
|
1470
1842
|
});
|
|
1471
|
-
for await (const event of watcher) if (event.filename?.endsWith("SKILL.md"))
|
|
1472
|
-
|
|
1473
|
-
|
|
1843
|
+
for await (const event of watcher) if (event.filename?.endsWith("SKILL.md")) this.invalidateCache();
|
|
1844
|
+
}
|
|
1845
|
+
invalidateCache() {
|
|
1846
|
+
this.clearCache();
|
|
1847
|
+
this.onCacheInvalidated?.();
|
|
1848
|
+
}
|
|
1849
|
+
isWatchResourceLimitError(error) {
|
|
1850
|
+
return error instanceof Error && "code" in error && (error.code === "EMFILE" || error.code === "ENOSPC");
|
|
1851
|
+
}
|
|
1852
|
+
startPollingDirectory(dirPath, signal) {
|
|
1853
|
+
this.createSkillSnapshot(dirPath).then((initialSnapshot) => {
|
|
1854
|
+
let previousSnapshot = initialSnapshot;
|
|
1855
|
+
const timer = setInterval(() => {
|
|
1856
|
+
if (signal.aborted) {
|
|
1857
|
+
clearInterval(timer);
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
this.createSkillSnapshot(dirPath).then((nextSnapshot) => {
|
|
1861
|
+
if (!this.snapshotsEqual(previousSnapshot, nextSnapshot)) {
|
|
1862
|
+
previousSnapshot = nextSnapshot;
|
|
1863
|
+
this.invalidateCache();
|
|
1864
|
+
}
|
|
1865
|
+
}).catch((error) => {
|
|
1866
|
+
console.error(`[skill-watcher] Polling failed for ${dirPath}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1867
|
+
});
|
|
1868
|
+
}, 100);
|
|
1869
|
+
this.pollingTimers.push(timer);
|
|
1870
|
+
signal.addEventListener("abort", () => {
|
|
1871
|
+
clearInterval(timer);
|
|
1872
|
+
this.pollingTimers = this.pollingTimers.filter((activeTimer) => activeTimer !== timer);
|
|
1873
|
+
}, { once: true });
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
async createSkillSnapshot(dirPath) {
|
|
1877
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
1878
|
+
await this.collectSkillSnapshots(dirPath, snapshot);
|
|
1879
|
+
return snapshot;
|
|
1880
|
+
}
|
|
1881
|
+
async collectSkillSnapshots(dirPath, snapshot) {
|
|
1882
|
+
let entries;
|
|
1883
|
+
try {
|
|
1884
|
+
entries = await readdir(dirPath);
|
|
1885
|
+
} catch (error) {
|
|
1886
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return;
|
|
1887
|
+
throw error;
|
|
1474
1888
|
}
|
|
1889
|
+
await Promise.all(entries.map(async (entry) => {
|
|
1890
|
+
const entryPath = join(dirPath, entry);
|
|
1891
|
+
const entryStat = await stat(entryPath);
|
|
1892
|
+
if (entryStat.isDirectory()) {
|
|
1893
|
+
await this.collectSkillSnapshots(entryPath, snapshot);
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
if (entry === "SKILL.md") snapshot.set(entryPath, entryStat.mtimeMs);
|
|
1897
|
+
}));
|
|
1898
|
+
}
|
|
1899
|
+
snapshotsEqual(left, right) {
|
|
1900
|
+
if (left.size !== right.size) return false;
|
|
1901
|
+
for (const [filePath, mtimeMs] of left) if (right.get(filePath) !== mtimeMs) return false;
|
|
1902
|
+
return true;
|
|
1475
1903
|
}
|
|
1476
1904
|
/**
|
|
1477
1905
|
* Load skills from a directory.
|
|
@@ -1579,32 +2007,6 @@ var SkillService = class {
|
|
|
1579
2007
|
}
|
|
1580
2008
|
};
|
|
1581
2009
|
|
|
1582
|
-
//#endregion
|
|
1583
|
-
//#region src/constants/index.ts
|
|
1584
|
-
/**
|
|
1585
|
-
* Shared constants for one-mcp package
|
|
1586
|
-
*/
|
|
1587
|
-
/**
|
|
1588
|
-
* Prefix added to skill names when they clash with MCP tool names.
|
|
1589
|
-
* This ensures skills can be uniquely identified even when a tool has the same name.
|
|
1590
|
-
*/
|
|
1591
|
-
const SKILL_PREFIX = "skill__";
|
|
1592
|
-
/**
|
|
1593
|
-
* Log prefix for skill detection messages.
|
|
1594
|
-
* Used to easily filter skill detection logs in stderr output.
|
|
1595
|
-
*/
|
|
1596
|
-
const LOG_PREFIX_SKILL_DETECTION = "[skill-detection]";
|
|
1597
|
-
/**
|
|
1598
|
-
* Prefix for prompt-based skill locations.
|
|
1599
|
-
* Format: "prompt:{serverName}:{promptName}"
|
|
1600
|
-
*/
|
|
1601
|
-
const PROMPT_LOCATION_PREFIX = "prompt:";
|
|
1602
|
-
/**
|
|
1603
|
-
* Default server ID used when no ID is provided via CLI or config.
|
|
1604
|
-
* This fallback is used when auto-generation also fails.
|
|
1605
|
-
*/
|
|
1606
|
-
const DEFAULT_SERVER_ID = "unknown";
|
|
1607
|
-
|
|
1608
2010
|
//#endregion
|
|
1609
2011
|
//#region src/templates/toolkit-description.liquid?raw
|
|
1610
2012
|
var toolkit_description_default = "<toolkit id=\"{{ serverId }}\">\n<instruction>\nBefore you use any capabilities below, you MUST call this tool with a list of names to learn how to use them properly; this includes:\n- For tools: Arguments schema needed to pass to use_tool\n- For skills: Detailed instructions that will expand when invoked (Prefer to be explored first when relevant)\n\nThis tool is optimized for batch queries - you can request multiple capabilities at once for better performance.\n\nHow to invoke:\n- For MCP tools: Use use_tool with toolName and toolArgs based on the schema\n- For skills: Use this tool with the skill name to get expanded instructions\n</instruction>\n\n<available_capabilities>\n{% for server in servers -%}\n<group name=\"{{ server.name }}\">\n{% if server.instruction -%}\n<group_instruction>{{ server.instruction }}</group_instruction>\n{% endif -%}\n{% if server.omitToolDescription -%}\n{% for toolName in server.toolNames -%}\n<item name=\"{{ toolName }}\"></item>\n{% endfor -%}\n{% else -%}\n{% for tool in server.tools -%}\n<item name=\"{{ tool.displayName }}\"><description>{{ tool.description | default: \"No description\" }}</description></item>\n{% endfor -%}\n{% endif -%}\n</group>\n{% endfor -%}\n{% if skills.size > 0 -%}\n<group name=\"skills\">\n{% for skill in skills -%}\n<item name=\"{{ skill.displayName }}\"><description>{{ skill.description }}</description></item>\n{% endfor -%}\n</group>\n{% endif -%}\n</available_capabilities>\n</toolkit>\n";
|
|
@@ -1643,6 +2045,7 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1643
2045
|
static TOOL_NAME = "describe_tools";
|
|
1644
2046
|
clientManager;
|
|
1645
2047
|
skillService;
|
|
2048
|
+
definitionsCacheService;
|
|
1646
2049
|
liquid = new Liquid();
|
|
1647
2050
|
/** Cache for auto-detected skills from prompt front-matter */
|
|
1648
2051
|
autoDetectedSkillsCache = null;
|
|
@@ -1654,10 +2057,11 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1654
2057
|
* @param skillService - Optional skill service for loading skills
|
|
1655
2058
|
* @param serverId - Unique server identifier for this one-mcp instance
|
|
1656
2059
|
*/
|
|
1657
|
-
constructor(clientManager, skillService, serverId) {
|
|
2060
|
+
constructor(clientManager, skillService, serverId, definitionsCacheService) {
|
|
1658
2061
|
this.clientManager = clientManager;
|
|
1659
2062
|
this.skillService = skillService;
|
|
1660
2063
|
this.serverId = serverId || DEFAULT_SERVER_ID;
|
|
2064
|
+
this.definitionsCacheService = definitionsCacheService || new DefinitionsCacheService(clientManager, skillService);
|
|
1661
2065
|
}
|
|
1662
2066
|
/**
|
|
1663
2067
|
* Clears the cached auto-detected skills from prompt front-matter.
|
|
@@ -1666,6 +2070,7 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1666
2070
|
*/
|
|
1667
2071
|
clearAutoDetectedSkillsCache() {
|
|
1668
2072
|
this.autoDetectedSkillsCache = null;
|
|
2073
|
+
this.definitionsCacheService.clearLiveCache();
|
|
1669
2074
|
}
|
|
1670
2075
|
/**
|
|
1671
2076
|
* Detects and caches skills from prompt front-matter across all connected MCP servers.
|
|
@@ -1731,21 +2136,12 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1731
2136
|
* @returns Array of skill template data derived from prompts
|
|
1732
2137
|
*/
|
|
1733
2138
|
async collectPromptSkills() {
|
|
1734
|
-
const clients = this.clientManager.getAllClients();
|
|
1735
2139
|
const promptSkills = [];
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
description: promptConfig.skill.description
|
|
1742
|
-
});
|
|
1743
|
-
}
|
|
1744
|
-
const autoDetectedSkills = await this.detectSkillsFromPromptFrontMatter();
|
|
1745
|
-
for (const autoSkill of autoDetectedSkills) promptSkills.push({
|
|
1746
|
-
name: autoSkill.skill.name,
|
|
1747
|
-
displayName: autoSkill.skill.name,
|
|
1748
|
-
description: autoSkill.skill.description
|
|
2140
|
+
const serverDefinitions = await this.definitionsCacheService.getServerDefinitions();
|
|
2141
|
+
for (const serverDefinition of serverDefinitions) for (const promptSkill of serverDefinition.promptSkills) promptSkills.push({
|
|
2142
|
+
name: promptSkill.skill.name,
|
|
2143
|
+
displayName: promptSkill.skill.name,
|
|
2144
|
+
description: promptSkill.skill.description
|
|
1749
2145
|
});
|
|
1750
2146
|
return promptSkills;
|
|
1751
2147
|
}
|
|
@@ -1758,22 +2154,7 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1758
2154
|
*/
|
|
1759
2155
|
async findPromptSkill(skillName) {
|
|
1760
2156
|
if (!skillName) return void 0;
|
|
1761
|
-
|
|
1762
|
-
for (const client of clients) {
|
|
1763
|
-
if (!client.prompts) continue;
|
|
1764
|
-
for (const [promptName, promptConfig] of Object.entries(client.prompts)) if (promptConfig.skill && promptConfig.skill.name === skillName) return {
|
|
1765
|
-
serverName: client.serverName,
|
|
1766
|
-
promptName,
|
|
1767
|
-
skill: promptConfig.skill
|
|
1768
|
-
};
|
|
1769
|
-
}
|
|
1770
|
-
const autoDetectedSkills = await this.detectSkillsFromPromptFrontMatter();
|
|
1771
|
-
for (const autoSkill of autoDetectedSkills) if (autoSkill.skill.name === skillName) return {
|
|
1772
|
-
serverName: autoSkill.serverName,
|
|
1773
|
-
promptName: autoSkill.promptName,
|
|
1774
|
-
skill: autoSkill.skill,
|
|
1775
|
-
autoDetected: true
|
|
1776
|
-
};
|
|
2157
|
+
return await this.definitionsCacheService.getPromptSkillByName(skillName);
|
|
1777
2158
|
}
|
|
1778
2159
|
/**
|
|
1779
2160
|
* Retrieves skill content from a prompt-based skill configuration.
|
|
@@ -1786,13 +2167,8 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1786
2167
|
async getPromptSkillContent(skillName) {
|
|
1787
2168
|
const promptSkill = await this.findPromptSkill(skillName);
|
|
1788
2169
|
if (!promptSkill) return void 0;
|
|
1789
|
-
const client = this.clientManager.getClient(promptSkill.serverName);
|
|
1790
|
-
if (!client) {
|
|
1791
|
-
console.error(`Client not found for server '${promptSkill.serverName}' when fetching prompt skill '${skillName}'`);
|
|
1792
|
-
return;
|
|
1793
|
-
}
|
|
1794
2170
|
try {
|
|
1795
|
-
const rawInstructions = (await
|
|
2171
|
+
const rawInstructions = (await (await this.clientManager.ensureConnected(promptSkill.serverName)).getPrompt(promptSkill.promptName)).messages?.map((m) => {
|
|
1796
2172
|
const content = m.content;
|
|
1797
2173
|
if (typeof content === "string") return content;
|
|
1798
2174
|
if (content && typeof content === "object" && "text" in content) return String(content.text);
|
|
@@ -1821,24 +2197,12 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1821
2197
|
* @returns Object with rendered description and set of all tool names
|
|
1822
2198
|
*/
|
|
1823
2199
|
async buildToolkitDescription() {
|
|
1824
|
-
const
|
|
2200
|
+
const serverDefinitions = await this.definitionsCacheService.getServerDefinitions();
|
|
1825
2201
|
const toolToServers = /* @__PURE__ */ new Map();
|
|
1826
|
-
const
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
const blacklist = new Set(client.toolBlacklist || []);
|
|
1831
|
-
const filteredTools = tools.filter((t) => !blacklist.has(t.name));
|
|
1832
|
-
serverToolsMap.set(client.serverName, filteredTools);
|
|
1833
|
-
for (const tool of filteredTools) {
|
|
1834
|
-
if (!toolToServers.has(tool.name)) toolToServers.set(tool.name, []);
|
|
1835
|
-
toolToServers.get(tool.name)?.push(client.serverName);
|
|
1836
|
-
}
|
|
1837
|
-
} catch (error) {
|
|
1838
|
-
console.error(`Failed to list tools from ${client.serverName}:`, error);
|
|
1839
|
-
serverToolsMap.set(client.serverName, []);
|
|
1840
|
-
}
|
|
1841
|
-
}));
|
|
2202
|
+
for (const serverDefinition of serverDefinitions) for (const tool of serverDefinition.tools) {
|
|
2203
|
+
if (!toolToServers.has(tool.name)) toolToServers.set(tool.name, []);
|
|
2204
|
+
toolToServers.get(tool.name)?.push(serverDefinition.serverName);
|
|
2205
|
+
}
|
|
1842
2206
|
/**
|
|
1843
2207
|
* Formats tool name with server prefix if the tool exists on multiple servers
|
|
1844
2208
|
*/
|
|
@@ -1846,21 +2210,25 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1846
2210
|
return (toolToServers.get(toolName) || []).length > 1 ? `${serverName}__${toolName}` : toolName;
|
|
1847
2211
|
};
|
|
1848
2212
|
const allToolNames = /* @__PURE__ */ new Set();
|
|
1849
|
-
const servers =
|
|
1850
|
-
const formattedTools =
|
|
1851
|
-
displayName: formatToolName(t.name,
|
|
2213
|
+
const servers = serverDefinitions.map((serverDefinition) => {
|
|
2214
|
+
const formattedTools = serverDefinition.tools.map((t) => ({
|
|
2215
|
+
displayName: formatToolName(t.name, serverDefinition.serverName),
|
|
1852
2216
|
description: t.description
|
|
1853
2217
|
}));
|
|
1854
2218
|
for (const tool of formattedTools) allToolNames.add(tool.displayName);
|
|
1855
2219
|
return {
|
|
1856
|
-
name:
|
|
1857
|
-
instruction:
|
|
1858
|
-
omitToolDescription:
|
|
2220
|
+
name: serverDefinition.serverName,
|
|
2221
|
+
instruction: serverDefinition.serverInstruction,
|
|
2222
|
+
omitToolDescription: serverDefinition.omitToolDescription || false,
|
|
1859
2223
|
tools: formattedTools,
|
|
1860
2224
|
toolNames: formattedTools.map((t) => t.displayName)
|
|
1861
2225
|
};
|
|
1862
2226
|
});
|
|
1863
|
-
const [rawSkills, promptSkills] = await Promise.all([
|
|
2227
|
+
const [rawSkills, cachedSkills, promptSkills] = await Promise.all([
|
|
2228
|
+
this.skillService ? this.skillService.getSkills() : Promise.resolve([]),
|
|
2229
|
+
this.definitionsCacheService.getCachedFileSkills(),
|
|
2230
|
+
this.collectPromptSkills()
|
|
2231
|
+
]);
|
|
1864
2232
|
const seenSkillNames = /* @__PURE__ */ new Set();
|
|
1865
2233
|
const allSkillsData = [];
|
|
1866
2234
|
for (const skill of rawSkills) if (!seenSkillNames.has(skill.name)) {
|
|
@@ -1871,6 +2239,14 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1871
2239
|
description: skill.description
|
|
1872
2240
|
});
|
|
1873
2241
|
}
|
|
2242
|
+
for (const skill of cachedSkills) if (!seenSkillNames.has(skill.name)) {
|
|
2243
|
+
seenSkillNames.add(skill.name);
|
|
2244
|
+
allSkillsData.push({
|
|
2245
|
+
name: skill.name,
|
|
2246
|
+
displayName: skill.name,
|
|
2247
|
+
description: skill.description
|
|
2248
|
+
});
|
|
2249
|
+
}
|
|
1874
2250
|
for (const skill of promptSkills) if (!seenSkillNames.has(skill.name)) {
|
|
1875
2251
|
seenSkillNames.add(skill.name);
|
|
1876
2252
|
allSkillsData.push(skill);
|
|
@@ -1940,7 +2316,7 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1940
2316
|
async execute(input) {
|
|
1941
2317
|
try {
|
|
1942
2318
|
const { toolNames } = input;
|
|
1943
|
-
const
|
|
2319
|
+
const serverDefinitions = await this.definitionsCacheService.getServerDefinitions();
|
|
1944
2320
|
if (!toolNames || toolNames.length === 0) return {
|
|
1945
2321
|
content: [{
|
|
1946
2322
|
type: "text",
|
|
@@ -1950,25 +2326,18 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1950
2326
|
};
|
|
1951
2327
|
const serverToolsMap = /* @__PURE__ */ new Map();
|
|
1952
2328
|
const toolToServers = /* @__PURE__ */ new Map();
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
for (const tool of typedTools) {
|
|
1964
|
-
if (!toolToServers.has(tool.name)) toolToServers.set(tool.name, []);
|
|
1965
|
-
toolToServers.get(tool.name)?.push(client.serverName);
|
|
1966
|
-
}
|
|
1967
|
-
} catch (error) {
|
|
1968
|
-
console.error(`Failed to list tools from ${client.serverName}:`, error);
|
|
1969
|
-
serverToolsMap.set(client.serverName, []);
|
|
2329
|
+
for (const serverDefinition of serverDefinitions) {
|
|
2330
|
+
const typedTools = serverDefinition.tools.map((tool) => ({
|
|
2331
|
+
name: tool.name,
|
|
2332
|
+
description: tool.description,
|
|
2333
|
+
inputSchema: tool.inputSchema
|
|
2334
|
+
}));
|
|
2335
|
+
serverToolsMap.set(serverDefinition.serverName, typedTools);
|
|
2336
|
+
for (const tool of typedTools) {
|
|
2337
|
+
if (!toolToServers.has(tool.name)) toolToServers.set(tool.name, []);
|
|
2338
|
+
toolToServers.get(tool.name)?.push(serverDefinition.serverName);
|
|
1970
2339
|
}
|
|
1971
|
-
}
|
|
2340
|
+
}
|
|
1972
2341
|
const lookupResults = await Promise.all(toolNames.map(async (requestedName) => {
|
|
1973
2342
|
const result$1 = {
|
|
1974
2343
|
tools: [],
|
|
@@ -2106,6 +2475,95 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
2106
2475
|
}
|
|
2107
2476
|
};
|
|
2108
2477
|
|
|
2478
|
+
//#endregion
|
|
2479
|
+
//#region src/utils/toolCapabilities.ts
|
|
2480
|
+
const TOOL_CAPABILITIES_META_KEY = "agiflowai/capabilities";
|
|
2481
|
+
function getToolCapabilities(tool) {
|
|
2482
|
+
const rawCapabilities = tool._meta?.[TOOL_CAPABILITIES_META_KEY];
|
|
2483
|
+
if (!Array.isArray(rawCapabilities)) return [];
|
|
2484
|
+
return rawCapabilities.filter((value) => typeof value === "string");
|
|
2485
|
+
}
|
|
2486
|
+
function getUniqueSortedCapabilities(tools) {
|
|
2487
|
+
return Array.from(new Set(tools.flatMap((tool) => getToolCapabilities(tool)))).sort();
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
//#endregion
|
|
2491
|
+
//#region src/tools/SearchListToolsTool.ts
|
|
2492
|
+
var SearchListToolsTool = class SearchListToolsTool {
|
|
2493
|
+
static TOOL_NAME = "list_tools";
|
|
2494
|
+
constructor(_clientManager, definitionsCacheService) {
|
|
2495
|
+
this._clientManager = _clientManager;
|
|
2496
|
+
this.definitionsCacheService = definitionsCacheService;
|
|
2497
|
+
}
|
|
2498
|
+
formatToolName(toolName, serverName, toolToServers) {
|
|
2499
|
+
return (toolToServers.get(toolName) || []).length > 1 ? `${serverName}__${toolName}` : toolName;
|
|
2500
|
+
}
|
|
2501
|
+
async getDefinition() {
|
|
2502
|
+
const serverDefinitions = await this.definitionsCacheService.getServerDefinitions();
|
|
2503
|
+
const capabilitySummary = serverDefinitions.length > 0 ? serverDefinitions.map((server) => {
|
|
2504
|
+
const capabilities = getUniqueSortedCapabilities(server.tools);
|
|
2505
|
+
const summary = capabilities.length > 0 ? capabilities.join(", ") : server.serverInstruction || "No capability summary available";
|
|
2506
|
+
return `${server.serverName}: ${summary}`;
|
|
2507
|
+
}).join("\n") : "No proxied servers available.";
|
|
2508
|
+
return {
|
|
2509
|
+
name: SearchListToolsTool.TOOL_NAME,
|
|
2510
|
+
description: `Search proxied MCP tools by server capability summary.\n\nAvailable capabilities:\n${capabilitySummary}`,
|
|
2511
|
+
inputSchema: {
|
|
2512
|
+
type: "object",
|
|
2513
|
+
properties: {
|
|
2514
|
+
capability: {
|
|
2515
|
+
type: "string",
|
|
2516
|
+
description: "Optional capability filter. Matches explicit capability tags first, then server summaries, server names, tool names, and tool descriptions."
|
|
2517
|
+
},
|
|
2518
|
+
serverName: {
|
|
2519
|
+
type: "string",
|
|
2520
|
+
description: "Optional server name filter."
|
|
2521
|
+
}
|
|
2522
|
+
},
|
|
2523
|
+
additionalProperties: false
|
|
2524
|
+
}
|
|
2525
|
+
};
|
|
2526
|
+
}
|
|
2527
|
+
async execute(input) {
|
|
2528
|
+
const serverDefinitions = await this.definitionsCacheService.getServerDefinitions();
|
|
2529
|
+
const capabilityFilter = input.capability?.trim().toLowerCase();
|
|
2530
|
+
const serverNameFilter = input.serverName?.trim().toLowerCase();
|
|
2531
|
+
const toolToServers = /* @__PURE__ */ new Map();
|
|
2532
|
+
for (const serverDefinition of serverDefinitions) for (const tool of serverDefinition.tools) {
|
|
2533
|
+
if (!toolToServers.has(tool.name)) toolToServers.set(tool.name, []);
|
|
2534
|
+
toolToServers.get(tool.name)?.push(serverDefinition.serverName);
|
|
2535
|
+
}
|
|
2536
|
+
const filteredServers = serverDefinitions.filter((serverDefinition) => {
|
|
2537
|
+
if (serverNameFilter && serverDefinition.serverName.toLowerCase() !== serverNameFilter) return false;
|
|
2538
|
+
if (!capabilityFilter) return true;
|
|
2539
|
+
if ((serverDefinition.serverInstruction?.toLowerCase() || "").includes(capabilityFilter)) return true;
|
|
2540
|
+
if (getUniqueSortedCapabilities(serverDefinition.tools).some((capability) => capability.toLowerCase().includes(capabilityFilter))) return true;
|
|
2541
|
+
return serverDefinition.tools.some((tool) => {
|
|
2542
|
+
const toolName = this.formatToolName(tool.name, serverDefinition.serverName, toolToServers);
|
|
2543
|
+
const toolCapabilities = getToolCapabilities(tool);
|
|
2544
|
+
return toolName.toLowerCase().includes(capabilityFilter) || (tool.description || "").toLowerCase().includes(capabilityFilter) || toolCapabilities.some((capability) => capability.toLowerCase().includes(capabilityFilter));
|
|
2545
|
+
});
|
|
2546
|
+
}).map((serverDefinition) => ({
|
|
2547
|
+
server: serverDefinition.serverName,
|
|
2548
|
+
capabilities: getUniqueSortedCapabilities(serverDefinition.tools),
|
|
2549
|
+
summary: serverDefinition.serverInstruction,
|
|
2550
|
+
tools: serverDefinition.tools.map((tool) => ({
|
|
2551
|
+
name: this.formatToolName(tool.name, serverDefinition.serverName, toolToServers),
|
|
2552
|
+
description: serverDefinition.omitToolDescription ? void 0 : tool.description,
|
|
2553
|
+
capabilities: getToolCapabilities(tool)
|
|
2554
|
+
}))
|
|
2555
|
+
})).filter((server) => server.tools.length > 0);
|
|
2556
|
+
const result = { servers: filteredServers };
|
|
2557
|
+
return {
|
|
2558
|
+
content: [{
|
|
2559
|
+
type: "text",
|
|
2560
|
+
text: JSON.stringify(result, null, 2)
|
|
2561
|
+
}],
|
|
2562
|
+
isError: filteredServers.length === 0 ? true : void 0
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2565
|
+
};
|
|
2566
|
+
|
|
2109
2567
|
//#endregion
|
|
2110
2568
|
//#region src/tools/UseToolTool.ts
|
|
2111
2569
|
/**
|
|
@@ -2125,6 +2583,7 @@ var UseToolTool = class UseToolTool {
|
|
|
2125
2583
|
static TOOL_NAME = "use_tool";
|
|
2126
2584
|
clientManager;
|
|
2127
2585
|
skillService;
|
|
2586
|
+
definitionsCacheService;
|
|
2128
2587
|
/** Unique server identifier for this one-mcp instance */
|
|
2129
2588
|
serverId;
|
|
2130
2589
|
/**
|
|
@@ -2133,9 +2592,10 @@ var UseToolTool = class UseToolTool {
|
|
|
2133
2592
|
* @param skillService - Optional skill service for loading and executing skills
|
|
2134
2593
|
* @param serverId - Unique server identifier for this one-mcp instance
|
|
2135
2594
|
*/
|
|
2136
|
-
constructor(clientManager, skillService, serverId) {
|
|
2595
|
+
constructor(clientManager, skillService, serverId, definitionsCacheService) {
|
|
2137
2596
|
this.clientManager = clientManager;
|
|
2138
2597
|
this.skillService = skillService;
|
|
2598
|
+
this.definitionsCacheService = definitionsCacheService || new DefinitionsCacheService(clientManager, skillService);
|
|
2139
2599
|
this.serverId = serverId || DEFAULT_SERVER_ID;
|
|
2140
2600
|
}
|
|
2141
2601
|
/**
|
|
@@ -2195,17 +2655,9 @@ IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverI
|
|
|
2195
2655
|
* @param skillName - The skill name to search for
|
|
2196
2656
|
* @returns PromptSkillMatch if found, undefined otherwise
|
|
2197
2657
|
*/
|
|
2198
|
-
findPromptSkill(skillName) {
|
|
2658
|
+
async findPromptSkill(skillName) {
|
|
2199
2659
|
if (!skillName) return void 0;
|
|
2200
|
-
|
|
2201
|
-
for (const client of clients) {
|
|
2202
|
-
if (!client.prompts) continue;
|
|
2203
|
-
for (const [promptName, promptConfig] of Object.entries(client.prompts)) if (promptConfig.skill && promptConfig.skill.name === skillName) return {
|
|
2204
|
-
serverName: client.serverName,
|
|
2205
|
-
promptName,
|
|
2206
|
-
skill: promptConfig.skill
|
|
2207
|
-
};
|
|
2208
|
-
}
|
|
2660
|
+
return await this.definitionsCacheService.getPromptSkillByName(skillName);
|
|
2209
2661
|
}
|
|
2210
2662
|
/**
|
|
2211
2663
|
* Returns guidance message for prompt-based skill invocation.
|
|
@@ -2245,7 +2697,7 @@ IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverI
|
|
|
2245
2697
|
const skill = await this.skillService.getSkill(skillName);
|
|
2246
2698
|
if (skill) return this.executeSkill(skill);
|
|
2247
2699
|
}
|
|
2248
|
-
const promptSkill = this.findPromptSkill(skillName);
|
|
2700
|
+
const promptSkill = await this.findPromptSkill(skillName);
|
|
2249
2701
|
if (promptSkill) return this.executePromptSkill(promptSkill);
|
|
2250
2702
|
return {
|
|
2251
2703
|
content: [{
|
|
@@ -2255,53 +2707,34 @@ IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverI
|
|
|
2255
2707
|
isError: true
|
|
2256
2708
|
};
|
|
2257
2709
|
}
|
|
2258
|
-
const
|
|
2710
|
+
const knownServerNames = this.clientManager.getKnownServerNames();
|
|
2259
2711
|
const { serverName, actualToolName } = parseToolName(inputToolName);
|
|
2260
|
-
if (serverName) {
|
|
2261
|
-
const client
|
|
2262
|
-
if (
|
|
2712
|
+
if (serverName) try {
|
|
2713
|
+
const client = await this.clientManager.ensureConnected(serverName);
|
|
2714
|
+
if (client.toolBlacklist?.includes(actualToolName)) return {
|
|
2263
2715
|
content: [{
|
|
2264
2716
|
type: "text",
|
|
2265
|
-
text: `
|
|
2717
|
+
text: `Tool "${actualToolName}" is blacklisted on server "${serverName}" and cannot be executed.`
|
|
2266
2718
|
}],
|
|
2267
2719
|
isError: true
|
|
2268
2720
|
};
|
|
2269
|
-
|
|
2721
|
+
return await client.callTool(actualToolName, toolArgs);
|
|
2722
|
+
} catch (error) {
|
|
2723
|
+
return {
|
|
2270
2724
|
content: [{
|
|
2271
2725
|
type: "text",
|
|
2272
|
-
text: `
|
|
2726
|
+
text: `Failed to call tool "${actualToolName}" on server "${serverName}". Available servers: ${knownServerNames.join(", ")}. ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2273
2727
|
}],
|
|
2274
2728
|
isError: true
|
|
2275
2729
|
};
|
|
2276
|
-
try {
|
|
2277
|
-
return await client$1.callTool(actualToolName, toolArgs);
|
|
2278
|
-
} catch (error) {
|
|
2279
|
-
return {
|
|
2280
|
-
content: [{
|
|
2281
|
-
type: "text",
|
|
2282
|
-
text: `Failed to call tool "${actualToolName}" on server "${serverName}": ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2283
|
-
}],
|
|
2284
|
-
isError: true
|
|
2285
|
-
};
|
|
2286
|
-
}
|
|
2287
2730
|
}
|
|
2288
|
-
const matchingServers =
|
|
2289
|
-
const results = await Promise.all(clients.map(async (client$1) => {
|
|
2290
|
-
try {
|
|
2291
|
-
if (client$1.toolBlacklist?.includes(actualToolName)) return null;
|
|
2292
|
-
if ((await client$1.listTools()).some((t) => t.name === actualToolName)) return client$1.serverName;
|
|
2293
|
-
} catch (error) {
|
|
2294
|
-
console.error(`Failed to list tools from ${client$1.serverName}:`, error);
|
|
2295
|
-
}
|
|
2296
|
-
return null;
|
|
2297
|
-
}));
|
|
2298
|
-
matchingServers.push(...results.filter((r) => r !== null));
|
|
2731
|
+
const matchingServers = await this.definitionsCacheService.getServersForTool(actualToolName);
|
|
2299
2732
|
if (matchingServers.length === 0) {
|
|
2300
2733
|
if (this.skillService) {
|
|
2301
2734
|
const skill = await this.skillService.getSkill(actualToolName);
|
|
2302
2735
|
if (skill) return this.executeSkill(skill);
|
|
2303
2736
|
}
|
|
2304
|
-
const promptSkill = this.findPromptSkill(actualToolName);
|
|
2737
|
+
const promptSkill = await this.findPromptSkill(actualToolName);
|
|
2305
2738
|
if (promptSkill) return this.executePromptSkill(promptSkill);
|
|
2306
2739
|
return {
|
|
2307
2740
|
content: [{
|
|
@@ -2318,17 +2751,9 @@ IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverI
|
|
|
2318
2751
|
}],
|
|
2319
2752
|
isError: true
|
|
2320
2753
|
};
|
|
2321
|
-
const targetServerName = matchingServers[0];
|
|
2322
|
-
const client = this.clientManager.getClient(targetServerName);
|
|
2323
|
-
if (!client) return {
|
|
2324
|
-
content: [{
|
|
2325
|
-
type: "text",
|
|
2326
|
-
text: `Internal error: Server "${targetServerName}" was found but is not connected`
|
|
2327
|
-
}],
|
|
2328
|
-
isError: true
|
|
2329
|
-
};
|
|
2330
2754
|
try {
|
|
2331
|
-
|
|
2755
|
+
const targetServerName = matchingServers[0];
|
|
2756
|
+
return await (await this.clientManager.ensureConnected(targetServerName)).callTool(actualToolName, toolArgs);
|
|
2332
2757
|
} catch (error) {
|
|
2333
2758
|
return {
|
|
2334
2759
|
content: [{
|
|
@@ -2350,6 +2775,10 @@ IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverI
|
|
|
2350
2775
|
}
|
|
2351
2776
|
};
|
|
2352
2777
|
|
|
2778
|
+
//#endregion
|
|
2779
|
+
//#region package.json
|
|
2780
|
+
var version = "0.3.11";
|
|
2781
|
+
|
|
2353
2782
|
//#endregion
|
|
2354
2783
|
//#region src/server/index.ts
|
|
2355
2784
|
/**
|
|
@@ -2365,17 +2794,110 @@ IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverI
|
|
|
2365
2794
|
* - Keep server setup modular and extensible
|
|
2366
2795
|
* - Import tools from ../tools/ and register them in the handlers
|
|
2367
2796
|
*/
|
|
2797
|
+
function summarizeServerTools(serverDefinition) {
|
|
2798
|
+
const toolNames = serverDefinition.tools.map((tool) => tool.name);
|
|
2799
|
+
const capabilities = getUniqueSortedCapabilities(serverDefinition.tools);
|
|
2800
|
+
const capabilitySummary = capabilities.length > 0 ? `; capabilities: ${capabilities.join(", ")}` : "";
|
|
2801
|
+
if (toolNames.length === 0) return `${serverDefinition.serverName} (no tools cached${capabilitySummary})`;
|
|
2802
|
+
return `${serverDefinition.serverName} (${toolNames.join(", ")})${capabilitySummary}`;
|
|
2803
|
+
}
|
|
2804
|
+
function buildFlatToolDescription(serverDefinition, tool) {
|
|
2805
|
+
const parts = [`Proxied from server "${serverDefinition.serverName}" as tool "${tool.name}".`];
|
|
2806
|
+
if (serverDefinition.serverInstruction) parts.push(`Server summary: ${serverDefinition.serverInstruction}`);
|
|
2807
|
+
if (tool.description && !serverDefinition.omitToolDescription) parts.push(tool.description);
|
|
2808
|
+
const capabilities = getToolCapabilities(tool);
|
|
2809
|
+
if (capabilities.length > 0) parts.push(`Capabilities: ${capabilities.join(", ")}`);
|
|
2810
|
+
return parts.join("\n\n");
|
|
2811
|
+
}
|
|
2812
|
+
function buildFlatToolDefinitions(serverDefinitions) {
|
|
2813
|
+
const toolToServers = /* @__PURE__ */ new Map();
|
|
2814
|
+
for (const serverDefinition of serverDefinitions) for (const tool of serverDefinition.tools) {
|
|
2815
|
+
if (!toolToServers.has(tool.name)) toolToServers.set(tool.name, []);
|
|
2816
|
+
toolToServers.get(tool.name)?.push(serverDefinition.serverName);
|
|
2817
|
+
}
|
|
2818
|
+
const definitions = [];
|
|
2819
|
+
for (const serverDefinition of serverDefinitions) for (const tool of serverDefinition.tools) {
|
|
2820
|
+
const hasClash = (toolToServers.get(tool.name) || []).length > 1;
|
|
2821
|
+
definitions.push({
|
|
2822
|
+
name: hasClash ? `${serverDefinition.serverName}__${tool.name}` : tool.name,
|
|
2823
|
+
description: buildFlatToolDescription(serverDefinition, tool),
|
|
2824
|
+
inputSchema: tool.inputSchema,
|
|
2825
|
+
_meta: tool._meta
|
|
2826
|
+
});
|
|
2827
|
+
}
|
|
2828
|
+
return definitions;
|
|
2829
|
+
}
|
|
2830
|
+
async function hasAnySkills(definitionsCacheService, skillService) {
|
|
2831
|
+
const [fileSkills, serverDefinitions] = await Promise.all([skillService ? skillService.getSkills() : definitionsCacheService.getCachedFileSkills(), definitionsCacheService.getServerDefinitions()]);
|
|
2832
|
+
return fileSkills.length > 0 || serverDefinitions.some((server) => server.promptSkills.length > 0);
|
|
2833
|
+
}
|
|
2834
|
+
function buildSkillsDescribeDefinition(serverDefinitions, serverId) {
|
|
2835
|
+
const proxySummary = serverDefinitions.length > 0 ? serverDefinitions.map(summarizeServerTools).join("; ") : "No proxied servers available.";
|
|
2836
|
+
return {
|
|
2837
|
+
name: DescribeToolsTool.TOOL_NAME,
|
|
2838
|
+
description: `Get detailed skill instructions for file-based skills and prompt-based skills proxied by one-mcp.\n\nProxy summary: ${proxySummary}\n\nUse this when you need the full instructions for a skill. For MCP tools, call the flat tool names directly. Only use skills discovered from describe_tools with id="${serverId}".`,
|
|
2839
|
+
inputSchema: {
|
|
2840
|
+
type: "object",
|
|
2841
|
+
properties: { toolNames: {
|
|
2842
|
+
type: "array",
|
|
2843
|
+
items: {
|
|
2844
|
+
type: "string",
|
|
2845
|
+
minLength: 1
|
|
2846
|
+
},
|
|
2847
|
+
description: "List of skill names to get detailed information about",
|
|
2848
|
+
minItems: 1
|
|
2849
|
+
} },
|
|
2850
|
+
required: ["toolNames"],
|
|
2851
|
+
additionalProperties: false
|
|
2852
|
+
}
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
function buildSearchDescribeDefinition(serverDefinitions, serverId) {
|
|
2856
|
+
const summary = serverDefinitions.length > 0 ? serverDefinitions.map(summarizeServerTools).join("; ") : "No proxied servers available.";
|
|
2857
|
+
return {
|
|
2858
|
+
name: DescribeToolsTool.TOOL_NAME,
|
|
2859
|
+
description: `Get detailed schemas and skill instructions for proxied MCP capabilities.\n\nProxy summary: ${summary}\n\nUse list_tools first to search capability summaries and discover tool names. Then use describe_tools to fetch full schemas or skill instructions. Only use capabilities discovered from one-mcp id="${serverId}".`,
|
|
2860
|
+
inputSchema: {
|
|
2861
|
+
type: "object",
|
|
2862
|
+
properties: { toolNames: {
|
|
2863
|
+
type: "array",
|
|
2864
|
+
items: {
|
|
2865
|
+
type: "string",
|
|
2866
|
+
minLength: 1
|
|
2867
|
+
},
|
|
2868
|
+
description: "List of tool or skill names to get detailed information about",
|
|
2869
|
+
minItems: 1
|
|
2870
|
+
} },
|
|
2871
|
+
required: ["toolNames"],
|
|
2872
|
+
additionalProperties: false
|
|
2873
|
+
}
|
|
2874
|
+
};
|
|
2875
|
+
}
|
|
2876
|
+
function buildProxyInstructions(serverDefinitions, mode, includeSkillsTool) {
|
|
2877
|
+
const summary = serverDefinitions.length > 0 ? serverDefinitions.map(summarizeServerTools).join("; ") : "No proxied servers available.";
|
|
2878
|
+
if (mode === "flat") return [
|
|
2879
|
+
"one-mcp proxies downstream MCP servers and exposes their tools and resources directly.",
|
|
2880
|
+
`Proxied servers and tools: ${summary}`,
|
|
2881
|
+
includeSkillsTool ? "Skills are still exposed through describe_tools when file-based skills or prompt-backed skills are configured." : "No skills are currently exposed through describe_tools."
|
|
2882
|
+
].join("\n\n");
|
|
2883
|
+
if (mode === "search") return [
|
|
2884
|
+
"one-mcp proxies downstream MCP servers in search mode.",
|
|
2885
|
+
`Proxied servers and tools: ${summary}`,
|
|
2886
|
+
"Use list_tools to search capability summaries and discover tool names, describe_tools to fetch schemas or skill instructions, and use_tool to execute tools."
|
|
2887
|
+
].join("\n\n");
|
|
2888
|
+
return [
|
|
2889
|
+
"one-mcp proxies downstream MCP servers in meta mode.",
|
|
2890
|
+
`Proxied servers and tools: ${summary}`,
|
|
2891
|
+
"Use describe_tools to inspect capabilities and use_tool to execute them."
|
|
2892
|
+
].join("\n\n");
|
|
2893
|
+
}
|
|
2368
2894
|
async function createServer(options) {
|
|
2369
|
-
const server = new Server({
|
|
2370
|
-
name: "@agiflowai/one-mcp",
|
|
2371
|
-
version: "0.1.0"
|
|
2372
|
-
}, { capabilities: {
|
|
2373
|
-
tools: {},
|
|
2374
|
-
prompts: {}
|
|
2375
|
-
} });
|
|
2376
2895
|
const clientManager = new McpClientManagerService();
|
|
2377
2896
|
let configSkills;
|
|
2378
2897
|
let configId;
|
|
2898
|
+
let configHash;
|
|
2899
|
+
let effectiveDefinitionsCachePath;
|
|
2900
|
+
let shouldStartFromCache = false;
|
|
2379
2901
|
if (options?.configFilePath) {
|
|
2380
2902
|
let config;
|
|
2381
2903
|
try {
|
|
@@ -2388,38 +2910,92 @@ async function createServer(options) {
|
|
|
2388
2910
|
}
|
|
2389
2911
|
configSkills = config.skills;
|
|
2390
2912
|
configId = config.id;
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
}
|
|
2404
|
-
}
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2913
|
+
configHash = DefinitionsCacheService.generateConfigHash(config);
|
|
2914
|
+
effectiveDefinitionsCachePath = options.definitionsCachePath || DefinitionsCacheService.getDefaultCachePath(options.configFilePath);
|
|
2915
|
+
clientManager.registerServerConfigs(config.mcpServers);
|
|
2916
|
+
if (options.clearDefinitionsCache && effectiveDefinitionsCachePath) {
|
|
2917
|
+
await DefinitionsCacheService.clearFile(effectiveDefinitionsCachePath);
|
|
2918
|
+
console.error(`[definitions-cache] Cleared ${effectiveDefinitionsCachePath}`);
|
|
2919
|
+
}
|
|
2920
|
+
if (effectiveDefinitionsCachePath) try {
|
|
2921
|
+
const cacheData = await DefinitionsCacheService.readFromFile(effectiveDefinitionsCachePath);
|
|
2922
|
+
if (DefinitionsCacheService.isCacheValid(cacheData, {
|
|
2923
|
+
configHash,
|
|
2924
|
+
oneMcpVersion: version
|
|
2925
|
+
})) shouldStartFromCache = true;
|
|
2926
|
+
} catch {}
|
|
2927
|
+
if (!shouldStartFromCache) {
|
|
2928
|
+
const failedConnections = [];
|
|
2929
|
+
const connectionPromises = Object.entries(config.mcpServers).map(async ([serverName, serverConfig]) => {
|
|
2930
|
+
try {
|
|
2931
|
+
await clientManager.connectToServer(serverName, serverConfig);
|
|
2932
|
+
console.error(`Connected to MCP server: ${serverName}`);
|
|
2933
|
+
} catch (error) {
|
|
2934
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
2935
|
+
failedConnections.push({
|
|
2936
|
+
serverName,
|
|
2937
|
+
error: err
|
|
2938
|
+
});
|
|
2939
|
+
console.error(`Failed to connect to ${serverName}:`, error);
|
|
2940
|
+
}
|
|
2941
|
+
});
|
|
2942
|
+
await Promise.all(connectionPromises);
|
|
2943
|
+
if (failedConnections.length > 0 && failedConnections.length < Object.keys(config.mcpServers).length) console.error(`Warning: Some MCP server connections failed: ${failedConnections.map((f) => f.serverName).join(", ")}`);
|
|
2944
|
+
if (failedConnections.length > 0 && failedConnections.length === Object.keys(config.mcpServers).length) throw new Error(`All MCP server connections failed: ${failedConnections.map((f) => `${f.serverName}: ${f.error.message}`).join(", ")}`);
|
|
2945
|
+
} else console.error(`[definitions-cache] Using cached definitions from ${effectiveDefinitionsCachePath}`);
|
|
2408
2946
|
}
|
|
2409
2947
|
const serverId = options?.serverId || configId || generateServerId();
|
|
2410
2948
|
console.error(`[one-mcp] Server ID: ${serverId}`);
|
|
2411
|
-
const
|
|
2949
|
+
const skillPaths = (options?.skills || configSkills)?.paths ?? [];
|
|
2412
2950
|
const toolsRef = { describeTools: null };
|
|
2413
|
-
const skillService =
|
|
2951
|
+
const skillService = skillPaths.length > 0 ? new SkillService(process.cwd(), skillPaths, { onCacheInvalidated: () => {
|
|
2414
2952
|
toolsRef.describeTools?.clearAutoDetectedSkillsCache();
|
|
2415
2953
|
} }) : void 0;
|
|
2416
|
-
|
|
2417
|
-
|
|
2954
|
+
let definitionsCacheService;
|
|
2955
|
+
if (effectiveDefinitionsCachePath) try {
|
|
2956
|
+
const cacheData = await DefinitionsCacheService.readFromFile(effectiveDefinitionsCachePath);
|
|
2957
|
+
if (DefinitionsCacheService.isCacheValid(cacheData, {
|
|
2958
|
+
configHash,
|
|
2959
|
+
oneMcpVersion: version
|
|
2960
|
+
})) definitionsCacheService = new DefinitionsCacheService(clientManager, skillService, { cacheData });
|
|
2961
|
+
else definitionsCacheService = new DefinitionsCacheService(clientManager, skillService);
|
|
2962
|
+
} catch (error) {
|
|
2963
|
+
console.error(`[definitions-cache] Failed to load ${effectiveDefinitionsCachePath}, falling back to live discovery: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2964
|
+
definitionsCacheService = new DefinitionsCacheService(clientManager, skillService);
|
|
2965
|
+
}
|
|
2966
|
+
else definitionsCacheService = new DefinitionsCacheService(clientManager, skillService);
|
|
2967
|
+
const describeTools = new DescribeToolsTool(clientManager, skillService, serverId, definitionsCacheService);
|
|
2968
|
+
const useToolWithCache = new UseToolTool(clientManager, skillService, serverId, definitionsCacheService);
|
|
2969
|
+
const searchListTools = new SearchListToolsTool(clientManager, definitionsCacheService);
|
|
2418
2970
|
toolsRef.describeTools = describeTools;
|
|
2971
|
+
const serverDefinitions = await definitionsCacheService.getServerDefinitions();
|
|
2972
|
+
const includeSkillsTool = await hasAnySkills(definitionsCacheService, skillService);
|
|
2973
|
+
const proxyMode = options?.proxyMode || "meta";
|
|
2974
|
+
const server = new Server({
|
|
2975
|
+
name: "@agiflowai/one-mcp",
|
|
2976
|
+
version: "0.1.0"
|
|
2977
|
+
}, {
|
|
2978
|
+
capabilities: {
|
|
2979
|
+
tools: {},
|
|
2980
|
+
resources: {},
|
|
2981
|
+
prompts: {}
|
|
2982
|
+
},
|
|
2983
|
+
instructions: buildProxyInstructions(serverDefinitions, proxyMode, includeSkillsTool)
|
|
2984
|
+
});
|
|
2419
2985
|
if (skillService) skillService.startWatching().catch((error) => {
|
|
2420
2986
|
console.error(`[skill-watcher] File watcher failed (non-critical): ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2421
2987
|
});
|
|
2422
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools:
|
|
2988
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: proxyMode === "flat" ? await (async () => {
|
|
2989
|
+
const currentServerDefinitions = await definitionsCacheService.getServerDefinitions();
|
|
2990
|
+
const shouldIncludeSkillsTool = await hasAnySkills(definitionsCacheService, skillService);
|
|
2991
|
+
return [...buildFlatToolDefinitions(currentServerDefinitions), ...shouldIncludeSkillsTool ? [buildSkillsDescribeDefinition(currentServerDefinitions, serverId)] : []];
|
|
2992
|
+
})() : proxyMode === "search" ? await (async () => {
|
|
2993
|
+
return [
|
|
2994
|
+
buildSearchDescribeDefinition(await definitionsCacheService.getServerDefinitions(), serverId),
|
|
2995
|
+
await searchListTools.getDefinition(),
|
|
2996
|
+
useToolWithCache.getDefinition()
|
|
2997
|
+
];
|
|
2998
|
+
})() : [await describeTools.getDefinition(), useToolWithCache.getDefinition()] }));
|
|
2423
2999
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
2424
3000
|
const { name, arguments: args } = request.params;
|
|
2425
3001
|
if (name === DescribeToolsTool.TOOL_NAME) try {
|
|
@@ -2428,36 +3004,65 @@ async function createServer(options) {
|
|
|
2428
3004
|
throw new Error(`Failed to execute ${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2429
3005
|
}
|
|
2430
3006
|
if (name === UseToolTool.TOOL_NAME) try {
|
|
2431
|
-
return await
|
|
3007
|
+
return await useToolWithCache.execute(args);
|
|
3008
|
+
} catch (error) {
|
|
3009
|
+
throw new Error(`Failed to execute ${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
3010
|
+
}
|
|
3011
|
+
if (name === SearchListToolsTool.TOOL_NAME && proxyMode === "search") try {
|
|
3012
|
+
return await searchListTools.execute(args);
|
|
2432
3013
|
} catch (error) {
|
|
2433
3014
|
throw new Error(`Failed to execute ${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2434
3015
|
}
|
|
3016
|
+
if (proxyMode === "flat") return await useToolWithCache.execute({
|
|
3017
|
+
toolName: name,
|
|
3018
|
+
toolArgs: args || {}
|
|
3019
|
+
});
|
|
2435
3020
|
throw new Error(`Unknown tool: ${name}`);
|
|
2436
3021
|
});
|
|
3022
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
3023
|
+
const serverDefinitions$1 = await definitionsCacheService.getServerDefinitions();
|
|
3024
|
+
const resourceToServers = /* @__PURE__ */ new Map();
|
|
3025
|
+
for (const serverDefinition of serverDefinitions$1) for (const resource of serverDefinition.resources) {
|
|
3026
|
+
if (!resourceToServers.has(resource.uri)) resourceToServers.set(resource.uri, []);
|
|
3027
|
+
resourceToServers.get(resource.uri)?.push(serverDefinition.serverName);
|
|
3028
|
+
}
|
|
3029
|
+
const resources = [];
|
|
3030
|
+
for (const serverDefinition of serverDefinitions$1) for (const resource of serverDefinition.resources) {
|
|
3031
|
+
const hasClash = (resourceToServers.get(resource.uri) || []).length > 1;
|
|
3032
|
+
resources.push({
|
|
3033
|
+
...resource,
|
|
3034
|
+
uri: hasClash ? `${serverDefinition.serverName}__${resource.uri}` : resource.uri
|
|
3035
|
+
});
|
|
3036
|
+
}
|
|
3037
|
+
return { resources };
|
|
3038
|
+
});
|
|
3039
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
3040
|
+
const { uri } = request.params;
|
|
3041
|
+
const { serverName, actualToolName: actualUri } = parseToolName(uri);
|
|
3042
|
+
if (serverName) return await (await clientManager.ensureConnected(serverName)).readResource(actualUri);
|
|
3043
|
+
const matchingServers = await definitionsCacheService.getServersForResource(actualUri);
|
|
3044
|
+
if (matchingServers.length === 0) throw new Error(`Resource not found: ${uri}`);
|
|
3045
|
+
if (matchingServers.length > 1) throw new Error(`Resource "${actualUri}" exists on multiple servers: ${matchingServers.join(", ")}. Use the prefixed format (e.g., "${matchingServers[0]}__${actualUri}") to specify which server to use.`);
|
|
3046
|
+
return await (await clientManager.ensureConnected(matchingServers[0])).readResource(actualUri);
|
|
3047
|
+
});
|
|
2437
3048
|
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
2438
|
-
const
|
|
3049
|
+
const serverDefinitions$1 = await definitionsCacheService.getServerDefinitions();
|
|
2439
3050
|
const promptToServers = /* @__PURE__ */ new Map();
|
|
2440
3051
|
const serverPromptsMap = /* @__PURE__ */ new Map();
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
if (!promptToServers.has(prompt.name)) promptToServers.set(prompt.name, []);
|
|
2447
|
-
promptToServers.get(prompt.name).push(client.serverName);
|
|
2448
|
-
}
|
|
2449
|
-
} catch (error) {
|
|
2450
|
-
console.error(`Failed to list prompts from ${client.serverName}:`, error);
|
|
2451
|
-
serverPromptsMap.set(client.serverName, []);
|
|
3052
|
+
for (const serverDefinition of serverDefinitions$1) {
|
|
3053
|
+
serverPromptsMap.set(serverDefinition.serverName, serverDefinition.prompts);
|
|
3054
|
+
for (const prompt of serverDefinition.prompts) {
|
|
3055
|
+
if (!promptToServers.has(prompt.name)) promptToServers.set(prompt.name, []);
|
|
3056
|
+
promptToServers.get(prompt.name).push(serverDefinition.serverName);
|
|
2452
3057
|
}
|
|
2453
|
-
}
|
|
3058
|
+
}
|
|
2454
3059
|
const aggregatedPrompts = [];
|
|
2455
|
-
for (const
|
|
2456
|
-
const prompts = serverPromptsMap.get(
|
|
3060
|
+
for (const serverDefinition of serverDefinitions$1) {
|
|
3061
|
+
const prompts = serverPromptsMap.get(serverDefinition.serverName) || [];
|
|
2457
3062
|
for (const prompt of prompts) {
|
|
2458
3063
|
const hasClash = (promptToServers.get(prompt.name) || []).length > 1;
|
|
2459
3064
|
aggregatedPrompts.push({
|
|
2460
|
-
name: hasClash ? `${
|
|
3065
|
+
name: hasClash ? `${serverDefinition.serverName}__${prompt.name}` : prompt.name,
|
|
2461
3066
|
description: prompt.description,
|
|
2462
3067
|
arguments: prompt.arguments
|
|
2463
3068
|
});
|
|
@@ -2467,27 +3072,27 @@ async function createServer(options) {
|
|
|
2467
3072
|
});
|
|
2468
3073
|
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
2469
3074
|
const { name, arguments: args } = request.params;
|
|
2470
|
-
const
|
|
3075
|
+
const serverDefinitions$1 = await definitionsCacheService.getServerDefinitions();
|
|
2471
3076
|
const { serverName, actualToolName: actualPromptName } = parseToolName(name);
|
|
2472
|
-
if (serverName)
|
|
2473
|
-
const client$1 = clientManager.getClient(serverName);
|
|
2474
|
-
if (!client$1) throw new Error(`Server not found: ${serverName}`);
|
|
2475
|
-
return await client$1.getPrompt(actualPromptName, args);
|
|
2476
|
-
}
|
|
3077
|
+
if (serverName) return await (await clientManager.ensureConnected(serverName)).getPrompt(actualPromptName, args);
|
|
2477
3078
|
const serversWithPrompt = [];
|
|
2478
|
-
|
|
2479
|
-
try {
|
|
2480
|
-
if ((await client$1.listPrompts()).some((p) => p.name === name)) serversWithPrompt.push(client$1.serverName);
|
|
2481
|
-
} catch (error) {
|
|
2482
|
-
console.error(`Failed to list prompts from ${client$1.serverName}:`, error);
|
|
2483
|
-
}
|
|
2484
|
-
}));
|
|
3079
|
+
for (const serverDefinition of serverDefinitions$1) if (serverDefinition.prompts.some((prompt) => prompt.name === name)) serversWithPrompt.push(serverDefinition.serverName);
|
|
2485
3080
|
if (serversWithPrompt.length === 0) throw new Error(`Prompt not found: ${name}`);
|
|
2486
3081
|
if (serversWithPrompt.length > 1) throw new Error(`Prompt "${name}" exists on multiple servers: ${serversWithPrompt.join(", ")}. Use the prefixed format (e.g., "${serversWithPrompt[0]}__${name}") to specify which server to use.`);
|
|
2487
3082
|
const client = clientManager.getClient(serversWithPrompt[0]);
|
|
2488
|
-
if (!client)
|
|
3083
|
+
if (!client) return await (await clientManager.ensureConnected(serversWithPrompt[0])).getPrompt(name, args);
|
|
2489
3084
|
return await client.getPrompt(name, args);
|
|
2490
3085
|
});
|
|
3086
|
+
if (!shouldStartFromCache && effectiveDefinitionsCachePath && options?.configFilePath) definitionsCacheService.collectForCache({
|
|
3087
|
+
configPath: options.configFilePath,
|
|
3088
|
+
configHash,
|
|
3089
|
+
oneMcpVersion: version,
|
|
3090
|
+
serverId
|
|
3091
|
+
}).then((definitionsCache) => DefinitionsCacheService.writeToFile(effectiveDefinitionsCachePath, definitionsCache)).then(() => {
|
|
3092
|
+
console.error(`[definitions-cache] Wrote ${effectiveDefinitionsCachePath}`);
|
|
3093
|
+
}).catch((error) => {
|
|
3094
|
+
console.error(`[definitions-cache] Failed to persist ${effectiveDefinitionsCachePath}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3095
|
+
});
|
|
2491
3096
|
return server;
|
|
2492
3097
|
}
|
|
2493
3098
|
|
|
@@ -2837,4 +3442,4 @@ var HttpTransportHandler = class {
|
|
|
2837
3442
|
};
|
|
2838
3443
|
|
|
2839
3444
|
//#endregion
|
|
2840
|
-
export {
|
|
3445
|
+
export { version as a, DescribeToolsTool as c, DefinitionsCacheService as d, generateServerId as f, createServer as i, SkillService as l, ConfigFetcherService as m, SseTransportHandler as n, UseToolTool as o, findConfigFile as p, StdioTransportHandler as r, SearchListToolsTool as s, HttpTransportHandler as t, McpClientManagerService as u };
|