@mozilla-ai/mcpd 0.1.3 → 0.1.4

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
@@ -1,1722 +1,1814 @@
1
- "use strict";
2
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const lruCache = require("lru-cache");
4
- const zod = require("zod");
5
- const PIPELINE_FLOW_REQUEST = "request";
6
- const PIPELINE_FLOW_RESPONSE = "response";
7
- class McpdError extends Error {
8
- constructor(message, cause) {
9
- super(message);
10
- this.name = "McpdError";
11
- this.cause = cause;
12
- Error.captureStackTrace(this, this.constructor);
13
- }
14
- }
15
- class ConnectionError extends McpdError {
16
- constructor(message, cause) {
17
- super(message, cause);
18
- this.name = "ConnectionError";
19
- Error.captureStackTrace(this, this.constructor);
20
- }
21
- }
22
- class AuthenticationError extends McpdError {
23
- constructor(message, cause) {
24
- super(message, cause);
25
- this.name = "AuthenticationError";
26
- Error.captureStackTrace(this, this.constructor);
27
- }
28
- }
29
- class ServerNotFoundError extends McpdError {
30
- serverName;
31
- constructor(message, serverName, cause) {
32
- super(message, cause);
33
- this.name = "ServerNotFoundError";
34
- this.serverName = serverName;
35
- Error.captureStackTrace(this, this.constructor);
36
- }
37
- }
38
- class ServerUnhealthyError extends McpdError {
39
- serverName;
40
- healthStatus;
41
- constructor(message, serverName, healthStatus, cause) {
42
- super(message, cause);
43
- this.name = "ServerUnhealthyError";
44
- this.serverName = serverName;
45
- this.healthStatus = healthStatus;
46
- Error.captureStackTrace(this, this.constructor);
47
- }
48
- }
49
- class ToolNotFoundError extends McpdError {
50
- serverName;
51
- toolName;
52
- constructor(message, serverName, toolName, cause) {
53
- super(message, cause);
54
- this.name = "ToolNotFoundError";
55
- this.serverName = serverName;
56
- this.toolName = toolName;
57
- Error.captureStackTrace(this, this.constructor);
58
- }
59
- }
60
- class ToolExecutionError extends McpdError {
61
- serverName;
62
- toolName;
63
- errorModel;
64
- constructor(message, serverName, toolName, errorModel, cause) {
65
- super(message, cause);
66
- this.name = "ToolExecutionError";
67
- this.serverName = serverName;
68
- this.toolName = toolName;
69
- this.errorModel = errorModel;
70
- Error.captureStackTrace(this, this.constructor);
71
- }
72
- }
73
- class ValidationError extends McpdError {
74
- validationErrors;
75
- constructor(message, validationErrors, cause) {
76
- super(message, cause);
77
- this.name = "ValidationError";
78
- this.validationErrors = validationErrors || [];
79
- Error.captureStackTrace(this, this.constructor);
80
- }
81
- }
82
- class TimeoutError extends McpdError {
83
- operation;
84
- timeout;
85
- constructor(message, operation, timeout, cause) {
86
- super(message, cause);
87
- this.name = "TimeoutError";
88
- this.operation = operation;
89
- this.timeout = timeout;
90
- Error.captureStackTrace(this, this.constructor);
91
- }
92
- }
93
- class PipelineError extends McpdError {
94
- serverName;
95
- operation;
96
- pipelineFlow;
97
- constructor(message, serverName, operation, pipelineFlow, cause) {
98
- super(message, cause);
99
- this.name = "PipelineError";
100
- this.serverName = serverName;
101
- this.operation = operation;
102
- this.pipelineFlow = pipelineFlow;
103
- Error.captureStackTrace(this, this.constructor);
104
- }
105
- }
106
- var HealthStatus = /* @__PURE__ */ ((HealthStatus2) => {
107
- HealthStatus2["OK"] = "ok";
108
- HealthStatus2["TIMEOUT"] = "timeout";
109
- HealthStatus2["UNREACHABLE"] = "unreachable";
110
- HealthStatus2["UNKNOWN"] = "unknown";
111
- return HealthStatus2;
112
- })(HealthStatus || {});
113
- const HealthStatusHelpers = {
114
- /**
115
- * Check if the given health status is a transient error state.
116
- */
117
- isTransient(status) {
118
- return status === "timeout" || status === "unknown";
119
- },
120
- /**
121
- * Check if the given status string represents a healthy state.
122
- */
123
- isHealthy(status) {
124
- return status === "ok";
125
- }
2
+ let lru_cache = require("lru-cache");
3
+ let zod = require("zod");
4
+ //#region src/errors.ts
5
+ /**
6
+ * Pipeline flow constant for request processing failures.
7
+ */
8
+ var PIPELINE_FLOW_REQUEST = "request";
9
+ /**
10
+ * Pipeline flow constant for response processing failures.
11
+ */
12
+ var PIPELINE_FLOW_RESPONSE = "response";
13
+ /**
14
+ * Base exception for all mcpd SDK errors.
15
+ *
16
+ * This exception wraps all errors that occur during interaction with the mcpd daemon,
17
+ * including network failures, authentication errors, server errors, and tool execution
18
+ * failures. The original exception is preserved via the cause property for debugging.
19
+ */
20
+ var McpdError = class extends Error {
21
+ constructor(message, cause) {
22
+ super(message);
23
+ this.name = "McpdError";
24
+ this.cause = cause;
25
+ Error.captureStackTrace(this, this.constructor);
26
+ }
27
+ };
28
+ /**
29
+ * Raised when unable to connect to the mcpd daemon.
30
+ *
31
+ * This typically indicates that:
32
+ * - The mcpd daemon is not running
33
+ * - The endpoint URL is incorrect
34
+ * - Network connectivity issues
35
+ * - Firewall blocking the connection
36
+ */
37
+ var ConnectionError = class extends McpdError {
38
+ constructor(message, cause) {
39
+ super(message, cause);
40
+ this.name = "ConnectionError";
41
+ Error.captureStackTrace(this, this.constructor);
42
+ }
43
+ };
44
+ /**
45
+ * Raised when authentication with the mcpd daemon fails.
46
+ *
47
+ * This indicates that:
48
+ * - The API key is invalid or expired
49
+ * - The API key is missing but required
50
+ * - The authentication method is not supported
51
+ */
52
+ var AuthenticationError = class extends McpdError {
53
+ constructor(message, cause) {
54
+ super(message, cause);
55
+ this.name = "AuthenticationError";
56
+ Error.captureStackTrace(this, this.constructor);
57
+ }
58
+ };
59
+ /**
60
+ * Raised when a specified MCP server doesn't exist.
61
+ *
62
+ * This error occurs when trying to access a server that:
63
+ * - Is not configured in the mcpd daemon
64
+ * - Has been removed or renamed
65
+ * - Is temporarily unavailable
66
+ */
67
+ var ServerNotFoundError = class extends McpdError {
68
+ serverName;
69
+ constructor(message, serverName, cause) {
70
+ super(message, cause);
71
+ this.name = "ServerNotFoundError";
72
+ this.serverName = serverName;
73
+ Error.captureStackTrace(this, this.constructor);
74
+ }
75
+ };
76
+ /**
77
+ * Raised when a specified MCP server is not healthy.
78
+ *
79
+ * This indicates that the server exists but is currently unhealthy:
80
+ * - The server is down or unreachable
81
+ * - Timeout occurred while checking health
82
+ * - No health data is available for the server
83
+ */
84
+ var ServerUnhealthyError = class extends McpdError {
85
+ serverName;
86
+ healthStatus;
87
+ constructor(message, serverName, healthStatus, cause) {
88
+ super(message, cause);
89
+ this.name = "ServerUnhealthyError";
90
+ this.serverName = serverName;
91
+ this.healthStatus = healthStatus;
92
+ Error.captureStackTrace(this, this.constructor);
93
+ }
94
+ };
95
+ /**
96
+ * Raised when a specified tool doesn't exist on a server.
97
+ *
98
+ * This error occurs when trying to call a tool that:
99
+ * - Doesn't exist on the specified server
100
+ * - Has been removed or renamed
101
+ * - Is temporarily unavailable
102
+ */
103
+ var ToolNotFoundError = class extends McpdError {
104
+ serverName;
105
+ toolName;
106
+ constructor(message, serverName, toolName, cause) {
107
+ super(message, cause);
108
+ this.name = "ToolNotFoundError";
109
+ this.serverName = serverName;
110
+ this.toolName = toolName;
111
+ Error.captureStackTrace(this, this.constructor);
112
+ }
113
+ };
114
+ /**
115
+ * Raised when a tool execution fails on the server side.
116
+ *
117
+ * This indicates that the tool was found and called, but failed during execution:
118
+ * - Invalid parameters provided
119
+ * - Server-side error during tool execution
120
+ * - Tool returned an error response
121
+ * - Timeout during tool execution
122
+ */
123
+ var ToolExecutionError = class extends McpdError {
124
+ serverName;
125
+ toolName;
126
+ errorModel;
127
+ constructor(message, serverName, toolName, errorModel, cause) {
128
+ super(message, cause);
129
+ this.name = "ToolExecutionError";
130
+ this.serverName = serverName;
131
+ this.toolName = toolName;
132
+ this.errorModel = errorModel;
133
+ Error.captureStackTrace(this, this.constructor);
134
+ }
135
+ };
136
+ /**
137
+ * Raised when input validation fails.
138
+ *
139
+ * This occurs when:
140
+ * - Required parameters are missing
141
+ * - Parameter types don't match the schema
142
+ * - Parameter values don't meet constraints
143
+ */
144
+ var ValidationError = class extends McpdError {
145
+ validationErrors;
146
+ constructor(message, validationErrors, cause) {
147
+ super(message, cause);
148
+ this.name = "ValidationError";
149
+ this.validationErrors = validationErrors || [];
150
+ Error.captureStackTrace(this, this.constructor);
151
+ }
126
152
  };
153
+ /**
154
+ * Raised when an operation times out.
155
+ *
156
+ * This can occur during:
157
+ * - Long-running tool executions
158
+ * - Slow network connections
159
+ * - Unresponsive mcpd daemon
160
+ */
161
+ var TimeoutError = class extends McpdError {
162
+ operation;
163
+ timeout;
164
+ constructor(message, operation, timeout, cause) {
165
+ super(message, cause);
166
+ this.name = "TimeoutError";
167
+ this.operation = operation;
168
+ this.timeout = timeout;
169
+ Error.captureStackTrace(this, this.constructor);
170
+ }
171
+ };
172
+ /**
173
+ * Raised when required pipeline processing fails.
174
+ *
175
+ * This indicates that required processing failed in the mcpd pipeline.
176
+ * The error occurs when a required plugin (such as authentication, validation,
177
+ * audit logging, monitoring, or response transformation) fails during request
178
+ * or response processing.
179
+ *
180
+ * Pipeline Flow Distinction:
181
+ * - **response-pipeline-failure**: The upstream request was processed (the tool
182
+ * was called), but results cannot be returned due to a required response
183
+ * processing step failure. Note: This does not indicate whether the tool
184
+ * itself succeeded or failed - only that the response cannot be delivered.
185
+ *
186
+ * - **request-pipeline-failure**: The request was rejected before reaching the
187
+ * upstream server due to a required request processing step failure (such as
188
+ * authentication, authorization, validation, or rate limiting plugin failure).
189
+ *
190
+ * This typically indicates a problem with a plugin or an external system
191
+ * that a plugin depends on (e.g., audit service, authentication provider).
192
+ * Retrying is unlikely to help as this usually indicates a configuration
193
+ * or dependency problem rather than a transient failure.
194
+ *
195
+ * @example
196
+ * ```typescript
197
+ * import { McpdClient, PipelineError } from '@mozilla-ai/mcpd';
198
+ *
199
+ * const client = new McpdClient({ apiEndpoint: 'http://localhost:8090' });
200
+ *
201
+ * try {
202
+ * const result = await client.servers.time.tools.get_current_time();
203
+ * } catch (error) {
204
+ * if (error instanceof PipelineError) {
205
+ * console.log(`Pipeline failure: ${error.message}`);
206
+ * console.log(`Flow: ${error.pipelineFlow}`);
207
+ *
208
+ * if (error.pipelineFlow === 'response') {
209
+ * console.log('Tool was called but results cannot be delivered');
210
+ * } else {
211
+ * console.log('Request was rejected by pipeline');
212
+ * console.log('Check authentication, authorization, or rate limiting');
213
+ * }
214
+ * }
215
+ * }
216
+ * ```
217
+ *
218
+ * @remarks
219
+ * This exception indicates a problem with a plugin or its dependencies, not
220
+ * with your request or the tool itself.
221
+ */
222
+ var PipelineError = class extends McpdError {
223
+ serverName;
224
+ operation;
225
+ pipelineFlow;
226
+ constructor(message, serverName, operation, pipelineFlow, cause) {
227
+ super(message, cause);
228
+ this.name = "PipelineError";
229
+ this.serverName = serverName;
230
+ this.operation = operation;
231
+ this.pipelineFlow = pipelineFlow;
232
+ Error.captureStackTrace(this, this.constructor);
233
+ }
234
+ };
235
+ //#endregion
236
+ //#region src/types.ts
237
+ /**
238
+ * Enumeration of possible MCP server health statuses.
239
+ */
240
+ var HealthStatus = /* @__PURE__ */ function(HealthStatus) {
241
+ HealthStatus["OK"] = "ok";
242
+ HealthStatus["TIMEOUT"] = "timeout";
243
+ HealthStatus["UNREACHABLE"] = "unreachable";
244
+ HealthStatus["UNKNOWN"] = "unknown";
245
+ return HealthStatus;
246
+ }({});
247
+ /**
248
+ * Helper functions for HealthStatus.
249
+ */
250
+ var HealthStatusHelpers = {
251
+ /**
252
+ * Check if the given health status is a transient error state.
253
+ */
254
+ isTransient(status) {
255
+ return status === "timeout" || status === "unknown";
256
+ },
257
+ /**
258
+ * Check if the given status string represents a healthy state.
259
+ */
260
+ isHealthy(status) {
261
+ return status === "ok";
262
+ }
263
+ };
264
+ //#endregion
265
+ //#region src/utils/cache.ts
266
+ /**
267
+ * Cache utilities for the mcpd SDK.
268
+ */
269
+ /**
270
+ * Creates a new LRU cache with TTL support.
271
+ *
272
+ * @param options - Cache configuration options
273
+ * @returns A configured LRU cache instance
274
+ */
127
275
  function createCache(options = {}) {
128
- return new lruCache.LRUCache({
129
- max: options.max ?? 100,
130
- ttl: options.ttl ?? 1e4,
131
- // Default 10 seconds
132
- updateAgeOnGet: false,
133
- updateAgeOnHas: false
134
- });
135
- }
136
- class ServersNamespace {
137
- #performCall;
138
- #getTools;
139
- #generatePrompt;
140
- #getPrompts;
141
- #getResources;
142
- #getResourceTemplates;
143
- #readResource;
144
- /**
145
- * Initialize the ServersNamespace with injected functions.
146
- *
147
- * @param options - Configuration object
148
- * @param options.performCall - Function to execute tool calls
149
- * @param options.getTools - Function to get tool schemas
150
- * @param options.generatePrompt - Function to generate prompts
151
- * @param options.getPrompts - Function to get prompt schemas
152
- * @param options.getResources - Function to get resources
153
- * @param options.getResourceTemplates - Function to get resource templates
154
- * @param options.readResource - Function to read resource content
155
- */
156
- constructor({
157
- performCall,
158
- getTools,
159
- generatePrompt,
160
- getPrompts,
161
- getResources,
162
- getResourceTemplates,
163
- readResource
164
- }) {
165
- this.#performCall = performCall;
166
- this.#getTools = getTools;
167
- this.#generatePrompt = generatePrompt;
168
- this.#getPrompts = getPrompts;
169
- this.#getResources = getResources;
170
- this.#getResourceTemplates = getResourceTemplates;
171
- this.#readResource = readResource;
172
- return new Proxy(this, {
173
- get: (target, serverName) => {
174
- if (typeof serverName !== "string") {
175
- return void 0;
176
- }
177
- return new Server(
178
- target.#performCall,
179
- target.#getTools,
180
- target.#generatePrompt,
181
- target.#getPrompts,
182
- target.#getResources,
183
- target.#getResourceTemplates,
184
- target.#readResource,
185
- serverName
186
- );
187
- }
188
- });
189
- }
190
- }
191
- class Server {
192
- tools;
193
- prompts;
194
- #performCall;
195
- #getTools;
196
- #generatePrompt;
197
- #getPrompts;
198
- #getResources;
199
- #getResourceTemplates;
200
- #readResource;
201
- #serverName;
202
- /**
203
- * Initialize a Server for a specific server.
204
- *
205
- * @param performCall - Function to execute tool calls
206
- * @param getTools - Function to get tool schemas
207
- * @param generatePrompt - Function to generate prompts
208
- * @param getPrompts - Function to get prompt schemas
209
- * @param getResources - Function to get resources
210
- * @param getResourceTemplates - Function to get resource templates
211
- * @param readResource - Function to read resource content
212
- * @param serverName - The name of the MCP server
213
- */
214
- constructor(performCall, getTools, generatePrompt, getPrompts, getResources, getResourceTemplates, readResource, serverName) {
215
- this.#performCall = performCall;
216
- this.#getTools = getTools;
217
- this.#generatePrompt = generatePrompt;
218
- this.#getPrompts = getPrompts;
219
- this.#getResources = getResources;
220
- this.#getResourceTemplates = getResourceTemplates;
221
- this.#readResource = readResource;
222
- this.#serverName = serverName;
223
- this.tools = new ToolsNamespace(
224
- this.#performCall,
225
- this.#getTools,
226
- this.#serverName
227
- );
228
- this.prompts = new PromptsNamespace(
229
- this.#generatePrompt,
230
- this.#getPrompts,
231
- this.#serverName
232
- );
233
- }
234
- /**
235
- * Get all tools available on this server.
236
- *
237
- * @returns Array of tool schemas
238
- * @throws {ServerNotFoundError} If the server doesn't exist
239
- * @throws {ServerUnhealthyError} If the server is unhealthy
240
- *
241
- * @example
242
- * ```typescript
243
- * const tools = await client.servers.time.getTools();
244
- * for (const tool of tools) {
245
- * console.log(`${tool.name}: ${tool.description}`);
246
- * }
247
- * ```
248
- */
249
- async getTools() {
250
- return this.#getTools(this.#serverName);
251
- }
252
- /**
253
- * Check if a tool exists on this server.
254
- *
255
- * The tool name must match exactly as returned by the server.
256
- *
257
- * This method is designed as a safe boolean predicate - it catches all errors
258
- * (ServerNotFoundError, ServerUnhealthyError, ConnectionError, etc.) and returns
259
- * false rather than throwing. This makes it safe to use in conditional checks
260
- * without requiring error handling.
261
- *
262
- * @param toolName - The exact name of the tool to check
263
- * @returns True if the tool exists, false otherwise (including on errors)
264
- *
265
- * @example
266
- * ```typescript
267
- * if (await client.servers.time.hasTool('get_current_time')) {
268
- * const result = await client.servers.time.callTool('get_current_time', { timezone: 'UTC' });
269
- * }
270
- * ```
271
- */
272
- async hasTool(toolName) {
273
- try {
274
- const tools = await this.#getTools(this.#serverName);
275
- return tools.some((t) => t.name === toolName);
276
- } catch {
277
- return false;
278
- }
279
- }
280
- /**
281
- * Call a tool by name with the given arguments.
282
- *
283
- * This method is useful for programmatic tool invocation when the tool name
284
- * is in a variable. The tool name must match exactly as returned by the server.
285
- *
286
- * @param toolName - The exact name of the tool to call
287
- * @param args - The arguments to pass to the tool
288
- * @returns The tool's response
289
- * @throws {ToolNotFoundError} If the tool doesn't exist on the server
290
- *
291
- * @example
292
- * ```typescript
293
- * // Call with explicit method (useful for dynamic tool names):
294
- * const toolName = 'get_current_time';
295
- * await client.servers.time.callTool(toolName, { timezone: 'UTC' });
296
- *
297
- * // Or with dynamic server name:
298
- * const serverName = 'time';
299
- * await client.servers[serverName].callTool(toolName, { timezone: 'UTC' });
300
- * ```
301
- */
302
- async callTool(toolName, args) {
303
- const tools = await this.#getTools(this.#serverName);
304
- const tool = tools.find((t) => t.name === toolName);
305
- if (!tool) {
306
- throw new ToolNotFoundError(
307
- `Tool '${toolName}' not found on server '${this.#serverName}'. Use client.servers.${this.#serverName}.getTools() to see available tools.`,
308
- this.#serverName,
309
- toolName
310
- );
311
- }
312
- return this.#performCall(this.#serverName, toolName, args);
313
- }
314
- /**
315
- * Get all prompts available on this server.
316
- *
317
- * Note: This method is marked `async` for consistency with other server methods,
318
- * even though it doesn't directly await. This maintains a uniform async interface
319
- * and allows for future enhancements without breaking the API contract.
320
- *
321
- * @returns Array of prompt schemas
322
- * @throws {ServerNotFoundError} If the server doesn't exist
323
- * @throws {ServerUnhealthyError} If the server is unhealthy
324
- *
325
- * @example
326
- * ```typescript
327
- * const prompts = await client.servers.github.getPrompts();
328
- * for (const prompt of prompts) {
329
- * console.log(`${prompt.name}: ${prompt.description}`);
330
- * }
331
- * ```
332
- */
333
- async getPrompts() {
334
- return this.#getPrompts(this.#serverName);
335
- }
336
- /**
337
- * Check if a prompt exists on this server.
338
- *
339
- * The prompt name must match exactly as returned by the server.
340
- *
341
- * This method is designed as a safe boolean predicate - it catches all errors
342
- * (ServerNotFoundError, ServerUnhealthyError, ConnectionError, etc.) and returns
343
- * false rather than throwing. This makes it safe to use in conditional checks
344
- * without requiring error handling.
345
- *
346
- * @param promptName - The exact name of the prompt to check
347
- * @returns True if the prompt exists, false otherwise (including on errors)
348
- *
349
- * @example
350
- * ```typescript
351
- * if (await client.servers.github.hasPrompt('create_pr')) {
352
- * const result = await client.servers.github.generatePrompt('create_pr', { title: 'Fix bug' });
353
- * }
354
- * ```
355
- */
356
- async hasPrompt(promptName) {
357
- try {
358
- const prompts = await this.#getPrompts(this.#serverName);
359
- return prompts.some((p) => p.name === promptName);
360
- } catch {
361
- return false;
362
- }
363
- }
364
- /**
365
- * Generate a prompt by name with the given arguments.
366
- *
367
- * This method is useful for programmatic prompt generation when the prompt name
368
- * is in a variable. The prompt name must match exactly as returned by the server.
369
- *
370
- * @param promptName - The exact name of the prompt to generate
371
- * @param args - The arguments to pass to the prompt template
372
- * @returns The generated prompt response
373
- * @throws {ToolNotFoundError} If the prompt doesn't exist on the server
374
- *
375
- * @example
376
- * ```typescript
377
- * // Call with explicit method (useful for dynamic prompt names):
378
- * const promptName = 'create_pr';
379
- * await client.servers.github.generatePrompt(promptName, { title: 'Fix bug' });
380
- *
381
- * // Or with dynamic server name:
382
- * const serverName = 'github';
383
- * await client.servers[serverName].generatePrompt(promptName, { title: 'Fix bug' });
384
- * ```
385
- */
386
- async generatePrompt(promptName, args) {
387
- const prompts = await this.#getPrompts(this.#serverName);
388
- const prompt = prompts.find((p) => p.name === promptName);
389
- if (!prompt) {
390
- throw new ToolNotFoundError(
391
- `Prompt '${promptName}' not found on server '${this.#serverName}'. Use client.servers.${this.#serverName}.getPrompts() to see available prompts.`,
392
- this.#serverName,
393
- promptName
394
- );
395
- }
396
- return this.#generatePrompt(this.#serverName, promptName, args);
397
- }
398
- /**
399
- * Get all resources available on this server.
400
- *
401
- * Note: This method is marked `async` for consistency with other server methods,
402
- * even though it doesn't directly await. This maintains a uniform async interface
403
- * and allows for future enhancements without breaking the API contract.
404
- *
405
- * @returns Array of resource schemas with original names
406
- * @throws {ServerNotFoundError} If the server doesn't exist
407
- * @throws {ServerUnhealthyError} If the server is unhealthy
408
- *
409
- * @example
410
- * ```typescript
411
- * const resources = await client.servers.github.getResources();
412
- * for (const resource of resources) {
413
- * console.log(`${resource.name}: ${resource.uri}`);
414
- * }
415
- * ```
416
- */
417
- async getResources() {
418
- return this.#getResources(this.#serverName);
419
- }
420
- /**
421
- * Get all resource templates available on this server.
422
- *
423
- * Note: This method is marked `async` for consistency with other server methods,
424
- * even though it doesn't directly await. This maintains a uniform async interface
425
- * and allows for future enhancements without breaking the API contract.
426
- *
427
- * @returns Array of resource template schemas with original names
428
- * @throws {ServerNotFoundError} If the server doesn't exist
429
- * @throws {ServerUnhealthyError} If the server is unhealthy
430
- *
431
- * @example
432
- * ```typescript
433
- * const templates = await client.servers.github.getResourceTemplates();
434
- * for (const template of templates) {
435
- * console.log(`${template.name}: ${template.uriTemplate}`);
436
- * }
437
- * ```
438
- */
439
- async getResourceTemplates() {
440
- return this.#getResourceTemplates(this.#serverName);
441
- }
442
- /**
443
- * Check if a resource exists on this server.
444
- *
445
- * The resource URI must match exactly as returned by the server.
446
- *
447
- * This method is designed as a safe boolean predicate - it catches all errors
448
- * (ServerNotFoundError, ServerUnhealthyError, ConnectionError, etc.) and returns
449
- * false rather than throwing. This makes it safe to use in conditional checks
450
- * without requiring error handling.
451
- *
452
- * @param uri - The exact URI of the resource to check
453
- * @returns True if the resource exists, false otherwise (including on errors)
454
- *
455
- * @example
456
- * ```typescript
457
- * if (await client.servers.github.hasResource('file:///repo/README.md')) {
458
- * const content = await client.servers.github.readResource('file:///repo/README.md');
459
- * }
460
- * ```
461
- */
462
- async hasResource(uri) {
463
- try {
464
- const resources = await this.#getResources(this.#serverName);
465
- return resources.some((r) => r.uri === uri);
466
- } catch {
467
- return false;
468
- }
469
- }
470
- /**
471
- * Read resource content by URI from this server.
472
- *
473
- * @param uri - The resource URI
474
- * @returns Array of resource contents (text or blob)
475
- * @throws {ServerNotFoundError} If the server doesn't exist
476
- * @throws {ServerUnhealthyError} If the server is unhealthy
477
- *
478
- * @example
479
- * ```typescript
480
- * const contents = await client.servers.github.readResource('file:///repo/README.md');
481
- * for (const content of contents) {
482
- * if (content.text) {
483
- * console.log(content.text);
484
- * } else if (content.blob) {
485
- * console.log('Binary content:', content.blob.substring(0, 50) + '...');
486
- * }
487
- * }
488
- * ```
489
- */
490
- async readResource(uri) {
491
- return this.#readResource(this.#serverName, uri);
492
- }
493
- }
494
- class ToolsNamespace {
495
- #performCall;
496
- #getTools;
497
- #serverName;
498
- /**
499
- * Initialize a ToolsNamespace for a specific server.
500
- *
501
- * @param performCall - Function to execute tool calls
502
- * @param getTools - Function to get tool schemas
503
- * @param serverName - The name of the MCP server
504
- */
505
- constructor(performCall, getTools, serverName) {
506
- this.#performCall = performCall;
507
- this.#getTools = getTools;
508
- this.#serverName = serverName;
509
- return new Proxy(this, {
510
- get: (target, prop) => {
511
- if (typeof prop !== "string") {
512
- return void 0;
513
- }
514
- return async (args) => {
515
- const toolName = prop;
516
- const tools = await target.#getTools(target.#serverName);
517
- const tool = tools.find((t) => t.name === toolName);
518
- if (!tool) {
519
- throw new ToolNotFoundError(
520
- `Tool '${toolName}' not found on server '${target.#serverName}'. Use client.servers.${target.#serverName}.getTools() to see available tools.`,
521
- target.#serverName,
522
- toolName
523
- );
524
- }
525
- return target.#performCall(target.#serverName, toolName, args);
526
- };
527
- }
528
- });
529
- }
530
- }
531
- class PromptsNamespace {
532
- #generatePrompt;
533
- #getPrompts;
534
- #serverName;
535
- /**
536
- * Initialize a PromptsNamespace for a specific server.
537
- *
538
- * @param generatePrompt - Function to generate prompts
539
- * @param getPrompts - Function to get prompt schemas
540
- * @param serverName - The name of the MCP server
541
- */
542
- constructor(generatePrompt, getPrompts, serverName) {
543
- this.#generatePrompt = generatePrompt;
544
- this.#getPrompts = getPrompts;
545
- this.#serverName = serverName;
546
- return new Proxy(this, {
547
- get: (target, prop) => {
548
- if (typeof prop !== "string") {
549
- return void 0;
550
- }
551
- return async (args) => {
552
- const promptName = prop;
553
- const prompt = await target.#getPromptByName(promptName);
554
- if (!prompt) {
555
- throw new ToolNotFoundError(
556
- `Prompt '${promptName}' not found on server '${target.#serverName}'. Use client.servers.${target.#serverName}.getPrompts() to see available prompts.`,
557
- target.#serverName,
558
- promptName
559
- );
560
- }
561
- return target.#generatePrompt(target.#serverName, promptName, args);
562
- };
563
- }
564
- });
565
- }
566
- /**
567
- * Helper method to find a prompt by name on this server.
568
- *
569
- * @param promptName - The exact name of the prompt to find
570
- * @returns The prompt if found, undefined otherwise
571
- * @internal
572
- */
573
- async #getPromptByName(promptName) {
574
- const prompts = await this.#getPrompts(this.#serverName);
575
- return prompts.find((p) => p.name === promptName);
576
- }
577
- }
578
- class TypeConverter {
579
- /**
580
- * Convert JSON schema types to JavaScript type names.
581
- *
582
- * Maps JSON Schema types to their JavaScript equivalents:
583
- * - "string" → "string"
584
- * - "integer" → "number"
585
- * - "number" → "number"
586
- * - "boolean" → "boolean"
587
- * - "array" → "array"
588
- * - "object" → "object"
589
- * - "null" → "null"
590
- * - unknown types → "any"
591
- */
592
- static jsonTypeToJavaScriptType(jsonType, schemaDef) {
593
- switch (jsonType) {
594
- case "string":
595
- if (schemaDef.enum && Array.isArray(schemaDef.enum)) {
596
- return "string";
597
- }
598
- return "string";
599
- case "number":
600
- case "integer":
601
- return "number";
602
- case "boolean":
603
- return "boolean";
604
- case "array":
605
- return "array";
606
- case "object":
607
- return "object";
608
- case "null":
609
- return "null";
610
- default:
611
- return "any";
612
- }
613
- }
614
- /**
615
- * Parse a schema definition and return the appropriate JavaScript type name.
616
- */
617
- static parseSchemaType(schemaDef) {
618
- if (schemaDef.anyOf && Array.isArray(schemaDef.anyOf)) {
619
- const firstType = schemaDef.anyOf[0];
620
- if (firstType && typeof firstType === "object" && firstType.type) {
621
- return this.jsonTypeToJavaScriptType(firstType.type, firstType);
622
- }
623
- return "any";
624
- }
625
- if (schemaDef.type) {
626
- return this.jsonTypeToJavaScriptType(schemaDef.type, schemaDef);
627
- }
628
- return "any";
629
- }
630
- /**
631
- * Get a human-readable description of the expected type.
632
- */
633
- static getTypeDescription(schemaDef) {
634
- const baseType = this.parseSchemaType(schemaDef);
635
- if (schemaDef.enum && Array.isArray(schemaDef.enum)) {
636
- return `one of: ${schemaDef.enum.map((v) => JSON.stringify(v)).join(", ")}`;
637
- }
638
- if (schemaDef.type === "array" && schemaDef.items) {
639
- const itemType = this.parseSchemaType(schemaDef.items);
640
- return `array of ${itemType}`;
641
- }
642
- return baseType;
643
- }
644
- /**
645
- * Validate a value against a JSON schema type.
646
- * Returns true if valid, false otherwise.
647
- */
648
- static validateValue(value, schemaDef) {
649
- if (value === null || value === void 0) {
650
- return schemaDef.type === "null" || (schemaDef.anyOf?.some((s) => s.type === "null") ?? false);
651
- }
652
- if (schemaDef.enum && Array.isArray(schemaDef.enum)) {
653
- return schemaDef.enum.includes(value);
654
- }
655
- if (schemaDef.anyOf && Array.isArray(schemaDef.anyOf)) {
656
- return schemaDef.anyOf.some(
657
- (subSchema) => this.validateValue(value, subSchema)
658
- );
659
- }
660
- if (!schemaDef.type) {
661
- return true;
662
- }
663
- switch (schemaDef.type) {
664
- case "string":
665
- return typeof value === "string";
666
- case "number":
667
- return typeof value === "number" && isFinite(value);
668
- case "integer":
669
- return typeof value === "number" && Number.isInteger(value);
670
- case "boolean":
671
- return typeof value === "boolean";
672
- case "array":
673
- if (!Array.isArray(value)) return false;
674
- if (schemaDef.items) {
675
- return value.every(
676
- (item) => this.validateValue(item, schemaDef.items)
677
- );
678
- }
679
- return true;
680
- case "object":
681
- return typeof value === "object" && value !== null && !Array.isArray(value);
682
- case "null":
683
- return value === null;
684
- default:
685
- return true;
686
- }
687
- }
688
- }
689
- class FunctionBuilder {
690
- #performCall;
691
- #functionCache = /* @__PURE__ */ new Map();
692
- /**
693
- * Initialize a FunctionBuilder with an injected function.
694
- *
695
- * @param performCall - Function to execute tool calls
696
- */
697
- constructor(performCall) {
698
- this.#performCall = performCall;
699
- }
700
- /**
701
- * Convert JSON schema to Zod schema.
702
- */
703
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
704
- #jsonSchemaToZod(jsonSchema) {
705
- if (!jsonSchema || typeof jsonSchema !== "object") {
706
- return zod.z.object({});
707
- }
708
- switch (jsonSchema.type) {
709
- case "string":
710
- return zod.z.string();
711
- case "number":
712
- return zod.z.number();
713
- case "integer":
714
- return zod.z.number().int();
715
- case "boolean":
716
- return zod.z.boolean();
717
- case "array":
718
- return zod.z.array(
719
- jsonSchema.items ? this.#jsonSchemaToZod(jsonSchema.items) : zod.z.any()
720
- );
721
- case "object":
722
- if (jsonSchema.properties) {
723
- const propertyKeys = Object.keys(jsonSchema.properties);
724
- if (propertyKeys.length > 0) {
725
- const shape = {};
726
- const required = new Set(jsonSchema.required || []);
727
- for (const [key, value] of Object.entries(jsonSchema.properties)) {
728
- let schema = this.#jsonSchemaToZod(value);
729
- if (!required.has(key)) {
730
- schema = schema.nullable().optional();
731
- }
732
- shape[key] = schema;
733
- }
734
- return zod.z.object(shape);
735
- }
736
- }
737
- return zod.z.object({});
738
- default:
739
- if (jsonSchema.properties) {
740
- const propertyKeys = Object.keys(jsonSchema.properties);
741
- if (propertyKeys.length > 0) {
742
- const shape = {};
743
- const required = new Set(jsonSchema.required || []);
744
- for (const [key, value] of Object.entries(jsonSchema.properties)) {
745
- let schema = this.#jsonSchemaToZod(value);
746
- if (!required.has(key)) {
747
- schema = schema.nullable().optional();
748
- }
749
- shape[key] = schema;
750
- }
751
- return zod.z.object(shape);
752
- }
753
- }
754
- return zod.z.object({});
755
- }
756
- }
757
- /**
758
- * Convert a string into a safe JavaScript identifier.
759
- *
760
- * This method sanitizes arbitrary strings (like server names or tool names) to create
761
- * valid JavaScript identifiers that can be used as function names.
762
- * It replaces non-word characters and handles edge cases like leading digits.
763
- *
764
- * @param name - The string to convert into a safe identifier
765
- * @returns A string that is a valid JavaScript identifier
766
- */
767
- safeName(name) {
768
- return name.replace(/\W|^(?=\d)/g, "_");
769
- }
770
- /**
771
- * Generate a unique function name from server and tool names.
772
- *
773
- * This method creates a qualified function name by combining the server name
774
- * and tool name with a double underscore separator. Both names are sanitized
775
- * using safeName() to ensure the result is a valid JavaScript identifier.
776
- *
777
- * @param serverName - The name of the MCP server hosting the tool
778
- * @param schemaName - The name of the tool from the schema definition
779
- * @returns A qualified function name in the format "{safe_server}__{safe_tool}"
780
- */
781
- functionName(serverName, schemaName) {
782
- return `${this.safeName(serverName)}__${this.safeName(schemaName)}`;
783
- }
784
- /**
785
- * Create a callable JavaScript function from an MCP tool's JSON Schema definition.
786
- *
787
- * This method generates a self-contained, callable function that validates parameters
788
- * and executes the corresponding MCP tool. The function includes proper parameter
789
- * validation and comprehensive metadata based on the tool's JSON Schema.
790
- *
791
- * Generated functions are cached for performance. If a function for the same
792
- * server/tool combination already exists in the cache, it returns the cached function.
793
- *
794
- * @param schema - The MCP tool's JSON Schema definition
795
- * @param serverName - The name of the MCP server hosting this tool
796
- * @returns A callable JavaScript function with metadata
797
- */
798
- createFunctionFromSchema(schema, serverName) {
799
- const cacheKey = `${serverName}__${schema.name}`;
800
- if (this.#functionCache.has(cacheKey)) {
801
- return this.#functionCache.get(cacheKey);
802
- }
803
- try {
804
- const generatedFunction = this.buildFunction(schema, serverName);
805
- this.#functionCache.set(cacheKey, generatedFunction);
806
- return generatedFunction;
807
- } catch (error) {
808
- throw new McpdError(
809
- `Error creating function ${cacheKey}: ${error.message}`,
810
- error
811
- );
812
- }
813
- }
814
- /**
815
- * Build the actual function from the schema.
816
- *
817
- * @param schema - The tool schema
818
- * @param serverName - The server name
819
- * @returns The generated function with metadata
820
- */
821
- buildFunction(schema, serverName) {
822
- const inputSchema = schema.inputSchema || {};
823
- const properties = inputSchema.properties || {};
824
- const required = new Set(inputSchema.required || []);
825
- const implementation = async (...args) => {
826
- let params = {};
827
- if (args.length === 1 && typeof args[0] === "object" && args[0] !== null && !Array.isArray(args[0])) {
828
- params = args[0];
829
- } else {
830
- const propertyNames = Object.keys(properties);
831
- for (let i = 0; i < args.length && i < propertyNames.length; i++) {
832
- const propertyName = propertyNames[i];
833
- if (propertyName) {
834
- params[propertyName] = args[i];
835
- }
836
- }
837
- }
838
- const missingParams = [];
839
- for (const paramName of required) {
840
- if (!(paramName in params) || params[paramName] === null || params[paramName] === void 0) {
841
- missingParams.push(paramName);
842
- }
843
- }
844
- if (missingParams.length > 0) {
845
- throw new ValidationError(
846
- `Missing required parameters: ${missingParams.join(", ")}`,
847
- missingParams
848
- );
849
- }
850
- const validationErrors = [];
851
- for (const [paramName, paramValue] of Object.entries(params)) {
852
- const paramSchema = properties[paramName];
853
- if (paramValue !== null && paramValue !== void 0 && paramSchema) {
854
- if (!TypeConverter.validateValue(paramValue, paramSchema)) {
855
- const expectedType = TypeConverter.getTypeDescription(paramSchema);
856
- validationErrors.push(
857
- `Parameter '${paramName}' should be ${expectedType}, got ${typeof paramValue}`
858
- );
859
- }
860
- }
861
- }
862
- if (validationErrors.length > 0) {
863
- throw new ValidationError(
864
- `Parameter validation failed: ${validationErrors.join("; ")}`,
865
- validationErrors
866
- );
867
- }
868
- const cleanParams = {};
869
- for (const [key, value] of Object.entries(params)) {
870
- if (value !== null && value !== void 0) {
871
- cleanParams[key] = value;
872
- }
873
- }
874
- return this.#performCall(serverName, schema.name, cleanParams);
875
- };
876
- const invoke = async (args) => {
877
- return implementation(args);
878
- };
879
- const execute = async (args) => {
880
- return implementation(args);
881
- };
882
- const zodSchema = this.#jsonSchemaToZod(inputSchema);
883
- const qualifiedName = this.functionName(serverName, schema.name);
884
- const docstring = this.createDocstring(schema);
885
- const agentFunction = implementation;
886
- Object.defineProperty(agentFunction, "name", {
887
- value: qualifiedName,
888
- writable: false,
889
- enumerable: true,
890
- configurable: true
891
- });
892
- agentFunction.description = schema.description || docstring.split("\n")[0] || "No description provided";
893
- agentFunction.schema = zodSchema;
894
- agentFunction.invoke = invoke;
895
- agentFunction.lc_namespace = ["mcpd", "tools"];
896
- agentFunction.returnDirect = false;
897
- agentFunction.inputSchema = zodSchema;
898
- agentFunction.execute = execute;
899
- agentFunction._schema = schema;
900
- agentFunction._serverName = serverName;
901
- agentFunction._toolName = schema.name;
902
- return agentFunction;
903
- }
904
- /**
905
- * Generate a comprehensive docstring for the dynamically created function.
906
- *
907
- * This method builds a properly formatted docstring that includes the
908
- * tool's description, parameter documentation with optional/required status,
909
- * return value information, and exception documentation.
910
- *
911
- * @param schema - The MCP tool's JSON Schema definition
912
- * @returns A multi-line string containing the complete docstring text
913
- */
914
- createDocstring(schema) {
915
- const description = schema.description || "No description provided";
916
- const inputSchema = schema.inputSchema || {};
917
- const properties = inputSchema.properties || {};
918
- const required = new Set(inputSchema.required || []);
919
- const docstringParts = [description];
920
- if (Object.keys(properties).length > 0) {
921
- docstringParts.push("");
922
- docstringParts.push("Parameters:");
923
- for (const [paramName, paramInfo] of Object.entries(properties)) {
924
- const isRequired = required.has(paramName);
925
- const paramDesc = paramInfo.description || "No description provided";
926
- const paramType = TypeConverter.getTypeDescription(paramInfo);
927
- const requiredText = isRequired ? "" : " (optional)";
928
- docstringParts.push(
929
- ` ${paramName} (${paramType}): ${paramDesc}${requiredText}`
930
- );
931
- }
932
- }
933
- docstringParts.push("");
934
- docstringParts.push("Returns:");
935
- docstringParts.push(" Promise<any>: Function execution result");
936
- docstringParts.push("");
937
- docstringParts.push("Throws:");
938
- docstringParts.push(
939
- " ValidationError: If required parameters are missing or invalid"
940
- );
941
- docstringParts.push(" McpdError: If the API call fails");
942
- return docstringParts.join("\n");
943
- }
944
- /**
945
- * Clear the function cache.
946
- *
947
- * This method clears all cached generated functions, forcing them to be
948
- * regenerated on the next call to createFunctionFromSchema().
949
- */
950
- clearCache() {
951
- this.#functionCache.clear();
952
- }
953
- /**
954
- * Get the current cache size.
955
- *
956
- * @returns The number of functions currently cached
957
- */
958
- getCacheSize() {
959
- return this.#functionCache.size;
960
- }
961
- /**
962
- * Get all cached functions.
963
- *
964
- * @returns Array of all cached agent functions, or empty array if cache is empty
965
- */
966
- getCachedFunctions() {
967
- return Array.from(this.#functionCache.values());
968
- }
276
+ return new lru_cache.LRUCache({
277
+ max: options.max ?? 100,
278
+ ttl: options.ttl ?? 1e4,
279
+ updateAgeOnGet: false,
280
+ updateAgeOnHas: false
281
+ });
969
282
  }
