@mozilla-ai/mcpd 0.0.3 → 0.1.0
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 +189 -7
- package/dist/client.d.ts +36 -47
- package/dist/client.d.ts.map +1 -1
- package/dist/errors.d.ts +68 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/functionBuilder.d.ts +6 -0
- package/dist/functionBuilder.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +288 -45
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +288 -45
- package/dist/index.mjs.map +1 -1
- package/dist/logger.d.ts +93 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/types.d.ts +106 -19
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const lruCache = require("lru-cache");
|
|
4
4
|
const zod = require("zod");
|
|
5
|
+
const PIPELINE_FLOW_REQUEST = "request";
|
|
6
|
+
const PIPELINE_FLOW_RESPONSE = "response";
|
|
5
7
|
class McpdError extends Error {
|
|
6
8
|
constructor(message, cause) {
|
|
7
9
|
super(message);
|
|
@@ -88,6 +90,19 @@ class TimeoutError extends McpdError {
|
|
|
88
90
|
Error.captureStackTrace(this, this.constructor);
|
|
89
91
|
}
|
|
90
92
|
}
|
|
93
|
+
class PipelineError extends McpdError {
|
|
94
|
+
serverName;
|
|
95
|
+
operation;
|
|
96
|
+
pipelineFlow;
|
|
97
|
+
constructor(message, serverName, operation, pipelineFlow, cause) {
|
|
98
|
+
super(message, cause);
|
|
99
|
+
this.name = "PipelineError";
|
|
100
|
+
this.serverName = serverName;
|
|
101
|
+
this.operation = operation;
|
|
102
|
+
this.pipelineFlow = pipelineFlow;
|
|
103
|
+
Error.captureStackTrace(this, this.constructor);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
91
106
|
var HealthStatus = /* @__PURE__ */ ((HealthStatus2) => {
|
|
92
107
|
HealthStatus2["OK"] = "ok";
|
|
93
108
|
HealthStatus2["TIMEOUT"] = "timeout";
|
|
@@ -943,6 +958,14 @@ class FunctionBuilder {
|
|
|
943
958
|
getCacheSize() {
|
|
944
959
|
return this.#functionCache.size;
|
|
945
960
|
}
|
|
961
|
+
/**
|
|
962
|
+
* Get all cached functions.
|
|
963
|
+
*
|
|
964
|
+
* @returns Array of all cached agent functions, or empty array if cache is empty
|
|
965
|
+
*/
|
|
966
|
+
getCachedFunctions() {
|
|
967
|
+
return Array.from(this.#functionCache.values());
|
|
968
|
+
}
|
|
946
969
|
}
|
|
947
970
|
const API_BASE = "/api/v1";
|
|
948
971
|
const SERVERS_BASE = `${API_BASE}/servers`;
|
|
@@ -973,13 +996,81 @@ const API_PATHS = {
|
|
|
973
996
|
HEALTH_ALL: HEALTH_SERVERS_BASE,
|
|
974
997
|
HEALTH_SERVER: (serverName) => `${HEALTH_SERVERS_BASE}/${encodeURIComponent(serverName)}`
|
|
975
998
|
};
|
|
999
|
+
const LogLevels = {
|
|
1000
|
+
OFF: "off"
|
|
1001
|
+
};
|
|
1002
|
+
const ranks = {
|
|
1003
|
+
trace: 5,
|
|
1004
|
+
debug: 10,
|
|
1005
|
+
info: 20,
|
|
1006
|
+
warn: 30,
|
|
1007
|
+
error: 40,
|
|
1008
|
+
off: 1e3
|
|
1009
|
+
};
|
|
1010
|
+
function resolve(raw) {
|
|
1011
|
+
const candidate = raw?.toLowerCase();
|
|
1012
|
+
return candidate && candidate in ranks ? candidate : LogLevels.OFF;
|
|
1013
|
+
}
|
|
1014
|
+
function getLevel() {
|
|
1015
|
+
return resolve(
|
|
1016
|
+
typeof process !== "undefined" ? process.env.MCPD_LOG_LEVEL : void 0
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
function defaultLogger() {
|
|
1020
|
+
return {
|
|
1021
|
+
trace: (...args) => {
|
|
1022
|
+
const lvl = getLevel();
|
|
1023
|
+
if (lvl !== LogLevels.OFF && ranks[lvl] <= ranks.trace)
|
|
1024
|
+
console.trace(...args);
|
|
1025
|
+
},
|
|
1026
|
+
debug: (...args) => {
|
|
1027
|
+
const lvl = getLevel();
|
|
1028
|
+
if (lvl !== LogLevels.OFF && ranks[lvl] <= ranks.debug)
|
|
1029
|
+
console.debug(...args);
|
|
1030
|
+
},
|
|
1031
|
+
info: (...args) => {
|
|
1032
|
+
const lvl = getLevel();
|
|
1033
|
+
if (lvl !== LogLevels.OFF && ranks[lvl] <= ranks.info)
|
|
1034
|
+
console.info(...args);
|
|
1035
|
+
},
|
|
1036
|
+
warn: (...args) => {
|
|
1037
|
+
const lvl = getLevel();
|
|
1038
|
+
if (lvl !== LogLevels.OFF && ranks[lvl] <= ranks.warn)
|
|
1039
|
+
console.warn(...args);
|
|
1040
|
+
},
|
|
1041
|
+
error: (...args) => {
|
|
1042
|
+
const lvl = getLevel();
|
|
1043
|
+
if (lvl !== LogLevels.OFF && ranks[lvl] <= ranks.error)
|
|
1044
|
+
console.error(...args);
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
function createLogger(impl) {
|
|
1049
|
+
const base = defaultLogger();
|
|
1050
|
+
return {
|
|
1051
|
+
trace: impl?.trace ?? base.trace,
|
|
1052
|
+
debug: impl?.debug ?? base.debug,
|
|
1053
|
+
info: impl?.info ?? base.info,
|
|
1054
|
+
warn: impl?.warn ?? base.warn,
|
|
1055
|
+
error: impl?.error ?? base.error
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
const REQUEST_TIMEOUT_SECONDS = 30;
|
|
1059
|
+
const SERVER_HEALTH_CACHE_TTL_SECONDS = 10;
|
|
976
1060
|
const SERVER_HEALTH_CACHE_MAXSIZE = 100;
|
|
1061
|
+
const TOOL_SEPARATOR = "__";
|
|
1062
|
+
const MCPD_ERROR_TYPE_HEADER = "Mcpd-Error-Type";
|
|
1063
|
+
const PIPELINE_ERROR_FLOWS = {
|
|
1064
|
+
"request-pipeline-failure": PIPELINE_FLOW_REQUEST,
|
|
1065
|
+
"response-pipeline-failure": PIPELINE_FLOW_RESPONSE
|
|
1066
|
+
};
|
|
977
1067
|
class McpdClient {
|
|
978
1068
|
#endpoint;
|
|
979
1069
|
#apiKey;
|
|
980
1070
|
#timeout;
|
|
981
1071
|
#serverHealthCache;
|
|
982
1072
|
#functionBuilder;
|
|
1073
|
+
#logger;
|
|
983
1074
|
#cacheableExceptions = /* @__PURE__ */ new Set([
|
|
984
1075
|
ServerNotFoundError,
|
|
985
1076
|
ServerUnhealthyError,
|
|
@@ -995,14 +1086,18 @@ class McpdClient {
|
|
|
995
1086
|
* @param options - Configuration options for the client
|
|
996
1087
|
*/
|
|
997
1088
|
constructor(options) {
|
|
1089
|
+
const toMs = (s) => s * 1e3;
|
|
998
1090
|
this.#endpoint = options.apiEndpoint.replace(/\/$/, "");
|
|
999
1091
|
this.#apiKey = options.apiKey;
|
|
1000
|
-
this.#timeout = options.timeout ??
|
|
1001
|
-
const healthCacheTtlMs = (
|
|
1092
|
+
this.#timeout = options.timeout ?? toMs(REQUEST_TIMEOUT_SECONDS);
|
|
1093
|
+
const healthCacheTtlMs = toMs(
|
|
1094
|
+
options.healthCacheTtl ?? SERVER_HEALTH_CACHE_TTL_SECONDS
|
|
1095
|
+
);
|
|
1002
1096
|
this.#serverHealthCache = createCache({
|
|
1003
1097
|
max: SERVER_HEALTH_CACHE_MAXSIZE,
|
|
1004
1098
|
ttl: healthCacheTtlMs
|
|
1005
1099
|
});
|
|
1100
|
+
this.#logger = createLogger(options.logger);
|
|
1006
1101
|
this.servers = new ServersNamespace({
|
|
1007
1102
|
performCall: this.#performCall.bind(this),
|
|
1008
1103
|
getTools: this.#getToolsByServer.bind(this),
|
|
@@ -1019,8 +1114,15 @@ class McpdClient {
|
|
|
1019
1114
|
*
|
|
1020
1115
|
* @param path - The API path (e.g., '/servers', '/servers/{server_name}/tools')
|
|
1021
1116
|
* @param options - Request options
|
|
1117
|
+
*
|
|
1022
1118
|
* @returns The JSON response from the daemon
|
|
1119
|
+
*
|
|
1120
|
+
* @throws {AuthenticationError} If API key was present and authentication fails
|
|
1121
|
+
* @throws {ConnectionError} If unable to connect to the mcpd daemon
|
|
1122
|
+
* @throws {TimeoutError} If the request times out
|
|
1023
1123
|
* @throws {McpdError} If the request fails
|
|
1124
|
+
*
|
|
1125
|
+
* @internal
|
|
1024
1126
|
*/
|
|
1025
1127
|
async #request(path, options = {}) {
|
|
1026
1128
|
const url = `${this.#endpoint}${path}`;
|
|
@@ -1048,6 +1150,21 @@ class McpdClient {
|
|
|
1048
1150
|
} catch {
|
|
1049
1151
|
errorModel = null;
|
|
1050
1152
|
}
|
|
1153
|
+
if (response.status === 500) {
|
|
1154
|
+
const errorType = response.headers.get(MCPD_ERROR_TYPE_HEADER)?.toLowerCase();
|
|
1155
|
+
const flow = errorType ? PIPELINE_ERROR_FLOWS[errorType] : void 0;
|
|
1156
|
+
if (flow) {
|
|
1157
|
+
const message = errorModel?.detail || body || "Pipeline failure";
|
|
1158
|
+
throw new PipelineError(
|
|
1159
|
+
message,
|
|
1160
|
+
void 0,
|
|
1161
|
+
// serverName - enriched by caller if available.
|
|
1162
|
+
void 0,
|
|
1163
|
+
// operation - enriched by caller if available.
|
|
1164
|
+
flow
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1051
1168
|
if (errorModel && errorModel.detail) {
|
|
1052
1169
|
const errorDetails = errorModel.errors?.map((e) => `${e.location}: ${e.message}`).join("; ");
|
|
1053
1170
|
const fullMessage = errorDetails ? `${errorModel.detail} - ${errorDetails}` : errorModel.detail;
|
|
@@ -1100,6 +1217,10 @@ class McpdClient {
|
|
|
1100
1217
|
* Get a list of all configured MCP servers.
|
|
1101
1218
|
*
|
|
1102
1219
|
* @returns Array of server names
|
|
1220
|
+
*
|
|
1221
|
+
* @throws {AuthenticationError} If API key was present and authentication fails
|
|
1222
|
+
* @throws {ConnectionError} If unable to connect to the mcpd daemon
|
|
1223
|
+
* @throws {TimeoutError} If the request times out
|
|
1103
1224
|
* @throws {McpdError} If the request fails
|
|
1104
1225
|
*
|
|
1105
1226
|
* @example
|
|
@@ -1112,16 +1233,23 @@ class McpdClient {
|
|
|
1112
1233
|
return await this.#request(API_PATHS.SERVERS);
|
|
1113
1234
|
}
|
|
1114
1235
|
/**
|
|
1115
|
-
*
|
|
1236
|
+
* Get tool schemas for a server.
|
|
1237
|
+
*
|
|
1238
|
+
* @privateRemarks
|
|
1116
1239
|
* Used by dependency injection for ServersNamespace and internally for getAgentTools.
|
|
1117
1240
|
*
|
|
1118
1241
|
* @param serverName - Server name to get tools for
|
|
1242
|
+
*
|
|
1119
1243
|
* @returns Tool schemas for the specified server
|
|
1244
|
+
*
|
|
1120
1245
|
* @throws {ServerNotFoundError} If the specified server doesn't exist
|
|
1121
1246
|
* @throws {ServerUnhealthyError} If the server is not healthy
|
|
1247
|
+
*
|
|
1248
|
+
* @throws {AuthenticationError} If API key was present and authentication fails
|
|
1122
1249
|
* @throws {ConnectionError} If unable to connect to the mcpd daemon
|
|
1123
1250
|
* @throws {TimeoutError} If the request times out
|
|
1124
1251
|
* @throws {McpdError} If the request fails
|
|
1252
|
+
*
|
|
1125
1253
|
* @internal
|
|
1126
1254
|
*/
|
|
1127
1255
|
async #getToolsByServer(serverName) {
|
|
@@ -1137,17 +1265,24 @@ class McpdClient {
|
|
|
1137
1265
|
return response.tools;
|
|
1138
1266
|
}
|
|
1139
1267
|
/**
|
|
1140
|
-
*
|
|
1268
|
+
* Get prompt schemas for a server.
|
|
1269
|
+
*
|
|
1270
|
+
* @privateRemarks
|
|
1141
1271
|
* Used internally for getPromptSchemas.
|
|
1142
1272
|
*
|
|
1143
1273
|
* @param serverName - Server name to get prompts for
|
|
1144
|
-
* @param cursor -
|
|
1274
|
+
* @param cursor - Cursor for pagination
|
|
1275
|
+
*
|
|
1145
1276
|
* @returns Prompt schemas for the specified server
|
|
1277
|
+
*
|
|
1146
1278
|
* @throws {ServerNotFoundError} If the specified server doesn't exist
|
|
1147
1279
|
* @throws {ServerUnhealthyError} If the server is not healthy
|
|
1280
|
+
*
|
|
1281
|
+
* @throws {AuthenticationError} If API key was present and authentication fails
|
|
1148
1282
|
* @throws {ConnectionError} If unable to connect to the mcpd daemon
|
|
1149
1283
|
* @throws {TimeoutError} If the request times out
|
|
1150
1284
|
* @throws {McpdError} If the request fails
|
|
1285
|
+
*
|
|
1151
1286
|
* @internal
|
|
1152
1287
|
*/
|
|
1153
1288
|
async #getPromptsByServer(serverName, cursor) {
|
|
@@ -1164,16 +1299,24 @@ class McpdClient {
|
|
|
1164
1299
|
}
|
|
1165
1300
|
}
|
|
1166
1301
|
/**
|
|
1167
|
-
*
|
|
1302
|
+
* Generate a prompt on a server.
|
|
1168
1303
|
*
|
|
1169
|
-
*
|
|
1304
|
+
* @privateRemarks
|
|
1305
|
+
* Used internally by:
|
|
1170
1306
|
* - PromptsNamespace (via dependency injection)
|
|
1171
1307
|
* - Server.generatePrompt() (via dependency injection)
|
|
1172
1308
|
*
|
|
1173
1309
|
* @param serverName - The name of the server
|
|
1174
1310
|
* @param promptName - The exact name of the prompt
|
|
1175
1311
|
* @param args - The prompt arguments
|
|
1312
|
+
*
|
|
1176
1313
|
* @returns The generated prompt response
|
|
1314
|
+
*
|
|
1315
|
+
* @throws {AuthenticationError} If API key was present and authentication fails
|
|
1316
|
+
* @throws {ConnectionError} If unable to connect to the mcpd daemon
|
|
1317
|
+
* @throws {TimeoutError} If the request times out
|
|
1318
|
+
* @throws {McpdError} If the request fails
|
|
1319
|
+
*
|
|
1177
1320
|
* @internal
|
|
1178
1321
|
*/
|
|
1179
1322
|
async #generatePromptInternal(serverName, promptName, args) {
|
|
@@ -1189,17 +1332,24 @@ class McpdClient {
|
|
|
1189
1332
|
return response;
|
|
1190
1333
|
}
|
|
1191
1334
|
/**
|
|
1192
|
-
*
|
|
1335
|
+
* Get resource schemas for a server.
|
|
1336
|
+
*
|
|
1337
|
+
* @privateRemarks
|
|
1193
1338
|
* Used internally for getResources and by dependency injection for ServersNamespace.
|
|
1194
1339
|
*
|
|
1195
1340
|
* @param serverName - Server name to get resources for
|
|
1196
|
-
* @param cursor -
|
|
1341
|
+
* @param cursor - Cursor for pagination
|
|
1342
|
+
*
|
|
1197
1343
|
* @returns Resource schemas for the specified server
|
|
1344
|
+
*
|
|
1198
1345
|
* @throws {ServerNotFoundError} If the specified server doesn't exist
|
|
1199
1346
|
* @throws {ServerUnhealthyError} If the server is not healthy
|
|
1347
|
+
*
|
|
1348
|
+
* @throws {AuthenticationError} If API key was present and authentication fails
|
|
1200
1349
|
* @throws {ConnectionError} If unable to connect to the mcpd daemon
|
|
1201
1350
|
* @throws {TimeoutError} If the request times out
|
|
1202
1351
|
* @throws {McpdError} If the request fails
|
|
1352
|
+
*
|
|
1203
1353
|
* @internal
|
|
1204
1354
|
*/
|
|
1205
1355
|
async #getResourcesByServer(serverName, cursor) {
|
|
@@ -1216,17 +1366,24 @@ class McpdClient {
|
|
|
1216
1366
|
}
|
|
1217
1367
|
}
|
|
1218
1368
|
/**
|
|
1219
|
-
*
|
|
1369
|
+
* Get resource template schemas for a server.
|
|
1370
|
+
*
|
|
1371
|
+
* @privateRemarks
|
|
1220
1372
|
* Used internally for getResourceTemplates and by dependency injection for ServersNamespace.
|
|
1221
1373
|
*
|
|
1222
1374
|
* @param serverName - Server name to get resource templates for
|
|
1223
|
-
* @param cursor -
|
|
1375
|
+
* @param cursor - Cursor for pagination
|
|
1376
|
+
*
|
|
1224
1377
|
* @returns Resource template schemas for the specified server
|
|
1378
|
+
*
|
|
1225
1379
|
* @throws {ServerNotFoundError} If the specified server doesn't exist
|
|
1226
1380
|
* @throws {ServerUnhealthyError} If the server is not healthy
|
|
1381
|
+
*
|
|
1382
|
+
* @throws {AuthenticationError} If API key was present and authentication fails
|
|
1227
1383
|
* @throws {ConnectionError} If unable to connect to the mcpd daemon
|
|
1228
1384
|
* @throws {TimeoutError} If the request times out
|
|
1229
1385
|
* @throws {McpdError} If the request fails
|
|
1386
|
+
*
|
|
1230
1387
|
* @internal
|
|
1231
1388
|
*/
|
|
1232
1389
|
async #getResourceTemplatesByServer(serverName, cursor) {
|
|
@@ -1243,17 +1400,24 @@ class McpdClient {
|
|
|
1243
1400
|
}
|
|
1244
1401
|
}
|
|
1245
1402
|
/**
|
|
1246
|
-
*
|
|
1403
|
+
* Read resource content from a server.
|
|
1404
|
+
*
|
|
1405
|
+
* @privateRemarks
|
|
1247
1406
|
* Used by dependency injection for ServersNamespace.
|
|
1248
1407
|
*
|
|
1249
1408
|
* @param serverName - Server name to read resource from
|
|
1250
1409
|
* @param uri - The resource URI
|
|
1410
|
+
*
|
|
1251
1411
|
* @returns Array of resource contents (text or blob)
|
|
1412
|
+
*
|
|
1252
1413
|
* @throws {ServerNotFoundError} If the specified server doesn't exist
|
|
1253
1414
|
* @throws {ServerUnhealthyError} If the server is not healthy
|
|
1415
|
+
*
|
|
1416
|
+
* @throws {AuthenticationError} If API key was present and authentication fails
|
|
1254
1417
|
* @throws {ConnectionError} If unable to connect to the mcpd daemon
|
|
1255
1418
|
* @throws {TimeoutError} If the request times out
|
|
1256
1419
|
* @throws {McpdError} If the request fails
|
|
1420
|
+
*
|
|
1257
1421
|
* @internal
|
|
1258
1422
|
*/
|
|
1259
1423
|
async #readResourceByServer(serverName, uri) {
|
|
@@ -1305,8 +1469,14 @@ class McpdClient {
|
|
|
1305
1469
|
* Check if a specific server is healthy.
|
|
1306
1470
|
*
|
|
1307
1471
|
* @param serverName - The name of the server to check
|
|
1472
|
+
*
|
|
1308
1473
|
* @returns True if the server is healthy, false otherwise
|
|
1309
1474
|
*
|
|
1475
|
+
* @throws {AuthenticationError} If API key was present and authentication fails
|
|
1476
|
+
* @throws {ConnectionError} If unable to connect to the mcpd daemon
|
|
1477
|
+
* @throws {TimeoutError} If the request times out
|
|
1478
|
+
* @throws {McpdError} If the request fails
|
|
1479
|
+
*
|
|
1310
1480
|
* @example
|
|
1311
1481
|
* ```typescript
|
|
1312
1482
|
* if (await client.isServerHealthy('time')) {
|
|
@@ -1329,8 +1499,14 @@ class McpdClient {
|
|
|
1329
1499
|
* Ensure a server is healthy before performing an operation.
|
|
1330
1500
|
*
|
|
1331
1501
|
* @param serverName - The name of the server to check
|
|
1502
|
+
*
|
|
1332
1503
|
* @throws {ServerNotFoundError} If the server doesn't exist
|
|
1333
1504
|
* @throws {ServerUnhealthyError} If the server is not healthy
|
|
1505
|
+
*
|
|
1506
|
+
* @throws {AuthenticationError} If API key was present and authentication fails
|
|
1507
|
+
* @throws {ConnectionError} If unable to connect to the mcpd daemon
|
|
1508
|
+
* @throws {TimeoutError} If the request times out
|
|
1509
|
+
* @throws {McpdError} If the request fails
|
|
1334
1510
|
*/
|
|
1335
1511
|
async #ensureServerHealthy(serverName) {
|
|
1336
1512
|
const health = await this.getServerHealth(serverName);
|
|
@@ -1349,35 +1525,60 @@ class McpdClient {
|
|
|
1349
1525
|
}
|
|
1350
1526
|
}
|
|
1351
1527
|
/**
|
|
1352
|
-
* Get list of healthy servers
|
|
1528
|
+
* Get list of healthy servers.
|
|
1353
1529
|
*
|
|
1354
|
-
*
|
|
1355
|
-
*
|
|
1530
|
+
* @remarks
|
|
1531
|
+
* If logging is enabled, warnings are logged for servers that do not exist or are unhealthy.
|
|
1356
1532
|
*
|
|
1357
|
-
* @param servers -
|
|
1358
|
-
*
|
|
1359
|
-
*
|
|
1533
|
+
* @param servers - List of server names to use for health checking.
|
|
1534
|
+
* If not provided, or empty, checks health for all servers.
|
|
1535
|
+
*
|
|
1536
|
+
* @returns List of server names with 'ok' health status.
|
|
1537
|
+
*
|
|
1538
|
+
* @throws {AuthenticationError} If API key was present and authentication fails
|
|
1539
|
+
* @throws {ConnectionError} If unable to connect to the mcpd daemon
|
|
1540
|
+
* @throws {TimeoutError} If the request times out
|
|
1541
|
+
* @throws {McpdError} If the request fails
|
|
1360
1542
|
*/
|
|
1361
1543
|
async #getHealthyServers(servers) {
|
|
1362
|
-
const serverNames = servers
|
|
1544
|
+
const serverNames = servers?.length ? servers : await this.listServers();
|
|
1363
1545
|
const healthMap = await this.getServerHealth();
|
|
1364
1546
|
return serverNames.filter((name) => {
|
|
1365
1547
|
const health = healthMap[name];
|
|
1366
|
-
|
|
1548
|
+
if (!health) {
|
|
1549
|
+
this.#logger.warn(`Skipping non-existent server '${name}'`);
|
|
1550
|
+
return false;
|
|
1551
|
+
}
|
|
1552
|
+
if (!HealthStatusHelpers.isHealthy(health.status)) {
|
|
1553
|
+
this.#logger.warn(
|
|
1554
|
+
`Skipping unhealthy server '${name}' with status '${health.status}'`
|
|
1555
|
+
);
|
|
1556
|
+
return false;
|
|
1557
|
+
}
|
|
1558
|
+
return true;
|
|
1367
1559
|
});
|
|
1368
1560
|
}
|
|
1369
1561
|
/**
|
|
1370
|
-
*
|
|
1562
|
+
* Perform a tool call on a server.
|
|
1371
1563
|
*
|
|
1372
|
-
*
|
|
1564
|
+
* @privateRemarks
|
|
1565
|
+
* Used internally by:
|
|
1373
1566
|
* - ToolsNamespace (via dependency injection)
|
|
1374
1567
|
* - FunctionBuilder (via dependency injection)
|
|
1375
1568
|
*
|
|
1376
1569
|
* @param serverName - The name of the server
|
|
1377
1570
|
* @param toolName - The exact name of the tool
|
|
1378
1571
|
* @param args - The tool arguments
|
|
1572
|
+
*
|
|
1379
1573
|
* @returns The tool's response
|
|
1574
|
+
*
|
|
1380
1575
|
* @throws {ToolExecutionError} If the tool execution fails
|
|
1576
|
+
*
|
|
1577
|
+
* @throws {AuthenticationError} If API key was present and authentication fails
|
|
1578
|
+
* @throws {ConnectionError} If unable to connect to the mcpd daemon
|
|
1579
|
+
* @throws {TimeoutError} If the request times out
|
|
1580
|
+
* @throws {McpdError} If the request fails
|
|
1581
|
+
*
|
|
1381
1582
|
* @internal
|
|
1382
1583
|
*/
|
|
1383
1584
|
async #performCall(serverName, toolName, args) {
|
|
@@ -1396,6 +1597,15 @@ class McpdClient {
|
|
|
1396
1597
|
}
|
|
1397
1598
|
return response;
|
|
1398
1599
|
} catch (error) {
|
|
1600
|
+
if (error instanceof PipelineError) {
|
|
1601
|
+
throw new PipelineError(
|
|
1602
|
+
error.message,
|
|
1603
|
+
serverName,
|
|
1604
|
+
`${serverName}.${toolName}`,
|
|
1605
|
+
error.pipelineFlow,
|
|
1606
|
+
error.cause
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1399
1609
|
if (error instanceof McpdError) {
|
|
1400
1610
|
throw error;
|
|
1401
1611
|
}
|
|
@@ -1423,29 +1633,33 @@ class McpdClient {
|
|
|
1423
1633
|
this.#serverHealthCache.clear();
|
|
1424
1634
|
}
|
|
1425
1635
|
/**
|
|
1426
|
-
*
|
|
1636
|
+
* Fetch and cache callable functions from all healthy servers.
|
|
1427
1637
|
*
|
|
1428
|
-
* This method queries servers and creates self-contained, callable functions
|
|
1638
|
+
* This method queries all healthy servers and creates self-contained, callable functions
|
|
1429
1639
|
* that can be passed to AI agent frameworks. Each function includes its schema
|
|
1430
1640
|
* as metadata and handles the MCP communication internally.
|
|
1431
1641
|
*
|
|
1432
|
-
*
|
|
1433
|
-
*
|
|
1434
|
-
* the method returns quickly without waiting for timeouts on failed servers.
|
|
1642
|
+
* Unhealthy servers are automatically filtered out and skipped (with optional warnings
|
|
1643
|
+
* when logging is enabled) to ensure the method returns quickly without waiting for timeouts.
|
|
1435
1644
|
*
|
|
1436
1645
|
* Tool fetches from multiple servers are executed concurrently for optimal performance.
|
|
1646
|
+
* Functions are cached indefinitely until explicitly cleared.
|
|
1437
1647
|
*
|
|
1438
|
-
* @
|
|
1439
|
-
* @returns Array of callable functions with metadata. Only includes tools from healthy servers.
|
|
1648
|
+
* @returns Array of callable functions with metadata from all healthy servers.
|
|
1440
1649
|
*
|
|
1650
|
+
* @throws {AuthenticationError} If API key was present and authentication fails
|
|
1441
1651
|
* @throws {ConnectionError} If unable to connect to the mcpd daemon
|
|
1442
|
-
* @throws {TimeoutError} If
|
|
1443
|
-
* @throws {
|
|
1444
|
-
*
|
|
1652
|
+
* @throws {TimeoutError} If the request times out
|
|
1653
|
+
* @throws {McpdError} If the request fails
|
|
1654
|
+
*
|
|
1445
1655
|
* @internal
|
|
1446
1656
|
*/
|
|
1447
|
-
async agentTools(
|
|
1448
|
-
const
|
|
1657
|
+
async #agentTools() {
|
|
1658
|
+
const cachedFunctions = this.#functionBuilder.getCachedFunctions();
|
|
1659
|
+
if (cachedFunctions.length > 0) {
|
|
1660
|
+
return cachedFunctions;
|
|
1661
|
+
}
|
|
1662
|
+
const healthyServers = await this.#getHealthyServers();
|
|
1449
1663
|
const results = await Promise.allSettled(
|
|
1450
1664
|
healthyServers.map(async (serverName) => ({
|
|
1451
1665
|
serverName,
|
|
@@ -1464,17 +1678,43 @@ class McpdClient {
|
|
|
1464
1678
|
return agentTools;
|
|
1465
1679
|
}
|
|
1466
1680
|
async getAgentTools(options = {}) {
|
|
1467
|
-
const { servers, format = "array" } = options;
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1681
|
+
const { servers, tools, format = "array", refreshCache = false } = options;
|
|
1682
|
+
if (refreshCache) this.#functionBuilder.clearCache();
|
|
1683
|
+
const allTools = await this.#agentTools();
|
|
1684
|
+
const filteredTools = allTools.filter((tool) => !servers || servers.includes(tool._serverName)).filter((tool) => !tools || this.#matchesToolFilter(tool, tools));
|
|
1685
|
+
const formatters = {
|
|
1686
|
+
array: (t) => t,
|
|
1687
|
+
object: (t) => Object.fromEntries(t.map((tool) => [tool.name, tool])),
|
|
1688
|
+
map: (t) => new Map(t.map((tool) => [tool.name, tool]))
|
|
1689
|
+
};
|
|
1690
|
+
return formatters[format](filteredTools);
|
|
1691
|
+
}
|
|
1692
|
+
/**
|
|
1693
|
+
* Check if a tool matches the tool filter.
|
|
1694
|
+
*
|
|
1695
|
+
* Supports two formats:
|
|
1696
|
+
* - Raw tool name: "get_current_time" (matches across all servers)
|
|
1697
|
+
* - Server-prefixed: "time__get_current_time" (matches specific server + tool)
|
|
1698
|
+
*
|
|
1699
|
+
* @remarks
|
|
1700
|
+
* When a filter contains "__", it's first checked as server-prefixed (exact match).
|
|
1701
|
+
* If that fails, it's checked as a raw tool name. This handles tools whose names
|
|
1702
|
+
* contain "__" (e.g., "my__special__tool").
|
|
1703
|
+
*
|
|
1704
|
+
* @param tool The tool to match.
|
|
1705
|
+
* @param tools List of tool names to match against.
|
|
1706
|
+
*
|
|
1707
|
+
* @returns True if a match is found in tools, based on the predicate.
|
|
1708
|
+
*
|
|
1709
|
+
* @internal
|
|
1710
|
+
*/
|
|
1711
|
+
#matchesToolFilter(tool, tools) {
|
|
1712
|
+
return tools.some((filterItem) => {
|
|
1713
|
+
if (filterItem.indexOf(TOOL_SEPARATOR) === -1) {
|
|
1714
|
+
return filterItem === tool._toolName;
|
|
1715
|
+
}
|
|
1716
|
+
return filterItem === tool.name || filterItem === tool._toolName;
|
|
1717
|
+
});
|
|
1478
1718
|
}
|
|
1479
1719
|
}
|
|
1480
1720
|
exports.AuthenticationError = AuthenticationError;
|
|
@@ -1483,6 +1723,9 @@ exports.HealthStatus = HealthStatus;
|
|
|
1483
1723
|
exports.HealthStatusHelpers = HealthStatusHelpers;
|
|
1484
1724
|
exports.McpdClient = McpdClient;
|
|
1485
1725
|
exports.McpdError = McpdError;
|
|
1726
|
+
exports.PIPELINE_FLOW_REQUEST = PIPELINE_FLOW_REQUEST;
|
|
1727
|
+
exports.PIPELINE_FLOW_RESPONSE = PIPELINE_FLOW_RESPONSE;
|
|
1728
|
+
exports.PipelineError = PipelineError;
|
|
1486
1729
|
exports.ServerNotFoundError = ServerNotFoundError;
|
|
1487
1730
|
exports.ServerUnhealthyError = ServerUnhealthyError;
|
|
1488
1731
|
exports.TimeoutError = TimeoutError;
|