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