@mozilla-ai/mcpd 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1797 -1704
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1795 -1717
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,1735 +1,1813 @@
|
|
|
1
1
|
import { LRUCache } from "lru-cache";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
const
|
|
969
|
-
const
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
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
|
-
|
|
998
|
-
|
|
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
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
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
|
-
|
|
1010
|
-
|
|
1138
|
+
const candidate = raw?.toLowerCase();
|
|
1139
|
+
return candidate && candidate in ranks ? candidate : LogLevels.OFF;
|
|
1011
1140
|
}
|
|
1012
1141
|
function getLevel() {
|
|
1013
|
-
|
|
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
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
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
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
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
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
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
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
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
|
-
//#
|
|
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
|