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