@agiflowai/one-mcp 0.3.11 → 0.3.13
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-SFQFxDCq.cjs} +1079 -450
- package/dist/{http-BDeLFFzK.mjs → http-_ThlSpST.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
|
@@ -789,14 +789,22 @@ var ConfigFetcherService = class {
|
|
|
789
789
|
*/
|
|
790
790
|
mergeConfigurations(localConfig, remoteConfig, mergeStrategy) {
|
|
791
791
|
switch (mergeStrategy) {
|
|
792
|
-
case "local-priority": return {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
}
|
|
792
|
+
case "local-priority": return {
|
|
793
|
+
id: localConfig.id ?? remoteConfig.id,
|
|
794
|
+
mcpServers: {
|
|
795
|
+
...remoteConfig.mcpServers,
|
|
796
|
+
...localConfig.mcpServers
|
|
797
|
+
},
|
|
798
|
+
skills: localConfig.skills ?? remoteConfig.skills
|
|
799
|
+
};
|
|
800
|
+
case "remote-priority": return {
|
|
801
|
+
id: remoteConfig.id ?? localConfig.id,
|
|
802
|
+
mcpServers: {
|
|
803
|
+
...localConfig.mcpServers,
|
|
804
|
+
...remoteConfig.mcpServers
|
|
805
|
+
},
|
|
806
|
+
skills: remoteConfig.skills ?? localConfig.skills
|
|
807
|
+
};
|
|
800
808
|
case "merge-deep": {
|
|
801
809
|
const merged = { ...remoteConfig.mcpServers };
|
|
802
810
|
for (const [serverName, localServerConfig] of Object.entries(localConfig.mcpServers)) if (merged[serverName]) {
|
|
@@ -823,7 +831,11 @@ var ConfigFetcherService = class {
|
|
|
823
831
|
config: mergedConfig
|
|
824
832
|
};
|
|
825
833
|
} else merged[serverName] = localServerConfig;
|
|
826
|
-
return {
|
|
834
|
+
return {
|
|
835
|
+
id: localConfig.id ?? remoteConfig.id,
|
|
836
|
+
mcpServers: merged,
|
|
837
|
+
skills: localConfig.skills ?? remoteConfig.skills
|
|
838
|
+
};
|
|
827
839
|
}
|
|
828
840
|
default: throw new Error(`Unknown merge strategy: ${mergeStrategy}`);
|
|
829
841
|
}
|
|
@@ -844,213 +856,6 @@ var ConfigFetcherService = class {
|
|
|
844
856
|
}
|
|
845
857
|
};
|
|
846
858
|
|
|
847
|
-
//#endregion
|
|
848
|
-
//#region src/services/McpClientManagerService.ts
|
|
849
|
-
/** Default connection timeout in milliseconds (30 seconds) */
|
|
850
|
-
const DEFAULT_CONNECTION_TIMEOUT_MS = 3e4;
|
|
851
|
-
/**
|
|
852
|
-
* MCP Client wrapper for managing individual server connections
|
|
853
|
-
* This is an internal class used by McpClientManagerService
|
|
854
|
-
*/
|
|
855
|
-
var McpClient = class {
|
|
856
|
-
serverName;
|
|
857
|
-
serverInstruction;
|
|
858
|
-
toolBlacklist;
|
|
859
|
-
omitToolDescription;
|
|
860
|
-
prompts;
|
|
861
|
-
transport;
|
|
862
|
-
client;
|
|
863
|
-
childProcess;
|
|
864
|
-
connected = false;
|
|
865
|
-
constructor(serverName, transport, client, config) {
|
|
866
|
-
this.serverName = serverName;
|
|
867
|
-
this.serverInstruction = config.instruction;
|
|
868
|
-
this.toolBlacklist = config.toolBlacklist;
|
|
869
|
-
this.omitToolDescription = config.omitToolDescription;
|
|
870
|
-
this.prompts = config.prompts;
|
|
871
|
-
this.transport = transport;
|
|
872
|
-
this.client = client;
|
|
873
|
-
}
|
|
874
|
-
setChildProcess(process$1) {
|
|
875
|
-
this.childProcess = process$1;
|
|
876
|
-
}
|
|
877
|
-
setConnected(connected) {
|
|
878
|
-
this.connected = connected;
|
|
879
|
-
}
|
|
880
|
-
async listTools() {
|
|
881
|
-
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
882
|
-
return (await this.client.listTools()).tools;
|
|
883
|
-
}
|
|
884
|
-
async listResources() {
|
|
885
|
-
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
886
|
-
return (await this.client.listResources()).resources;
|
|
887
|
-
}
|
|
888
|
-
async listPrompts() {
|
|
889
|
-
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
890
|
-
return (await this.client.listPrompts()).prompts;
|
|
891
|
-
}
|
|
892
|
-
async callTool(name, args) {
|
|
893
|
-
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
894
|
-
return await this.client.callTool({
|
|
895
|
-
name,
|
|
896
|
-
arguments: args
|
|
897
|
-
});
|
|
898
|
-
}
|
|
899
|
-
async readResource(uri) {
|
|
900
|
-
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
901
|
-
return await this.client.readResource({ uri });
|
|
902
|
-
}
|
|
903
|
-
async getPrompt(name, args) {
|
|
904
|
-
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
905
|
-
return await this.client.getPrompt({
|
|
906
|
-
name,
|
|
907
|
-
arguments: args
|
|
908
|
-
});
|
|
909
|
-
}
|
|
910
|
-
async close() {
|
|
911
|
-
if (this.childProcess) this.childProcess.kill();
|
|
912
|
-
await this.client.close();
|
|
913
|
-
this.connected = false;
|
|
914
|
-
}
|
|
915
|
-
};
|
|
916
|
-
/**
|
|
917
|
-
* Service for managing MCP client connections to remote servers
|
|
918
|
-
*/
|
|
919
|
-
var McpClientManagerService = class {
|
|
920
|
-
clients = /* @__PURE__ */ new Map();
|
|
921
|
-
constructor() {
|
|
922
|
-
process.on("exit", () => {
|
|
923
|
-
this.cleanupOnExit();
|
|
924
|
-
});
|
|
925
|
-
process.on("SIGINT", () => {
|
|
926
|
-
this.cleanupOnExit();
|
|
927
|
-
process.exit(0);
|
|
928
|
-
});
|
|
929
|
-
process.on("SIGTERM", () => {
|
|
930
|
-
this.cleanupOnExit();
|
|
931
|
-
process.exit(0);
|
|
932
|
-
});
|
|
933
|
-
}
|
|
934
|
-
/**
|
|
935
|
-
* Cleanup all resources on exit (child processes)
|
|
936
|
-
*/
|
|
937
|
-
cleanupOnExit() {
|
|
938
|
-
for (const [serverName, client] of this.clients) try {
|
|
939
|
-
const childProcess = client["childProcess"];
|
|
940
|
-
if (childProcess && !childProcess.killed) {
|
|
941
|
-
console.error(`Killing stdio MCP server: ${serverName} (PID: ${childProcess.pid})`);
|
|
942
|
-
childProcess.kill("SIGTERM");
|
|
943
|
-
setTimeout(() => {
|
|
944
|
-
if (!childProcess.killed) {
|
|
945
|
-
console.error(`Force killing stdio MCP server: ${serverName} (PID: ${childProcess.pid})`);
|
|
946
|
-
childProcess.kill("SIGKILL");
|
|
947
|
-
}
|
|
948
|
-
}, 1e3);
|
|
949
|
-
}
|
|
950
|
-
} catch (error) {
|
|
951
|
-
console.error(`Failed to kill child process for ${serverName}:`, error);
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
/**
|
|
955
|
-
* Connect to an MCP server based on its configuration with timeout
|
|
956
|
-
* Uses the timeout from server config, falling back to default (30s)
|
|
957
|
-
*/
|
|
958
|
-
async connectToServer(serverName, config) {
|
|
959
|
-
const timeoutMs = config.timeout ?? DEFAULT_CONNECTION_TIMEOUT_MS;
|
|
960
|
-
if (this.clients.has(serverName)) throw new Error(`Client for ${serverName} is already connected`);
|
|
961
|
-
const client = new __modelcontextprotocol_sdk_client_index_js.Client({
|
|
962
|
-
name: `@agiflowai/one-mcp-client`,
|
|
963
|
-
version: "0.1.0"
|
|
964
|
-
}, { capabilities: {} });
|
|
965
|
-
const mcpClient = new McpClient(serverName, config.transport, client, {
|
|
966
|
-
instruction: config.instruction,
|
|
967
|
-
toolBlacklist: config.toolBlacklist,
|
|
968
|
-
omitToolDescription: config.omitToolDescription,
|
|
969
|
-
prompts: config.prompts
|
|
970
|
-
});
|
|
971
|
-
try {
|
|
972
|
-
await Promise.race([this.performConnection(mcpClient, config), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error(`Connection timeout after ${timeoutMs}ms`)), timeoutMs))]);
|
|
973
|
-
mcpClient.setConnected(true);
|
|
974
|
-
if (!mcpClient.serverInstruction) try {
|
|
975
|
-
const serverInstruction = mcpClient["client"].getInstructions();
|
|
976
|
-
if (serverInstruction) mcpClient.serverInstruction = serverInstruction;
|
|
977
|
-
} catch (error) {
|
|
978
|
-
console.error(`Failed to get server instruction from ${serverName}:`, error);
|
|
979
|
-
}
|
|
980
|
-
this.clients.set(serverName, mcpClient);
|
|
981
|
-
} catch (error) {
|
|
982
|
-
await mcpClient.close();
|
|
983
|
-
throw error;
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
/**
|
|
987
|
-
* Perform the actual connection to MCP server
|
|
988
|
-
*/
|
|
989
|
-
async performConnection(mcpClient, config) {
|
|
990
|
-
if (config.transport === "stdio") await this.connectStdioClient(mcpClient, config.config);
|
|
991
|
-
else if (config.transport === "http") await this.connectHttpClient(mcpClient, config.config);
|
|
992
|
-
else if (config.transport === "sse") await this.connectSseClient(mcpClient, config.config);
|
|
993
|
-
else throw new Error(`Unsupported transport type: ${config.transport}`);
|
|
994
|
-
}
|
|
995
|
-
async connectStdioClient(mcpClient, config) {
|
|
996
|
-
const transport = new __modelcontextprotocol_sdk_client_stdio_js.StdioClientTransport({
|
|
997
|
-
command: config.command,
|
|
998
|
-
args: config.args,
|
|
999
|
-
env: {
|
|
1000
|
-
...process.env,
|
|
1001
|
-
...config.env ?? {}
|
|
1002
|
-
}
|
|
1003
|
-
});
|
|
1004
|
-
await mcpClient["client"].connect(transport);
|
|
1005
|
-
const childProcess = transport["_process"];
|
|
1006
|
-
if (childProcess) mcpClient.setChildProcess(childProcess);
|
|
1007
|
-
}
|
|
1008
|
-
async connectHttpClient(mcpClient, config) {
|
|
1009
|
-
const transport = new __modelcontextprotocol_sdk_client_streamableHttp_js.StreamableHTTPClientTransport(new URL(config.url), { requestInit: config.headers ? { headers: config.headers } : void 0 });
|
|
1010
|
-
await mcpClient["client"].connect(transport);
|
|
1011
|
-
}
|
|
1012
|
-
async connectSseClient(mcpClient, config) {
|
|
1013
|
-
const transport = new __modelcontextprotocol_sdk_client_sse_js.SSEClientTransport(new URL(config.url));
|
|
1014
|
-
await mcpClient["client"].connect(transport);
|
|
1015
|
-
}
|
|
1016
|
-
/**
|
|
1017
|
-
* Get a connected client by server name
|
|
1018
|
-
*/
|
|
1019
|
-
getClient(serverName) {
|
|
1020
|
-
return this.clients.get(serverName);
|
|
1021
|
-
}
|
|
1022
|
-
/**
|
|
1023
|
-
* Get all connected clients
|
|
1024
|
-
*/
|
|
1025
|
-
getAllClients() {
|
|
1026
|
-
return Array.from(this.clients.values());
|
|
1027
|
-
}
|
|
1028
|
-
/**
|
|
1029
|
-
* Disconnect from a specific server
|
|
1030
|
-
*/
|
|
1031
|
-
async disconnectServer(serverName) {
|
|
1032
|
-
const client = this.clients.get(serverName);
|
|
1033
|
-
if (client) {
|
|
1034
|
-
await client.close();
|
|
1035
|
-
this.clients.delete(serverName);
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
/**
|
|
1039
|
-
* Disconnect from all servers
|
|
1040
|
-
*/
|
|
1041
|
-
async disconnectAll() {
|
|
1042
|
-
const disconnectPromises = Array.from(this.clients.values()).map((client) => client.close());
|
|
1043
|
-
await Promise.all(disconnectPromises);
|
|
1044
|
-
this.clients.clear();
|
|
1045
|
-
}
|
|
1046
|
-
/**
|
|
1047
|
-
* Check if a server is connected
|
|
1048
|
-
*/
|
|
1049
|
-
isConnected(serverName) {
|
|
1050
|
-
return this.clients.has(serverName);
|
|
1051
|
-
}
|
|
1052
|
-
};
|
|
1053
|
-
|
|
1054
859
|
//#endregion
|
|
1055
860
|
//#region src/utils/findConfigFile.ts
|
|
1056
861
|
/**
|
|
@@ -1316,8 +1121,565 @@ function generateServerId(length = DEFAULT_ID_LENGTH) {
|
|
|
1316
1121
|
remaining--;
|
|
1317
1122
|
}
|
|
1318
1123
|
}
|
|
1319
|
-
return result;
|
|
1320
|
-
}
|
|
1124
|
+
return result;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
//#endregion
|
|
1128
|
+
//#region src/constants/index.ts
|
|
1129
|
+
/**
|
|
1130
|
+
* Shared constants for one-mcp package
|
|
1131
|
+
*/
|
|
1132
|
+
/**
|
|
1133
|
+
* Prefix added to skill names when they clash with MCP tool names.
|
|
1134
|
+
* This ensures skills can be uniquely identified even when a tool has the same name.
|
|
1135
|
+
*/
|
|
1136
|
+
const SKILL_PREFIX = "skill__";
|
|
1137
|
+
/**
|
|
1138
|
+
* Log prefix for skill detection messages.
|
|
1139
|
+
* Used to easily filter skill detection logs in stderr output.
|
|
1140
|
+
*/
|
|
1141
|
+
const LOG_PREFIX_SKILL_DETECTION = "[skill-detection]";
|
|
1142
|
+
/**
|
|
1143
|
+
* Log prefix for general MCP capability discovery messages.
|
|
1144
|
+
*/
|
|
1145
|
+
const LOG_PREFIX_CAPABILITY_DISCOVERY = "[capability-discovery]";
|
|
1146
|
+
/**
|
|
1147
|
+
* Prefix for prompt-based skill locations.
|
|
1148
|
+
* Format: "prompt:{serverName}:{promptName}"
|
|
1149
|
+
*/
|
|
1150
|
+
const PROMPT_LOCATION_PREFIX = "prompt:";
|
|
1151
|
+
/**
|
|
1152
|
+
* Default server ID used when no ID is provided via CLI or config.
|
|
1153
|
+
* This fallback is used when auto-generation also fails.
|
|
1154
|
+
*/
|
|
1155
|
+
const DEFAULT_SERVER_ID = "unknown";
|
|
1156
|
+
|
|
1157
|
+
//#endregion
|
|
1158
|
+
//#region src/services/DefinitionsCacheService.ts
|
|
1159
|
+
/**
|
|
1160
|
+
* DefinitionsCacheService
|
|
1161
|
+
*
|
|
1162
|
+
* Provides shared discovery, caching, and serialization for startup-time MCP
|
|
1163
|
+
* capability metadata. This avoids repeated remote enumeration during
|
|
1164
|
+
* mcp-serve startup and describe_tools generation.
|
|
1165
|
+
*/
|
|
1166
|
+
function isYamlPath(filePath) {
|
|
1167
|
+
return filePath.endsWith(".yaml") || filePath.endsWith(".yml");
|
|
1168
|
+
}
|
|
1169
|
+
function toErrorMessage(error) {
|
|
1170
|
+
return error instanceof Error ? error.message : String(error);
|
|
1171
|
+
}
|
|
1172
|
+
function sanitizeConfigPathForFilename(configFilePath) {
|
|
1173
|
+
const absoluteConfigPath = (0, node_path.resolve)(configFilePath);
|
|
1174
|
+
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;
|
|
1175
|
+
let result = "";
|
|
1176
|
+
let previousWasUnderscore = false;
|
|
1177
|
+
for (const char of normalizedPath) {
|
|
1178
|
+
if (char >= "a" && char <= "z" || char >= "A" && char <= "Z" || char >= "0" && char <= "9" || char === "." || char === "_" || char === "-") {
|
|
1179
|
+
result += char;
|
|
1180
|
+
previousWasUnderscore = false;
|
|
1181
|
+
continue;
|
|
1182
|
+
}
|
|
1183
|
+
if (!previousWasUnderscore) {
|
|
1184
|
+
result += "_";
|
|
1185
|
+
previousWasUnderscore = true;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
let start = 0;
|
|
1189
|
+
let end = result.length;
|
|
1190
|
+
while (start < end && result[start] === "_") start += 1;
|
|
1191
|
+
while (end > start && result[end - 1] === "_") end -= 1;
|
|
1192
|
+
return result.slice(start, end);
|
|
1193
|
+
}
|
|
1194
|
+
function cloneCache(cache) {
|
|
1195
|
+
return {
|
|
1196
|
+
...cache,
|
|
1197
|
+
failures: [...cache.failures ?? []],
|
|
1198
|
+
skills: (cache.skills ?? []).map((skill) => ({ ...skill })),
|
|
1199
|
+
servers: Object.fromEntries(Object.entries(cache.servers).map(([serverName, server]) => [serverName, {
|
|
1200
|
+
...server,
|
|
1201
|
+
tools: (server.tools ?? []).map((tool) => ({ ...tool })),
|
|
1202
|
+
resources: (server.resources ?? []).map((resource) => ({ ...resource })),
|
|
1203
|
+
prompts: (server.prompts ?? []).map((prompt) => ({
|
|
1204
|
+
...prompt,
|
|
1205
|
+
arguments: prompt.arguments?.map((arg) => ({ ...arg }))
|
|
1206
|
+
})),
|
|
1207
|
+
promptSkills: (server.promptSkills ?? []).map((promptSkill) => ({
|
|
1208
|
+
...promptSkill,
|
|
1209
|
+
skill: { ...promptSkill.skill }
|
|
1210
|
+
}))
|
|
1211
|
+
}]))
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
var DefinitionsCacheService = class {
|
|
1215
|
+
clientManager;
|
|
1216
|
+
skillService;
|
|
1217
|
+
cacheData;
|
|
1218
|
+
liveDefinitionsPromise = null;
|
|
1219
|
+
mergedDefinitionsPromise = null;
|
|
1220
|
+
constructor(clientManager, skillService, options) {
|
|
1221
|
+
this.clientManager = clientManager;
|
|
1222
|
+
this.skillService = skillService;
|
|
1223
|
+
this.cacheData = options?.cacheData;
|
|
1224
|
+
}
|
|
1225
|
+
static async readFromFile(filePath) {
|
|
1226
|
+
const content = await (0, node_fs_promises.readFile)(filePath, "utf-8");
|
|
1227
|
+
const parsed = isYamlPath(filePath) ? js_yaml.default.load(content) : JSON.parse(content);
|
|
1228
|
+
if (!parsed || typeof parsed !== "object") throw new Error("Definitions cache must be an object");
|
|
1229
|
+
const cache = parsed;
|
|
1230
|
+
if (cache.version !== 1 || !cache.servers) throw new Error("Definitions cache is missing required fields");
|
|
1231
|
+
return {
|
|
1232
|
+
...cache,
|
|
1233
|
+
failures: Array.isArray(cache.failures) ? cache.failures : [],
|
|
1234
|
+
skills: Array.isArray(cache.skills) ? cache.skills : [],
|
|
1235
|
+
servers: Object.fromEntries(Object.entries(cache.servers).map(([serverName, server]) => [serverName, {
|
|
1236
|
+
...server,
|
|
1237
|
+
tools: Array.isArray(server.tools) ? server.tools : [],
|
|
1238
|
+
resources: Array.isArray(server.resources) ? server.resources : [],
|
|
1239
|
+
prompts: Array.isArray(server.prompts) ? server.prompts : [],
|
|
1240
|
+
promptSkills: Array.isArray(server.promptSkills) ? server.promptSkills : []
|
|
1241
|
+
}]))
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
static async writeToFile(filePath, cache) {
|
|
1245
|
+
const serialized = isYamlPath(filePath) ? js_yaml.default.dump(cache, { noRefs: true }) : JSON.stringify(cache, null, 2);
|
|
1246
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(filePath), { recursive: true });
|
|
1247
|
+
await (0, node_fs_promises.writeFile)(filePath, serialized, "utf-8");
|
|
1248
|
+
}
|
|
1249
|
+
static getDefaultCachePath(configFilePath) {
|
|
1250
|
+
const sanitizedPath = sanitizeConfigPathForFilename(configFilePath);
|
|
1251
|
+
return (0, node_path.join)((0, node_os.homedir)(), ".aicode-toolkit", `${sanitizedPath}.definitions-cache.json`);
|
|
1252
|
+
}
|
|
1253
|
+
static generateConfigHash(config) {
|
|
1254
|
+
return (0, node_crypto.createHash)("sha256").update(JSON.stringify(config)).digest("hex");
|
|
1255
|
+
}
|
|
1256
|
+
static isCacheValid(cache, options) {
|
|
1257
|
+
if (options.configHash && cache.configHash && cache.configHash !== options.configHash) return false;
|
|
1258
|
+
if (options.oneMcpVersion && cache.oneMcpVersion && cache.oneMcpVersion !== options.oneMcpVersion) return false;
|
|
1259
|
+
return true;
|
|
1260
|
+
}
|
|
1261
|
+
static async clearFile(filePath) {
|
|
1262
|
+
await (0, node_fs_promises.rm)(filePath, { force: true });
|
|
1263
|
+
}
|
|
1264
|
+
clearLiveCache() {
|
|
1265
|
+
this.liveDefinitionsPromise = null;
|
|
1266
|
+
this.mergedDefinitionsPromise = null;
|
|
1267
|
+
}
|
|
1268
|
+
setCacheData(cacheData) {
|
|
1269
|
+
this.cacheData = cacheData;
|
|
1270
|
+
this.mergedDefinitionsPromise = null;
|
|
1271
|
+
}
|
|
1272
|
+
async collectForCache(options) {
|
|
1273
|
+
const liveDefinitions = await this.collectLiveDefinitions(options);
|
|
1274
|
+
this.setCacheData(liveDefinitions);
|
|
1275
|
+
this.liveDefinitionsPromise = Promise.resolve(cloneCache(liveDefinitions));
|
|
1276
|
+
return cloneCache(liveDefinitions);
|
|
1277
|
+
}
|
|
1278
|
+
async getDefinitions() {
|
|
1279
|
+
if (this.mergedDefinitionsPromise) return this.mergedDefinitionsPromise;
|
|
1280
|
+
this.mergedDefinitionsPromise = (async () => {
|
|
1281
|
+
const clients = this.clientManager.getAllClients();
|
|
1282
|
+
if (!this.cacheData) return this.getLiveDefinitions();
|
|
1283
|
+
const missingServers = clients.map((client) => client.serverName).filter((serverName) => !this.cacheData?.servers[serverName]);
|
|
1284
|
+
if (missingServers.length === 0) return cloneCache(this.cacheData);
|
|
1285
|
+
const liveDefinitions = await this.getLiveDefinitions();
|
|
1286
|
+
const merged = cloneCache(this.cacheData);
|
|
1287
|
+
for (const serverName of missingServers) {
|
|
1288
|
+
const serverDefinition = liveDefinitions.servers[serverName];
|
|
1289
|
+
if (serverDefinition) merged.servers[serverName] = serverDefinition;
|
|
1290
|
+
}
|
|
1291
|
+
const failureMap = /* @__PURE__ */ new Map();
|
|
1292
|
+
for (const failure of [...merged.failures, ...liveDefinitions.failures]) failureMap.set(failure.serverName, failure.error);
|
|
1293
|
+
merged.failures = Array.from(failureMap.entries()).map(([serverName, error]) => ({
|
|
1294
|
+
serverName,
|
|
1295
|
+
error
|
|
1296
|
+
}));
|
|
1297
|
+
if (merged.skills.length === 0 && liveDefinitions.skills.length > 0) merged.skills = liveDefinitions.skills.map((skill) => ({ ...skill }));
|
|
1298
|
+
return merged;
|
|
1299
|
+
})();
|
|
1300
|
+
return this.mergedDefinitionsPromise;
|
|
1301
|
+
}
|
|
1302
|
+
async getServerDefinitions() {
|
|
1303
|
+
const definitions = await this.getDefinitions();
|
|
1304
|
+
const serverOrder = this.clientManager.getKnownServerNames();
|
|
1305
|
+
if (serverOrder.length === 0) return Object.values(definitions.servers);
|
|
1306
|
+
return serverOrder.map((serverName) => definitions.servers[serverName]).filter((server) => server !== void 0);
|
|
1307
|
+
}
|
|
1308
|
+
async getServersForTool(toolName) {
|
|
1309
|
+
return (await this.getServerDefinitions()).filter((serverDefinition) => serverDefinition.tools.some((tool) => tool.name === toolName)).map((serverDefinition) => serverDefinition.serverName);
|
|
1310
|
+
}
|
|
1311
|
+
async getServersForResource(uri) {
|
|
1312
|
+
return (await this.getServerDefinitions()).filter((serverDefinition) => serverDefinition.resources.some((resource) => resource.uri === uri)).map((serverDefinition) => serverDefinition.serverName);
|
|
1313
|
+
}
|
|
1314
|
+
async getPromptSkillByName(skillName) {
|
|
1315
|
+
const definitions = await this.getDefinitions();
|
|
1316
|
+
for (const [serverName, server] of Object.entries(definitions.servers)) for (const promptSkill of server.promptSkills) if (promptSkill.skill.name === skillName) return {
|
|
1317
|
+
serverName,
|
|
1318
|
+
promptName: promptSkill.promptName,
|
|
1319
|
+
skill: promptSkill.skill,
|
|
1320
|
+
autoDetected: promptSkill.autoDetected
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
async getCachedFileSkills() {
|
|
1324
|
+
return (await this.getDefinitions()).skills.map((skill) => ({ ...skill }));
|
|
1325
|
+
}
|
|
1326
|
+
async getLiveDefinitions() {
|
|
1327
|
+
if (!this.liveDefinitionsPromise) this.liveDefinitionsPromise = this.collectLiveDefinitions();
|
|
1328
|
+
return this.liveDefinitionsPromise;
|
|
1329
|
+
}
|
|
1330
|
+
async collectLiveDefinitions(options) {
|
|
1331
|
+
const clients = this.clientManager.getAllClients();
|
|
1332
|
+
const failures = [];
|
|
1333
|
+
const servers = {};
|
|
1334
|
+
const serverResults = await Promise.all(clients.map(async (client) => {
|
|
1335
|
+
try {
|
|
1336
|
+
const tools = await client.listTools();
|
|
1337
|
+
const resources = await this.listResourcesSafe(client);
|
|
1338
|
+
const prompts = await this.listPromptsSafe(client);
|
|
1339
|
+
const blacklist = new Set(client.toolBlacklist || []);
|
|
1340
|
+
const filteredTools = tools.filter((tool) => !blacklist.has(tool.name));
|
|
1341
|
+
const promptSkills = await this.collectPromptSkillsForClient(client, prompts);
|
|
1342
|
+
return {
|
|
1343
|
+
serverName: client.serverName,
|
|
1344
|
+
serverInstruction: client.serverInstruction,
|
|
1345
|
+
omitToolDescription: client.omitToolDescription,
|
|
1346
|
+
toolBlacklist: client.toolBlacklist,
|
|
1347
|
+
tools: filteredTools.map((tool) => ({
|
|
1348
|
+
name: tool.name,
|
|
1349
|
+
description: tool.description,
|
|
1350
|
+
inputSchema: tool.inputSchema,
|
|
1351
|
+
_meta: tool._meta
|
|
1352
|
+
})),
|
|
1353
|
+
resources,
|
|
1354
|
+
prompts,
|
|
1355
|
+
promptSkills
|
|
1356
|
+
};
|
|
1357
|
+
} catch (error) {
|
|
1358
|
+
failures.push({
|
|
1359
|
+
serverName: client.serverName,
|
|
1360
|
+
error: toErrorMessage(error)
|
|
1361
|
+
});
|
|
1362
|
+
return null;
|
|
1363
|
+
}
|
|
1364
|
+
}));
|
|
1365
|
+
for (const serverDefinition of serverResults) if (serverDefinition) servers[serverDefinition.serverName] = serverDefinition;
|
|
1366
|
+
return {
|
|
1367
|
+
version: 1,
|
|
1368
|
+
oneMcpVersion: options?.oneMcpVersion,
|
|
1369
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1370
|
+
configPath: options?.configPath,
|
|
1371
|
+
configHash: options?.configHash,
|
|
1372
|
+
serverId: options?.serverId,
|
|
1373
|
+
servers,
|
|
1374
|
+
skills: await this.collectFileSkills(),
|
|
1375
|
+
failures
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
async collectFileSkills() {
|
|
1379
|
+
if (!this.skillService) return [];
|
|
1380
|
+
return (await this.skillService.getSkills()).map((skill) => this.toCachedFileSkill(skill));
|
|
1381
|
+
}
|
|
1382
|
+
toCachedFileSkill(skill) {
|
|
1383
|
+
return {
|
|
1384
|
+
name: skill.name,
|
|
1385
|
+
description: skill.description,
|
|
1386
|
+
location: skill.location,
|
|
1387
|
+
basePath: skill.basePath
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
async listPromptsSafe(client) {
|
|
1391
|
+
try {
|
|
1392
|
+
return (await client.listPrompts()).map((prompt) => ({
|
|
1393
|
+
name: prompt.name,
|
|
1394
|
+
description: prompt.description,
|
|
1395
|
+
arguments: prompt.arguments?.map((arg) => ({ ...arg }))
|
|
1396
|
+
}));
|
|
1397
|
+
} catch (error) {
|
|
1398
|
+
console.error(`${LOG_PREFIX_SKILL_DETECTION} Failed to list prompts from ${client.serverName}: ${toErrorMessage(error)}`);
|
|
1399
|
+
return [];
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
async listResourcesSafe(client) {
|
|
1403
|
+
try {
|
|
1404
|
+
return (await client.listResources()).map((resource) => ({
|
|
1405
|
+
uri: resource.uri,
|
|
1406
|
+
name: resource.name,
|
|
1407
|
+
description: resource.description,
|
|
1408
|
+
mimeType: resource.mimeType
|
|
1409
|
+
}));
|
|
1410
|
+
} catch (error) {
|
|
1411
|
+
console.error(`${LOG_PREFIX_CAPABILITY_DISCOVERY} Failed to list resources from ${client.serverName}: ${toErrorMessage(error)}`);
|
|
1412
|
+
return [];
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
async collectPromptSkillsForClient(client, prompts) {
|
|
1416
|
+
const configuredPromptNames = new Set(client.prompts ? Object.keys(client.prompts) : []);
|
|
1417
|
+
const promptSkills = [];
|
|
1418
|
+
if (client.prompts) {
|
|
1419
|
+
for (const [promptName, promptConfig] of Object.entries(client.prompts)) if (promptConfig.skill) promptSkills.push({
|
|
1420
|
+
promptName,
|
|
1421
|
+
skill: { ...promptConfig.skill }
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
const autoDetectedSkills = await Promise.all(prompts.map(async (prompt) => {
|
|
1425
|
+
if (configuredPromptNames.has(prompt.name)) return null;
|
|
1426
|
+
try {
|
|
1427
|
+
const skillExtraction = extractSkillFrontMatter((await client.getPrompt(prompt.name)).messages?.map((message) => {
|
|
1428
|
+
const content = message.content;
|
|
1429
|
+
if (typeof content === "string") return content;
|
|
1430
|
+
if (content && typeof content === "object" && "text" in content) return String(content.text);
|
|
1431
|
+
return "";
|
|
1432
|
+
}).join("\n") || "");
|
|
1433
|
+
if (!skillExtraction) return null;
|
|
1434
|
+
return {
|
|
1435
|
+
promptName: prompt.name,
|
|
1436
|
+
skill: skillExtraction.skill,
|
|
1437
|
+
autoDetected: true
|
|
1438
|
+
};
|
|
1439
|
+
} catch (error) {
|
|
1440
|
+
console.error(`${LOG_PREFIX_SKILL_DETECTION} Failed to fetch prompt '${prompt.name}' from ${client.serverName}: ${toErrorMessage(error)}`);
|
|
1441
|
+
return null;
|
|
1442
|
+
}
|
|
1443
|
+
}));
|
|
1444
|
+
for (const autoDetectedSkill of autoDetectedSkills) if (autoDetectedSkill) promptSkills.push(autoDetectedSkill);
|
|
1445
|
+
return promptSkills;
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
|
|
1449
|
+
//#endregion
|
|
1450
|
+
//#region src/services/McpClientManagerService.ts
|
|
1451
|
+
/** Default connection timeout in milliseconds (30 seconds) */
|
|
1452
|
+
const DEFAULT_CONNECTION_TIMEOUT_MS = 3e4;
|
|
1453
|
+
/**
|
|
1454
|
+
* MCP Client wrapper for managing individual server connections
|
|
1455
|
+
* This is an internal class used by McpClientManagerService
|
|
1456
|
+
*/
|
|
1457
|
+
var McpClient = class {
|
|
1458
|
+
serverName;
|
|
1459
|
+
serverInstruction;
|
|
1460
|
+
toolBlacklist;
|
|
1461
|
+
omitToolDescription;
|
|
1462
|
+
prompts;
|
|
1463
|
+
transport;
|
|
1464
|
+
client;
|
|
1465
|
+
childProcess;
|
|
1466
|
+
connected = false;
|
|
1467
|
+
constructor(serverName, transport, client, config) {
|
|
1468
|
+
this.serverName = serverName;
|
|
1469
|
+
this.serverInstruction = config.instruction;
|
|
1470
|
+
this.toolBlacklist = config.toolBlacklist;
|
|
1471
|
+
this.omitToolDescription = config.omitToolDescription;
|
|
1472
|
+
this.prompts = config.prompts;
|
|
1473
|
+
this.transport = transport;
|
|
1474
|
+
this.client = client;
|
|
1475
|
+
}
|
|
1476
|
+
setChildProcess(process$1) {
|
|
1477
|
+
this.childProcess = process$1;
|
|
1478
|
+
}
|
|
1479
|
+
setConnected(connected) {
|
|
1480
|
+
this.connected = connected;
|
|
1481
|
+
}
|
|
1482
|
+
async listTools() {
|
|
1483
|
+
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
1484
|
+
return (await this.client.listTools()).tools;
|
|
1485
|
+
}
|
|
1486
|
+
async listResources() {
|
|
1487
|
+
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
1488
|
+
return (await this.client.listResources()).resources;
|
|
1489
|
+
}
|
|
1490
|
+
async listPrompts() {
|
|
1491
|
+
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
1492
|
+
return (await this.client.listPrompts()).prompts;
|
|
1493
|
+
}
|
|
1494
|
+
async callTool(name, args) {
|
|
1495
|
+
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
1496
|
+
return await this.client.callTool({
|
|
1497
|
+
name,
|
|
1498
|
+
arguments: args
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1501
|
+
async readResource(uri) {
|
|
1502
|
+
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
1503
|
+
return await this.client.readResource({ uri });
|
|
1504
|
+
}
|
|
1505
|
+
async getPrompt(name, args) {
|
|
1506
|
+
if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
|
|
1507
|
+
return await this.client.getPrompt({
|
|
1508
|
+
name,
|
|
1509
|
+
arguments: args
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
async close() {
|
|
1513
|
+
if (this.childProcess) this.childProcess.kill();
|
|
1514
|
+
await this.client.close();
|
|
1515
|
+
this.connected = false;
|
|
1516
|
+
}
|
|
1517
|
+
};
|
|
1518
|
+
/**
|
|
1519
|
+
* Service for managing MCP client connections to remote servers
|
|
1520
|
+
*/
|
|
1521
|
+
var McpClientManagerService = class {
|
|
1522
|
+
clients = /* @__PURE__ */ new Map();
|
|
1523
|
+
serverConfigs = /* @__PURE__ */ new Map();
|
|
1524
|
+
connectionPromises = /* @__PURE__ */ new Map();
|
|
1525
|
+
constructor() {
|
|
1526
|
+
process.on("exit", () => {
|
|
1527
|
+
this.cleanupOnExit();
|
|
1528
|
+
});
|
|
1529
|
+
process.on("SIGINT", () => {
|
|
1530
|
+
this.cleanupOnExit();
|
|
1531
|
+
process.exit(0);
|
|
1532
|
+
});
|
|
1533
|
+
process.on("SIGTERM", () => {
|
|
1534
|
+
this.cleanupOnExit();
|
|
1535
|
+
process.exit(0);
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Cleanup all resources on exit (child processes)
|
|
1540
|
+
*/
|
|
1541
|
+
cleanupOnExit() {
|
|
1542
|
+
for (const [serverName, client] of this.clients) try {
|
|
1543
|
+
const childProcess = client["childProcess"];
|
|
1544
|
+
if (childProcess && !childProcess.killed) {
|
|
1545
|
+
console.error(`Killing stdio MCP server: ${serverName} (PID: ${childProcess.pid})`);
|
|
1546
|
+
childProcess.kill("SIGTERM");
|
|
1547
|
+
setTimeout(() => {
|
|
1548
|
+
if (!childProcess.killed) {
|
|
1549
|
+
console.error(`Force killing stdio MCP server: ${serverName} (PID: ${childProcess.pid})`);
|
|
1550
|
+
childProcess.kill("SIGKILL");
|
|
1551
|
+
}
|
|
1552
|
+
}, 1e3);
|
|
1553
|
+
}
|
|
1554
|
+
} catch (error) {
|
|
1555
|
+
console.error(`Failed to kill child process for ${serverName}:`, error);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* Connect to an MCP server based on its configuration with timeout
|
|
1560
|
+
* Uses the timeout from server config, falling back to default (30s)
|
|
1561
|
+
*/
|
|
1562
|
+
async connectToServer(serverName, config) {
|
|
1563
|
+
this.serverConfigs.set(serverName, config);
|
|
1564
|
+
await this.ensureConnected(serverName);
|
|
1565
|
+
}
|
|
1566
|
+
registerServerConfigs(configs) {
|
|
1567
|
+
for (const [serverName, config] of Object.entries(configs)) this.serverConfigs.set(serverName, config);
|
|
1568
|
+
}
|
|
1569
|
+
getKnownServerNames() {
|
|
1570
|
+
return Array.from(this.serverConfigs.keys());
|
|
1571
|
+
}
|
|
1572
|
+
async ensureConnected(serverName) {
|
|
1573
|
+
const existingClient = this.clients.get(serverName);
|
|
1574
|
+
if (existingClient) return existingClient;
|
|
1575
|
+
const inflightConnection = this.connectionPromises.get(serverName);
|
|
1576
|
+
if (inflightConnection) return await inflightConnection;
|
|
1577
|
+
const config = this.serverConfigs.get(serverName);
|
|
1578
|
+
if (!config) throw new Error(`No configuration found for server "${serverName}"`);
|
|
1579
|
+
const connectionPromise = this.createConnection(serverName, config);
|
|
1580
|
+
this.connectionPromises.set(serverName, connectionPromise);
|
|
1581
|
+
try {
|
|
1582
|
+
return await connectionPromise;
|
|
1583
|
+
} finally {
|
|
1584
|
+
this.connectionPromises.delete(serverName);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
async createConnection(serverName, config) {
|
|
1588
|
+
const timeoutMs = config.timeout ?? DEFAULT_CONNECTION_TIMEOUT_MS;
|
|
1589
|
+
const client = new __modelcontextprotocol_sdk_client_index_js.Client({
|
|
1590
|
+
name: `@agiflowai/one-mcp-client`,
|
|
1591
|
+
version: "0.1.0"
|
|
1592
|
+
}, { capabilities: {} });
|
|
1593
|
+
const mcpClient = new McpClient(serverName, config.transport, client, {
|
|
1594
|
+
instruction: config.instruction,
|
|
1595
|
+
toolBlacklist: config.toolBlacklist,
|
|
1596
|
+
omitToolDescription: config.omitToolDescription,
|
|
1597
|
+
prompts: config.prompts
|
|
1598
|
+
});
|
|
1599
|
+
try {
|
|
1600
|
+
await Promise.race([this.performConnection(mcpClient, config), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error(`Connection timeout after ${timeoutMs}ms`)), timeoutMs))]);
|
|
1601
|
+
mcpClient.setConnected(true);
|
|
1602
|
+
if (!mcpClient.serverInstruction) try {
|
|
1603
|
+
const serverInstruction = mcpClient["client"].getInstructions();
|
|
1604
|
+
if (serverInstruction) mcpClient.serverInstruction = serverInstruction;
|
|
1605
|
+
} catch (error) {
|
|
1606
|
+
console.error(`Failed to get server instruction from ${serverName}:`, error);
|
|
1607
|
+
}
|
|
1608
|
+
this.clients.set(serverName, mcpClient);
|
|
1609
|
+
return mcpClient;
|
|
1610
|
+
} catch (error) {
|
|
1611
|
+
await mcpClient.close();
|
|
1612
|
+
throw error;
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Perform the actual connection to MCP server
|
|
1617
|
+
*/
|
|
1618
|
+
async performConnection(mcpClient, config) {
|
|
1619
|
+
if (config.transport === "stdio") await this.connectStdioClient(mcpClient, config.config);
|
|
1620
|
+
else if (config.transport === "http") await this.connectHttpClient(mcpClient, config.config);
|
|
1621
|
+
else if (config.transport === "sse") await this.connectSseClient(mcpClient, config.config);
|
|
1622
|
+
else throw new Error(`Unsupported transport type: ${config.transport}`);
|
|
1623
|
+
}
|
|
1624
|
+
async connectStdioClient(mcpClient, config) {
|
|
1625
|
+
const transport = new __modelcontextprotocol_sdk_client_stdio_js.StdioClientTransport({
|
|
1626
|
+
command: config.command,
|
|
1627
|
+
args: config.args,
|
|
1628
|
+
env: {
|
|
1629
|
+
...process.env,
|
|
1630
|
+
...config.env ?? {}
|
|
1631
|
+
}
|
|
1632
|
+
});
|
|
1633
|
+
await mcpClient["client"].connect(transport);
|
|
1634
|
+
const childProcess = transport["_process"];
|
|
1635
|
+
if (childProcess) mcpClient.setChildProcess(childProcess);
|
|
1636
|
+
}
|
|
1637
|
+
async connectHttpClient(mcpClient, config) {
|
|
1638
|
+
const transport = new __modelcontextprotocol_sdk_client_streamableHttp_js.StreamableHTTPClientTransport(new URL(config.url), { requestInit: config.headers ? { headers: config.headers } : void 0 });
|
|
1639
|
+
await mcpClient["client"].connect(transport);
|
|
1640
|
+
}
|
|
1641
|
+
async connectSseClient(mcpClient, config) {
|
|
1642
|
+
const transport = new __modelcontextprotocol_sdk_client_sse_js.SSEClientTransport(new URL(config.url));
|
|
1643
|
+
await mcpClient["client"].connect(transport);
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Get a connected client by server name
|
|
1647
|
+
*/
|
|
1648
|
+
getClient(serverName) {
|
|
1649
|
+
return this.clients.get(serverName);
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* Get all connected clients
|
|
1653
|
+
*/
|
|
1654
|
+
getAllClients() {
|
|
1655
|
+
return Array.from(this.clients.values());
|
|
1656
|
+
}
|
|
1657
|
+
/**
|
|
1658
|
+
* Disconnect from a specific server
|
|
1659
|
+
*/
|
|
1660
|
+
async disconnectServer(serverName) {
|
|
1661
|
+
const client = this.clients.get(serverName);
|
|
1662
|
+
if (client) {
|
|
1663
|
+
await client.close();
|
|
1664
|
+
this.clients.delete(serverName);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* Disconnect from all servers
|
|
1669
|
+
*/
|
|
1670
|
+
async disconnectAll() {
|
|
1671
|
+
const disconnectPromises = Array.from(this.clients.values()).map((client) => client.close());
|
|
1672
|
+
await Promise.all(disconnectPromises);
|
|
1673
|
+
this.clients.clear();
|
|
1674
|
+
this.connectionPromises.clear();
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Check if a server is connected
|
|
1678
|
+
*/
|
|
1679
|
+
isConnected(serverName) {
|
|
1680
|
+
return this.clients.has(serverName);
|
|
1681
|
+
}
|
|
1682
|
+
};
|
|
1321
1683
|
|
|
1322
1684
|
//#endregion
|
|
1323
1685
|
//#region src/services/SkillService.ts
|
|
@@ -1391,6 +1753,8 @@ var SkillService = class {
|
|
|
1391
1753
|
skillsByName = null;
|
|
1392
1754
|
/** Active file watchers for skill directories */
|
|
1393
1755
|
watchers = [];
|
|
1756
|
+
/** Polling timers used when native file watching is unavailable */
|
|
1757
|
+
pollingTimers = [];
|
|
1394
1758
|
/** Callback invoked when cache is invalidated due to file changes */
|
|
1395
1759
|
onCacheInvalidated;
|
|
1396
1760
|
/**
|
|
@@ -1475,7 +1839,13 @@ var SkillService = class {
|
|
|
1475
1839
|
const abortController = new AbortController();
|
|
1476
1840
|
this.watchers.push(abortController);
|
|
1477
1841
|
this.watchDirectory(skillsDir, abortController.signal).catch((error) => {
|
|
1478
|
-
if (error?.name !== "AbortError")
|
|
1842
|
+
if (error?.name !== "AbortError") {
|
|
1843
|
+
if (this.isWatchResourceLimitError(error)) {
|
|
1844
|
+
this.startPollingDirectory(skillsDir, abortController.signal);
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1847
|
+
console.error(`[skill-watcher] Error watching ${skillsDir}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1848
|
+
}
|
|
1479
1849
|
});
|
|
1480
1850
|
}
|
|
1481
1851
|
}
|
|
@@ -1486,6 +1856,8 @@ var SkillService = class {
|
|
|
1486
1856
|
stopWatching() {
|
|
1487
1857
|
for (const controller of this.watchers) controller.abort();
|
|
1488
1858
|
this.watchers = [];
|
|
1859
|
+
for (const timer of this.pollingTimers) clearInterval(timer);
|
|
1860
|
+
this.pollingTimers = [];
|
|
1489
1861
|
}
|
|
1490
1862
|
/**
|
|
1491
1863
|
* Watches a directory for changes to SKILL.md files.
|
|
@@ -1497,10 +1869,66 @@ var SkillService = class {
|
|
|
1497
1869
|
recursive: true,
|
|
1498
1870
|
signal
|
|
1499
1871
|
});
|
|
1500
|
-
for await (const event of watcher) if (event.filename?.endsWith("SKILL.md"))
|
|
1501
|
-
|
|
1502
|
-
|
|
1872
|
+
for await (const event of watcher) if (event.filename?.endsWith("SKILL.md")) this.invalidateCache();
|
|
1873
|
+
}
|
|
1874
|
+
invalidateCache() {
|
|
1875
|
+
this.clearCache();
|
|
1876
|
+
this.onCacheInvalidated?.();
|
|
1877
|
+
}
|
|
1878
|
+
isWatchResourceLimitError(error) {
|
|
1879
|
+
return error instanceof Error && "code" in error && (error.code === "EMFILE" || error.code === "ENOSPC");
|
|
1880
|
+
}
|
|
1881
|
+
startPollingDirectory(dirPath, signal) {
|
|
1882
|
+
this.createSkillSnapshot(dirPath).then((initialSnapshot) => {
|
|
1883
|
+
let previousSnapshot = initialSnapshot;
|
|
1884
|
+
const timer = setInterval(() => {
|
|
1885
|
+
if (signal.aborted) {
|
|
1886
|
+
clearInterval(timer);
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
this.createSkillSnapshot(dirPath).then((nextSnapshot) => {
|
|
1890
|
+
if (!this.snapshotsEqual(previousSnapshot, nextSnapshot)) {
|
|
1891
|
+
previousSnapshot = nextSnapshot;
|
|
1892
|
+
this.invalidateCache();
|
|
1893
|
+
}
|
|
1894
|
+
}).catch((error) => {
|
|
1895
|
+
console.error(`[skill-watcher] Polling failed for ${dirPath}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1896
|
+
});
|
|
1897
|
+
}, 100);
|
|
1898
|
+
this.pollingTimers.push(timer);
|
|
1899
|
+
signal.addEventListener("abort", () => {
|
|
1900
|
+
clearInterval(timer);
|
|
1901
|
+
this.pollingTimers = this.pollingTimers.filter((activeTimer) => activeTimer !== timer);
|
|
1902
|
+
}, { once: true });
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1905
|
+
async createSkillSnapshot(dirPath) {
|
|
1906
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
1907
|
+
await this.collectSkillSnapshots(dirPath, snapshot);
|
|
1908
|
+
return snapshot;
|
|
1909
|
+
}
|
|
1910
|
+
async collectSkillSnapshots(dirPath, snapshot) {
|
|
1911
|
+
let entries;
|
|
1912
|
+
try {
|
|
1913
|
+
entries = await (0, node_fs_promises.readdir)(dirPath);
|
|
1914
|
+
} catch (error) {
|
|
1915
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return;
|
|
1916
|
+
throw error;
|
|
1503
1917
|
}
|
|
1918
|
+
await Promise.all(entries.map(async (entry) => {
|
|
1919
|
+
const entryPath = (0, node_path.join)(dirPath, entry);
|
|
1920
|
+
const entryStat = await (0, node_fs_promises.stat)(entryPath);
|
|
1921
|
+
if (entryStat.isDirectory()) {
|
|
1922
|
+
await this.collectSkillSnapshots(entryPath, snapshot);
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
if (entry === "SKILL.md") snapshot.set(entryPath, entryStat.mtimeMs);
|
|
1926
|
+
}));
|
|
1927
|
+
}
|
|
1928
|
+
snapshotsEqual(left, right) {
|
|
1929
|
+
if (left.size !== right.size) return false;
|
|
1930
|
+
for (const [filePath, mtimeMs] of left) if (right.get(filePath) !== mtimeMs) return false;
|
|
1931
|
+
return true;
|
|
1504
1932
|
}
|
|
1505
1933
|
/**
|
|
1506
1934
|
* Load skills from a directory.
|
|
@@ -1608,32 +2036,6 @@ var SkillService = class {
|
|
|
1608
2036
|
}
|
|
1609
2037
|
};
|
|
1610
2038
|
|
|
1611
|
-
//#endregion
|
|
1612
|
-
//#region src/constants/index.ts
|
|
1613
|
-
/**
|
|
1614
|
-
* Shared constants for one-mcp package
|
|
1615
|
-
*/
|
|
1616
|
-
/**
|
|
1617
|
-
* Prefix added to skill names when they clash with MCP tool names.
|
|
1618
|
-
* This ensures skills can be uniquely identified even when a tool has the same name.
|
|
1619
|
-
*/
|
|
1620
|
-
const SKILL_PREFIX = "skill__";
|
|
1621
|
-
/**
|
|
1622
|
-
* Log prefix for skill detection messages.
|
|
1623
|
-
* Used to easily filter skill detection logs in stderr output.
|
|
1624
|
-
*/
|
|
1625
|
-
const LOG_PREFIX_SKILL_DETECTION = "[skill-detection]";
|
|
1626
|
-
/**
|
|
1627
|
-
* Prefix for prompt-based skill locations.
|
|
1628
|
-
* Format: "prompt:{serverName}:{promptName}"
|
|
1629
|
-
*/
|
|
1630
|
-
const PROMPT_LOCATION_PREFIX = "prompt:";
|
|
1631
|
-
/**
|
|
1632
|
-
* Default server ID used when no ID is provided via CLI or config.
|
|
1633
|
-
* This fallback is used when auto-generation also fails.
|
|
1634
|
-
*/
|
|
1635
|
-
const DEFAULT_SERVER_ID = "unknown";
|
|
1636
|
-
|
|
1637
2039
|
//#endregion
|
|
1638
2040
|
//#region src/templates/toolkit-description.liquid?raw
|
|
1639
2041
|
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";
|
|
@@ -1672,6 +2074,7 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1672
2074
|
static TOOL_NAME = "describe_tools";
|
|
1673
2075
|
clientManager;
|
|
1674
2076
|
skillService;
|
|
2077
|
+
definitionsCacheService;
|
|
1675
2078
|
liquid = new liquidjs.Liquid();
|
|
1676
2079
|
/** Cache for auto-detected skills from prompt front-matter */
|
|
1677
2080
|
autoDetectedSkillsCache = null;
|
|
@@ -1683,10 +2086,11 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1683
2086
|
* @param skillService - Optional skill service for loading skills
|
|
1684
2087
|
* @param serverId - Unique server identifier for this one-mcp instance
|
|
1685
2088
|
*/
|
|
1686
|
-
constructor(clientManager, skillService, serverId) {
|
|
2089
|
+
constructor(clientManager, skillService, serverId, definitionsCacheService) {
|
|
1687
2090
|
this.clientManager = clientManager;
|
|
1688
2091
|
this.skillService = skillService;
|
|
1689
2092
|
this.serverId = serverId || DEFAULT_SERVER_ID;
|
|
2093
|
+
this.definitionsCacheService = definitionsCacheService || new DefinitionsCacheService(clientManager, skillService);
|
|
1690
2094
|
}
|
|
1691
2095
|
/**
|
|
1692
2096
|
* Clears the cached auto-detected skills from prompt front-matter.
|
|
@@ -1695,6 +2099,7 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1695
2099
|
*/
|
|
1696
2100
|
clearAutoDetectedSkillsCache() {
|
|
1697
2101
|
this.autoDetectedSkillsCache = null;
|
|
2102
|
+
this.definitionsCacheService.clearLiveCache();
|
|
1698
2103
|
}
|
|
1699
2104
|
/**
|
|
1700
2105
|
* Detects and caches skills from prompt front-matter across all connected MCP servers.
|
|
@@ -1760,21 +2165,12 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1760
2165
|
* @returns Array of skill template data derived from prompts
|
|
1761
2166
|
*/
|
|
1762
2167
|
async collectPromptSkills() {
|
|
1763
|
-
const clients = this.clientManager.getAllClients();
|
|
1764
2168
|
const promptSkills = [];
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
description: promptConfig.skill.description
|
|
1771
|
-
});
|
|
1772
|
-
}
|
|
1773
|
-
const autoDetectedSkills = await this.detectSkillsFromPromptFrontMatter();
|
|
1774
|
-
for (const autoSkill of autoDetectedSkills) promptSkills.push({
|
|
1775
|
-
name: autoSkill.skill.name,
|
|
1776
|
-
displayName: autoSkill.skill.name,
|
|
1777
|
-
description: autoSkill.skill.description
|
|
2169
|
+
const serverDefinitions = await this.definitionsCacheService.getServerDefinitions();
|
|
2170
|
+
for (const serverDefinition of serverDefinitions) for (const promptSkill of serverDefinition.promptSkills) promptSkills.push({
|
|
2171
|
+
name: promptSkill.skill.name,
|
|
2172
|
+
displayName: promptSkill.skill.name,
|
|
2173
|
+
description: promptSkill.skill.description
|
|
1778
2174
|
});
|
|
1779
2175
|
return promptSkills;
|
|
1780
2176
|
}
|
|
@@ -1787,22 +2183,7 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1787
2183
|
*/
|
|
1788
2184
|
async findPromptSkill(skillName) {
|
|
1789
2185
|
if (!skillName) return void 0;
|
|
1790
|
-
|
|
1791
|
-
for (const client of clients) {
|
|
1792
|
-
if (!client.prompts) continue;
|
|
1793
|
-
for (const [promptName, promptConfig] of Object.entries(client.prompts)) if (promptConfig.skill && promptConfig.skill.name === skillName) return {
|
|
1794
|
-
serverName: client.serverName,
|
|
1795
|
-
promptName,
|
|
1796
|
-
skill: promptConfig.skill
|
|
1797
|
-
};
|
|
1798
|
-
}
|
|
1799
|
-
const autoDetectedSkills = await this.detectSkillsFromPromptFrontMatter();
|
|
1800
|
-
for (const autoSkill of autoDetectedSkills) if (autoSkill.skill.name === skillName) return {
|
|
1801
|
-
serverName: autoSkill.serverName,
|
|
1802
|
-
promptName: autoSkill.promptName,
|
|
1803
|
-
skill: autoSkill.skill,
|
|
1804
|
-
autoDetected: true
|
|
1805
|
-
};
|
|
2186
|
+
return await this.definitionsCacheService.getPromptSkillByName(skillName);
|
|
1806
2187
|
}
|
|
1807
2188
|
/**
|
|
1808
2189
|
* Retrieves skill content from a prompt-based skill configuration.
|
|
@@ -1815,13 +2196,8 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1815
2196
|
async getPromptSkillContent(skillName) {
|
|
1816
2197
|
const promptSkill = await this.findPromptSkill(skillName);
|
|
1817
2198
|
if (!promptSkill) return void 0;
|
|
1818
|
-
const client = this.clientManager.getClient(promptSkill.serverName);
|
|
1819
|
-
if (!client) {
|
|
1820
|
-
console.error(`Client not found for server '${promptSkill.serverName}' when fetching prompt skill '${skillName}'`);
|
|
1821
|
-
return;
|
|
1822
|
-
}
|
|
1823
2199
|
try {
|
|
1824
|
-
const rawInstructions = (await
|
|
2200
|
+
const rawInstructions = (await (await this.clientManager.ensureConnected(promptSkill.serverName)).getPrompt(promptSkill.promptName)).messages?.map((m) => {
|
|
1825
2201
|
const content = m.content;
|
|
1826
2202
|
if (typeof content === "string") return content;
|
|
1827
2203
|
if (content && typeof content === "object" && "text" in content) return String(content.text);
|
|
@@ -1850,24 +2226,12 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1850
2226
|
* @returns Object with rendered description and set of all tool names
|
|
1851
2227
|
*/
|
|
1852
2228
|
async buildToolkitDescription() {
|
|
1853
|
-
const
|
|
2229
|
+
const serverDefinitions = await this.definitionsCacheService.getServerDefinitions();
|
|
1854
2230
|
const toolToServers = /* @__PURE__ */ new Map();
|
|
1855
|
-
const
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
const blacklist = new Set(client.toolBlacklist || []);
|
|
1860
|
-
const filteredTools = tools.filter((t) => !blacklist.has(t.name));
|
|
1861
|
-
serverToolsMap.set(client.serverName, filteredTools);
|
|
1862
|
-
for (const tool of filteredTools) {
|
|
1863
|
-
if (!toolToServers.has(tool.name)) toolToServers.set(tool.name, []);
|
|
1864
|
-
toolToServers.get(tool.name)?.push(client.serverName);
|
|
1865
|
-
}
|
|
1866
|
-
} catch (error) {
|
|
1867
|
-
console.error(`Failed to list tools from ${client.serverName}:`, error);
|
|
1868
|
-
serverToolsMap.set(client.serverName, []);
|
|
1869
|
-
}
|
|
1870
|
-
}));
|
|
2231
|
+
for (const serverDefinition of serverDefinitions) for (const tool of serverDefinition.tools) {
|
|
2232
|
+
if (!toolToServers.has(tool.name)) toolToServers.set(tool.name, []);
|
|
2233
|
+
toolToServers.get(tool.name)?.push(serverDefinition.serverName);
|
|
2234
|
+
}
|
|
1871
2235
|
/**
|
|
1872
2236
|
* Formats tool name with server prefix if the tool exists on multiple servers
|
|
1873
2237
|
*/
|
|
@@ -1875,21 +2239,25 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1875
2239
|
return (toolToServers.get(toolName) || []).length > 1 ? `${serverName}__${toolName}` : toolName;
|
|
1876
2240
|
};
|
|
1877
2241
|
const allToolNames = /* @__PURE__ */ new Set();
|
|
1878
|
-
const servers =
|
|
1879
|
-
const formattedTools =
|
|
1880
|
-
displayName: formatToolName(t.name,
|
|
2242
|
+
const servers = serverDefinitions.map((serverDefinition) => {
|
|
2243
|
+
const formattedTools = serverDefinition.tools.map((t) => ({
|
|
2244
|
+
displayName: formatToolName(t.name, serverDefinition.serverName),
|
|
1881
2245
|
description: t.description
|
|
1882
2246
|
}));
|
|
1883
2247
|
for (const tool of formattedTools) allToolNames.add(tool.displayName);
|
|
1884
2248
|
return {
|
|
1885
|
-
name:
|
|
1886
|
-
instruction:
|
|
1887
|
-
omitToolDescription:
|
|
2249
|
+
name: serverDefinition.serverName,
|
|
2250
|
+
instruction: serverDefinition.serverInstruction,
|
|
2251
|
+
omitToolDescription: serverDefinition.omitToolDescription || false,
|
|
1888
2252
|
tools: formattedTools,
|
|
1889
2253
|
toolNames: formattedTools.map((t) => t.displayName)
|
|
1890
2254
|
};
|
|
1891
2255
|
});
|
|
1892
|
-
const [rawSkills, promptSkills] = await Promise.all([
|
|
2256
|
+
const [rawSkills, cachedSkills, promptSkills] = await Promise.all([
|
|
2257
|
+
this.skillService ? this.skillService.getSkills() : Promise.resolve([]),
|
|
2258
|
+
this.definitionsCacheService.getCachedFileSkills(),
|
|
2259
|
+
this.collectPromptSkills()
|
|
2260
|
+
]);
|
|
1893
2261
|
const seenSkillNames = /* @__PURE__ */ new Set();
|
|
1894
2262
|
const allSkillsData = [];
|
|
1895
2263
|
for (const skill of rawSkills) if (!seenSkillNames.has(skill.name)) {
|
|
@@ -1900,6 +2268,14 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1900
2268
|
description: skill.description
|
|
1901
2269
|
});
|
|
1902
2270
|
}
|
|
2271
|
+
for (const skill of cachedSkills) if (!seenSkillNames.has(skill.name)) {
|
|
2272
|
+
seenSkillNames.add(skill.name);
|
|
2273
|
+
allSkillsData.push({
|
|
2274
|
+
name: skill.name,
|
|
2275
|
+
displayName: skill.name,
|
|
2276
|
+
description: skill.description
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
1903
2279
|
for (const skill of promptSkills) if (!seenSkillNames.has(skill.name)) {
|
|
1904
2280
|
seenSkillNames.add(skill.name);
|
|
1905
2281
|
allSkillsData.push(skill);
|
|
@@ -1969,7 +2345,7 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1969
2345
|
async execute(input) {
|
|
1970
2346
|
try {
|
|
1971
2347
|
const { toolNames } = input;
|
|
1972
|
-
const
|
|
2348
|
+
const serverDefinitions = await this.definitionsCacheService.getServerDefinitions();
|
|
1973
2349
|
if (!toolNames || toolNames.length === 0) return {
|
|
1974
2350
|
content: [{
|
|
1975
2351
|
type: "text",
|
|
@@ -1979,25 +2355,18 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
1979
2355
|
};
|
|
1980
2356
|
const serverToolsMap = /* @__PURE__ */ new Map();
|
|
1981
2357
|
const toolToServers = /* @__PURE__ */ new Map();
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
for (const tool of typedTools) {
|
|
1993
|
-
if (!toolToServers.has(tool.name)) toolToServers.set(tool.name, []);
|
|
1994
|
-
toolToServers.get(tool.name)?.push(client.serverName);
|
|
1995
|
-
}
|
|
1996
|
-
} catch (error) {
|
|
1997
|
-
console.error(`Failed to list tools from ${client.serverName}:`, error);
|
|
1998
|
-
serverToolsMap.set(client.serverName, []);
|
|
2358
|
+
for (const serverDefinition of serverDefinitions) {
|
|
2359
|
+
const typedTools = serverDefinition.tools.map((tool) => ({
|
|
2360
|
+
name: tool.name,
|
|
2361
|
+
description: tool.description,
|
|
2362
|
+
inputSchema: tool.inputSchema
|
|
2363
|
+
}));
|
|
2364
|
+
serverToolsMap.set(serverDefinition.serverName, typedTools);
|
|
2365
|
+
for (const tool of typedTools) {
|
|
2366
|
+
if (!toolToServers.has(tool.name)) toolToServers.set(tool.name, []);
|
|
2367
|
+
toolToServers.get(tool.name)?.push(serverDefinition.serverName);
|
|
1999
2368
|
}
|
|
2000
|
-
}
|
|
2369
|
+
}
|
|
2001
2370
|
const lookupResults = await Promise.all(toolNames.map(async (requestedName) => {
|
|
2002
2371
|
const result$1 = {
|
|
2003
2372
|
tools: [],
|
|
@@ -2135,6 +2504,95 @@ var DescribeToolsTool = class DescribeToolsTool {
|
|
|
2135
2504
|
}
|
|
2136
2505
|
};
|
|
2137
2506
|
|
|
2507
|
+
//#endregion
|
|
2508
|
+
//#region src/utils/toolCapabilities.ts
|
|
2509
|
+
const TOOL_CAPABILITIES_META_KEY = "agiflowai/capabilities";
|
|
2510
|
+
function getToolCapabilities(tool) {
|
|
2511
|
+
const rawCapabilities = tool._meta?.[TOOL_CAPABILITIES_META_KEY];
|
|
2512
|
+
if (!Array.isArray(rawCapabilities)) return [];
|
|
2513
|
+
return rawCapabilities.filter((value) => typeof value === "string");
|
|
2514
|
+
}
|
|
2515
|
+
function getUniqueSortedCapabilities(tools) {
|
|
2516
|
+
return Array.from(new Set(tools.flatMap((tool) => getToolCapabilities(tool)))).sort();
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
//#endregion
|
|
2520
|
+
//#region src/tools/SearchListToolsTool.ts
|
|
2521
|
+
var SearchListToolsTool = class SearchListToolsTool {
|
|
2522
|
+
static TOOL_NAME = "list_tools";
|
|
2523
|
+
constructor(_clientManager, definitionsCacheService) {
|
|
2524
|
+
this._clientManager = _clientManager;
|
|
2525
|
+
this.definitionsCacheService = definitionsCacheService;
|
|
2526
|
+
}
|
|
2527
|
+
formatToolName(toolName, serverName, toolToServers) {
|
|
2528
|
+
return (toolToServers.get(toolName) || []).length > 1 ? `${serverName}__${toolName}` : toolName;
|
|
2529
|
+
}
|
|
2530
|
+
async getDefinition() {
|
|
2531
|
+
const serverDefinitions = await this.definitionsCacheService.getServerDefinitions();
|
|
2532
|
+
const capabilitySummary = serverDefinitions.length > 0 ? serverDefinitions.map((server) => {
|
|
2533
|
+
const capabilities = getUniqueSortedCapabilities(server.tools);
|
|
2534
|
+
const summary = capabilities.length > 0 ? capabilities.join(", ") : server.serverInstruction || "No capability summary available";
|
|
2535
|
+
return `${server.serverName}: ${summary}`;
|
|
2536
|
+
}).join("\n") : "No proxied servers available.";
|
|
2537
|
+
return {
|
|
2538
|
+
name: SearchListToolsTool.TOOL_NAME,
|
|
2539
|
+
description: `Search proxied MCP tools by server capability summary.\n\nAvailable capabilities:\n${capabilitySummary}`,
|
|
2540
|
+
inputSchema: {
|
|
2541
|
+
type: "object",
|
|
2542
|
+
properties: {
|
|
2543
|
+
capability: {
|
|
2544
|
+
type: "string",
|
|
2545
|
+
description: "Optional capability filter. Matches explicit capability tags first, then server summaries, server names, tool names, and tool descriptions."
|
|
2546
|
+
},
|
|
2547
|
+
serverName: {
|
|
2548
|
+
type: "string",
|
|
2549
|
+
description: "Optional server name filter."
|
|
2550
|
+
}
|
|
2551
|
+
},
|
|
2552
|
+
additionalProperties: false
|
|
2553
|
+
}
|
|
2554
|
+
};
|
|
2555
|
+
}
|
|
2556
|
+
async execute(input) {
|
|
2557
|
+
const serverDefinitions = await this.definitionsCacheService.getServerDefinitions();
|
|
2558
|
+
const capabilityFilter = input.capability?.trim().toLowerCase();
|
|
2559
|
+
const serverNameFilter = input.serverName?.trim().toLowerCase();
|
|
2560
|
+
const toolToServers = /* @__PURE__ */ new Map();
|
|
2561
|
+
for (const serverDefinition of serverDefinitions) for (const tool of serverDefinition.tools) {
|
|
2562
|
+
if (!toolToServers.has(tool.name)) toolToServers.set(tool.name, []);
|
|
2563
|
+
toolToServers.get(tool.name)?.push(serverDefinition.serverName);
|
|
2564
|
+
}
|
|
2565
|
+
const filteredServers = serverDefinitions.filter((serverDefinition) => {
|
|
2566
|
+
if (serverNameFilter && serverDefinition.serverName.toLowerCase() !== serverNameFilter) return false;
|
|
2567
|
+
if (!capabilityFilter) return true;
|
|
2568
|
+
if ((serverDefinition.serverInstruction?.toLowerCase() || "").includes(capabilityFilter)) return true;
|
|
2569
|
+
if (getUniqueSortedCapabilities(serverDefinition.tools).some((capability) => capability.toLowerCase().includes(capabilityFilter))) return true;
|
|
2570
|
+
return serverDefinition.tools.some((tool) => {
|
|
2571
|
+
const toolName = this.formatToolName(tool.name, serverDefinition.serverName, toolToServers);
|
|
2572
|
+
const toolCapabilities = getToolCapabilities(tool);
|
|
2573
|
+
return toolName.toLowerCase().includes(capabilityFilter) || (tool.description || "").toLowerCase().includes(capabilityFilter) || toolCapabilities.some((capability) => capability.toLowerCase().includes(capabilityFilter));
|
|
2574
|
+
});
|
|
2575
|
+
}).map((serverDefinition) => ({
|
|
2576
|
+
server: serverDefinition.serverName,
|
|
2577
|
+
capabilities: getUniqueSortedCapabilities(serverDefinition.tools),
|
|
2578
|
+
summary: serverDefinition.serverInstruction,
|
|
2579
|
+
tools: serverDefinition.tools.map((tool) => ({
|
|
2580
|
+
name: this.formatToolName(tool.name, serverDefinition.serverName, toolToServers),
|
|
2581
|
+
description: serverDefinition.omitToolDescription ? void 0 : tool.description,
|
|
2582
|
+
capabilities: getToolCapabilities(tool)
|
|
2583
|
+
}))
|
|
2584
|
+
})).filter((server) => server.tools.length > 0);
|
|
2585
|
+
const result = { servers: filteredServers };
|
|
2586
|
+
return {
|
|
2587
|
+
content: [{
|
|
2588
|
+
type: "text",
|
|
2589
|
+
text: JSON.stringify(result, null, 2)
|
|
2590
|
+
}],
|
|
2591
|
+
isError: filteredServers.length === 0 ? true : void 0
|
|
2592
|
+
};
|
|
2593
|
+
}
|
|
2594
|
+
};
|
|
2595
|
+
|
|
2138
2596
|
//#endregion
|
|
2139
2597
|
//#region src/tools/UseToolTool.ts
|
|
2140
2598
|
/**
|
|
@@ -2154,6 +2612,7 @@ var UseToolTool = class UseToolTool {
|
|
|
2154
2612
|
static TOOL_NAME = "use_tool";
|
|
2155
2613
|
clientManager;
|
|
2156
2614
|
skillService;
|
|
2615
|
+
definitionsCacheService;
|
|
2157
2616
|
/** Unique server identifier for this one-mcp instance */
|
|
2158
2617
|
serverId;
|
|
2159
2618
|
/**
|
|
@@ -2162,9 +2621,10 @@ var UseToolTool = class UseToolTool {
|
|
|
2162
2621
|
* @param skillService - Optional skill service for loading and executing skills
|
|
2163
2622
|
* @param serverId - Unique server identifier for this one-mcp instance
|
|
2164
2623
|
*/
|
|
2165
|
-
constructor(clientManager, skillService, serverId) {
|
|
2624
|
+
constructor(clientManager, skillService, serverId, definitionsCacheService) {
|
|
2166
2625
|
this.clientManager = clientManager;
|
|
2167
2626
|
this.skillService = skillService;
|
|
2627
|
+
this.definitionsCacheService = definitionsCacheService || new DefinitionsCacheService(clientManager, skillService);
|
|
2168
2628
|
this.serverId = serverId || DEFAULT_SERVER_ID;
|
|
2169
2629
|
}
|
|
2170
2630
|
/**
|
|
@@ -2224,17 +2684,9 @@ IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverI
|
|
|
2224
2684
|
* @param skillName - The skill name to search for
|
|
2225
2685
|
* @returns PromptSkillMatch if found, undefined otherwise
|
|
2226
2686
|
*/
|
|
2227
|
-
findPromptSkill(skillName) {
|
|
2687
|
+
async findPromptSkill(skillName) {
|
|
2228
2688
|
if (!skillName) return void 0;
|
|
2229
|
-
|
|
2230
|
-
for (const client of clients) {
|
|
2231
|
-
if (!client.prompts) continue;
|
|
2232
|
-
for (const [promptName, promptConfig] of Object.entries(client.prompts)) if (promptConfig.skill && promptConfig.skill.name === skillName) return {
|
|
2233
|
-
serverName: client.serverName,
|
|
2234
|
-
promptName,
|
|
2235
|
-
skill: promptConfig.skill
|
|
2236
|
-
};
|
|
2237
|
-
}
|
|
2689
|
+
return await this.definitionsCacheService.getPromptSkillByName(skillName);
|
|
2238
2690
|
}
|
|
2239
2691
|
/**
|
|
2240
2692
|
* Returns guidance message for prompt-based skill invocation.
|
|
@@ -2274,7 +2726,7 @@ IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverI
|
|
|
2274
2726
|
const skill = await this.skillService.getSkill(skillName);
|
|
2275
2727
|
if (skill) return this.executeSkill(skill);
|
|
2276
2728
|
}
|
|
2277
|
-
const promptSkill = this.findPromptSkill(skillName);
|
|
2729
|
+
const promptSkill = await this.findPromptSkill(skillName);
|
|
2278
2730
|
if (promptSkill) return this.executePromptSkill(promptSkill);
|
|
2279
2731
|
return {
|
|
2280
2732
|
content: [{
|
|
@@ -2284,53 +2736,34 @@ IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverI
|
|
|
2284
2736
|
isError: true
|
|
2285
2737
|
};
|
|
2286
2738
|
}
|
|
2287
|
-
const
|
|
2739
|
+
const knownServerNames = this.clientManager.getKnownServerNames();
|
|
2288
2740
|
const { serverName, actualToolName } = parseToolName(inputToolName);
|
|
2289
|
-
if (serverName) {
|
|
2290
|
-
const client
|
|
2291
|
-
if (
|
|
2741
|
+
if (serverName) try {
|
|
2742
|
+
const client = await this.clientManager.ensureConnected(serverName);
|
|
2743
|
+
if (client.toolBlacklist?.includes(actualToolName)) return {
|
|
2292
2744
|
content: [{
|
|
2293
2745
|
type: "text",
|
|
2294
|
-
text: `
|
|
2746
|
+
text: `Tool "${actualToolName}" is blacklisted on server "${serverName}" and cannot be executed.`
|
|
2295
2747
|
}],
|
|
2296
2748
|
isError: true
|
|
2297
2749
|
};
|
|
2298
|
-
|
|
2750
|
+
return await client.callTool(actualToolName, toolArgs);
|
|
2751
|
+
} catch (error) {
|
|
2752
|
+
return {
|
|
2299
2753
|
content: [{
|
|
2300
2754
|
type: "text",
|
|
2301
|
-
text: `
|
|
2755
|
+
text: `Failed to call tool "${actualToolName}" on server "${serverName}". Available servers: ${knownServerNames.join(", ")}. ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2302
2756
|
}],
|
|
2303
2757
|
isError: true
|
|
2304
2758
|
};
|
|
2305
|
-
try {
|
|
2306
|
-
return await client$1.callTool(actualToolName, toolArgs);
|
|
2307
|
-
} catch (error) {
|
|
2308
|
-
return {
|
|
2309
|
-
content: [{
|
|
2310
|
-
type: "text",
|
|
2311
|
-
text: `Failed to call tool "${actualToolName}" on server "${serverName}": ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2312
|
-
}],
|
|
2313
|
-
isError: true
|
|
2314
|
-
};
|
|
2315
|
-
}
|
|
2316
2759
|
}
|
|
2317
|
-
const matchingServers =
|
|
2318
|
-
const results = await Promise.all(clients.map(async (client$1) => {
|
|
2319
|
-
try {
|
|
2320
|
-
if (client$1.toolBlacklist?.includes(actualToolName)) return null;
|
|
2321
|
-
if ((await client$1.listTools()).some((t) => t.name === actualToolName)) return client$1.serverName;
|
|
2322
|
-
} catch (error) {
|
|
2323
|
-
console.error(`Failed to list tools from ${client$1.serverName}:`, error);
|
|
2324
|
-
}
|
|
2325
|
-
return null;
|
|
2326
|
-
}));
|
|
2327
|
-
matchingServers.push(...results.filter((r) => r !== null));
|
|
2760
|
+
const matchingServers = await this.definitionsCacheService.getServersForTool(actualToolName);
|
|
2328
2761
|
if (matchingServers.length === 0) {
|
|
2329
2762
|
if (this.skillService) {
|
|
2330
2763
|
const skill = await this.skillService.getSkill(actualToolName);
|
|
2331
2764
|
if (skill) return this.executeSkill(skill);
|
|
2332
2765
|
}
|
|
2333
|
-
const promptSkill = this.findPromptSkill(actualToolName);
|
|
2766
|
+
const promptSkill = await this.findPromptSkill(actualToolName);
|
|
2334
2767
|
if (promptSkill) return this.executePromptSkill(promptSkill);
|
|
2335
2768
|
return {
|
|
2336
2769
|
content: [{
|
|
@@ -2347,17 +2780,9 @@ IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverI
|
|
|
2347
2780
|
}],
|
|
2348
2781
|
isError: true
|
|
2349
2782
|
};
|
|
2350
|
-
const targetServerName = matchingServers[0];
|
|
2351
|
-
const client = this.clientManager.getClient(targetServerName);
|
|
2352
|
-
if (!client) return {
|
|
2353
|
-
content: [{
|
|
2354
|
-
type: "text",
|
|
2355
|
-
text: `Internal error: Server "${targetServerName}" was found but is not connected`
|
|
2356
|
-
}],
|
|
2357
|
-
isError: true
|
|
2358
|
-
};
|
|
2359
2783
|
try {
|
|
2360
|
-
|
|
2784
|
+
const targetServerName = matchingServers[0];
|
|
2785
|
+
return await (await this.clientManager.ensureConnected(targetServerName)).callTool(actualToolName, toolArgs);
|
|
2361
2786
|
} catch (error) {
|
|
2362
2787
|
return {
|
|
2363
2788
|
content: [{
|
|
@@ -2379,6 +2804,10 @@ IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverI
|
|
|
2379
2804
|
}
|
|
2380
2805
|
};
|
|
2381
2806
|
|
|
2807
|
+
//#endregion
|
|
2808
|
+
//#region package.json
|
|
2809
|
+
var version = "0.3.12";
|
|
2810
|
+
|
|
2382
2811
|
//#endregion
|
|
2383
2812
|
//#region src/server/index.ts
|
|
2384
2813
|
/**
|
|
@@ -2394,17 +2823,110 @@ IMPORTANT: Only use tools discovered from describe_tools with id="${this.serverI
|
|
|
2394
2823
|
* - Keep server setup modular and extensible
|
|
2395
2824
|
* - Import tools from ../tools/ and register them in the handlers
|
|
2396
2825
|
*/
|
|
2826
|
+
function summarizeServerTools(serverDefinition) {
|
|
2827
|
+
const toolNames = serverDefinition.tools.map((tool) => tool.name);
|
|
2828
|
+
const capabilities = getUniqueSortedCapabilities(serverDefinition.tools);
|
|
2829
|
+
const capabilitySummary = capabilities.length > 0 ? `; capabilities: ${capabilities.join(", ")}` : "";
|
|
2830
|
+
if (toolNames.length === 0) return `${serverDefinition.serverName} (no tools cached${capabilitySummary})`;
|
|
2831
|
+
return `${serverDefinition.serverName} (${toolNames.join(", ")})${capabilitySummary}`;
|
|
2832
|
+
}
|
|
2833
|
+
function buildFlatToolDescription(serverDefinition, tool) {
|
|
2834
|
+
const parts = [`Proxied from server "${serverDefinition.serverName}" as tool "${tool.name}".`];
|
|
2835
|
+
if (serverDefinition.serverInstruction) parts.push(`Server summary: ${serverDefinition.serverInstruction}`);
|
|
2836
|
+
if (tool.description && !serverDefinition.omitToolDescription) parts.push(tool.description);
|
|
2837
|
+
const capabilities = getToolCapabilities(tool);
|
|
2838
|
+
if (capabilities.length > 0) parts.push(`Capabilities: ${capabilities.join(", ")}`);
|
|
2839
|
+
return parts.join("\n\n");
|
|
2840
|
+
}
|
|
2841
|
+
function buildFlatToolDefinitions(serverDefinitions) {
|
|
2842
|
+
const toolToServers = /* @__PURE__ */ new Map();
|
|
2843
|
+
for (const serverDefinition of serverDefinitions) for (const tool of serverDefinition.tools) {
|
|
2844
|
+
if (!toolToServers.has(tool.name)) toolToServers.set(tool.name, []);
|
|
2845
|
+
toolToServers.get(tool.name)?.push(serverDefinition.serverName);
|
|
2846
|
+
}
|
|
2847
|
+
const definitions = [];
|
|
2848
|
+
for (const serverDefinition of serverDefinitions) for (const tool of serverDefinition.tools) {
|
|
2849
|
+
const hasClash = (toolToServers.get(tool.name) || []).length > 1;
|
|
2850
|
+
definitions.push({
|
|
2851
|
+
name: hasClash ? `${serverDefinition.serverName}__${tool.name}` : tool.name,
|
|
2852
|
+
description: buildFlatToolDescription(serverDefinition, tool),
|
|
2853
|
+
inputSchema: tool.inputSchema,
|
|
2854
|
+
_meta: tool._meta
|
|
2855
|
+
});
|
|
2856
|
+
}
|
|
2857
|
+
return definitions;
|
|
2858
|
+
}
|
|
2859
|
+
async function hasAnySkills(definitionsCacheService, skillService) {
|
|
2860
|
+
const [fileSkills, serverDefinitions] = await Promise.all([skillService ? skillService.getSkills() : definitionsCacheService.getCachedFileSkills(), definitionsCacheService.getServerDefinitions()]);
|
|
2861
|
+
return fileSkills.length > 0 || serverDefinitions.some((server) => server.promptSkills.length > 0);
|
|
2862
|
+
}
|
|
2863
|
+
function buildSkillsDescribeDefinition(serverDefinitions, serverId) {
|
|
2864
|
+
const proxySummary = serverDefinitions.length > 0 ? serverDefinitions.map(summarizeServerTools).join("; ") : "No proxied servers available.";
|
|
2865
|
+
return {
|
|
2866
|
+
name: DescribeToolsTool.TOOL_NAME,
|
|
2867
|
+
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}".`,
|
|
2868
|
+
inputSchema: {
|
|
2869
|
+
type: "object",
|
|
2870
|
+
properties: { toolNames: {
|
|
2871
|
+
type: "array",
|
|
2872
|
+
items: {
|
|
2873
|
+
type: "string",
|
|
2874
|
+
minLength: 1
|
|
2875
|
+
},
|
|
2876
|
+
description: "List of skill names to get detailed information about",
|
|
2877
|
+
minItems: 1
|
|
2878
|
+
} },
|
|
2879
|
+
required: ["toolNames"],
|
|
2880
|
+
additionalProperties: false
|
|
2881
|
+
}
|
|
2882
|
+
};
|
|
2883
|
+
}
|
|
2884
|
+
function buildSearchDescribeDefinition(serverDefinitions, serverId) {
|
|
2885
|
+
const summary = serverDefinitions.length > 0 ? serverDefinitions.map(summarizeServerTools).join("; ") : "No proxied servers available.";
|
|
2886
|
+
return {
|
|
2887
|
+
name: DescribeToolsTool.TOOL_NAME,
|
|
2888
|
+
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}".`,
|
|
2889
|
+
inputSchema: {
|
|
2890
|
+
type: "object",
|
|
2891
|
+
properties: { toolNames: {
|
|
2892
|
+
type: "array",
|
|
2893
|
+
items: {
|
|
2894
|
+
type: "string",
|
|
2895
|
+
minLength: 1
|
|
2896
|
+
},
|
|
2897
|
+
description: "List of tool or skill names to get detailed information about",
|
|
2898
|
+
minItems: 1
|
|
2899
|
+
} },
|
|
2900
|
+
required: ["toolNames"],
|
|
2901
|
+
additionalProperties: false
|
|
2902
|
+
}
|
|
2903
|
+
};
|
|
2904
|
+
}
|
|
2905
|
+
function buildProxyInstructions(serverDefinitions, mode, includeSkillsTool) {
|
|
2906
|
+
const summary = serverDefinitions.length > 0 ? serverDefinitions.map(summarizeServerTools).join("; ") : "No proxied servers available.";
|
|
2907
|
+
if (mode === "flat") return [
|
|
2908
|
+
"one-mcp proxies downstream MCP servers and exposes their tools and resources directly.",
|
|
2909
|
+
`Proxied servers and tools: ${summary}`,
|
|
2910
|
+
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."
|
|
2911
|
+
].join("\n\n");
|
|
2912
|
+
if (mode === "search") return [
|
|
2913
|
+
"one-mcp proxies downstream MCP servers in search mode.",
|
|
2914
|
+
`Proxied servers and tools: ${summary}`,
|
|
2915
|
+
"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."
|
|
2916
|
+
].join("\n\n");
|
|
2917
|
+
return [
|
|
2918
|
+
"one-mcp proxies downstream MCP servers in meta mode.",
|
|
2919
|
+
`Proxied servers and tools: ${summary}`,
|
|
2920
|
+
"Use describe_tools to inspect capabilities and use_tool to execute them."
|
|
2921
|
+
].join("\n\n");
|
|
2922
|
+
}
|
|
2397
2923
|
async function createServer(options) {
|
|
2398
|
-
const server = new __modelcontextprotocol_sdk_server_index_js.Server({
|
|
2399
|
-
name: "@agiflowai/one-mcp",
|
|
2400
|
-
version: "0.1.0"
|
|
2401
|
-
}, { capabilities: {
|
|
2402
|
-
tools: {},
|
|
2403
|
-
prompts: {}
|
|
2404
|
-
} });
|
|
2405
2924
|
const clientManager = new McpClientManagerService();
|
|
2406
2925
|
let configSkills;
|
|
2407
2926
|
let configId;
|
|
2927
|
+
let configHash;
|
|
2928
|
+
let effectiveDefinitionsCachePath;
|
|
2929
|
+
let shouldStartFromCache = false;
|
|
2408
2930
|
if (options?.configFilePath) {
|
|
2409
2931
|
let config;
|
|
2410
2932
|
try {
|
|
@@ -2417,38 +2939,92 @@ async function createServer(options) {
|
|
|
2417
2939
|
}
|
|
2418
2940
|
configSkills = config.skills;
|
|
2419
2941
|
configId = config.id;
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
}
|
|
2433
|
-
}
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2942
|
+
configHash = DefinitionsCacheService.generateConfigHash(config);
|
|
2943
|
+
effectiveDefinitionsCachePath = options.definitionsCachePath || DefinitionsCacheService.getDefaultCachePath(options.configFilePath);
|
|
2944
|
+
clientManager.registerServerConfigs(config.mcpServers);
|
|
2945
|
+
if (options.clearDefinitionsCache && effectiveDefinitionsCachePath) {
|
|
2946
|
+
await DefinitionsCacheService.clearFile(effectiveDefinitionsCachePath);
|
|
2947
|
+
console.error(`[definitions-cache] Cleared ${effectiveDefinitionsCachePath}`);
|
|
2948
|
+
}
|
|
2949
|
+
if (effectiveDefinitionsCachePath) try {
|
|
2950
|
+
const cacheData = await DefinitionsCacheService.readFromFile(effectiveDefinitionsCachePath);
|
|
2951
|
+
if (DefinitionsCacheService.isCacheValid(cacheData, {
|
|
2952
|
+
configHash,
|
|
2953
|
+
oneMcpVersion: version
|
|
2954
|
+
})) shouldStartFromCache = true;
|
|
2955
|
+
} catch {}
|
|
2956
|
+
if (!shouldStartFromCache) {
|
|
2957
|
+
const failedConnections = [];
|
|
2958
|
+
const connectionPromises = Object.entries(config.mcpServers).map(async ([serverName, serverConfig]) => {
|
|
2959
|
+
try {
|
|
2960
|
+
await clientManager.connectToServer(serverName, serverConfig);
|
|
2961
|
+
console.error(`Connected to MCP server: ${serverName}`);
|
|
2962
|
+
} catch (error) {
|
|
2963
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
2964
|
+
failedConnections.push({
|
|
2965
|
+
serverName,
|
|
2966
|
+
error: err
|
|
2967
|
+
});
|
|
2968
|
+
console.error(`Failed to connect to ${serverName}:`, error);
|
|
2969
|
+
}
|
|
2970
|
+
});
|
|
2971
|
+
await Promise.all(connectionPromises);
|
|
2972
|
+
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(", ")}`);
|
|
2973
|
+
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(", ")}`);
|
|
2974
|
+
} else console.error(`[definitions-cache] Using cached definitions from ${effectiveDefinitionsCachePath}`);
|
|
2437
2975
|
}
|
|
2438
2976
|
const serverId = options?.serverId || configId || generateServerId();
|
|
2439
2977
|
console.error(`[one-mcp] Server ID: ${serverId}`);
|
|
2440
|
-
const
|
|
2978
|
+
const skillPaths = (options?.skills || configSkills)?.paths ?? [];
|
|
2441
2979
|
const toolsRef = { describeTools: null };
|
|
2442
|
-
const skillService =
|
|
2980
|
+
const skillService = skillPaths.length > 0 ? new SkillService(process.cwd(), skillPaths, { onCacheInvalidated: () => {
|
|
2443
2981
|
toolsRef.describeTools?.clearAutoDetectedSkillsCache();
|
|
2444
2982
|
} }) : void 0;
|
|
2445
|
-
|
|
2446
|
-
|
|
2983
|
+
let definitionsCacheService;
|
|
2984
|
+
if (effectiveDefinitionsCachePath) try {
|
|
2985
|
+
const cacheData = await DefinitionsCacheService.readFromFile(effectiveDefinitionsCachePath);
|
|
2986
|
+
if (DefinitionsCacheService.isCacheValid(cacheData, {
|
|
2987
|
+
configHash,
|
|
2988
|
+
oneMcpVersion: version
|
|
2989
|
+
})) definitionsCacheService = new DefinitionsCacheService(clientManager, skillService, { cacheData });
|
|
2990
|
+
else definitionsCacheService = new DefinitionsCacheService(clientManager, skillService);
|
|
2991
|
+
} catch (error) {
|
|
2992
|
+
console.error(`[definitions-cache] Failed to load ${effectiveDefinitionsCachePath}, falling back to live discovery: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2993
|
+
definitionsCacheService = new DefinitionsCacheService(clientManager, skillService);
|
|
2994
|
+
}
|
|
2995
|
+
else definitionsCacheService = new DefinitionsCacheService(clientManager, skillService);
|
|
2996
|
+
const describeTools = new DescribeToolsTool(clientManager, skillService, serverId, definitionsCacheService);
|
|
2997
|
+
const useToolWithCache = new UseToolTool(clientManager, skillService, serverId, definitionsCacheService);
|
|
2998
|
+
const searchListTools = new SearchListToolsTool(clientManager, definitionsCacheService);
|
|
2447
2999
|
toolsRef.describeTools = describeTools;
|
|
3000
|
+
const serverDefinitions = await definitionsCacheService.getServerDefinitions();
|
|
3001
|
+
const includeSkillsTool = await hasAnySkills(definitionsCacheService, skillService);
|
|
3002
|
+
const proxyMode = options?.proxyMode || "meta";
|
|
3003
|
+
const server = new __modelcontextprotocol_sdk_server_index_js.Server({
|
|
3004
|
+
name: "@agiflowai/one-mcp",
|
|
3005
|
+
version: "0.1.0"
|
|
3006
|
+
}, {
|
|
3007
|
+
capabilities: {
|
|
3008
|
+
tools: {},
|
|
3009
|
+
resources: {},
|
|
3010
|
+
prompts: {}
|
|
3011
|
+
},
|
|
3012
|
+
instructions: buildProxyInstructions(serverDefinitions, proxyMode, includeSkillsTool)
|
|
3013
|
+
});
|
|
2448
3014
|
if (skillService) skillService.startWatching().catch((error) => {
|
|
2449
3015
|
console.error(`[skill-watcher] File watcher failed (non-critical): ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2450
3016
|
});
|
|
2451
|
-
server.setRequestHandler(__modelcontextprotocol_sdk_types_js.ListToolsRequestSchema, async () => ({ tools:
|
|
3017
|
+
server.setRequestHandler(__modelcontextprotocol_sdk_types_js.ListToolsRequestSchema, async () => ({ tools: proxyMode === "flat" ? await (async () => {
|
|
3018
|
+
const currentServerDefinitions = await definitionsCacheService.getServerDefinitions();
|
|
3019
|
+
const shouldIncludeSkillsTool = await hasAnySkills(definitionsCacheService, skillService);
|
|
3020
|
+
return [...buildFlatToolDefinitions(currentServerDefinitions), ...shouldIncludeSkillsTool ? [buildSkillsDescribeDefinition(currentServerDefinitions, serverId)] : []];
|
|
3021
|
+
})() : proxyMode === "search" ? await (async () => {
|
|
3022
|
+
return [
|
|
3023
|
+
buildSearchDescribeDefinition(await definitionsCacheService.getServerDefinitions(), serverId),
|
|
3024
|
+
await searchListTools.getDefinition(),
|
|
3025
|
+
useToolWithCache.getDefinition()
|
|
3026
|
+
];
|
|
3027
|
+
})() : [await describeTools.getDefinition(), useToolWithCache.getDefinition()] }));
|
|
2452
3028
|
server.setRequestHandler(__modelcontextprotocol_sdk_types_js.CallToolRequestSchema, async (request) => {
|
|
2453
3029
|
const { name, arguments: args } = request.params;
|
|
2454
3030
|
if (name === DescribeToolsTool.TOOL_NAME) try {
|
|
@@ -2457,36 +3033,65 @@ async function createServer(options) {
|
|
|
2457
3033
|
throw new Error(`Failed to execute ${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2458
3034
|
}
|
|
2459
3035
|
if (name === UseToolTool.TOOL_NAME) try {
|
|
2460
|
-
return await
|
|
3036
|
+
return await useToolWithCache.execute(args);
|
|
3037
|
+
} catch (error) {
|
|
3038
|
+
throw new Error(`Failed to execute ${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
3039
|
+
}
|
|
3040
|
+
if (name === SearchListToolsTool.TOOL_NAME && proxyMode === "search") try {
|
|
3041
|
+
return await searchListTools.execute(args);
|
|
2461
3042
|
} catch (error) {
|
|
2462
3043
|
throw new Error(`Failed to execute ${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2463
3044
|
}
|
|
3045
|
+
if (proxyMode === "flat") return await useToolWithCache.execute({
|
|
3046
|
+
toolName: name,
|
|
3047
|
+
toolArgs: args || {}
|
|
3048
|
+
});
|
|
2464
3049
|
throw new Error(`Unknown tool: ${name}`);
|
|
2465
3050
|
});
|
|
3051
|
+
server.setRequestHandler(__modelcontextprotocol_sdk_types_js.ListResourcesRequestSchema, async () => {
|
|
3052
|
+
const serverDefinitions$1 = await definitionsCacheService.getServerDefinitions();
|
|
3053
|
+
const resourceToServers = /* @__PURE__ */ new Map();
|
|
3054
|
+
for (const serverDefinition of serverDefinitions$1) for (const resource of serverDefinition.resources) {
|
|
3055
|
+
if (!resourceToServers.has(resource.uri)) resourceToServers.set(resource.uri, []);
|
|
3056
|
+
resourceToServers.get(resource.uri)?.push(serverDefinition.serverName);
|
|
3057
|
+
}
|
|
3058
|
+
const resources = [];
|
|
3059
|
+
for (const serverDefinition of serverDefinitions$1) for (const resource of serverDefinition.resources) {
|
|
3060
|
+
const hasClash = (resourceToServers.get(resource.uri) || []).length > 1;
|
|
3061
|
+
resources.push({
|
|
3062
|
+
...resource,
|
|
3063
|
+
uri: hasClash ? `${serverDefinition.serverName}__${resource.uri}` : resource.uri
|
|
3064
|
+
});
|
|
3065
|
+
}
|
|
3066
|
+
return { resources };
|
|
3067
|
+
});
|
|
3068
|
+
server.setRequestHandler(__modelcontextprotocol_sdk_types_js.ReadResourceRequestSchema, async (request) => {
|
|
3069
|
+
const { uri } = request.params;
|
|
3070
|
+
const { serverName, actualToolName: actualUri } = parseToolName(uri);
|
|
3071
|
+
if (serverName) return await (await clientManager.ensureConnected(serverName)).readResource(actualUri);
|
|
3072
|
+
const matchingServers = await definitionsCacheService.getServersForResource(actualUri);
|
|
3073
|
+
if (matchingServers.length === 0) throw new Error(`Resource not found: ${uri}`);
|
|
3074
|
+
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.`);
|
|
3075
|
+
return await (await clientManager.ensureConnected(matchingServers[0])).readResource(actualUri);
|
|
3076
|
+
});
|
|
2466
3077
|
server.setRequestHandler(__modelcontextprotocol_sdk_types_js.ListPromptsRequestSchema, async () => {
|
|
2467
|
-
const
|
|
3078
|
+
const serverDefinitions$1 = await definitionsCacheService.getServerDefinitions();
|
|
2468
3079
|
const promptToServers = /* @__PURE__ */ new Map();
|
|
2469
3080
|
const serverPromptsMap = /* @__PURE__ */ new Map();
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
if (!promptToServers.has(prompt.name)) promptToServers.set(prompt.name, []);
|
|
2476
|
-
promptToServers.get(prompt.name).push(client.serverName);
|
|
2477
|
-
}
|
|
2478
|
-
} catch (error) {
|
|
2479
|
-
console.error(`Failed to list prompts from ${client.serverName}:`, error);
|
|
2480
|
-
serverPromptsMap.set(client.serverName, []);
|
|
3081
|
+
for (const serverDefinition of serverDefinitions$1) {
|
|
3082
|
+
serverPromptsMap.set(serverDefinition.serverName, serverDefinition.prompts);
|
|
3083
|
+
for (const prompt of serverDefinition.prompts) {
|
|
3084
|
+
if (!promptToServers.has(prompt.name)) promptToServers.set(prompt.name, []);
|
|
3085
|
+
promptToServers.get(prompt.name).push(serverDefinition.serverName);
|
|
2481
3086
|
}
|
|
2482
|
-
}
|
|
3087
|
+
}
|
|
2483
3088
|
const aggregatedPrompts = [];
|
|
2484
|
-
for (const
|
|
2485
|
-
const prompts = serverPromptsMap.get(
|
|
3089
|
+
for (const serverDefinition of serverDefinitions$1) {
|
|
3090
|
+
const prompts = serverPromptsMap.get(serverDefinition.serverName) || [];
|
|
2486
3091
|
for (const prompt of prompts) {
|
|
2487
3092
|
const hasClash = (promptToServers.get(prompt.name) || []).length > 1;
|
|
2488
3093
|
aggregatedPrompts.push({
|
|
2489
|
-
name: hasClash ? `${
|
|
3094
|
+
name: hasClash ? `${serverDefinition.serverName}__${prompt.name}` : prompt.name,
|
|
2490
3095
|
description: prompt.description,
|
|
2491
3096
|
arguments: prompt.arguments
|
|
2492
3097
|
});
|
|
@@ -2496,27 +3101,27 @@ async function createServer(options) {
|
|
|
2496
3101
|
});
|
|
2497
3102
|
server.setRequestHandler(__modelcontextprotocol_sdk_types_js.GetPromptRequestSchema, async (request) => {
|
|
2498
3103
|
const { name, arguments: args } = request.params;
|
|
2499
|
-
const
|
|
3104
|
+
const serverDefinitions$1 = await definitionsCacheService.getServerDefinitions();
|
|
2500
3105
|
const { serverName, actualToolName: actualPromptName } = parseToolName(name);
|
|
2501
|
-
if (serverName)
|
|
2502
|
-
const client$1 = clientManager.getClient(serverName);
|
|
2503
|
-
if (!client$1) throw new Error(`Server not found: ${serverName}`);
|
|
2504
|
-
return await client$1.getPrompt(actualPromptName, args);
|
|
2505
|
-
}
|
|
3106
|
+
if (serverName) return await (await clientManager.ensureConnected(serverName)).getPrompt(actualPromptName, args);
|
|
2506
3107
|
const serversWithPrompt = [];
|
|
2507
|
-
|
|
2508
|
-
try {
|
|
2509
|
-
if ((await client$1.listPrompts()).some((p) => p.name === name)) serversWithPrompt.push(client$1.serverName);
|
|
2510
|
-
} catch (error) {
|
|
2511
|
-
console.error(`Failed to list prompts from ${client$1.serverName}:`, error);
|
|
2512
|
-
}
|
|
2513
|
-
}));
|
|
3108
|
+
for (const serverDefinition of serverDefinitions$1) if (serverDefinition.prompts.some((prompt) => prompt.name === name)) serversWithPrompt.push(serverDefinition.serverName);
|
|
2514
3109
|
if (serversWithPrompt.length === 0) throw new Error(`Prompt not found: ${name}`);
|
|
2515
3110
|
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.`);
|
|
2516
3111
|
const client = clientManager.getClient(serversWithPrompt[0]);
|
|
2517
|
-
if (!client)
|
|
3112
|
+
if (!client) return await (await clientManager.ensureConnected(serversWithPrompt[0])).getPrompt(name, args);
|
|
2518
3113
|
return await client.getPrompt(name, args);
|
|
2519
3114
|
});
|
|
3115
|
+
if (!shouldStartFromCache && effectiveDefinitionsCachePath && options?.configFilePath) definitionsCacheService.collectForCache({
|
|
3116
|
+
configPath: options.configFilePath,
|
|
3117
|
+
configHash,
|
|
3118
|
+
oneMcpVersion: version,
|
|
3119
|
+
serverId
|
|
3120
|
+
}).then((definitionsCache) => DefinitionsCacheService.writeToFile(effectiveDefinitionsCachePath, definitionsCache)).then(() => {
|
|
3121
|
+
console.error(`[definitions-cache] Wrote ${effectiveDefinitionsCachePath}`);
|
|
3122
|
+
}).catch((error) => {
|
|
3123
|
+
console.error(`[definitions-cache] Failed to persist ${effectiveDefinitionsCachePath}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3124
|
+
});
|
|
2520
3125
|
return server;
|
|
2521
3126
|
}
|
|
2522
3127
|
|
|
@@ -2648,14 +3253,14 @@ var SseTransportHandler = class {
|
|
|
2648
3253
|
}
|
|
2649
3254
|
}
|
|
2650
3255
|
async start() {
|
|
2651
|
-
return new Promise((resolve$
|
|
3256
|
+
return new Promise((resolve$2, reject) => {
|
|
2652
3257
|
try {
|
|
2653
3258
|
this.server = this.app.listen(this.config.port, this.config.host, () => {
|
|
2654
3259
|
console.error(`@agiflowai/one-mcp MCP server started with SSE transport on http://${this.config.host}:${this.config.port}`);
|
|
2655
3260
|
console.error(`SSE endpoint: http://${this.config.host}:${this.config.port}/sse`);
|
|
2656
3261
|
console.error(`Messages endpoint: http://${this.config.host}:${this.config.port}/messages`);
|
|
2657
3262
|
console.error(`Health check: http://${this.config.host}:${this.config.port}/health`);
|
|
2658
|
-
resolve$
|
|
3263
|
+
resolve$2();
|
|
2659
3264
|
});
|
|
2660
3265
|
this.server.on("error", (error) => {
|
|
2661
3266
|
reject(error);
|
|
@@ -2666,17 +3271,17 @@ var SseTransportHandler = class {
|
|
|
2666
3271
|
});
|
|
2667
3272
|
}
|
|
2668
3273
|
async stop() {
|
|
2669
|
-
return new Promise((resolve$
|
|
3274
|
+
return new Promise((resolve$2, reject) => {
|
|
2670
3275
|
if (this.server) {
|
|
2671
3276
|
this.sessionManager.clear();
|
|
2672
3277
|
this.server.close((err) => {
|
|
2673
3278
|
if (err) reject(err);
|
|
2674
3279
|
else {
|
|
2675
3280
|
this.server = null;
|
|
2676
|
-
resolve$
|
|
3281
|
+
resolve$2();
|
|
2677
3282
|
}
|
|
2678
3283
|
});
|
|
2679
|
-
} else resolve$
|
|
3284
|
+
} else resolve$2();
|
|
2680
3285
|
});
|
|
2681
3286
|
}
|
|
2682
3287
|
getPort() {
|
|
@@ -2828,12 +3433,12 @@ var HttpTransportHandler = class {
|
|
|
2828
3433
|
this.sessionManager.deleteSession(sessionId);
|
|
2829
3434
|
}
|
|
2830
3435
|
async start() {
|
|
2831
|
-
return new Promise((resolve$
|
|
3436
|
+
return new Promise((resolve$2, reject) => {
|
|
2832
3437
|
try {
|
|
2833
3438
|
this.server = this.app.listen(this.config.port, this.config.host, () => {
|
|
2834
3439
|
console.error(`@agiflowai/one-mcp MCP server started on http://${this.config.host}:${this.config.port}/mcp`);
|
|
2835
3440
|
console.error(`Health check: http://${this.config.host}:${this.config.port}/health`);
|
|
2836
|
-
resolve$
|
|
3441
|
+
resolve$2();
|
|
2837
3442
|
});
|
|
2838
3443
|
this.server.on("error", (error) => {
|
|
2839
3444
|
reject(error);
|
|
@@ -2844,17 +3449,17 @@ var HttpTransportHandler = class {
|
|
|
2844
3449
|
});
|
|
2845
3450
|
}
|
|
2846
3451
|
async stop() {
|
|
2847
|
-
return new Promise((resolve$
|
|
3452
|
+
return new Promise((resolve$2, reject) => {
|
|
2848
3453
|
if (this.server) {
|
|
2849
3454
|
this.sessionManager.clear();
|
|
2850
3455
|
this.server.close((err) => {
|
|
2851
3456
|
if (err) reject(err);
|
|
2852
3457
|
else {
|
|
2853
3458
|
this.server = null;
|
|
2854
|
-
resolve$
|
|
3459
|
+
resolve$2();
|
|
2855
3460
|
}
|
|
2856
3461
|
});
|
|
2857
|
-
} else resolve$
|
|
3462
|
+
} else resolve$2();
|
|
2858
3463
|
});
|
|
2859
3464
|
}
|
|
2860
3465
|
getPort() {
|
|
@@ -2872,6 +3477,12 @@ Object.defineProperty(exports, 'ConfigFetcherService', {
|
|
|
2872
3477
|
return ConfigFetcherService;
|
|
2873
3478
|
}
|
|
2874
3479
|
});
|
|
3480
|
+
Object.defineProperty(exports, 'DefinitionsCacheService', {
|
|
3481
|
+
enumerable: true,
|
|
3482
|
+
get: function () {
|
|
3483
|
+
return DefinitionsCacheService;
|
|
3484
|
+
}
|
|
3485
|
+
});
|
|
2875
3486
|
Object.defineProperty(exports, 'DescribeToolsTool', {
|
|
2876
3487
|
enumerable: true,
|
|
2877
3488
|
get: function () {
|
|
@@ -2890,6 +3501,12 @@ Object.defineProperty(exports, 'McpClientManagerService', {
|
|
|
2890
3501
|
return McpClientManagerService;
|
|
2891
3502
|
}
|
|
2892
3503
|
});
|
|
3504
|
+
Object.defineProperty(exports, 'SearchListToolsTool', {
|
|
3505
|
+
enumerable: true,
|
|
3506
|
+
get: function () {
|
|
3507
|
+
return SearchListToolsTool;
|
|
3508
|
+
}
|
|
3509
|
+
});
|
|
2893
3510
|
Object.defineProperty(exports, 'SkillService', {
|
|
2894
3511
|
enumerable: true,
|
|
2895
3512
|
get: function () {
|
|
@@ -2931,4 +3548,16 @@ Object.defineProperty(exports, 'findConfigFile', {
|
|
|
2931
3548
|
get: function () {
|
|
2932
3549
|
return findConfigFile;
|
|
2933
3550
|
}
|
|
3551
|
+
});
|
|
3552
|
+
Object.defineProperty(exports, 'generateServerId', {
|
|
3553
|
+
enumerable: true,
|
|
3554
|
+
get: function () {
|
|
3555
|
+
return generateServerId;
|
|
3556
|
+
}
|
|
3557
|
+
});
|
|
3558
|
+
Object.defineProperty(exports, 'version', {
|
|
3559
|
+
enumerable: true,
|
|
3560
|
+
get: function () {
|
|
3561
|
+
return version;
|
|
3562
|
+
}
|
|
2934
3563
|
});
|