@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/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 performCall - Function to execute tool calls
126
- * @param getTools - Function to get tool schemas
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(performCall, getTools) {
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(target.#performCall, target.#getTools, serverName);
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
- * List all tools available on this server.
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.listTools();
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 listTools() {
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}.listTools() to see available tools.`,
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}.listTools() to see available tools.`,
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: 100,
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
- * Internal method to perform a tool call on a server.
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
- * ⚠️ This method is truly private and cannot be accessed by SDK consumers.
995
- * Use the fluent API instead: `client.servers.foo.tools.bar(args)`
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
- * - ToolsProxy (via dependency injection)
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 serverNames = servers && servers.length > 0 ? servers : await this.listServers();
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
- for (const result of results) {
1087
- if (result.status === "fulfilled") {
1088
- const { serverName, tools } = result.value;
1089
- for (const toolSchema of tools) {
1090
- const func = this.#functionBuilder.createFunctionFromSchema(
1091
- toolSchema,
1092
- serverName
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 = {}) {