970
- const API_BASE = "/api/v1";
971
- const SERVERS_BASE = `${API_BASE}/servers`;
972
- const HEALTH_SERVERS_BASE = `${API_BASE}/health/servers`;
973
- const API_PATHS = {
974
- // Server management
975
- SERVERS: SERVERS_BASE,
976
- // Tools
977
- SERVER_TOOLS: (serverName) => `${SERVERS_BASE}/${encodeURIComponent(serverName)}/tools`,
978
- TOOL_CALL: (serverName, toolName) => `${SERVERS_BASE}/${encodeURIComponent(serverName)}/tools/${encodeURIComponent(toolName)}`,
979
- // Prompts
980
- SERVER_PROMPTS: (serverName, cursor) => {
981
- const base = `${SERVERS_BASE}/${encodeURIComponent(serverName)}/prompts`;
982
- return cursor ? `${base}?cursor=${encodeURIComponent(cursor)}` : base;
983
- },
984
- PROMPT_GET_GENERATED: (serverName, promptName) => `${SERVERS_BASE}/${encodeURIComponent(serverName)}/prompts/${encodeURIComponent(promptName)}`,
985
- // Resources
986
- SERVER_RESOURCES: (serverName, cursor) => {
987
- const base = `${SERVERS_BASE}/${encodeURIComponent(serverName)}/resources`;
988
- return cursor ? `${base}?cursor=${encodeURIComponent(cursor)}` : base;
989
- },
990
- SERVER_RESOURCE_TEMPLATES: (serverName, cursor) => {
991
- const base = `${SERVERS_BASE}/${encodeURIComponent(serverName)}/resources/templates`;
992
- return cursor ? `${base}?cursor=${encodeURIComponent(cursor)}` : base;
993
- },
994
- RESOURCE_CONTENT: (serverName, uri) => `${SERVERS_BASE}/${encodeURIComponent(serverName)}/resources/content?uri=${encodeURIComponent(uri)}`,
995
- // Health
996
- HEALTH_ALL: HEALTH_SERVERS_BASE,
997
- HEALTH_SERVER: (serverName) => `${HEALTH_SERVERS_BASE}/${encodeURIComponent(serverName)}`
283
+ //#endregion
284
+ //#region src/dynamicCaller.ts
285
+ /**
286
+ * Dynamic tool invocation for mcpd client.
287
+ *
288
+ * This module provides the ServersNamespace, Server, and ToolsNamespace classes
289
+ * that enable natural JavaScript syntax for calling MCP tools, such as:
290
+ * client.servers.time.tools.get_current_time(args)
291
+ *
292
+ * The dynamic calling system uses JavaScript's Proxy to create
293
+ * a fluent interface that resolves server and tool names at runtime.
294
+ *
295
+ * Naming convention:
296
+ * - *Namespace classes use Proxy for dynamic property access
297
+ * - Server is a concrete class representing one MCP server
298
+ */
299
+ /**
300
+ * Namespace for accessing MCP servers via proxy.
301
+ *
302
+ * This class provides the `client.servers.*` namespace, allowing you to access
303
+ * servers and their tools with natural JavaScript syntax.
304
+ *
305
+ * @example
306
+ * ```typescript
307
+ * const client = new McpdClient({ apiEndpoint: 'http://localhost:8090' });
308
+ *
309
+ * // Access tools through the servers namespace
310
+ * const result = await client.servers.time.tools.get_current_time({ timezone: "UTC" });
311
+ *
312
+ * // Check if a tool exists
313
+ * if (await client.servers.time.hasTool("get_current_time")) {
314
+ * // ...
315
+ * }
316
+ * ```
317
+ */
318
+ var ServersNamespace = class {
319
+ #performCall;
320
+ #getTools;
321
+ #generatePrompt;
322
+ #getPrompts;
323
+ #getResources;
324
+ #getResourceTemplates;
325
+ #readResource;
326
+ /**
327
+ * Initialize the ServersNamespace with injected functions.
328
+ *
329
+ * @param options - Configuration object
330
+ * @param options.performCall - Function to execute tool calls
331
+ * @param options.getTools - Function to get tool schemas
332
+ * @param options.generatePrompt - Function to generate prompts
333
+ * @param options.getPrompts - Function to get prompt schemas
334
+ * @param options.getResources - Function to get resources
335
+ * @param options.getResourceTemplates - Function to get resource templates
336
+ * @param options.readResource - Function to read resource content
337
+ */
338
+ constructor({ performCall, getTools, generatePrompt, getPrompts, getResources, getResourceTemplates, readResource }) {
339
+ this.#performCall = performCall;
340
+ this.#getTools = getTools;
341
+ this.#generatePrompt = generatePrompt;
342
+ this.#getPrompts = getPrompts;
343
+ this.#getResources = getResources;
344
+ this.#getResourceTemplates = getResourceTemplates;
345
+ this.#readResource = readResource;
346
+ return new Proxy(this, { get: (target, serverName) => {
347
+ if (typeof serverName !== "string") return;
348
+ return new Server(target.#performCall, target.#getTools, target.#generatePrompt, target.#getPrompts, target.#getResources, target.#getResourceTemplates, target.#readResource, serverName);
349
+ } });
350
+ }
998
351
  };
999
- const LogLevels = {
1000
- OFF: "off"
352
+ /**
353
+ * Represents a specific MCP server, providing access to its tools and operations.
354
+ *
355
+ * This class represents a specific MCP server and provides access to its tools
356
+ * through the `.tools` namespace, as well as server-level operations like listing tools.
357
+ *
358
+ * @example
359
+ * ```typescript
360
+ * // Server is created when you access a server:
361
+ * const timeServer = client.servers.time; // Returns Server(...)
362
+ *
363
+ * // List available tools
364
+ * const tools = await timeServer.getTools();
365
+ *
366
+ * // Call tools through the .tools namespace:
367
+ * await timeServer.tools.get_current_time({ timezone: "UTC" })
368
+ * ```
369
+ */
370
+ var Server = class {
371
+ tools;
372
+ prompts;
373
+ #performCall;
374
+ #getTools;
375
+ #generatePrompt;
376
+ #getPrompts;
377
+ #getResources;
378
+ #getResourceTemplates;
379
+ #readResource;
380
+ #serverName;
381
+ /**
382
+ * Initialize a Server for a specific server.
383
+ *
384
+ * @param performCall - Function to execute tool calls
385
+ * @param getTools - Function to get tool schemas
386
+ * @param generatePrompt - Function to generate prompts
387
+ * @param getPrompts - Function to get prompt schemas
388
+ * @param getResources - Function to get resources
389
+ * @param getResourceTemplates - Function to get resource templates
390
+ * @param readResource - Function to read resource content
391
+ * @param serverName - The name of the MCP server
392
+ */
393
+ constructor(performCall, getTools, generatePrompt, getPrompts, getResources, getResourceTemplates, readResource, serverName) {
394
+ this.#performCall = performCall;
395
+ this.#getTools = getTools;
396
+ this.#generatePrompt = generatePrompt;
397
+ this.#getPrompts = getPrompts;
398
+ this.#getResources = getResources;
399
+ this.#getResourceTemplates = getResourceTemplates;
400
+ this.#readResource = readResource;
401
+ this.#serverName = serverName;
402
+ this.tools = new ToolsNamespace(this.#performCall, this.#getTools, this.#serverName);
403
+ this.prompts = new PromptsNamespace(this.#generatePrompt, this.#getPrompts, this.#serverName);
404
+ }
405
+ /**
406
+ * Get all tools available on this server.
407
+ *
408
+ * @returns Array of tool schemas
409
+ * @throws {ServerNotFoundError} If the server doesn't exist
410
+ * @throws {ServerUnhealthyError} If the server is unhealthy
411
+ *
412
+ * @example
413
+ * ```typescript
414
+ * const tools = await client.servers.time.getTools();
415
+ * for (const tool of tools) {
416
+ * console.log(`${tool.name}: ${tool.description}`);
417
+ * }
418
+ * ```
419
+ */
420
+ async getTools() {
421
+ return this.#getTools(this.#serverName);
422
+ }
423
+ /**
424
+ * Check if a tool exists on this server.
425
+ *
426
+ * The tool name must match exactly as returned by the server.
427
+ *
428
+ * This method is designed as a safe boolean predicate - it catches all errors
429
+ * (ServerNotFoundError, ServerUnhealthyError, ConnectionError, etc.) and returns
430
+ * false rather than throwing. This makes it safe to use in conditional checks
431
+ * without requiring error handling.
432
+ *
433
+ * @param toolName - The exact name of the tool to check
434
+ * @returns True if the tool exists, false otherwise (including on errors)
435
+ *
436
+ * @example
437
+ * ```typescript
438
+ * if (await client.servers.time.hasTool('get_current_time')) {
439
+ * const result = await client.servers.time.callTool('get_current_time', { timezone: 'UTC' });
440
+ * }
441
+ * ```
442
+ */
443
+ async hasTool(toolName) {
444
+ try {
445
+ return (await this.#getTools(this.#serverName)).some((t) => t.name === toolName);
446
+ } catch {
447
+ return false;
448
+ }
449
+ }
450
+ /**
451
+ * Call a tool by name with the given arguments.
452
+ *
453
+ * This method is useful for programmatic tool invocation when the tool name
454
+ * is in a variable. The tool name must match exactly as returned by the server.
455
+ *
456
+ * @param toolName - The exact name of the tool to call
457
+ * @param args - The arguments to pass to the tool
458
+ * @returns The tool's response
459
+ * @throws {ToolNotFoundError} If the tool doesn't exist on the server
460
+ *
461
+ * @example
462
+ * ```typescript
463
+ * // Call with explicit method (useful for dynamic tool names):
464
+ * const toolName = 'get_current_time';
465
+ * await client.servers.time.callTool(toolName, { timezone: 'UTC' });
466
+ *
467
+ * // Or with dynamic server name:
468
+ * const serverName = 'time';
469
+ * await client.servers[serverName].callTool(toolName, { timezone: 'UTC' });
470
+ * ```
471
+ */
472
+ async callTool(toolName, args) {
473
+ if (!(await this.#getTools(this.#serverName)).find((t) => t.name === toolName)) throw new ToolNotFoundError(`Tool '${toolName}' not found on server '${this.#serverName}'. Use client.servers.${this.#serverName}.getTools() to see available tools.`, this.#serverName, toolName);
474
+ return this.#performCall(this.#serverName, toolName, args);
475
+ }
476
+ /**
477
+ * Get all prompts available on this server.
478
+ *
479
+ * Note: This method is marked `async` for consistency with other server methods,
480
+ * even though it doesn't directly await. This maintains a uniform async interface
481
+ * and allows for future enhancements without breaking the API contract.
482
+ *
483
+ * @returns Array of prompt schemas
484
+ * @throws {ServerNotFoundError} If the server doesn't exist
485
+ * @throws {ServerUnhealthyError} If the server is unhealthy
486
+ *
487
+ * @example
488
+ * ```typescript
489
+ * const prompts = await client.servers.github.getPrompts();
490
+ * for (const prompt of prompts) {
491
+ * console.log(`${prompt.name}: ${prompt.description}`);
492
+ * }
493
+ * ```
494
+ */
495
+ async getPrompts() {
496
+ return this.#getPrompts(this.#serverName);
497
+ }
498
+ /**
499
+ * Check if a prompt exists on this server.
500
+ *
501
+ * The prompt name must match exactly as returned by the server.
502
+ *
503
+ * This method is designed as a safe boolean predicate - it catches all errors
504
+ * (ServerNotFoundError, ServerUnhealthyError, ConnectionError, etc.) and returns
505
+ * false rather than throwing. This makes it safe to use in conditional checks
506
+ * without requiring error handling.
507
+ *
508
+ * @param promptName - The exact name of the prompt to check
509
+ * @returns True if the prompt exists, false otherwise (including on errors)
510
+ *
511
+ * @example
512
+ * ```typescript
513
+ * if (await client.servers.github.hasPrompt('create_pr')) {
514
+ * const result = await client.servers.github.generatePrompt('create_pr', { title: 'Fix bug' });
515
+ * }
516
+ * ```
517
+ */
518
+ async hasPrompt(promptName) {
519
+ try {
520
+ return (await this.#getPrompts(this.#serverName)).some((p) => p.name === promptName);
521
+ } catch {
522
+ return false;
523
+ }
524
+ }
525
+ /**
526
+ * Generate a prompt by name with the given arguments.
527
+ *
528
+ * This method is useful for programmatic prompt generation when the prompt name
529
+ * is in a variable. The prompt name must match exactly as returned by the server.
530
+ *
531
+ * @param promptName - The exact name of the prompt to generate
532
+ * @param args - The arguments to pass to the prompt template
533
+ * @returns The generated prompt response
534
+ * @throws {ToolNotFoundError} If the prompt doesn't exist on the server
535
+ *
536
+ * @example
537
+ * ```typescript
538
+ * // Call with explicit method (useful for dynamic prompt names):
539
+ * const promptName = 'create_pr';
540
+ * await client.servers.github.generatePrompt(promptName, { title: 'Fix bug' });
541
+ *
542
+ * // Or with dynamic server name:
543
+ * const serverName = 'github';
544
+ * await client.servers[serverName].generatePrompt(promptName, { title: 'Fix bug' });
545
+ * ```
546
+ */
547
+ async generatePrompt(promptName, args) {
548
+ if (!(await this.#getPrompts(this.#serverName)).find((p) => p.name === promptName)) throw new ToolNotFoundError(`Prompt '${promptName}' not found on server '${this.#serverName}'. Use client.servers.${this.#serverName}.getPrompts() to see available prompts.`, this.#serverName, promptName);
549
+ return this.#generatePrompt(this.#serverName, promptName, args);
550
+ }
551
+ /**
552
+ * Get all resources available on this server.
553
+ *
554
+ * Note: This method is marked `async` for consistency with other server methods,
555
+ * even though it doesn't directly await. This maintains a uniform async interface
556
+ * and allows for future enhancements without breaking the API contract.
557
+ *
558
+ * @returns Array of resource schemas with original names
559
+ * @throws {ServerNotFoundError} If the server doesn't exist
560
+ * @throws {ServerUnhealthyError} If the server is unhealthy
561
+ *
562
+ * @example
563
+ * ```typescript
564
+ * const resources = await client.servers.github.getResources();
565
+ * for (const resource of resources) {
566
+ * console.log(`${resource.name}: ${resource.uri}`);
567
+ * }
568
+ * ```
569
+ */
570
+ async getResources() {
571
+ return this.#getResources(this.#serverName);
572
+ }
573
+ /**
574
+ * Get all resource templates available on this server.
575
+ *
576
+ * Note: This method is marked `async` for consistency with other server methods,
577
+ * even though it doesn't directly await. This maintains a uniform async interface
578
+ * and allows for future enhancements without breaking the API contract.
579
+ *
580
+ * @returns Array of resource template schemas with original names
581
+ * @throws {ServerNotFoundError} If the server doesn't exist
582
+ * @throws {ServerUnhealthyError} If the server is unhealthy
583
+ *
584
+ * @example
585
+ * ```typescript
586
+ * const templates = await client.servers.github.getResourceTemplates();
587
+ * for (const template of templates) {
588
+ * console.log(`${template.name}: ${template.uriTemplate}`);
589
+ * }
590
+ * ```
591
+ */
592
+ async getResourceTemplates() {
593
+ return this.#getResourceTemplates(this.#serverName);
594
+ }
595
+ /**
596
+ * Check if a resource exists on this server.
597
+ *
598
+ * The resource URI must match exactly as returned by the server.
599
+ *
600
+ * This method is designed as a safe boolean predicate - it catches all errors
601
+ * (ServerNotFoundError, ServerUnhealthyError, ConnectionError, etc.) and returns
602
+ * false rather than throwing. This makes it safe to use in conditional checks
603
+ * without requiring error handling.
604
+ *
605
+ * @param uri - The exact URI of the resource to check
606
+ * @returns True if the resource exists, false otherwise (including on errors)
607
+ *
608
+ * @example
609
+ * ```typescript
610
+ * if (await client.servers.github.hasResource('file:///repo/README.md')) {
611
+ * const content = await client.servers.github.readResource('file:///repo/README.md');
612
+ * }
613
+ * ```
614
+ */
615
+ async hasResource(uri) {
616
+ try {
617
+ return (await this.#getResources(this.#serverName)).some((r) => r.uri === uri);
618
+ } catch {
619
+ return false;
620
+ }
621
+ }
622
+ /**
623
+ * Read resource content by URI from this server.
624
+ *
625
+ * @param uri - The resource URI
626
+ * @returns Array of resource contents (text or blob)
627
+ * @throws {ServerNotFoundError} If the server doesn't exist
628
+ * @throws {ServerUnhealthyError} If the server is unhealthy
629
+ *
630
+ * @example
631
+ * ```typescript
632
+ * const contents = await client.servers.github.readResource('file:///repo/README.md');
633
+ * for (const content of contents) {
634
+ * if (content.text) {
635
+ * console.log(content.text);
636
+ * } else if (content.blob) {
637
+ * console.log('Binary content:', content.blob.substring(0, 50) + '...');
638
+ * }
639
+ * }
640
+ * ```
641
+ */
642
+ async readResource(uri) {
643
+ return this.#readResource(this.#serverName, uri);
644
+ }
1001
645
  };
1002
- const ranks = {
1003
- trace: 5,
1004
- debug: 10,
1005
- info: 20,
1006
- warn: 30,
1007
- error: 40,
1008
- off: 1e3
646
+ /**
647
+ * Namespace for accessing tools on a specific MCP server via proxy.
648
+ *
649
+ * This class provides the `.tools` namespace for a server, allowing you to call
650
+ * tools as if they were methods. All tool names must match exactly as returned
651
+ * by the MCP server.
652
+ *
653
+ * NOTE: Use `client.servers.foo.callTool()` and `client.servers.foo.hasTool()`
654
+ * instead of putting them in the `.tools` namespace to avoid collisions with
655
+ * actual tools named "callTool" or "hasTool".
656
+ *
657
+ * @example
658
+ * ```typescript
659
+ * // Call tools via .tools namespace with static names
660
+ * const result = await client.servers.time.tools.get_current_time({ timezone: "UTC" });
661
+ * ```
662
+ */
663
+ var ToolsNamespace = class {
664
+ #performCall;
665
+ #getTools;
666
+ #serverName;
667
+ /**
668
+ * Initialize a ToolsNamespace for a specific server.
669
+ *
670
+ * @param performCall - Function to execute tool calls
671
+ * @param getTools - Function to get tool schemas
672
+ * @param serverName - The name of the MCP server
673
+ */
674
+ constructor(performCall, getTools, serverName) {
675
+ this.#performCall = performCall;
676
+ this.#getTools = getTools;
677
+ this.#serverName = serverName;
678
+ return new Proxy(this, { get: (target, prop) => {
679
+ if (typeof prop !== "string") return;
680
+ return async (args) => {
681
+ const toolName = prop;
682
+ if (!(await target.#getTools(target.#serverName)).find((t) => t.name === toolName)) throw new ToolNotFoundError(`Tool '${toolName}' not found on server '${target.#serverName}'. Use client.servers.${target.#serverName}.getTools() to see available tools.`, target.#serverName, toolName);
683
+ return target.#performCall(target.#serverName, toolName, args);
684
+ };
685
+ } });
686
+ }
687
+ };
688
+ /**
689
+ * Namespace for accessing prompts on a specific MCP server via proxy.
690
+ *
691
+ * This class provides the `.prompts` namespace for a server, allowing you to generate
692
+ * prompts as if they were methods. All prompt names must match exactly as returned
693
+ * by the MCP server.
694
+ *
695
+ * NOTE: Use `client.servers.foo.generatePrompt()` and `client.servers.foo.hasPrompt()`
696
+ * instead of putting them in the `.prompts` namespace to avoid collisions with
697
+ * actual prompts named "generatePrompt" or "hasPrompt".
698
+ *
699
+ * @example
700
+ * ```typescript
701
+ * // Generate prompts via .prompts namespace with static names
702
+ * const result = await client.servers.github.prompts.create_pr({
703
+ * title: "Fix bug",
704
+ * description: "Fixed auth issue"
705
+ * });
706
+ * ```
707
+ */
708
+ var PromptsNamespace = class {
709
+ #generatePrompt;
710
+ #getPrompts;
711
+ #serverName;
712
+ /**
713
+ * Initialize a PromptsNamespace for a specific server.
714
+ *
715
+ * @param generatePrompt - Function to generate prompts
716
+ * @param getPrompts - Function to get prompt schemas
717
+ * @param serverName - The name of the MCP server
718
+ */
719
+ constructor(generatePrompt, getPrompts, serverName) {
720
+ this.#generatePrompt = generatePrompt;
721
+ this.#getPrompts = getPrompts;
722
+ this.#serverName = serverName;
723
+ return new Proxy(this, { get: (target, prop) => {
724
+ if (typeof prop !== "string") return;
725
+ return async (args) => {
726
+ const promptName = prop;
727
+ if (!await target.#getPromptByName(promptName)) throw new ToolNotFoundError(`Prompt '${promptName}' not found on server '${target.#serverName}'. Use client.servers.${target.#serverName}.getPrompts() to see available prompts.`, target.#serverName, promptName);
728
+ return target.#generatePrompt(target.#serverName, promptName, args);
729
+ };
730
+ } });
731
+ }
732
+ /**
733
+ * Helper method to find a prompt by name on this server.
734
+ *
735
+ * @param promptName - The exact name of the prompt to find
736
+ * @returns The prompt if found, undefined otherwise
737
+ * @internal
738
+ */
739
+ async #getPromptByName(promptName) {
740
+ return (await this.#getPrompts(this.#serverName)).find((p) => p.name === promptName);
741
+ }
742
+ };
743
+ //#endregion
744
+ //#region src/utils/typeConverter.ts
745
+ /**
746
+ * Maps JSON Schema types to JavaScript/TypeScript types.
747
+ */
748
+ var TypeConverter = class {
749
+ /**
750
+ * Convert JSON schema types to JavaScript type names.
751
+ *
752
+ * Maps JSON Schema types to their JavaScript equivalents:
753
+ * - "string" → "string"
754
+ * - "integer" → "number"
755
+ * - "number" → "number"
756
+ * - "boolean" → "boolean"
757
+ * - "array" → "array"
758
+ * - "object" → "object"
759
+ * - "null" → "null"
760
+ * - unknown types → "any"
761
+ */
762
+ static jsonTypeToJavaScriptType(jsonType, schemaDef) {
763
+ switch (jsonType) {
764
+ case "string":
765
+ if (schemaDef.enum && Array.isArray(schemaDef.enum)) return "string";
766
+ return "string";
767
+ case "number":
768
+ case "integer": return "number";
769
+ case "boolean": return "boolean";
770
+ case "array": return "array";
771
+ case "object": return "object";
772
+ case "null": return "null";
773
+ default: return "any";
774
+ }
775
+ }
776
+ /**
777
+ * Parse a schema definition and return the appropriate JavaScript type name.
778
+ */
779
+ static parseSchemaType(schemaDef) {
780
+ if (schemaDef.anyOf && Array.isArray(schemaDef.anyOf)) {
781
+ const firstType = schemaDef.anyOf[0];
782
+ if (firstType && typeof firstType === "object" && firstType.type) return this.jsonTypeToJavaScriptType(firstType.type, firstType);
783
+ return "any";
784
+ }
785
+ if (schemaDef.type) return this.jsonTypeToJavaScriptType(schemaDef.type, schemaDef);
786
+ return "any";
787
+ }
788
+ /**
789
+ * Get a human-readable description of the expected type.
790
+ */
791
+ static getTypeDescription(schemaDef) {
792
+ const baseType = this.parseSchemaType(schemaDef);
793
+ if (schemaDef.enum && Array.isArray(schemaDef.enum)) return `one of: ${schemaDef.enum.map((v) => JSON.stringify(v)).join(", ")}`;
794
+ if (schemaDef.type === "array" && schemaDef.items) return `array of ${this.parseSchemaType(schemaDef.items)}`;
795
+ return baseType;
796
+ }
797
+ /**
798
+ * Validate a value against a JSON schema type.
799
+ * Returns true if valid, false otherwise.
800
+ */
801
+ static validateValue(value, schemaDef) {
802
+ if (value === null || value === void 0) return schemaDef.type === "null" || (schemaDef.anyOf?.some((s) => s.type === "null") ?? false);
803
+ if (schemaDef.enum && Array.isArray(schemaDef.enum)) return schemaDef.enum.includes(value);
804
+ if (schemaDef.anyOf && Array.isArray(schemaDef.anyOf)) return schemaDef.anyOf.some((subSchema) => this.validateValue(value, subSchema));
805
+ if (!schemaDef.type) return true;
806
+ switch (schemaDef.type) {
807
+ case "string": return typeof value === "string";
808
+ case "number": return typeof value === "number" && isFinite(value);
809
+ case "integer": return typeof value === "number" && Number.isInteger(value);
810
+ case "boolean": return typeof value === "boolean";
811
+ case "array":
812
+ if (!Array.isArray(value)) return false;
813
+ if (schemaDef.items) return value.every((item) => this.validateValue(item, schemaDef.items));
814
+ return true;
815
+ case "object": return typeof value === "object" && value !== null && !Array.isArray(value);
816
+ case "null": return value === null;
817
+ default: return true;
818
+ }
819
+ }
820
+ };
821
+ //#endregion
822
+ //#region src/functionBuilder.ts
823
+ /**
824
+ * Function generation from MCP tool schemas.
825
+ *
826
+ * This module provides the FunctionBuilder class that dynamically generates
827
+ * callable JavaScript functions from MCP tool JSON Schema definitions. These
828
+ * functions can be used with AI agent frameworks and include proper parameter
829
+ * validation and comprehensive metadata.
830
+ *
831
+ * The generated functions are self-contained and cached for performance.
832
+ */
833
+ /**
834
+ * Builds callable JavaScript functions from MCP tool JSON schemas.
835
+ *
836
+ * This class generates self-contained functions that can be used with AI agent
837
+ * frameworks. The generated functions include parameter validation and
838
+ * comprehensive metadata based on the tool's JSON Schema definition.
839
+ *
840
+ * The generated functions are cached for performance, with cache invalidation
841
+ * controlled by the owning McpdClient via clearCache().
842
+ */
843
+ var FunctionBuilder = class {
844
+ #performCall;
845
+ #functionCache = /* @__PURE__ */ new Map();
846
+ /**
847
+ * Initialize a FunctionBuilder with an injected function.
848
+ *
849
+ * @param performCall - Function to execute tool calls
850
+ */
851
+ constructor(performCall) {
852
+ this.#performCall = performCall;
853
+ }
854
+ /**
855
+ * Convert JSON schema to Zod schema.
856
+ */
857
+ #jsonSchemaToZod(jsonSchema) {
858
+ if (!jsonSchema || typeof jsonSchema !== "object") return zod.z.object({});
859
+ switch (jsonSchema.type) {
860
+ case "string": return zod.z.string();
861
+ case "number": return zod.z.number();
862
+ case "integer": return zod.z.number().int();
863
+ case "boolean": return zod.z.boolean();
864
+ case "array": return zod.z.array(jsonSchema.items ? this.#jsonSchemaToZod(jsonSchema.items) : zod.z.any());
865
+ case "object":
866
+ if (jsonSchema.properties) {
867
+ if (Object.keys(jsonSchema.properties).length > 0) {
868
+ const shape = {};
869
+ const required = new Set(jsonSchema.required || []);
870
+ for (const [key, value] of Object.entries(jsonSchema.properties)) {
871
+ let schema = this.#jsonSchemaToZod(value);
872
+ if (!required.has(key)) schema = schema.nullable().optional();
873
+ shape[key] = schema;
874
+ }
875
+ return zod.z.object(shape);
876
+ }
877
+ }
878
+ return zod.z.object({});
879
+ default:
880
+ if (jsonSchema.properties) {
881
+ if (Object.keys(jsonSchema.properties).length > 0) {
882
+ const shape = {};
883
+ const required = new Set(jsonSchema.required || []);
884
+ for (const [key, value] of Object.entries(jsonSchema.properties)) {
885
+ let schema = this.#jsonSchemaToZod(value);
886
+ if (!required.has(key)) schema = schema.nullable().optional();
887
+ shape[key] = schema;
888
+ }
889
+ return zod.z.object(shape);
890
+ }
891
+ }
892
+ return zod.z.object({});
893
+ }
894
+ }
895
+ /**
896
+ * Convert a string into a safe JavaScript identifier.
897
+ *
898
+ * This method sanitizes arbitrary strings (like server names or tool names) to create
899
+ * valid JavaScript identifiers that can be used as function names.
900
+ * It replaces non-word characters and handles edge cases like leading digits.
901
+ *
902
+ * @param name - The string to convert into a safe identifier
903
+ * @returns A string that is a valid JavaScript identifier
904
+ */
905
+ safeName(name) {
906
+ return name.replace(/\W|^(?=\d)/g, "_");
907
+ }
908
+ /**
909
+ * Generate a unique function name from server and tool names.
910
+ *
911
+ * This method creates a qualified function name by combining the server name
912
+ * and tool name with a double underscore separator. Both names are sanitized
913
+ * using safeName() to ensure the result is a valid JavaScript identifier.
914
+ *
915
+ * @param serverName - The name of the MCP server hosting the tool
916
+ * @param schemaName - The name of the tool from the schema definition
917
+ * @returns A qualified function name in the format "{safe_server}__{safe_tool}"
918
+ */
919
+ functionName(serverName, schemaName) {
920
+ return `${this.safeName(serverName)}__${this.safeName(schemaName)}`;
921
+ }
922
+ /**
923
+ * Create a callable JavaScript function from an MCP tool's JSON Schema definition.
924
+ *
925
+ * This method generates a self-contained, callable function that validates parameters
926
+ * and executes the corresponding MCP tool. The function includes proper parameter
927
+ * validation and comprehensive metadata based on the tool's JSON Schema.
928
+ *
929
+ * Generated functions are cached for performance. If a function for the same
930
+ * server/tool combination already exists in the cache, it returns the cached function.
931
+ *
932
+ * @param schema - The MCP tool's JSON Schema definition
933
+ * @param serverName - The name of the MCP server hosting this tool
934
+ * @returns A callable JavaScript function with metadata
935
+ */
936
+ createFunctionFromSchema(schema, serverName) {
937
+ const cacheKey = `${serverName}__${schema.name}`;
938
+ if (this.#functionCache.has(cacheKey)) return this.#functionCache.get(cacheKey);
939
+ try {
940
+ const generatedFunction = this.buildFunction(schema, serverName);
941
+ this.#functionCache.set(cacheKey, generatedFunction);
942
+ return generatedFunction;
943
+ } catch (error) {
944
+ throw new McpdError(`Error creating function ${cacheKey}: ${error.message}`, error);
945
+ }
946
+ }
947
+ /**
948
+ * Build the actual function from the schema.
949
+ *
950
+ * @param schema - The tool schema
951
+ * @param serverName - The server name
952
+ * @returns The generated function with metadata
953
+ */
954
+ buildFunction(schema, serverName) {
955
+ const inputSchema = schema.inputSchema || {};
956
+ const properties = inputSchema.properties || {};
957
+ const required = new Set(inputSchema.required || []);
958
+ const implementation = async (...args) => {
959
+ let params = {};
960
+ if (args.length === 1 && typeof args[0] === "object" && args[0] !== null && !Array.isArray(args[0])) params = args[0];
961
+ else {
962
+ const propertyNames = Object.keys(properties);
963
+ for (let i = 0; i < args.length && i < propertyNames.length; i++) {
964
+ const propertyName = propertyNames[i];
965
+ if (propertyName) params[propertyName] = args[i];
966
+ }
967
+ }
968
+ const missingParams = [];
969
+ for (const paramName of required) if (!(paramName in params) || params[paramName] === null || params[paramName] === void 0) missingParams.push(paramName);
970
+ if (missingParams.length > 0) throw new ValidationError(`Missing required parameters: ${missingParams.join(", ")}`, missingParams);
971
+ const validationErrors = [];
972
+ for (const [paramName, paramValue] of Object.entries(params)) {
973
+ const paramSchema = properties[paramName];
974
+ if (paramValue !== null && paramValue !== void 0 && paramSchema) {
975
+ if (!TypeConverter.validateValue(paramValue, paramSchema)) {
976
+ const expectedType = TypeConverter.getTypeDescription(paramSchema);
977
+ validationErrors.push(`Parameter '${paramName}' should be ${expectedType}, got ${typeof paramValue}`);
978
+ }
979
+ }
980
+ }
981
+ if (validationErrors.length > 0) throw new ValidationError(`Parameter validation failed: ${validationErrors.join("; ")}`, validationErrors);
982
+ const cleanParams = {};
983
+ for (const [key, value] of Object.entries(params)) if (value !== null && value !== void 0) cleanParams[key] = value;
984
+ return this.#performCall(serverName, schema.name, cleanParams);
985
+ };
986
+ const invoke = async (args) => {
987
+ return implementation(args);
988
+ };
989
+ const execute = async (args) => {
990
+ return implementation(args);
991
+ };
992
+ const zodSchema = this.#jsonSchemaToZod(inputSchema);
993
+ const qualifiedName = this.functionName(serverName, schema.name);
994
+ const docstring = this.createDocstring(schema);
995
+ const agentFunction = implementation;
996
+ Object.defineProperty(agentFunction, "name", {
997
+ value: qualifiedName,
998
+ writable: false,
999
+ enumerable: true,
1000
+ configurable: true
1001
+ });
1002
+ agentFunction.description = schema.description || docstring.split("\n")[0] || "No description provided";
1003
+ agentFunction.schema = zodSchema;
1004
+ agentFunction.invoke = invoke;
1005
+ agentFunction.lc_namespace = ["mcpd", "tools"];
1006
+ agentFunction.returnDirect = false;
1007
+ agentFunction.inputSchema = zodSchema;
1008
+ agentFunction.execute = execute;
1009
+ agentFunction._schema = schema;
1010
+ agentFunction._serverName = serverName;
1011
+ agentFunction._toolName = schema.name;
1012
+ return agentFunction;
1013
+ }
1014
+ /**
1015
+ * Generate a comprehensive docstring for the dynamically created function.
1016
+ *
1017
+ * This method builds a properly formatted docstring that includes the
1018
+ * tool's description, parameter documentation with optional/required status,
1019
+ * return value information, and exception documentation.
1020
+ *
1021
+ * @param schema - The MCP tool's JSON Schema definition
1022
+ * @returns A multi-line string containing the complete docstring text
1023
+ */
1024
+ createDocstring(schema) {
1025
+ const description = schema.description || "No description provided";
1026
+ const inputSchema = schema.inputSchema || {};
1027
+ const properties = inputSchema.properties || {};
1028
+ const required = new Set(inputSchema.required || []);
1029
+ const docstringParts = [description];
1030
+ if (Object.keys(properties).length > 0) {
1031
+ docstringParts.push("");
1032
+ docstringParts.push("Parameters:");
1033
+ for (const [paramName, paramInfo] of Object.entries(properties)) {
1034
+ const isRequired = required.has(paramName);
1035
+ const paramDesc = paramInfo.description || "No description provided";
1036
+ const paramType = TypeConverter.getTypeDescription(paramInfo);
1037
+ const requiredText = isRequired ? "" : " (optional)";
1038
+ docstringParts.push(` ${paramName} (${paramType}): ${paramDesc}${requiredText}`);
1039
+ }
1040
+ }
1041
+ docstringParts.push("");
1042
+ docstringParts.push("Returns:");
1043
+ docstringParts.push(" Promise<any>: Function execution result");
1044
+ docstringParts.push("");
1045
+ docstringParts.push("Throws:");
1046
+ docstringParts.push(" ValidationError: If required parameters are missing or invalid");
1047
+ docstringParts.push(" McpdError: If the API call fails");
1048
+ return docstringParts.join("\n");
1049
+ }
1050
+ /**
1051
+ * Clear the function cache.
1052
+ *
1053
+ * This method clears all cached generated functions, forcing them to be
1054
+ * regenerated on the next call to createFunctionFromSchema().
1055
+ */
1056
+ clearCache() {
1057
+ this.#functionCache.clear();
1058
+ }
1059
+ /**
1060
+ * Get the current cache size.
1061
+ *
1062
+ * @returns The number of functions currently cached
1063
+ */
1064
+ getCacheSize() {
1065
+ return this.#functionCache.size;
1066
+ }
1067
+ /**
1068
+ * Get all cached functions.
1069
+ *
1070
+ * @returns Array of all cached agent functions, or empty array if cache is empty
1071
+ */
1072
+ getCachedFunctions() {
1073
+ return Array.from(this.#functionCache.values());
1074
+ }
1075
+ };
1076
+ //#endregion
1077
+ //#region src/apiPaths.ts
1078
+ /**
1079
+ * Centralized API path constants for mcpd daemon endpoints.
1080
+ */
1081
+ var API_BASE = "/api/v1";
1082
+ var SERVERS_BASE = `${API_BASE}/servers`;
1083
+ var HEALTH_SERVERS_BASE = `${API_BASE}/health/servers`;
1084
+ var API_PATHS = {
1085
+ SERVERS: SERVERS_BASE,
1086
+ SERVER_TOOLS: (serverName) => `${SERVERS_BASE}/${encodeURIComponent(serverName)}/tools`,
1087
+ TOOL_CALL: (serverName, toolName) => `${SERVERS_BASE}/${encodeURIComponent(serverName)}/tools/${encodeURIComponent(toolName)}`,
1088
+ SERVER_PROMPTS: (serverName, cursor) => {
1089
+ const base = `${SERVERS_BASE}/${encodeURIComponent(serverName)}/prompts`;
1090
+ return cursor ? `${base}?cursor=${encodeURIComponent(cursor)}` : base;
1091
+ },
1092
+ PROMPT_GET_GENERATED: (serverName, promptName) => `${SERVERS_BASE}/${encodeURIComponent(serverName)}/prompts/${encodeURIComponent(promptName)}`,
1093
+ SERVER_RESOURCES: (serverName, cursor) => {
1094
+ const base = `${SERVERS_BASE}/${encodeURIComponent(serverName)}/resources`;
1095
+ return cursor ? `${base}?cursor=${encodeURIComponent(cursor)}` : base;
1096
+ },
1097
+ SERVER_RESOURCE_TEMPLATES: (serverName, cursor) => {
1098
+ const base = `${SERVERS_BASE}/${encodeURIComponent(serverName)}/resources/templates`;
1099
+ return cursor ? `${base}?cursor=${encodeURIComponent(cursor)}` : base;
1100
+ },
1101
+ RESOURCE_CONTENT: (serverName, uri) => `${SERVERS_BASE}/${encodeURIComponent(serverName)}/resources/content?uri=${encodeURIComponent(uri)}`,
1102
+ HEALTH_ALL: HEALTH_SERVERS_BASE,
1103
+ HEALTH_SERVER: (serverName) => `${HEALTH_SERVERS_BASE}/${encodeURIComponent(serverName)}`
1104
+ };
1105
+ //#endregion
1106
+ //#region src/logger.ts
1107
+ /**
1108
+ * Internal logging infrastructure for the mcpd SDK.
1109
+ *
1110
+ * This module provides a logging shim controlled by the MCPD_LOG_LEVEL environment
1111
+ * variable.
1112
+ *
1113
+ * Logging is disabled by default.
1114
+ *
1115
+ * NOTE: It is recommended that you only enable MCPD_LOG_LEVEL in non-MCP-server contexts.
1116
+ * MCP servers using stdio transport for JSON-RPC communication should avoid enabling logging
1117
+ * to avoid contaminating stdout/stderr.
1118
+ */
1119
+ /**
1120
+ * Valid {@link LogLevel} values for MCPD_LOG_LEVEL environment variable.
1121
+ */
1122
+ var LogLevels = {
1123
+ TRACE: "trace",
1124
+ DEBUG: "debug",
1125
+ INFO: "info",
1126
+ WARN: "warn",
1127
+ ERROR: "error",
1128
+ OFF: "off"
1129
+ };
1130
+ var ranks = {
1131
+ trace: 5,
1132
+ debug: 10,
1133
+ info: 20,
1134
+ warn: 30,
1135
+ error: 40,
1136
+ off: 1e3
1009
1137
  };
1010
1138
  function resolve(raw) {
1011
- const candidate = raw?.toLowerCase();
1012
- return candidate && candidate in ranks ? candidate : LogLevels.OFF;
1139
+ const candidate = raw?.toLowerCase();
1140
+ return candidate && candidate in ranks ? candidate : LogLevels.OFF;
1013
1141
  }
1014
1142
  function getLevel() {
1015
- return resolve(
1016
- typeof process !== "undefined" ? process.env.MCPD_LOG_LEVEL : void 0
1017
- );
1143
+ return resolve(typeof process !== "undefined" ? process.env.MCPD_LOG_LEVEL : void 0);
1018
1144
  }
1019
1145
  function defaultLogger() {
1020
- return {
1021
- trace: (...args) => {
1022
- const lvl = getLevel();
1023
- if (lvl !== LogLevels.OFF && ranks[lvl] <= ranks.trace)
1024
- console.trace(...args);
1025
- },
1026
- debug: (...args) => {
1027
- const lvl = getLevel();
1028
- if (lvl !== LogLevels.OFF && ranks[lvl] <= ranks.debug)
1029
- console.debug(...args);
1030
- },
1031
- info: (...args) => {
1032
- const lvl = getLevel();
1033
- if (lvl !== LogLevels.OFF && ranks[lvl] <= ranks.info)
1034
- console.info(...args);
1035
- },
1036
- warn: (...args) => {
1037
- const lvl = getLevel();
1038
- if (lvl !== LogLevels.OFF && ranks[lvl] <= ranks.warn)
1039
- console.warn(...args);
1040
- },
1041
- error: (...args) => {
1042
- const lvl = getLevel();
1043
- if (lvl !== LogLevels.OFF && ranks[lvl] <= ranks.error)
1044
- console.error(...args);
1045
- }
1046
- };
1146
+ return {
1147
+ trace: (...args) => {
1148
+ const lvl = getLevel();
1149
+ if (lvl !== LogLevels.OFF && ranks[lvl] <= ranks.trace) console.trace(...args);
1150
+ },
1151
+ debug: (...args) => {
1152
+ const lvl = getLevel();
1153
+ if (lvl !== LogLevels.OFF && ranks[lvl] <= ranks.debug) console.debug(...args);
1154
+ },
1155
+ info: (...args) => {
1156
+ const lvl = getLevel();
1157
+ if (lvl !== LogLevels.OFF && ranks[lvl] <= ranks.info) console.info(...args);
1158
+ },
1159
+ warn: (...args) => {
1160
+ const lvl = getLevel();
1161
+ if (lvl !== LogLevels.OFF && ranks[lvl] <= ranks.warn) console.warn(...args);
1162
+ },
1163
+ error: (...args) => {
1164
+ const lvl = getLevel();
1165
+ if (lvl !== LogLevels.OFF && ranks[lvl] <= ranks.error) console.error(...args);
1166
+ }
1167
+ };
1047
1168
  }
1169
+ /**
1170
+ * Create a logger, optionally using a custom implementation.
1171
+ *
1172
+ * This function allows SDK users to inject their own logger implementation.
1173
+ * Supports partial implementations - any omitted methods will fall back to the
1174
+ * default logger, which respects the MCPD_LOG_LEVEL environment variable.
1175
+ *
1176
+ * @param impl - Custom Logger implementation or partial implementation.
1177
+ * If not provided, uses default logger controlled by MCPD_LOG_LEVEL.
1178
+ * If partially provided, custom methods are used and omitted methods
1179
+ * fall back to default logger (which respects MCPD_LOG_LEVEL).
1180
+ * @returns A Logger instance with all methods implemented.
1181
+ *
1182
+ * @example
1183
+ * ```typescript
1184
+ * // Use default logger (controlled by MCPD_LOG_LEVEL).
1185
+ * const logger = createLogger();
1186
+ *
1187
+ * // Partial logger: custom warn/error, default (MCPD_LOG_LEVEL-aware) for others.
1188
+ * const logger = createLogger({
1189
+ * warn: (msg) => myCustomLogger.warning(msg),
1190
+ * error: (msg) => myCustomLogger.error(msg),
1191
+ * // trace, debug, info fall back to default logger (respects MCPD_LOG_LEVEL)
1192
+ * });
1193
+ * ```
1194
+ */
1048
1195
  function createLogger(impl) {
1049
- const base = defaultLogger();
1050
- return {
1051
- trace: impl?.trace ?? base.trace,
1052
- debug: impl?.debug ?? base.debug,
1053
- info: impl?.info ?? base.info,
1054
- warn: impl?.warn ?? base.warn,
1055
- error: impl?.error ?? base.error
1056
- };
1196
+ const base = defaultLogger();
1197
+ return {
1198
+ trace: impl?.trace ?? base.trace,
1199
+ debug: impl?.debug ?? base.debug,
1200
+ info: impl?.info ?? base.info,
1201
+ warn: impl?.warn ?? base.warn,
1202
+ error: impl?.error ?? base.error
1203
+ };
1057
1204
  }
1058
- const REQUEST_TIMEOUT_SECONDS = 30;
1059
- const SERVER_HEALTH_CACHE_TTL_SECONDS = 10;
1060
- const SERVER_HEALTH_CACHE_MAXSIZE = 100;
1061
- const TOOL_SEPARATOR = "__";
1062
- const MCPD_ERROR_TYPE_HEADER = "Mcpd-Error-Type";
1063
- const PIPELINE_ERROR_FLOWS = {
1064
- "request-pipeline-failure": PIPELINE_FLOW_REQUEST,
1065
- "response-pipeline-failure": PIPELINE_FLOW_RESPONSE
1205
+ //#endregion
1206
+ //#region src/client.ts
1207
+ /**
1208
+ * Default timeout for API requests to mcpd, in seconds.
1209
+ */
1210
+ var REQUEST_TIMEOUT_SECONDS = 30;
1211
+ /**
1212
+ * Default TTL for server health cache entries, in seconds.
1213
+ *
1214
+ * @remarks
1215
+ * Used to avoid excessive health check requests while keeping data reasonably fresh.
1216
+ */
1217
+ var SERVER_HEALTH_CACHE_TTL_SECONDS = 10;
1218
+ /**
1219
+ * Maximum number of server health entries to cache.
1220
+ * Prevents unbounded memory growth while allowing legitimate large-scale monitoring.
1221
+ */
1222
+ var SERVER_HEALTH_CACHE_MAXSIZE = 100;
1223
+ /**
1224
+ * Separator used between server name and tool name in qualified tool names.
1225
+ * Format: `{serverName}{TOOL_SEPARATOR}{toolName}`
1226
+ * Example: "time__get_current_time" where "time" is server and "get_current_time" is tool.
1227
+ */
1228
+ var TOOL_SEPARATOR = "__";
1229
+ /**
1230
+ * Header name for mcpd pipeline error type.
1231
+ */
1232
+ var MCPD_ERROR_TYPE_HEADER = "Mcpd-Error-Type";
1233
+ /**
1234
+ * Maps mcpd error type header values to pipeline flows.
1235
+ */
1236
+ var PIPELINE_ERROR_FLOWS = {
1237
+ "request-pipeline-failure": PIPELINE_FLOW_REQUEST,
1238
+ "response-pipeline-failure": PIPELINE_FLOW_RESPONSE
1066
1239
  };
1067
- class McpdClient {
1068
- #endpoint;
1069
- #apiKey;
1070
- #timeout;
1071
- #serverHealthCache;
1072
- #functionBuilder;
1073
- #logger;
1074
- #cacheableExceptions = /* @__PURE__ */ new Set([
1075
- ServerNotFoundError,
1076
- ServerUnhealthyError,
1077
- AuthenticationError
1078
- ]);
1079
- /**
1080
- * Namespace for accessing MCP servers and their tools.
1081
- */
1082
- servers;
1083
- /**
1084
- * Initialize a new McpdClient instance.
1085
- *
1086
- * @param options - Configuration options for the client
1087
- */
1088
- constructor(options) {
1089
- const toMs = (s) => s * 1e3;
1090
- this.#endpoint = options.apiEndpoint.replace(/\/$/, "");
1091
- this.#apiKey = options.apiKey;
1092
- this.#timeout = options.timeout ?? toMs(REQUEST_TIMEOUT_SECONDS);
1093
- const healthCacheTtlMs = toMs(
1094
- options.healthCacheTtl ?? SERVER_HEALTH_CACHE_TTL_SECONDS
1095
- );
1096
- this.#serverHealthCache = createCache({
1097
- max: SERVER_HEALTH_CACHE_MAXSIZE,
1098
- ttl: healthCacheTtlMs
1099
- });
1100
- this.#logger = createLogger(options.logger);
1101
- this.servers = new ServersNamespace({
1102
- performCall: this.#performCall.bind(this),
1103
- getTools: this.#getToolsByServer.bind(this),
1104
- generatePrompt: this.#generatePromptInternal.bind(this),
1105
- getPrompts: this.#getPromptsByServer.bind(this),
1106
- getResources: this.#getResourcesByServer.bind(this),
1107
- getResourceTemplates: this.#getResourceTemplatesByServer.bind(this),
1108
- readResource: this.#readResourceByServer.bind(this)
1109
- });
1110
- this.#functionBuilder = new FunctionBuilder(this.#performCall.bind(this));
1111
- }
1112
- /**
1113
- * Make an HTTP request to the mcpd daemon.
1114
- *
1115
- * @param path - The API path (e.g., '/servers', '/servers/{server_name}/tools')
1116
- * @param options - Request options
1117
- *
1118
- * @returns The JSON response from the daemon
1119
- *
1120
- * @throws {AuthenticationError} If API key was present and authentication fails
1121
- * @throws {ConnectionError} If unable to connect to the mcpd daemon
1122
- * @throws {TimeoutError} If the request times out
1123
- * @throws {McpdError} If the request fails
1124
- *
1125
- * @internal
1126
- */
1127
- async #request(path, options = {}) {
1128
- const url = `${this.#endpoint}${path}`;
1129
- const headers = {
1130
- "Content-Type": "application/json",
1131
- ...options.headers || {}
1132
- };
1133
- if (this.#apiKey) {
1134
- headers["Authorization"] = `Bearer ${this.#apiKey}`;
1135
- }
1136
- const controller = new AbortController();
1137
- const timeoutId = setTimeout(() => controller.abort(), this.#timeout);
1138
- try {
1139
- const response = await fetch(url, {
1140
- ...options,
1141
- headers,
1142
- signal: controller.signal
1143
- });
1144
- clearTimeout(timeoutId);
1145
- if (!response.ok) {
1146
- const body = await response.text();
1147
- let errorModel = null;
1148
- try {
1149
- errorModel = JSON.parse(body);
1150
- } catch {
1151
- errorModel = null;
1152
- }
1153
- if (response.status === 500) {
1154
- const errorType = response.headers.get(MCPD_ERROR_TYPE_HEADER)?.toLowerCase();
1155
- const flow = errorType ? PIPELINE_ERROR_FLOWS[errorType] : void 0;
1156
- if (flow) {
1157
- const message = errorModel?.detail || body || "Pipeline failure";
1158
- throw new PipelineError(
1159
- message,
1160
- void 0,
1161
- // serverName - enriched by caller if available.
1162
- void 0,
1163
- // operation - enriched by caller if available.
1164
- flow
1165
- );
1166
- }
1167
- }
1168
- if (errorModel && errorModel.detail) {
1169
- const errorDetails = errorModel.errors?.map((e) => `${e.location}: ${e.message}`).join("; ");
1170
- const fullMessage = errorDetails ? `${errorModel.detail} - ${errorDetails}` : errorModel.detail;
1171
- if (response.status === 401 || response.status === 403) {
1172
- throw new AuthenticationError(fullMessage);
1173
- }
1174
- throw new McpdError(
1175
- `${errorModel.title || "Request failed"}: ${fullMessage}`
1176
- );
1177
- }
1178
- if (response.status === 401 || response.status === 403) {
1179
- throw new AuthenticationError(
1180
- `Authentication failed: ${response.status} ${response.statusText}`
1181
- );
1182
- }
1183
- throw new McpdError(
1184
- `Request failed: ${response.status} ${response.statusText} - ${body}`
1185
- );
1186
- }
1187
- try {
1188
- return await response.json();
1189
- } catch (error) {
1190
- throw new McpdError("Failed to parse JSON response", error);
1191
- }
1192
- } catch (error) {
1193
- clearTimeout(timeoutId);
1194
- if (error.name === "AbortError") {
1195
- throw new TimeoutError(
1196
- `Request timed out after ${this.#timeout}ms`,
1197
- path,
1198
- this.#timeout
1199
- );
1200
- }
1201
- if (error instanceof TypeError && error.message.includes("fetch")) {
1202
- throw new ConnectionError(
1203
- `Cannot connect to mcpd daemon at ${this.#endpoint}. Is it running?`,
1204
- error
1205
- );
1206
- }
1207
- if (error instanceof McpdError) {
1208
- throw error;
1209
- }
1210
- throw new McpdError(
1211
- `Request failed: ${error.message}`,
1212
- error
1213
- );
1214
- }
1215
- }
1216
- /**
1217
- * Get a list of all configured MCP servers.
1218
- *
1219
- * @returns Array of server names
1220
- *
1221
- * @throws {AuthenticationError} If API key was present and authentication fails
1222
- * @throws {ConnectionError} If unable to connect to the mcpd daemon
1223
- * @throws {TimeoutError} If the request times out
1224
- * @throws {McpdError} If the request fails
1225
- *
1226
- * @example
1227
- * ```typescript
1228
- * const servers = await client.listServers();
1229
- * console.log(servers); // ['time', 'fetch', 'git']
1230
- * ```
1231
- */
1232
- async listServers() {
1233
- return await this.#request(API_PATHS.SERVERS);
1234
- }
1235
- /**
1236
- * Get tool schemas for a server.
1237
- *
1238
- * @privateRemarks
1239
- * Used by dependency injection for ServersNamespace and internally for getAgentTools.
1240
- *
1241
- * @param serverName - Server name to get tools for
1242
- *
1243
- * @returns Tool schemas for the specified server
1244
- *
1245
- * @throws {ServerNotFoundError} If the specified server doesn't exist
1246
- * @throws {ServerUnhealthyError} If the server is not healthy
1247
- *
1248
- * @throws {AuthenticationError} If API key was present and authentication fails
1249
- * @throws {ConnectionError} If unable to connect to the mcpd daemon
1250
- * @throws {TimeoutError} If the request times out
1251
- * @throws {McpdError} If the request fails
1252
- *
1253
- * @internal
1254
- */
1255
- async #getToolsByServer(serverName) {
1256
- await this.#ensureServerHealthy(serverName);
1257
- const path = API_PATHS.SERVER_TOOLS(serverName);
1258
- const response = await this.#request(path);
1259
- if (!response.tools) {
1260
- throw new ServerNotFoundError(
1261
- `Server '${serverName}' not found`,
1262
- serverName
1263
- );
1264
- }
1265
- return response.tools;
1266
- }
1267
- /**
1268
- * Get prompt schemas for a server.
1269
- *
1270
- * @privateRemarks
1271
- * Used internally for getPromptSchemas.
1272
- *
1273
- * @param serverName - Server name to get prompts for
1274
- * @param cursor - Cursor for pagination
1275
- *
1276
- * @returns Prompt schemas for the specified server
1277
- *
1278
- * @throws {ServerNotFoundError} If the specified server doesn't exist
1279
- * @throws {ServerUnhealthyError} If the server is not healthy
1280
- *
1281
- * @throws {AuthenticationError} If API key was present and authentication fails
1282
- * @throws {ConnectionError} If unable to connect to the mcpd daemon
1283
- * @throws {TimeoutError} If the request times out
1284
- * @throws {McpdError} If the request fails
1285
- *
1286
- * @internal
1287
- */
1288
- async #getPromptsByServer(serverName, cursor) {
1289
- try {
1290
- await this.#ensureServerHealthy(serverName);
1291
- const path = API_PATHS.SERVER_PROMPTS(serverName, cursor);
1292
- const response = await this.#request(path);
1293
- return response.prompts || [];
1294
- } catch (error) {
1295
- if (error instanceof McpdError && error.message.includes("501") && error.message.includes("Not Implemented")) {
1296
- return [];
1297
- }
1298
- throw error;
1299
- }
1300
- }
1301
- /**
1302
- * Generate a prompt on a server.
1303
- *
1304
- * @privateRemarks
1305
- * Used internally by:
1306
- * - PromptsNamespace (via dependency injection)
1307
- * - Server.generatePrompt() (via dependency injection)
1308
- *
1309
- * @param serverName - The name of the server
1310
- * @param promptName - The exact name of the prompt
1311
- * @param args - The prompt arguments
1312
- *
1313
- * @returns The generated prompt response
1314
- *
1315
- * @throws {AuthenticationError} If API key was present and authentication fails
1316
- * @throws {ConnectionError} If unable to connect to the mcpd daemon
1317
- * @throws {TimeoutError} If the request times out
1318
- * @throws {McpdError} If the request fails
1319
- *
1320
- * @internal
1321
- */
1322
- async #generatePromptInternal(serverName, promptName, args) {
1323
- await this.#ensureServerHealthy(serverName);
1324
- const path = API_PATHS.PROMPT_GET_GENERATED(serverName, promptName);
1325
- const requestBody = {
1326
- arguments: args || {}
1327
- };
1328
- const response = await this.#request(path, {
1329
- method: "POST",
1330
- body: JSON.stringify(requestBody)
1331
- });
1332
- return response;
1333
- }
1334
- /**
1335
- * Get resource schemas for a server.
1336
- *
1337
- * @privateRemarks
1338
- * Used internally for getResources and by dependency injection for ServersNamespace.
1339
- *
1340
- * @param serverName - Server name to get resources for
1341
- * @param cursor - Cursor for pagination
1342
- *
1343
- * @returns Resource schemas for the specified server
1344
- *
1345
- * @throws {ServerNotFoundError} If the specified server doesn't exist
1346
- * @throws {ServerUnhealthyError} If the server is not healthy
1347
- *
1348
- * @throws {AuthenticationError} If API key was present and authentication fails
1349
- * @throws {ConnectionError} If unable to connect to the mcpd daemon
1350
- * @throws {TimeoutError} If the request times out
1351
- * @throws {McpdError} If the request fails
1352
- *
1353
- * @internal
1354
- */
1355
- async #getResourcesByServer(serverName, cursor) {
1356
- try {
1357
- await this.#ensureServerHealthy(serverName);
1358
- const path = API_PATHS.SERVER_RESOURCES(serverName, cursor);
1359
- const response = await this.#request(path);
1360
- return response.resources || [];
1361
- } catch (error) {
1362
- if (error instanceof McpdError && error.message.includes("501") && error.message.includes("Not Implemented")) {
1363
- return [];
1364
- }
1365
- throw error;
1366
- }
1367
- }
1368
- /**
1369
- * Get resource template schemas for a server.
1370
- *
1371
- * @privateRemarks
1372
- * Used internally for getResourceTemplates and by dependency injection for ServersNamespace.
1373
- *
1374
- * @param serverName - Server name to get resource templates for
1375
- * @param cursor - Cursor for pagination
1376
- *
1377
- * @returns Resource template schemas for the specified server
1378
- *
1379
- * @throws {ServerNotFoundError} If the specified server doesn't exist
1380
- * @throws {ServerUnhealthyError} If the server is not healthy
1381
- *
1382
- * @throws {AuthenticationError} If API key was present and authentication fails
1383
- * @throws {ConnectionError} If unable to connect to the mcpd daemon
1384
- * @throws {TimeoutError} If the request times out
1385
- * @throws {McpdError} If the request fails
1386
- *
1387
- * @internal
1388
- */
1389
- async #getResourceTemplatesByServer(serverName, cursor) {
1390
- try {
1391
- await this.#ensureServerHealthy(serverName);
1392
- const path = API_PATHS.SERVER_RESOURCE_TEMPLATES(serverName, cursor);
1393
- const response = await this.#request(path);
1394
- return response.templates || [];
1395
- } catch (error) {
1396
- if (error instanceof McpdError && error.message.includes("501") && error.message.includes("Not Implemented")) {
1397
- return [];
1398
- }
1399
- throw error;
1400
- }
1401
- }
1402
- /**
1403
- * Read resource content from a server.
1404
- *
1405
- * @privateRemarks
1406
- * Used by dependency injection for ServersNamespace.
1407
- *
1408
- * @param serverName - Server name to read resource from
1409
- * @param uri - The resource URI
1410
- *
1411
- * @returns Array of resource contents (text or blob)
1412
- *
1413
- * @throws {ServerNotFoundError} If the specified server doesn't exist
1414
- * @throws {ServerUnhealthyError} If the server is not healthy
1415
- *
1416
- * @throws {AuthenticationError} If API key was present and authentication fails
1417
- * @throws {ConnectionError} If unable to connect to the mcpd daemon
1418
- * @throws {TimeoutError} If the request times out
1419
- * @throws {McpdError} If the request fails
1420
- *
1421
- * @internal
1422
- */
1423
- async #readResourceByServer(serverName, uri) {
1424
- await this.#ensureServerHealthy(serverName);
1425
- const path = API_PATHS.RESOURCE_CONTENT(serverName, uri);
1426
- const response = await this.#request(path);
1427
- return response || [];
1428
- }
1429
- async getServerHealth(serverName) {
1430
- if (serverName) {
1431
- const cacheKey = `health:${serverName}`;
1432
- const cached = this.#serverHealthCache.get(cacheKey);
1433
- if (cached !== void 0) {
1434
- if (cached instanceof Error) {
1435
- throw cached;
1436
- }
1437
- return cached;
1438
- }
1439
- try {
1440
- const path = API_PATHS.HEALTH_SERVER(serverName);
1441
- const health = await this.#request(path);
1442
- this.#serverHealthCache.set(cacheKey, health);
1443
- return health;
1444
- } catch (error) {
1445
- if (error instanceof Error) {
1446
- for (const errorType of this.#cacheableExceptions) {
1447
- if (error instanceof errorType) {
1448
- this.#serverHealthCache.set(cacheKey, error);
1449
- break;
1450
- }
1451
- }
1452
- }
1453
- throw error;
1454
- }
1455
- } else {
1456
- const response = await this.#request(
1457
- API_PATHS.HEALTH_ALL
1458
- );
1459
- const healthMap = {};
1460
- for (const server of response.servers) {
1461
- healthMap[server.name] = server;
1462
- const cacheKey = `health:${server.name}`;
1463
- this.#serverHealthCache.set(cacheKey, server);
1464
- }
1465
- return healthMap;
1466
- }
1467
- }
1468
- /**
1469
- * Check if a specific server is healthy.
1470
- *
1471
- * @param serverName - The name of the server to check
1472
- *
1473
- * @returns True if the server is healthy, false otherwise
1474
- *
1475
- * @throws {AuthenticationError} If API key was present and authentication fails
1476
- * @throws {ConnectionError} If unable to connect to the mcpd daemon
1477
- * @throws {TimeoutError} If the request times out
1478
- * @throws {McpdError} If the request fails
1479
- *
1480
- * @example
1481
- * ```typescript
1482
- * if (await client.isServerHealthy('time')) {
1483
- * const time = await client.servers.time.get_current_time();
1484
- * }
1485
- * ```
1486
- */
1487
- async isServerHealthy(serverName) {
1488
- try {
1489
- const health = await this.getServerHealth(serverName);
1490
- return HealthStatusHelpers.isHealthy(health.status);
1491
- } catch (error) {
1492
- if (error instanceof ServerNotFoundError) {
1493
- return false;
1494
- }
1495
- throw error;
1496
- }
1497
- }
1498
- /**
1499
- * Ensure a server is healthy before performing an operation.
1500
- *
1501
- * @param serverName - The name of the server to check
1502
- *
1503
- * @throws {ServerNotFoundError} If the server doesn't exist
1504
- * @throws {ServerUnhealthyError} If the server is not healthy
1505
- *
1506
- * @throws {AuthenticationError} If API key was present and authentication fails
1507
- * @throws {ConnectionError} If unable to connect to the mcpd daemon
1508
- * @throws {TimeoutError} If the request times out
1509
- * @throws {McpdError} If the request fails
1510
- */
1511
- async #ensureServerHealthy(serverName) {
1512
- const health = await this.getServerHealth(serverName);
1513
- if (!health) {
1514
- throw new ServerNotFoundError(
1515
- `Server '${serverName}' not found`,
1516
- serverName
1517
- );
1518
- }
1519
- if (!HealthStatusHelpers.isHealthy(health.status)) {
1520
- throw new ServerUnhealthyError(
1521
- `Server '${serverName}' is not healthy: ${health.status}`,
1522
- serverName,
1523
- health.status
1524
- );
1525
- }
1526
- }
1527
- /**
1528
- * Get list of healthy servers.
1529
- *
1530
- * @remarks
1531
- * If logging is enabled, warnings are logged for servers that do not exist or are unhealthy.
1532
- *
1533
- * @param servers - List of server names to use for health checking.
1534
- * If not provided, or empty, checks health for all servers.
1535
- *
1536
- * @returns List of server names with 'ok' health status.
1537
- *
1538
- * @throws {AuthenticationError} If API key was present and authentication fails
1539
- * @throws {ConnectionError} If unable to connect to the mcpd daemon
1540
- * @throws {TimeoutError} If the request times out
1541
- * @throws {McpdError} If the request fails
1542
- */
1543
- async #getHealthyServers(servers) {
1544
- const serverNames = servers?.length ? servers : await this.listServers();
1545
- const healthMap = await this.getServerHealth();
1546
- return serverNames.filter((name) => {
1547
- const health = healthMap[name];
1548
- if (!health) {
1549
- this.#logger.warn(`Skipping non-existent server '${name}'`);
1550
- return false;
1551
- }
1552
- if (!HealthStatusHelpers.isHealthy(health.status)) {
1553
- this.#logger.warn(
1554
- `Skipping unhealthy server '${name}' with status '${health.status}'`
1555
- );
1556
- return false;
1557
- }
1558
- return true;
1559
- });
1560
- }
1561
- /**
1562
- * Perform a tool call on a server.
1563
- *
1564
- * @privateRemarks
1565
- * Used internally by:
1566
- * - ToolsNamespace (via dependency injection)
1567
- * - FunctionBuilder (via dependency injection)
1568
- *
1569
- * @param serverName - The name of the server
1570
- * @param toolName - The exact name of the tool
1571
- * @param args - The tool arguments
1572
- *
1573
- * @returns The tool's response
1574
- *
1575
- * @throws {ToolExecutionError} If the tool execution fails
1576
- *
1577
- * @throws {AuthenticationError} If API key was present and authentication fails
1578
- * @throws {ConnectionError} If unable to connect to the mcpd daemon
1579
- * @throws {TimeoutError} If the request times out
1580
- * @throws {McpdError} If the request fails
1581
- *
1582
- * @internal
1583
- */
1584
- async #performCall(serverName, toolName, args) {
1585
- const path = API_PATHS.TOOL_CALL(serverName, toolName);
1586
- try {
1587
- const response = await this.#request(path, {
1588
- method: "POST",
1589
- body: JSON.stringify(args || {})
1590
- });
1591
- if (typeof response === "string") {
1592
- try {
1593
- return JSON.parse(response);
1594
- } catch {
1595
- return response;
1596
- }
1597
- }
1598
- return response;
1599
- } catch (error) {
1600
- if (error instanceof PipelineError) {
1601
- throw new PipelineError(
1602
- error.message,
1603
- serverName,
1604
- `${serverName}.${toolName}`,
1605
- error.pipelineFlow,
1606
- error.cause
1607
- );
1608
- }
1609
- if (error instanceof McpdError) {
1610
- throw error;
1611
- }
1612
- throw new ToolExecutionError(
1613
- `Failed to execute tool '${toolName}' on server '${serverName}': ${error.message}`,
1614
- serverName,
1615
- toolName,
1616
- void 0,
1617
- error
1618
- );
1619
- }
1620
- }
1621
- /**
1622
- * Clear the cached agent tools functions.
1623
- * This should be called when the tool schemas might have changed.
1624
- */
1625
- clearAgentToolsCache() {
1626
- this.#functionBuilder.clearCache();
1627
- }
1628
- /**
1629
- * Clear the server health cache.
1630
- * This forces fresh health checks on the next getServerHealth() or isServerHealthy() call.
1631
- */
1632
- clearServerHealthCache() {
1633
- this.#serverHealthCache.clear();
1634
- }
1635
- /**
1636
- * Fetch and cache callable functions from all healthy servers.
1637
- *
1638
- * This method queries all healthy servers and creates self-contained, callable functions
1639
- * that can be passed to AI agent frameworks. Each function includes its schema
1640
- * as metadata and handles the MCP communication internally.
1641
- *
1642
- * Unhealthy servers are automatically filtered out and skipped (with optional warnings
1643
- * when logging is enabled) to ensure the method returns quickly without waiting for timeouts.
1644
- *
1645
- * Tool fetches from multiple servers are executed concurrently for optimal performance.
1646
- * Functions are cached indefinitely until explicitly cleared.
1647
- *
1648
- * @returns Array of callable functions with metadata from all healthy servers.
1649
- *
1650
- * @throws {AuthenticationError} If API key was present and authentication fails
1651
- * @throws {ConnectionError} If unable to connect to the mcpd daemon
1652
- * @throws {TimeoutError} If the request times out
1653
- * @throws {McpdError} If the request fails
1654
- *
1655
- * @internal
1656
- */
1657
- async #agentTools() {
1658
- const cachedFunctions = this.#functionBuilder.getCachedFunctions();
1659
- if (cachedFunctions.length > 0) {
1660
- return cachedFunctions;
1661
- }
1662
- const healthyServers = await this.#getHealthyServers();
1663
- const results = await Promise.allSettled(
1664
- healthyServers.map(async (serverName) => ({
1665
- serverName,
1666
- tools: await this.#getToolsByServer(serverName)
1667
- }))
1668
- );
1669
- const agentTools = results.filter((result) => result.status === "fulfilled").flatMap((result) => {
1670
- const { serverName, tools } = result.value;
1671
- return tools.map(
1672
- (toolSchema) => this.#functionBuilder.createFunctionFromSchema(
1673
- toolSchema,
1674
- serverName
1675
- )
1676
- );
1677
- });
1678
- return agentTools;
1679
- }
1680
- async getAgentTools(options = {}) {
1681
- const { servers, tools, format = "array", refreshCache = false } = options;
1682
- if (refreshCache) this.#functionBuilder.clearCache();
1683
- const allTools = await this.#agentTools();
1684
- const filteredTools = allTools.filter((tool) => !servers || servers.includes(tool._serverName)).filter((tool) => !tools || this.#matchesToolFilter(tool, tools));
1685
- const formatters = {
1686
- array: (t) => t,
1687
- object: (t) => Object.fromEntries(t.map((tool) => [tool.name, tool])),
1688
- map: (t) => new Map(t.map((tool) => [tool.name, tool]))
1689
- };
1690
- return formatters[format](filteredTools);
1691
- }
1692
- /**
1693
- * Check if a tool matches the tool filter.
1694
- *
1695
- * Supports two formats:
1696
- * - Raw tool name: "get_current_time" (matches across all servers)
1697
- * - Server-prefixed: "time__get_current_time" (matches specific server + tool)
1698
- *
1699
- * @remarks
1700
- * When a filter contains "__", it's first checked as server-prefixed (exact match).
1701
- * If that fails, it's checked as a raw tool name. This handles tools whose names
1702
- * contain "__" (e.g., "my__special__tool").
1703
- *
1704
- * @param tool The tool to match.
1705
- * @param tools List of tool names to match against.
1706
- *
1707
- * @returns True if a match is found in tools, based on the predicate.
1708
- *
1709
- * @internal
1710
- */
1711
- #matchesToolFilter(tool, tools) {
1712
- return tools.some((filterItem) => {
1713
- if (filterItem.indexOf(TOOL_SEPARATOR) === -1) {
1714
- return filterItem === tool._toolName;
1715
- }
1716
- return filterItem === tool.name || filterItem === tool._toolName;
1717
- });
1718
- }
1719
- }
1240
+ /**
1241
+ * Client for interacting with MCP (Model Context Protocol) servers through an mcpd daemon.
1242
+ *
1243
+ * The McpdClient provides a high-level interface to discover, inspect, and invoke tools
1244
+ * exposed by MCP servers running behind an mcpd daemon proxy/gateway.
1245
+ *
1246
+ * Thread Safety:
1247
+ * This client is safe to use from multiple async contexts. The internal health
1248
+ * check cache is protected by the LRUCache implementation.
1249
+ *
1250
+ * @example
1251
+ * ```typescript
1252
+ * import { McpdClient } from '@mozilla-ai/mcpd';
1253
+ *
1254
+ * // Initialize client
1255
+ * const client = new McpdClient({
1256
+ * apiEndpoint: 'http://localhost:8090',
1257
+ * apiKey: 'optional-key',
1258
+ * healthCacheTtl: 10,
1259
+ * });
1260
+ *
1261
+ * // List available servers
1262
+ * const servers = await client.listServers();
1263
+ * console.log(servers); // ['time', 'fetch', 'git']
1264
+ *
1265
+ * // Invoke a tool dynamically
1266
+ * const result = await client.servers.time.get_current_time({ timezone: 'UTC' });
1267
+ * console.log(result); // { time: '2024-01-15T10:30:00Z' }
1268
+ * ```
1269
+ */
1270
+ var McpdClient = class {
1271
+ #endpoint;
1272
+ #apiKey;
1273
+ #timeout;
1274
+ #serverHealthCache;
1275
+ #functionBuilder;
1276
+ #logger;
1277
+ #cacheableExceptions = new Set([
1278
+ ServerNotFoundError,
1279
+ ServerUnhealthyError,
1280
+ AuthenticationError
1281
+ ]);
1282
+ /**
1283
+ * Namespace for accessing MCP servers and their tools.
1284
+ */
1285
+ servers;
1286
+ /**
1287
+ * Initialize a new McpdClient instance.
1288
+ *
1289
+ * @param options - Configuration options for the client
1290
+ */
1291
+ constructor(options) {
1292
+ const toMs = (s) => s * 1e3;
1293
+ this.#endpoint = options.apiEndpoint.replace(/\/$/, "");
1294
+ this.#apiKey = options.apiKey;
1295
+ this.#timeout = options.timeout ?? toMs(REQUEST_TIMEOUT_SECONDS);
1296
+ const healthCacheTtlMs = toMs(options.healthCacheTtl ?? SERVER_HEALTH_CACHE_TTL_SECONDS);
1297
+ this.#serverHealthCache = createCache({
1298
+ max: SERVER_HEALTH_CACHE_MAXSIZE,
1299
+ ttl: healthCacheTtlMs
1300
+ });
1301
+ this.#logger = createLogger(options.logger);
1302
+ this.servers = new ServersNamespace({
1303
+ performCall: this.#performCall.bind(this),
1304
+ getTools: this.#getToolsByServer.bind(this),
1305
+ generatePrompt: this.#generatePromptInternal.bind(this),
1306
+ getPrompts: this.#getPromptsByServer.bind(this),
1307
+ getResources: this.#getResourcesByServer.bind(this),
1308
+ getResourceTemplates: this.#getResourceTemplatesByServer.bind(this),
1309
+ readResource: this.#readResourceByServer.bind(this)
1310
+ });
1311
+ this.#functionBuilder = new FunctionBuilder(this.#performCall.bind(this));
1312
+ }
1313
+ /**
1314
+ * Make an HTTP request to the mcpd daemon.
1315
+ *
1316
+ * @param path - The API path (e.g., '/servers', '/servers/{server_name}/tools')
1317
+ * @param options - Request options
1318
+ *
1319
+ * @returns The JSON response from the daemon
1320
+ *
1321
+ * @throws {AuthenticationError} If API key was present and authentication fails
1322
+ * @throws {ConnectionError} If unable to connect to the mcpd daemon
1323
+ * @throws {TimeoutError} If the request times out
1324
+ * @throws {McpdError} If the request fails
1325
+ *
1326
+ * @internal
1327
+ */
1328
+ async #request(path, options = {}) {
1329
+ const url = `${this.#endpoint}${path}`;
1330
+ const headers = {
1331
+ "Content-Type": "application/json",
1332
+ ...options.headers || {}
1333
+ };
1334
+ if (this.#apiKey) headers["Authorization"] = `Bearer ${this.#apiKey}`;
1335
+ const controller = new AbortController();
1336
+ const timeoutId = setTimeout(() => controller.abort(), this.#timeout);
1337
+ try {
1338
+ const response = await fetch(url, {
1339
+ ...options,
1340
+ headers,
1341
+ signal: controller.signal
1342
+ });
1343
+ clearTimeout(timeoutId);
1344
+ if (!response.ok) {
1345
+ const body = await response.text();
1346
+ let errorModel = null;
1347
+ try {
1348
+ errorModel = JSON.parse(body);
1349
+ } catch {
1350
+ errorModel = null;
1351
+ }
1352
+ if (response.status === 500) {
1353
+ const errorType = response.headers.get(MCPD_ERROR_TYPE_HEADER)?.toLowerCase();
1354
+ const flow = errorType ? PIPELINE_ERROR_FLOWS[errorType] : void 0;
1355
+ if (flow) throw new PipelineError(errorModel?.detail || body || "Pipeline failure", void 0, void 0, flow);
1356
+ }
1357
+ if (errorModel && errorModel.detail) {
1358
+ const errorDetails = errorModel.errors?.map((e) => `${e.location}: ${e.message}`).join("; ");
1359
+ const fullMessage = errorDetails ? `${errorModel.detail} - ${errorDetails}` : errorModel.detail;
1360
+ if (response.status === 401 || response.status === 403) throw new AuthenticationError(fullMessage);
1361
+ throw new McpdError(`${errorModel.title || "Request failed"}: ${fullMessage}`);
1362
+ }
1363
+ if (response.status === 401 || response.status === 403) throw new AuthenticationError(`Authentication failed: ${response.status} ${response.statusText}`);
1364
+ throw new McpdError(`Request failed: ${response.status} ${response.statusText} - ${body}`);
1365
+ }
1366
+ try {
1367
+ return await response.json();
1368
+ } catch (error) {
1369
+ throw new McpdError("Failed to parse JSON response", error);
1370
+ }
1371
+ } catch (error) {
1372
+ clearTimeout(timeoutId);
1373
+ if (error.name === "AbortError") throw new TimeoutError(`Request timed out after ${this.#timeout}ms`, path, this.#timeout);
1374
+ if (error instanceof TypeError && error.message.includes("fetch")) throw new ConnectionError(`Cannot connect to mcpd daemon at ${this.#endpoint}. Is it running?`, error);
1375
+ if (error instanceof McpdError) throw error;
1376
+ throw new McpdError(`Request failed: ${error.message}`, error);
1377
+ }
1378
+ }
1379
+ /**
1380
+ * Get a list of all configured MCP servers.
1381
+ *
1382
+ * @returns Array of server names
1383
+ *
1384
+ * @throws {AuthenticationError} If API key was present and authentication fails
1385
+ * @throws {ConnectionError} If unable to connect to the mcpd daemon
1386
+ * @throws {TimeoutError} If the request times out
1387
+ * @throws {McpdError} If the request fails
1388
+ *
1389
+ * @example
1390
+ * ```typescript
1391
+ * const servers = await client.listServers();
1392
+ * console.log(servers); // ['time', 'fetch', 'git']
1393
+ * ```
1394
+ */
1395
+ async listServers() {
1396
+ return await this.#request(API_PATHS.SERVERS);
1397
+ }
1398
+ /**
1399
+ * Get tool schemas for a server.
1400
+ *
1401
+ * @privateRemarks
1402
+ * Used by dependency injection for ServersNamespace and internally for getAgentTools.
1403
+ *
1404
+ * @param serverName - Server name to get tools for
1405
+ *
1406
+ * @returns Tool schemas for the specified server
1407
+ *
1408
+ * @throws {ServerNotFoundError} If the specified server doesn't exist
1409
+ * @throws {ServerUnhealthyError} If the server is not healthy
1410
+ *
1411
+ * @throws {AuthenticationError} If API key was present and authentication fails
1412
+ * @throws {ConnectionError} If unable to connect to the mcpd daemon
1413
+ * @throws {TimeoutError} If the request times out
1414
+ * @throws {McpdError} If the request fails
1415
+ *
1416
+ * @internal
1417
+ */
1418
+ async #getToolsByServer(serverName) {
1419
+ await this.#ensureServerHealthy(serverName);
1420
+ const path = API_PATHS.SERVER_TOOLS(serverName);
1421
+ const response = await this.#request(path);
1422
+ if (!response.tools) throw new ServerNotFoundError(`Server '${serverName}' not found`, serverName);
1423
+ return response.tools;
1424
+ }
1425
+ /**
1426
+ * Get prompt schemas for a server.
1427
+ *
1428
+ * @privateRemarks
1429
+ * Used internally for getPromptSchemas.
1430
+ *
1431
+ * @param serverName - Server name to get prompts for
1432
+ * @param cursor - Cursor for pagination
1433
+ *
1434
+ * @returns Prompt schemas for the specified server
1435
+ *
1436
+ * @throws {ServerNotFoundError} If the specified server doesn't exist
1437
+ * @throws {ServerUnhealthyError} If the server is not healthy
1438
+ *
1439
+ * @throws {AuthenticationError} If API key was present and authentication fails
1440
+ * @throws {ConnectionError} If unable to connect to the mcpd daemon
1441
+ * @throws {TimeoutError} If the request times out
1442
+ * @throws {McpdError} If the request fails
1443
+ *
1444
+ * @internal
1445
+ */
1446
+ async #getPromptsByServer(serverName, cursor) {
1447
+ try {
1448
+ await this.#ensureServerHealthy(serverName);
1449
+ const path = API_PATHS.SERVER_PROMPTS(serverName, cursor);
1450
+ return (await this.#request(path)).prompts || [];
1451
+ } catch (error) {
1452
+ if (error instanceof McpdError && error.message.includes("501") && error.message.includes("Not Implemented")) return [];
1453
+ throw error;
1454
+ }
1455
+ }
1456
+ /**
1457
+ * Generate a prompt on a server.
1458
+ *
1459
+ * @privateRemarks
1460
+ * Used internally by:
1461
+ * - PromptsNamespace (via dependency injection)
1462
+ * - Server.generatePrompt() (via dependency injection)
1463
+ *
1464
+ * @param serverName - The name of the server
1465
+ * @param promptName - The exact name of the prompt
1466
+ * @param args - The prompt arguments
1467
+ *
1468
+ * @returns The generated prompt response
1469
+ *
1470
+ * @throws {AuthenticationError} If API key was present and authentication fails
1471
+ * @throws {ConnectionError} If unable to connect to the mcpd daemon
1472
+ * @throws {TimeoutError} If the request times out
1473
+ * @throws {McpdError} If the request fails
1474
+ *
1475
+ * @internal
1476
+ */
1477
+ async #generatePromptInternal(serverName, promptName, args) {
1478
+ await this.#ensureServerHealthy(serverName);
1479
+ const path = API_PATHS.PROMPT_GET_GENERATED(serverName, promptName);
1480
+ const requestBody = { arguments: args || {} };
1481
+ return await this.#request(path, {
1482
+ method: "POST",
1483
+ body: JSON.stringify(requestBody)
1484
+ });
1485
+ }
1486
+ /**
1487
+ * Get resource schemas for a server.
1488
+ *
1489
+ * @privateRemarks
1490
+ * Used internally for getResources and by dependency injection for ServersNamespace.
1491
+ *
1492
+ * @param serverName - Server name to get resources for
1493
+ * @param cursor - Cursor for pagination
1494
+ *
1495
+ * @returns Resource schemas for the specified server
1496
+ *
1497
+ * @throws {ServerNotFoundError} If the specified server doesn't exist
1498
+ * @throws {ServerUnhealthyError} If the server is not healthy
1499
+ *
1500
+ * @throws {AuthenticationError} If API key was present and authentication fails
1501
+ * @throws {ConnectionError} If unable to connect to the mcpd daemon
1502
+ * @throws {TimeoutError} If the request times out
1503
+ * @throws {McpdError} If the request fails
1504
+ *
1505
+ * @internal
1506
+ */
1507
+ async #getResourcesByServer(serverName, cursor) {
1508
+ try {
1509
+ await this.#ensureServerHealthy(serverName);
1510
+ const path = API_PATHS.SERVER_RESOURCES(serverName, cursor);
1511
+ return (await this.#request(path)).resources || [];
1512
+ } catch (error) {
1513
+ if (error instanceof McpdError && error.message.includes("501") && error.message.includes("Not Implemented")) return [];
1514
+ throw error;
1515
+ }
1516
+ }
1517
+ /**
1518
+ * Get resource template schemas for a server.
1519
+ *
1520
+ * @privateRemarks
1521
+ * Used internally for getResourceTemplates and by dependency injection for ServersNamespace.
1522
+ *
1523
+ * @param serverName - Server name to get resource templates for
1524
+ * @param cursor - Cursor for pagination
1525
+ *
1526
+ * @returns Resource template schemas for the specified server
1527
+ *
1528
+ * @throws {ServerNotFoundError} If the specified server doesn't exist
1529
+ * @throws {ServerUnhealthyError} If the server is not healthy
1530
+ *
1531
+ * @throws {AuthenticationError} If API key was present and authentication fails
1532
+ * @throws {ConnectionError} If unable to connect to the mcpd daemon
1533
+ * @throws {TimeoutError} If the request times out
1534
+ * @throws {McpdError} If the request fails
1535
+ *
1536
+ * @internal
1537
+ */
1538
+ async #getResourceTemplatesByServer(serverName, cursor) {
1539
+ try {
1540
+ await this.#ensureServerHealthy(serverName);
1541
+ const path = API_PATHS.SERVER_RESOURCE_TEMPLATES(serverName, cursor);
1542
+ return (await this.#request(path)).templates || [];
1543
+ } catch (error) {
1544
+ if (error instanceof McpdError && error.message.includes("501") && error.message.includes("Not Implemented")) return [];
1545
+ throw error;
1546
+ }
1547
+ }
1548
+ /**
1549
+ * Read resource content from a server.
1550
+ *
1551
+ * @privateRemarks
1552
+ * Used by dependency injection for ServersNamespace.
1553
+ *
1554
+ * @param serverName - Server name to read resource from
1555
+ * @param uri - The resource URI
1556
+ *
1557
+ * @returns Array of resource contents (text or blob)
1558
+ *
1559
+ * @throws {ServerNotFoundError} If the specified server doesn't exist
1560
+ * @throws {ServerUnhealthyError} If the server is not healthy
1561
+ *
1562
+ * @throws {AuthenticationError} If API key was present and authentication fails
1563
+ * @throws {ConnectionError} If unable to connect to the mcpd daemon
1564
+ * @throws {TimeoutError} If the request times out
1565
+ * @throws {McpdError} If the request fails
1566
+ *
1567
+ * @internal
1568
+ */
1569
+ async #readResourceByServer(serverName, uri) {
1570
+ await this.#ensureServerHealthy(serverName);
1571
+ const path = API_PATHS.RESOURCE_CONTENT(serverName, uri);
1572
+ return await this.#request(path) || [];
1573
+ }
1574
+ async getServerHealth(serverName) {
1575
+ if (serverName) {
1576
+ const cacheKey = `health:${serverName}`;
1577
+ const cached = this.#serverHealthCache.get(cacheKey);
1578
+ if (cached !== void 0) {
1579
+ if (cached instanceof Error) throw cached;
1580
+ return cached;
1581
+ }
1582
+ try {
1583
+ const path = API_PATHS.HEALTH_SERVER(serverName);
1584
+ const health = await this.#request(path);
1585
+ this.#serverHealthCache.set(cacheKey, health);
1586
+ return health;
1587
+ } catch (error) {
1588
+ if (error instanceof Error) {
1589
+ for (const errorType of this.#cacheableExceptions) if (error instanceof errorType) {
1590
+ this.#serverHealthCache.set(cacheKey, error);
1591
+ break;
1592
+ }
1593
+ }
1594
+ throw error;
1595
+ }
1596
+ } else {
1597
+ const response = await this.#request(API_PATHS.HEALTH_ALL);
1598
+ const healthMap = {};
1599
+ for (const server of response.servers) {
1600
+ healthMap[server.name] = server;
1601
+ const cacheKey = `health:${server.name}`;
1602
+ this.#serverHealthCache.set(cacheKey, server);
1603
+ }
1604
+ return healthMap;
1605
+ }
1606
+ }
1607
+ /**
1608
+ * Check if a specific server is healthy.
1609
+ *
1610
+ * @param serverName - The name of the server to check
1611
+ *
1612
+ * @returns True if the server is healthy, false otherwise
1613
+ *
1614
+ * @throws {AuthenticationError} If API key was present and authentication fails
1615
+ * @throws {ConnectionError} If unable to connect to the mcpd daemon
1616
+ * @throws {TimeoutError} If the request times out
1617
+ * @throws {McpdError} If the request fails
1618
+ *
1619
+ * @example
1620
+ * ```typescript
1621
+ * if (await client.isServerHealthy('time')) {
1622
+ * const time = await client.servers.time.get_current_time();
1623
+ * }
1624
+ * ```
1625
+ */
1626
+ async isServerHealthy(serverName) {
1627
+ try {
1628
+ const health = await this.getServerHealth(serverName);
1629
+ return HealthStatusHelpers.isHealthy(health.status);
1630
+ } catch (error) {
1631
+ if (error instanceof ServerNotFoundError) return false;
1632
+ throw error;
1633
+ }
1634
+ }
1635
+ /**
1636
+ * Ensure a server is healthy before performing an operation.
1637
+ *
1638
+ * @param serverName - The name of the server to check
1639
+ *
1640
+ * @throws {ServerNotFoundError} If the server doesn't exist
1641
+ * @throws {ServerUnhealthyError} If the server is not healthy
1642
+ *
1643
+ * @throws {AuthenticationError} If API key was present and authentication fails
1644
+ * @throws {ConnectionError} If unable to connect to the mcpd daemon
1645
+ * @throws {TimeoutError} If the request times out
1646
+ * @throws {McpdError} If the request fails
1647
+ */
1648
+ async #ensureServerHealthy(serverName) {
1649
+ const health = await this.getServerHealth(serverName);
1650
+ if (!health) throw new ServerNotFoundError(`Server '${serverName}' not found`, serverName);
1651
+ if (!HealthStatusHelpers.isHealthy(health.status)) throw new ServerUnhealthyError(`Server '${serverName}' is not healthy: ${health.status}`, serverName, health.status);
1652
+ }
1653
+ /**
1654
+ * Get list of healthy servers.
1655
+ *
1656
+ * @remarks
1657
+ * If logging is enabled, warnings are logged for servers that do not exist or are unhealthy.
1658
+ *
1659
+ * @param servers - List of server names to use for health checking.
1660
+ * If not provided, or empty, checks health for all servers.
1661
+ *
1662
+ * @returns List of server names with 'ok' health status.
1663
+ *
1664
+ * @throws {AuthenticationError} If API key was present and authentication fails
1665
+ * @throws {ConnectionError} If unable to connect to the mcpd daemon
1666
+ * @throws {TimeoutError} If the request times out
1667
+ * @throws {McpdError} If the request fails
1668
+ */
1669
+ async #getHealthyServers(servers) {
1670
+ const serverNames = servers?.length ? servers : await this.listServers();
1671
+ const healthMap = await this.getServerHealth();
1672
+ return serverNames.filter((name) => {
1673
+ const health = healthMap[name];
1674
+ if (!health) {
1675
+ this.#logger.warn(`Skipping non-existent server '${name}'`);
1676
+ return false;
1677
+ }
1678
+ if (!HealthStatusHelpers.isHealthy(health.status)) {
1679
+ this.#logger.warn(`Skipping unhealthy server '${name}' with status '${health.status}'`);
1680
+ return false;
1681
+ }
1682
+ return true;
1683
+ });
1684
+ }
1685
+ /**
1686
+ * Perform a tool call on a server.
1687
+ *
1688
+ * @privateRemarks
1689
+ * Used internally by:
1690
+ * - ToolsNamespace (via dependency injection)
1691
+ * - FunctionBuilder (via dependency injection)
1692
+ *
1693
+ * @param serverName - The name of the server
1694
+ * @param toolName - The exact name of the tool
1695
+ * @param args - The tool arguments
1696
+ *
1697
+ * @returns The tool's response
1698
+ *
1699
+ * @throws {ToolExecutionError} If the tool execution fails
1700
+ *
1701
+ * @throws {AuthenticationError} If API key was present and authentication fails
1702
+ * @throws {ConnectionError} If unable to connect to the mcpd daemon
1703
+ * @throws {TimeoutError} If the request times out
1704
+ * @throws {McpdError} If the request fails
1705
+ *
1706
+ * @internal
1707
+ */
1708
+ async #performCall(serverName, toolName, args) {
1709
+ const path = API_PATHS.TOOL_CALL(serverName, toolName);
1710
+ try {
1711
+ const response = await this.#request(path, {
1712
+ method: "POST",
1713
+ body: JSON.stringify(args || {})
1714
+ });
1715
+ if (typeof response === "string") try {
1716
+ return JSON.parse(response);
1717
+ } catch {
1718
+ return response;
1719
+ }
1720
+ return response;
1721
+ } catch (error) {
1722
+ if (error instanceof PipelineError) throw new PipelineError(error.message, serverName, `${serverName}.${toolName}`, error.pipelineFlow, error.cause);
1723
+ if (error instanceof McpdError) throw error;
1724
+ throw new ToolExecutionError(`Failed to execute tool '${toolName}' on server '${serverName}': ${error.message}`, serverName, toolName, void 0, error);
1725
+ }
1726
+ }
1727
+ /**
1728
+ * Clear the cached agent tools functions.
1729
+ * This should be called when the tool schemas might have changed.
1730
+ */
1731
+ clearAgentToolsCache() {
1732
+ this.#functionBuilder.clearCache();
1733
+ }
1734
+ /**
1735
+ * Clear the server health cache.
1736
+ * This forces fresh health checks on the next getServerHealth() or isServerHealthy() call.
1737
+ */
1738
+ clearServerHealthCache() {
1739
+ this.#serverHealthCache.clear();
1740
+ }
1741
+ /**
1742
+ * Fetch and cache callable functions from all healthy servers.
1743
+ *
1744
+ * This method queries all healthy servers and creates self-contained, callable functions
1745
+ * that can be passed to AI agent frameworks. Each function includes its schema
1746
+ * as metadata and handles the MCP communication internally.
1747
+ *
1748
+ * Unhealthy servers are automatically filtered out and skipped (with optional warnings
1749
+ * when logging is enabled) to ensure the method returns quickly without waiting for timeouts.
1750
+ *
1751
+ * Tool fetches from multiple servers are executed concurrently for optimal performance.
1752
+ * Functions are cached indefinitely until explicitly cleared.
1753
+ *
1754
+ * @returns Array of callable functions with metadata from all healthy servers.
1755
+ *
1756
+ * @throws {AuthenticationError} If API key was present and authentication fails
1757
+ * @throws {ConnectionError} If unable to connect to the mcpd daemon
1758
+ * @throws {TimeoutError} If the request times out
1759
+ * @throws {McpdError} If the request fails
1760
+ *
1761
+ * @internal
1762
+ */
1763
+ async #agentTools() {
1764
+ const cachedFunctions = this.#functionBuilder.getCachedFunctions();
1765
+ if (cachedFunctions.length > 0) return cachedFunctions;
1766
+ const healthyServers = await this.#getHealthyServers();
1767
+ return (await Promise.allSettled(healthyServers.map(async (serverName) => ({
1768
+ serverName,
1769
+ tools: await this.#getToolsByServer(serverName)
1770
+ })))).filter((result) => result.status === "fulfilled").flatMap((result) => {
1771
+ const { serverName, tools } = result.value;
1772
+ return tools.map((toolSchema) => this.#functionBuilder.createFunctionFromSchema(toolSchema, serverName));
1773
+ });
1774
+ }
1775
+ async getAgentTools(options = {}) {
1776
+ const { servers, tools, format = "array", refreshCache = false } = options;
1777
+ if (refreshCache) this.#functionBuilder.clearCache();
1778
+ const filteredTools = (await this.#agentTools()).filter((tool) => !servers || servers.includes(tool._serverName)).filter((tool) => !tools || this.#matchesToolFilter(tool, tools));
1779
+ return {
1780
+ array: (t) => t,
1781
+ object: (t) => Object.fromEntries(t.map((tool) => [tool.name, tool])),
1782
+ map: (t) => new Map(t.map((tool) => [tool.name, tool]))
1783
+ }[format](filteredTools);
1784
+ }
1785
+ /**
1786
+ * Check if a tool matches the tool filter.
1787
+ *
1788
+ * Supports two formats:
1789
+ * - Raw tool name: "get_current_time" (matches across all servers)
1790
+ * - Server-prefixed: "time__get_current_time" (matches specific server + tool)
1791
+ *
1792
+ * @remarks
1793
+ * When a filter contains "__", it's first checked as server-prefixed (exact match).
1794
+ * If that fails, it's checked as a raw tool name. This handles tools whose names
1795
+ * contain "__" (e.g., "my__special__tool").
1796
+ *
1797
+ * @param tool The tool to match.
1798
+ * @param tools List of tool names to match against.
1799
+ *
1800
+ * @returns True if a match is found in tools, based on the predicate.
1801
+ *
1802
+ * @internal
1803
+ */
1804
+ #matchesToolFilter(tool, tools) {
1805
+ return tools.some((filterItem) => {
1806
+ if (filterItem.indexOf(TOOL_SEPARATOR) === -1) return filterItem === tool._toolName;
1807
+ return filterItem === tool.name || filterItem === tool._toolName;
1808
+ });
1809
+ }
1810
+ };
1811
+ //#endregion
1720
1812
  exports.AuthenticationError = AuthenticationError;
1721
1813
  exports.ConnectionError = ConnectionError;
1722
1814
  exports.HealthStatus = HealthStatus;
@@ -1732,4 +1824,5 @@ exports.TimeoutError = TimeoutError;
1732
1824
  exports.ToolExecutionError = ToolExecutionError;
1733
1825
  exports.ToolNotFoundError = ToolNotFoundError;
1734
1826
  exports.ValidationError = ValidationError;
1735
- //# sourceMappingURL=index.js.map
1827
+
1828
+ //# sourceMappingURL=index.js.map