@mozilla-ai/mcpd 0.0.1 → 0.0.2
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 +138 -35
- package/dist/apiPaths.d.ts +5 -0
- package/dist/apiPaths.d.ts.map +1 -1
- package/dist/client.d.ts +1 -46
- package/dist/client.d.ts.map +1 -1
- package/dist/dynamicCaller.d.ts +213 -10
- package/dist/dynamicCaller.d.ts.map +1 -1
- package/dist/index.js +479 -117
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +479 -117
- package/dist/index.mjs.map +1 -1
- package/dist/types.d.ts +30 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -119,49 +119,103 @@ function createCache(options = {}) {
|
|
|
119
119
|
class ServersNamespace {
|
|
120
120
|
#performCall;
|
|
121
121
|
#getTools;
|
|
122
|
+
#generatePrompt;
|
|
123
|
+
#getPrompts;
|
|
124
|
+
#getResources;
|
|
125
|
+
#getResourceTemplates;
|
|
126
|
+
#readResource;
|
|
122
127
|
/**
|
|
123
128
|
* Initialize the ServersNamespace with injected functions.
|
|
124
129
|
*
|
|
125
|
-
* @param
|
|
126
|
-
* @param
|
|
130
|
+
* @param options - Configuration object
|
|
131
|
+
* @param options.performCall - Function to execute tool calls
|
|
132
|
+
* @param options.getTools - Function to get tool schemas
|
|
133
|
+
* @param options.generatePrompt - Function to generate prompts
|
|
134
|
+
* @param options.getPrompts - Function to get prompt schemas
|
|
135
|
+
* @param options.getResources - Function to get resources
|
|
136
|
+
* @param options.getResourceTemplates - Function to get resource templates
|
|
137
|
+
* @param options.readResource - Function to read resource content
|
|
127
138
|
*/
|
|
128
|
-
constructor(
|
|
139
|
+
constructor({
|
|
140
|
+
performCall,
|
|
141
|
+
getTools,
|
|
142
|
+
generatePrompt,
|
|
143
|
+
getPrompts,
|
|
144
|
+
getResources,
|
|
145
|
+
getResourceTemplates,
|
|
146
|
+
readResource
|
|
147
|
+
}) {
|
|
129
148
|
this.#performCall = performCall;
|
|
130
149
|
this.#getTools = getTools;
|
|
150
|
+
this.#generatePrompt = generatePrompt;
|
|
151
|
+
this.#getPrompts = getPrompts;
|
|
152
|
+
this.#getResources = getResources;
|
|
153
|
+
this.#getResourceTemplates = getResourceTemplates;
|
|
154
|
+
this.#readResource = readResource;
|
|
131
155
|
return new Proxy(this, {
|
|
132
156
|
get: (target, serverName) => {
|
|
133
157
|
if (typeof serverName !== "string") {
|
|
134
158
|
return void 0;
|
|
135
159
|
}
|
|
136
|
-
return new Server(
|
|
160
|
+
return new Server(
|
|
161
|
+
target.#performCall,
|
|
162
|
+
target.#getTools,
|
|
163
|
+
target.#generatePrompt,
|
|
164
|
+
target.#getPrompts,
|
|
165
|
+
target.#getResources,
|
|
166
|
+
target.#getResourceTemplates,
|
|
167
|
+
target.#readResource,
|
|
168
|
+
serverName
|
|
169
|
+
);
|
|
137
170
|
}
|
|
138
171
|
});
|
|
139
172
|
}
|
|
140
173
|
}
|
|
141
174
|
class Server {
|
|
142
175
|
tools;
|
|
176
|
+
prompts;
|
|
143
177
|
#performCall;
|
|
144
178
|
#getTools;
|
|
179
|
+
#generatePrompt;
|
|
180
|
+
#getPrompts;
|
|
181
|
+
#getResources;
|
|
182
|
+
#getResourceTemplates;
|
|
183
|
+
#readResource;
|
|
145
184
|
#serverName;
|
|
146
185
|
/**
|
|
147
186
|
* Initialize a Server for a specific server.
|
|
148
187
|
*
|
|
149
188
|
* @param performCall - Function to execute tool calls
|
|
150
189
|
* @param getTools - Function to get tool schemas
|
|
190
|
+
* @param generatePrompt - Function to generate prompts
|
|
191
|
+
* @param getPrompts - Function to get prompt schemas
|
|
192
|
+
* @param getResources - Function to get resources
|
|
193
|
+
* @param getResourceTemplates - Function to get resource templates
|
|
194
|
+
* @param readResource - Function to read resource content
|
|
151
195
|
* @param serverName - The name of the MCP server
|
|
152
196
|
*/
|
|
153
|
-
constructor(performCall, getTools, serverName) {
|
|
197
|
+
constructor(performCall, getTools, generatePrompt, getPrompts, getResources, getResourceTemplates, readResource, serverName) {
|
|
154
198
|
this.#performCall = performCall;
|
|
155
199
|
this.#getTools = getTools;
|
|
200
|
+
this.#generatePrompt = generatePrompt;
|
|
201
|
+
this.#getPrompts = getPrompts;
|
|
202
|
+
this.#getResources = getResources;
|
|
203
|
+
this.#getResourceTemplates = getResourceTemplates;
|
|
204
|
+
this.#readResource = readResource;
|
|
156
205
|
this.#serverName = serverName;
|
|
157
206
|
this.tools = new ToolsNamespace(
|
|
158
207
|
this.#performCall,
|
|
159
208
|
this.#getTools,
|
|
160
209
|
this.#serverName
|
|
161
210
|
);
|
|
211
|
+
this.prompts = new PromptsNamespace(
|
|
212
|
+
this.#generatePrompt,
|
|
213
|
+
this.#getPrompts,
|
|
214
|
+
this.#serverName
|
|
215
|
+
);
|
|
162
216
|
}
|
|
163
217
|
/**
|
|
164
|
-
*
|
|
218
|
+
* Get all tools available on this server.
|
|
165
219
|
*
|
|
166
220
|
* @returns Array of tool schemas
|
|
167
221
|
* @throws {ServerNotFoundError} If the server doesn't exist
|
|
@@ -169,13 +223,13 @@ class Server {
|
|
|
169
223
|
*
|
|
170
224
|
* @example
|
|
171
225
|
* ```typescript
|
|
172
|
-
* const tools = await client.servers.time.
|
|
226
|
+
* const tools = await client.servers.time.getTools();
|
|
173
227
|
* for (const tool of tools) {
|
|
174
228
|
* console.log(`${tool.name}: ${tool.description}`);
|
|
175
229
|
* }
|
|
176
230
|
* ```
|
|
177
231
|
*/
|
|
178
|
-
async
|
|
232
|
+
async getTools() {
|
|
179
233
|
return this.#getTools(this.#serverName);
|
|
180
234
|
}
|
|
181
235
|
/**
|
|
@@ -183,8 +237,13 @@ class Server {
|
|
|
183
237
|
*
|
|
184
238
|
* The tool name must match exactly as returned by the server.
|
|
185
239
|
*
|
|
240
|
+
* This method is designed as a safe boolean predicate - it catches all errors
|
|
241
|
+
* (ServerNotFoundError, ServerUnhealthyError, ConnectionError, etc.) and returns
|
|
242
|
+
* false rather than throwing. This makes it safe to use in conditional checks
|
|
243
|
+
* without requiring error handling.
|
|
244
|
+
*
|
|
186
245
|
* @param toolName - The exact name of the tool to check
|
|
187
|
-
* @returns True if the tool exists, false otherwise
|
|
246
|
+
* @returns True if the tool exists, false otherwise (including on errors)
|
|
188
247
|
*
|
|
189
248
|
* @example
|
|
190
249
|
* ```typescript
|
|
@@ -228,13 +287,192 @@ class Server {
|
|
|
228
287
|
const tool = tools.find((t) => t.name === toolName);
|
|
229
288
|
if (!tool) {
|
|
230
289
|
throw new ToolNotFoundError(
|
|
231
|
-
`Tool '${toolName}' not found on server '${this.#serverName}'. Use client.servers.${this.#serverName}.
|
|
290
|
+
`Tool '${toolName}' not found on server '${this.#serverName}'. Use client.servers.${this.#serverName}.getTools() to see available tools.`,
|
|
232
291
|
this.#serverName,
|
|
233
292
|
toolName
|
|
234
293
|
);
|
|
235
294
|
}
|
|
236
295
|
return this.#performCall(this.#serverName, toolName, args);
|
|
237
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* Get all prompts available on this server.
|
|
299
|
+
*
|
|
300
|
+
* Note: This method is marked `async` for consistency with other server methods,
|
|
301
|
+
* even though it doesn't directly await. This maintains a uniform async interface
|
|
302
|
+
* and allows for future enhancements without breaking the API contract.
|
|
303
|
+
*
|
|
304
|
+
* @returns Array of prompt schemas
|
|
305
|
+
* @throws {ServerNotFoundError} If the server doesn't exist
|
|
306
|
+
* @throws {ServerUnhealthyError} If the server is unhealthy
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```typescript
|
|
310
|
+
* const prompts = await client.servers.github.getPrompts();
|
|
311
|
+
* for (const prompt of prompts) {
|
|
312
|
+
* console.log(`${prompt.name}: ${prompt.description}`);
|
|
313
|
+
* }
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
async getPrompts() {
|
|
317
|
+
return this.#getPrompts(this.#serverName);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Check if a prompt exists on this server.
|
|
321
|
+
*
|
|
322
|
+
* The prompt name must match exactly as returned by the server.
|
|
323
|
+
*
|
|
324
|
+
* This method is designed as a safe boolean predicate - it catches all errors
|
|
325
|
+
* (ServerNotFoundError, ServerUnhealthyError, ConnectionError, etc.) and returns
|
|
326
|
+
* false rather than throwing. This makes it safe to use in conditional checks
|
|
327
|
+
* without requiring error handling.
|
|
328
|
+
*
|
|
329
|
+
* @param promptName - The exact name of the prompt to check
|
|
330
|
+
* @returns True if the prompt exists, false otherwise (including on errors)
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* ```typescript
|
|
334
|
+
* if (await client.servers.github.hasPrompt('create_pr')) {
|
|
335
|
+
* const result = await client.servers.github.generatePrompt('create_pr', { title: 'Fix bug' });
|
|
336
|
+
* }
|
|
337
|
+
* ```
|
|
338
|
+
*/
|
|
339
|
+
async hasPrompt(promptName) {
|
|
340
|
+
try {
|
|
341
|
+
const prompts = await this.#getPrompts(this.#serverName);
|
|
342
|
+
return prompts.some((p) => p.name === promptName);
|
|
343
|
+
} catch {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Generate a prompt by name with the given arguments.
|
|
349
|
+
*
|
|
350
|
+
* This method is useful for programmatic prompt generation when the prompt name
|
|
351
|
+
* is in a variable. The prompt name must match exactly as returned by the server.
|
|
352
|
+
*
|
|
353
|
+
* @param promptName - The exact name of the prompt to generate
|
|
354
|
+
* @param args - The arguments to pass to the prompt template
|
|
355
|
+
* @returns The generated prompt response
|
|
356
|
+
* @throws {ToolNotFoundError} If the prompt doesn't exist on the server
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* ```typescript
|
|
360
|
+
* // Call with explicit method (useful for dynamic prompt names):
|
|
361
|
+
* const promptName = 'create_pr';
|
|
362
|
+
* await client.servers.github.generatePrompt(promptName, { title: 'Fix bug' });
|
|
363
|
+
*
|
|
364
|
+
* // Or with dynamic server name:
|
|
365
|
+
* const serverName = 'github';
|
|
366
|
+
* await client.servers[serverName].generatePrompt(promptName, { title: 'Fix bug' });
|
|
367
|
+
* ```
|
|
368
|
+
*/
|
|
369
|
+
async generatePrompt(promptName, args) {
|
|
370
|
+
const prompts = await this.#getPrompts(this.#serverName);
|
|
371
|
+
const prompt = prompts.find((p) => p.name === promptName);
|
|
372
|
+
if (!prompt) {
|
|
373
|
+
throw new ToolNotFoundError(
|
|
374
|
+
`Prompt '${promptName}' not found on server '${this.#serverName}'. Use client.servers.${this.#serverName}.getPrompts() to see available prompts.`,
|
|
375
|
+
this.#serverName,
|
|
376
|
+
promptName
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return this.#generatePrompt(this.#serverName, promptName, args);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Get all resources available on this server.
|
|
383
|
+
*
|
|
384
|
+
* Note: This method is marked `async` for consistency with other server methods,
|
|
385
|
+
* even though it doesn't directly await. This maintains a uniform async interface
|
|
386
|
+
* and allows for future enhancements without breaking the API contract.
|
|
387
|
+
*
|
|
388
|
+
* @returns Array of resource schemas with original names
|
|
389
|
+
* @throws {ServerNotFoundError} If the server doesn't exist
|
|
390
|
+
* @throws {ServerUnhealthyError} If the server is unhealthy
|
|
391
|
+
*
|
|
392
|
+
* @example
|
|
393
|
+
* ```typescript
|
|
394
|
+
* const resources = await client.servers.github.getResources();
|
|
395
|
+
* for (const resource of resources) {
|
|
396
|
+
* console.log(`${resource.name}: ${resource.uri}`);
|
|
397
|
+
* }
|
|
398
|
+
* ```
|
|
399
|
+
*/
|
|
400
|
+
async getResources() {
|
|
401
|
+
return this.#getResources(this.#serverName);
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Get all resource templates available on this server.
|
|
405
|
+
*
|
|
406
|
+
* Note: This method is marked `async` for consistency with other server methods,
|
|
407
|
+
* even though it doesn't directly await. This maintains a uniform async interface
|
|
408
|
+
* and allows for future enhancements without breaking the API contract.
|
|
409
|
+
*
|
|
410
|
+
* @returns Array of resource template schemas with original names
|
|
411
|
+
* @throws {ServerNotFoundError} If the server doesn't exist
|
|
412
|
+
* @throws {ServerUnhealthyError} If the server is unhealthy
|
|
413
|
+
*
|
|
414
|
+
* @example
|
|
415
|
+
* ```typescript
|
|
416
|
+
* const templates = await client.servers.github.getResourceTemplates();
|
|
417
|
+
* for (const template of templates) {
|
|
418
|
+
* console.log(`${template.name}: ${template.uriTemplate}`);
|
|
419
|
+
* }
|
|
420
|
+
* ```
|
|
421
|
+
*/
|
|
422
|
+
async getResourceTemplates() {
|
|
423
|
+
return this.#getResourceTemplates(this.#serverName);
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Check if a resource exists on this server.
|
|
427
|
+
*
|
|
428
|
+
* The resource URI must match exactly as returned by the server.
|
|
429
|
+
*
|
|
430
|
+
* This method is designed as a safe boolean predicate - it catches all errors
|
|
431
|
+
* (ServerNotFoundError, ServerUnhealthyError, ConnectionError, etc.) and returns
|
|
432
|
+
* false rather than throwing. This makes it safe to use in conditional checks
|
|
433
|
+
* without requiring error handling.
|
|
434
|
+
*
|
|
435
|
+
* @param uri - The exact URI of the resource to check
|
|
436
|
+
* @returns True if the resource exists, false otherwise (including on errors)
|
|
437
|
+
*
|
|
438
|
+
* @example
|
|
439
|
+
* ```typescript
|
|
440
|
+
* if (await client.servers.github.hasResource('file:///repo/README.md')) {
|
|
441
|
+
* const content = await client.servers.github.readResource('file:///repo/README.md');
|
|
442
|
+
* }
|
|
443
|
+
* ```
|
|
444
|
+
*/
|
|
445
|
+
async hasResource(uri) {
|
|
446
|
+
try {
|
|
447
|
+
const resources = await this.#getResources(this.#serverName);
|
|
448
|
+
return resources.some((r) => r.uri === uri);
|
|
449
|
+
} catch {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Read resource content by URI from this server.
|
|
455
|
+
*
|
|
456
|
+
* @param uri - The resource URI
|
|
457
|
+
* @returns Array of resource contents (text or blob)
|
|
458
|
+
* @throws {ServerNotFoundError} If the server doesn't exist
|
|
459
|
+
* @throws {ServerUnhealthyError} If the server is unhealthy
|
|
460
|
+
*
|
|
461
|
+
* @example
|
|
462
|
+
* ```typescript
|
|
463
|
+
* const contents = await client.servers.github.readResource('file:///repo/README.md');
|
|
464
|
+
* for (const content of contents) {
|
|
465
|
+
* if (content.text) {
|
|
466
|
+
* console.log(content.text);
|
|
467
|
+
* } else if (content.blob) {
|
|
468
|
+
* console.log('Binary content:', content.blob.substring(0, 50) + '...');
|
|
469
|
+
* }
|
|
470
|
+
* }
|
|
471
|
+
* ```
|
|
472
|
+
*/
|
|
473
|
+
async readResource(uri) {
|
|
474
|
+
return this.#readResource(this.#serverName, uri);
|
|
475
|
+
}
|
|
238
476
|
}
|
|
239
477
|
class ToolsNamespace {
|
|
240
478
|
#performCall;
|
|
@@ -262,7 +500,7 @@ class ToolsNamespace {
|
|
|
262
500
|
const tool = tools.find((t) => t.name === toolName);
|
|
263
501
|
if (!tool) {
|
|
264
502
|
throw new ToolNotFoundError(
|
|
265
|
-
`Tool '${toolName}' not found on server '${target.#serverName}'. Use client.servers.${target.#serverName}.
|
|
503
|
+
`Tool '${toolName}' not found on server '${target.#serverName}'. Use client.servers.${target.#serverName}.getTools() to see available tools.`,
|
|
266
504
|
target.#serverName,
|
|
267
505
|
toolName
|
|
268
506
|
);
|
|
@@ -273,6 +511,53 @@ class ToolsNamespace {
|
|
|
273
511
|
});
|
|
274
512
|
}
|
|
275
513
|
}
|
|
514
|
+
class PromptsNamespace {
|
|
515
|
+
#generatePrompt;
|
|
516
|
+
#getPrompts;
|
|
517
|
+
#serverName;
|
|
518
|
+
/**
|
|
519
|
+
* Initialize a PromptsNamespace for a specific server.
|
|
520
|
+
*
|
|
521
|
+
* @param generatePrompt - Function to generate prompts
|
|
522
|
+
* @param getPrompts - Function to get prompt schemas
|
|
523
|
+
* @param serverName - The name of the MCP server
|
|
524
|
+
*/
|
|
525
|
+
constructor(generatePrompt, getPrompts, serverName) {
|
|
526
|
+
this.#generatePrompt = generatePrompt;
|
|
527
|
+
this.#getPrompts = getPrompts;
|
|
528
|
+
this.#serverName = serverName;
|
|
529
|
+
return new Proxy(this, {
|
|
530
|
+
get: (target, prop) => {
|
|
531
|
+
if (typeof prop !== "string") {
|
|
532
|
+
return void 0;
|
|
533
|
+
}
|
|
534
|
+
return async (args) => {
|
|
535
|
+
const promptName = prop;
|
|
536
|
+
const prompt = await target.#getPromptByName(promptName);
|
|
537
|
+
if (!prompt) {
|
|
538
|
+
throw new ToolNotFoundError(
|
|
539
|
+
`Prompt '${promptName}' not found on server '${target.#serverName}'. Use client.servers.${target.#serverName}.getPrompts() to see available prompts.`,
|
|
540
|
+
target.#serverName,
|
|
541
|
+
promptName
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
return target.#generatePrompt(target.#serverName, promptName, args);
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Helper method to find a prompt by name on this server.
|
|
551
|
+
*
|
|
552
|
+
* @param promptName - The exact name of the prompt to find
|
|
553
|
+
* @returns The prompt if found, undefined otherwise
|
|
554
|
+
* @internal
|
|
555
|
+
*/
|
|
556
|
+
async #getPromptByName(promptName) {
|
|
557
|
+
const prompts = await this.#getPrompts(this.#serverName);
|
|
558
|
+
return prompts.find((p) => p.name === promptName);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
276
561
|
class TypeConverter {
|
|
277
562
|
/**
|
|
278
563
|
* Convert JSON schema types to JavaScript type names.
|
|
@@ -666,10 +951,27 @@ const API_PATHS = {
|
|
|
666
951
|
// Tools
|
|
667
952
|
SERVER_TOOLS: (serverName) => `${SERVERS_BASE}/${encodeURIComponent(serverName)}/tools`,
|
|
668
953
|
TOOL_CALL: (serverName, toolName) => `${SERVERS_BASE}/${encodeURIComponent(serverName)}/tools/${encodeURIComponent(toolName)}`,
|
|
954
|
+
// Prompts
|
|
955
|
+
SERVER_PROMPTS: (serverName, cursor) => {
|
|
956
|
+
const base = `${SERVERS_BASE}/${encodeURIComponent(serverName)}/prompts`;
|
|
957
|
+
return cursor ? `${base}?cursor=${encodeURIComponent(cursor)}` : base;
|
|
958
|
+
},
|
|
959
|
+
PROMPT_GET_GENERATED: (serverName, promptName) => `${SERVERS_BASE}/${encodeURIComponent(serverName)}/prompts/${encodeURIComponent(promptName)}`,
|
|
960
|
+
// Resources
|
|
961
|
+
SERVER_RESOURCES: (serverName, cursor) => {
|
|
962
|
+
const base = `${SERVERS_BASE}/${encodeURIComponent(serverName)}/resources`;
|
|
963
|
+
return cursor ? `${base}?cursor=${encodeURIComponent(cursor)}` : base;
|
|
964
|
+
},
|
|
965
|
+
SERVER_RESOURCE_TEMPLATES: (serverName, cursor) => {
|
|
966
|
+
const base = `${SERVERS_BASE}/${encodeURIComponent(serverName)}/resources/templates`;
|
|
967
|
+
return cursor ? `${base}?cursor=${encodeURIComponent(cursor)}` : base;
|
|
968
|
+
},
|
|
969
|
+
RESOURCE_CONTENT: (serverName, uri) => `${SERVERS_BASE}/${encodeURIComponent(serverName)}/resources/content?uri=${encodeURIComponent(uri)}`,
|
|
669
970
|
// Health
|
|
670
971
|
HEALTH_ALL: HEALTH_SERVERS_BASE,
|
|
671
972
|
HEALTH_SERVER: (serverName) => `${HEALTH_SERVERS_BASE}/${encodeURIComponent(serverName)}`
|
|
672
973
|
};
|
|
974
|
+
const SERVER_HEALTH_CACHE_MAXSIZE = 100;
|
|
673
975
|
class McpdClient {
|
|
674
976
|
#endpoint;
|
|
675
977
|
#apiKey;
|
|
@@ -696,17 +998,18 @@ class McpdClient {
|
|
|
696
998
|
this.#timeout = options.timeout ?? 3e4;
|
|
697
999
|
const healthCacheTtlMs = (options.healthCacheTtl ?? 10) * 1e3;
|
|
698
1000
|
this.#serverHealthCache = createCache({
|
|
699
|
-
max:
|
|
700
|
-
// TODO: Extract to const like Python SDK see:
|
|
701
|
-
// _SERVER_HEALTH_CACHE_MAXSIZE: int = 100
|
|
702
|
-
// """Maximum number of server health entries to cache.
|
|
703
|
-
// Prevents unbounded memory growth while allowing legitimate large-scale monitoring."""
|
|
1001
|
+
max: SERVER_HEALTH_CACHE_MAXSIZE,
|
|
704
1002
|
ttl: healthCacheTtlMs
|
|
705
1003
|
});
|
|
706
|
-
this.servers = new ServersNamespace(
|
|
707
|
-
this.#performCall.bind(this),
|
|
708
|
-
this.#getToolsByServer.bind(this)
|
|
709
|
-
|
|
1004
|
+
this.servers = new ServersNamespace({
|
|
1005
|
+
performCall: this.#performCall.bind(this),
|
|
1006
|
+
getTools: this.#getToolsByServer.bind(this),
|
|
1007
|
+
generatePrompt: this.#generatePromptInternal.bind(this),
|
|
1008
|
+
getPrompts: this.#getPromptsByServer.bind(this),
|
|
1009
|
+
getResources: this.#getResourcesByServer.bind(this),
|
|
1010
|
+
getResourceTemplates: this.#getResourceTemplatesByServer.bind(this),
|
|
1011
|
+
readResource: this.#readResourceByServer.bind(this)
|
|
1012
|
+
});
|
|
710
1013
|
this.#functionBuilder = new FunctionBuilder(this.#performCall.bind(this));
|
|
711
1014
|
}
|
|
712
1015
|
/**
|
|
@@ -741,6 +1044,7 @@ class McpdClient {
|
|
|
741
1044
|
try {
|
|
742
1045
|
errorModel = JSON.parse(body);
|
|
743
1046
|
} catch {
|
|
1047
|
+
errorModel = null;
|
|
744
1048
|
}
|
|
745
1049
|
if (errorModel && errorModel.detail) {
|
|
746
1050
|
const errorDetails = errorModel.errors?.map((e) => `${e.location}: ${e.message}`).join("; ");
|
|
@@ -805,78 +1109,6 @@ class McpdClient {
|
|
|
805
1109
|
async listServers() {
|
|
806
1110
|
return await this.#request(API_PATHS.SERVERS);
|
|
807
1111
|
}
|
|
808
|
-
/**
|
|
809
|
-
* Get tool schemas from all (or specific) MCP servers with transformed names.
|
|
810
|
-
*
|
|
811
|
-
* IMPORTANT: Tool names are transformed to `serverName__toolName` format to:
|
|
812
|
-
* 1. Prevent naming clashes when aggregating tools from multiple servers
|
|
813
|
-
* 2. Identify which server each tool belongs to
|
|
814
|
-
*
|
|
815
|
-
* This method automatically filters out unhealthy servers by checking their health
|
|
816
|
-
* status before fetching tools. Unhealthy servers are silently skipped to ensure
|
|
817
|
-
* the method returns quickly without waiting for timeouts on failed servers.
|
|
818
|
-
*
|
|
819
|
-
* Tool fetches from multiple servers are executed concurrently for optimal performance.
|
|
820
|
-
*
|
|
821
|
-
* This is useful for:
|
|
822
|
-
* - MCP servers that aggregate and re-expose tools from multiple upstream servers
|
|
823
|
-
* - Tool inspection and discovery across all servers
|
|
824
|
-
* - Custom tooling that needs raw MCP tool schemas
|
|
825
|
-
*
|
|
826
|
-
* @param options - Optional configuration
|
|
827
|
-
* @param options.servers - Array of server names to include. If not specified, includes all servers.
|
|
828
|
-
* @returns Array of tool schemas with transformed names (serverName__toolName). Only includes tools from healthy servers.
|
|
829
|
-
* @throws {ConnectionError} If unable to connect to the mcpd daemon
|
|
830
|
-
* @throws {TimeoutError} If requests to the daemon time out
|
|
831
|
-
* @throws {AuthenticationError} If API key authentication fails
|
|
832
|
-
* @throws {McpdError} If health check or initial server listing fails
|
|
833
|
-
*
|
|
834
|
-
* @example
|
|
835
|
-
* ```typescript
|
|
836
|
-
* // Get all tools from all servers
|
|
837
|
-
* const allTools = await client.getToolSchemas();
|
|
838
|
-
* // Returns: [
|
|
839
|
-
* // { name: "time__get_current_time", description: "...", ... },
|
|
840
|
-
* // { name: "fetch__fetch_url", description: "...", ... }
|
|
841
|
-
* // ]
|
|
842
|
-
*
|
|
843
|
-
* // Get tools from specific servers only
|
|
844
|
-
* const someTools = await client.getToolSchemas({ servers: ['time', 'fetch'] });
|
|
845
|
-
*
|
|
846
|
-
* // Original tool name "get_current_time" becomes "time__get_current_time"
|
|
847
|
-
* // This prevents clashes if multiple servers have tools with the same name
|
|
848
|
-
* ```
|
|
849
|
-
*/
|
|
850
|
-
async getToolSchemas(options) {
|
|
851
|
-
const { servers } = options || {};
|
|
852
|
-
const serverNames = servers && servers.length > 0 ? servers : await this.listServers();
|
|
853
|
-
const healthMap = await this.getServerHealth();
|
|
854
|
-
const healthyServers = serverNames.filter((name) => {
|
|
855
|
-
const health = healthMap[name];
|
|
856
|
-
return health && HealthStatusHelpers.isHealthy(health.status);
|
|
857
|
-
});
|
|
858
|
-
const results = await Promise.allSettled(
|
|
859
|
-
healthyServers.map(async (serverName) => ({
|
|
860
|
-
serverName,
|
|
861
|
-
tools: await this.#getToolsByServer(serverName)
|
|
862
|
-
}))
|
|
863
|
-
);
|
|
864
|
-
const allTools = [];
|
|
865
|
-
for (const result of results) {
|
|
866
|
-
if (result.status === "fulfilled") {
|
|
867
|
-
const { serverName, tools } = result.value;
|
|
868
|
-
for (const tool of tools) {
|
|
869
|
-
allTools.push({
|
|
870
|
-
...tool,
|
|
871
|
-
name: `${serverName}__${tool.name}`
|
|
872
|
-
});
|
|
873
|
-
}
|
|
874
|
-
} else {
|
|
875
|
-
console.warn(`Failed to get tools for server:`, result.reason);
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
return allTools;
|
|
879
|
-
}
|
|
880
1112
|
/**
|
|
881
1113
|
* Internal method to get tool schemas for a server.
|
|
882
1114
|
* Used by dependency injection for ServersNamespace and internally for getAgentTools.
|
|
@@ -902,6 +1134,132 @@ class McpdClient {
|
|
|
902
1134
|
}
|
|
903
1135
|
return response.tools;
|
|
904
1136
|
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Internal method to get prompt schemas for a server.
|
|
1139
|
+
* Used internally for getPromptSchemas.
|
|
1140
|
+
*
|
|
1141
|
+
* @param serverName - Server name to get prompts for
|
|
1142
|
+
* @param cursor - Optional cursor for pagination
|
|
1143
|
+
* @returns Prompt schemas for the specified server
|
|
1144
|
+
* @throws {ServerNotFoundError} If the specified server doesn't exist
|
|
1145
|
+
* @throws {ServerUnhealthyError} If the server is not healthy
|
|
1146
|
+
* @throws {ConnectionError} If unable to connect to the mcpd daemon
|
|
1147
|
+
* @throws {TimeoutError} If the request times out
|
|
1148
|
+
* @throws {McpdError} If the request fails
|
|
1149
|
+
* @internal
|
|
1150
|
+
*/
|
|
1151
|
+
async #getPromptsByServer(serverName, cursor) {
|
|
1152
|
+
try {
|
|
1153
|
+
await this.#ensureServerHealthy(serverName);
|
|
1154
|
+
const path = API_PATHS.SERVER_PROMPTS(serverName, cursor);
|
|
1155
|
+
const response = await this.#request(path);
|
|
1156
|
+
return response.prompts || [];
|
|
1157
|
+
} catch (error) {
|
|
1158
|
+
if (error instanceof McpdError && error.message.includes("501") && error.message.includes("Not Implemented")) {
|
|
1159
|
+
return [];
|
|
1160
|
+
}
|
|
1161
|
+
throw error;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Internal method to generate a prompt on a server.
|
|
1166
|
+
*
|
|
1167
|
+
* This method is used internally by:
|
|
1168
|
+
* - PromptsNamespace (via dependency injection)
|
|
1169
|
+
* - Server.generatePrompt() (via dependency injection)
|
|
1170
|
+
*
|
|
1171
|
+
* @param serverName - The name of the server
|
|
1172
|
+
* @param promptName - The exact name of the prompt
|
|
1173
|
+
* @param args - The prompt arguments
|
|
1174
|
+
* @returns The generated prompt response
|
|
1175
|
+
* @internal
|
|
1176
|
+
*/
|
|
1177
|
+
async #generatePromptInternal(serverName, promptName, args) {
|
|
1178
|
+
await this.#ensureServerHealthy(serverName);
|
|
1179
|
+
const path = API_PATHS.PROMPT_GET_GENERATED(serverName, promptName);
|
|
1180
|
+
const requestBody = {
|
|
1181
|
+
arguments: args || {}
|
|
1182
|
+
};
|
|
1183
|
+
const response = await this.#request(path, {
|
|
1184
|
+
method: "POST",
|
|
1185
|
+
body: JSON.stringify(requestBody)
|
|
1186
|
+
});
|
|
1187
|
+
return response;
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Internal method to get resource schemas for a server.
|
|
1191
|
+
* Used internally for getResources and by dependency injection for ServersNamespace.
|
|
1192
|
+
*
|
|
1193
|
+
* @param serverName - Server name to get resources for
|
|
1194
|
+
* @param cursor - Optional cursor for pagination
|
|
1195
|
+
* @returns Resource schemas for the specified server
|
|
1196
|
+
* @throws {ServerNotFoundError} If the specified server doesn't exist
|
|
1197
|
+
* @throws {ServerUnhealthyError} If the server is not healthy
|
|
1198
|
+
* @throws {ConnectionError} If unable to connect to the mcpd daemon
|
|
1199
|
+
* @throws {TimeoutError} If the request times out
|
|
1200
|
+
* @throws {McpdError} If the request fails
|
|
1201
|
+
* @internal
|
|
1202
|
+
*/
|
|
1203
|
+
async #getResourcesByServer(serverName, cursor) {
|
|
1204
|
+
try {
|
|
1205
|
+
await this.#ensureServerHealthy(serverName);
|
|
1206
|
+
const path = API_PATHS.SERVER_RESOURCES(serverName, cursor);
|
|
1207
|
+
const response = await this.#request(path);
|
|
1208
|
+
return response.resources || [];
|
|
1209
|
+
} catch (error) {
|
|
1210
|
+
if (error instanceof McpdError && error.message.includes("501") && error.message.includes("Not Implemented")) {
|
|
1211
|
+
return [];
|
|
1212
|
+
}
|
|
1213
|
+
throw error;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Internal method to get resource template schemas for a server.
|
|
1218
|
+
* Used internally for getResourceTemplates and by dependency injection for ServersNamespace.
|
|
1219
|
+
*
|
|
1220
|
+
* @param serverName - Server name to get resource templates for
|
|
1221
|
+
* @param cursor - Optional cursor for pagination
|
|
1222
|
+
* @returns Resource template schemas for the specified server
|
|
1223
|
+
* @throws {ServerNotFoundError} If the specified server doesn't exist
|
|
1224
|
+
* @throws {ServerUnhealthyError} If the server is not healthy
|
|
1225
|
+
* @throws {ConnectionError} If unable to connect to the mcpd daemon
|
|
1226
|
+
* @throws {TimeoutError} If the request times out
|
|
1227
|
+
* @throws {McpdError} If the request fails
|
|
1228
|
+
* @internal
|
|
1229
|
+
*/
|
|
1230
|
+
async #getResourceTemplatesByServer(serverName, cursor) {
|
|
1231
|
+
try {
|
|
1232
|
+
await this.#ensureServerHealthy(serverName);
|
|
1233
|
+
const path = API_PATHS.SERVER_RESOURCE_TEMPLATES(serverName, cursor);
|
|
1234
|
+
const response = await this.#request(path);
|
|
1235
|
+
return response.templates || [];
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
if (error instanceof McpdError && error.message.includes("501") && error.message.includes("Not Implemented")) {
|
|
1238
|
+
return [];
|
|
1239
|
+
}
|
|
1240
|
+
throw error;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Internal method to read resource content from a server.
|
|
1245
|
+
* Used by dependency injection for ServersNamespace.
|
|
1246
|
+
*
|
|
1247
|
+
* @param serverName - Server name to read resource from
|
|
1248
|
+
* @param uri - The resource URI
|
|
1249
|
+
* @returns Array of resource contents (text or blob)
|
|
1250
|
+
* @throws {ServerNotFoundError} If the specified server doesn't exist
|
|
1251
|
+
* @throws {ServerUnhealthyError} If the server is not healthy
|
|
1252
|
+
* @throws {ConnectionError} If unable to connect to the mcpd daemon
|
|
1253
|
+
* @throws {TimeoutError} If the request times out
|
|
1254
|
+
* @throws {McpdError} If the request fails
|
|
1255
|
+
* @internal
|
|
1256
|
+
*/
|
|
1257
|
+
async #readResourceByServer(serverName, uri) {
|
|
1258
|
+
await this.#ensureServerHealthy(serverName);
|
|
1259
|
+
const path = API_PATHS.RESOURCE_CONTENT(serverName, uri);
|
|
1260
|
+
const response = await this.#request(path);
|
|
1261
|
+
return response || [];
|
|
1262
|
+
}
|
|
905
1263
|
async getServerHealth(serverName) {
|
|
906
1264
|
if (serverName) {
|
|
907
1265
|
const cacheKey = `health:${serverName}`;
|
|
@@ -989,13 +1347,28 @@ class McpdClient {
|
|
|
989
1347
|
}
|
|
990
1348
|
}
|
|
991
1349
|
/**
|
|
992
|
-
*
|
|
1350
|
+
* Get list of healthy servers from optional server names.
|
|
1351
|
+
*
|
|
1352
|
+
* This helper fetches server names (if not provided) and filters to only healthy servers.
|
|
1353
|
+
* Used by getToolSchemas(), getPrompts(), and agentTools() to avoid timeouts on failed servers.
|
|
993
1354
|
*
|
|
994
|
-
*
|
|
995
|
-
*
|
|
1355
|
+
* @param servers - Optional array of server names. If not provided, fetches all servers.
|
|
1356
|
+
* @returns Array of healthy server names
|
|
1357
|
+
* @internal
|
|
1358
|
+
*/
|
|
1359
|
+
async #getHealthyServers(servers) {
|
|
1360
|
+
const serverNames = servers && servers.length > 0 ? servers : await this.listServers();
|
|
1361
|
+
const healthMap = await this.getServerHealth();
|
|
1362
|
+
return serverNames.filter((name) => {
|
|
1363
|
+
const health = healthMap[name];
|
|
1364
|
+
return health && HealthStatusHelpers.isHealthy(health.status);
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Internal method to perform a tool call on a server.
|
|
996
1369
|
*
|
|
997
1370
|
* This method is used internally by:
|
|
998
|
-
* -
|
|
1371
|
+
* - ToolsNamespace (via dependency injection)
|
|
999
1372
|
* - FunctionBuilder (via dependency injection)
|
|
1000
1373
|
*
|
|
1001
1374
|
* @param serverName - The name of the server
|
|
@@ -1070,33 +1443,22 @@ class McpdClient {
|
|
|
1070
1443
|
* @internal
|
|
1071
1444
|
*/
|
|
1072
1445
|
async agentTools(servers) {
|
|
1073
|
-
const
|
|
1074
|
-
const healthMap = await this.getServerHealth();
|
|
1075
|
-
const healthyServers = serverNames.filter((name) => {
|
|
1076
|
-
const health = healthMap[name];
|
|
1077
|
-
return health && HealthStatusHelpers.isHealthy(health.status);
|
|
1078
|
-
});
|
|
1446
|
+
const healthyServers = await this.#getHealthyServers(servers);
|
|
1079
1447
|
const results = await Promise.allSettled(
|
|
1080
1448
|
healthyServers.map(async (serverName) => ({
|
|
1081
1449
|
serverName,
|
|
1082
1450
|
tools: await this.#getToolsByServer(serverName)
|
|
1083
1451
|
}))
|
|
1084
1452
|
);
|
|
1085
|
-
const agentTools =
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
agentTools.push(func);
|
|
1095
|
-
}
|
|
1096
|
-
} else {
|
|
1097
|
-
console.warn(`Failed to get tools for server:`, result.reason);
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1453
|
+
const agentTools = results.filter((result) => result.status === "fulfilled").flatMap((result) => {
|
|
1454
|
+
const { serverName, tools } = result.value;
|
|
1455
|
+
return tools.map(
|
|
1456
|
+
(toolSchema) => this.#functionBuilder.createFunctionFromSchema(
|
|
1457
|
+
toolSchema,
|
|
1458
|
+
serverName
|
|
1459
|
+
)
|
|
1460
|
+
);
|
|
1461
|
+
});
|
|
1100
1462
|
return agentTools;
|
|
1101
1463
|
}
|
|
1102
1464
|
async getAgentTools(options = {}) {
|