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