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