@eaperezc/mcpgen 0.1.0
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/README.md +328 -0
- package/dist/cli/index.cjs +625 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +623 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.cjs +1516 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +297 -0
- package/dist/index.d.ts +297 -0
- package/dist/index.js +1481 -0
- package/dist/index.js.map +1 -0
- package/dist/testing/index.cjs +1056 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +183 -0
- package/dist/testing/index.d.ts +183 -0
- package/dist/testing/index.js +1049 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/types-B9yzEar_.d.cts +895 -0
- package/dist/types-B9yzEar_.d.ts +895 -0
- package/package.json +81 -0
|
@@ -0,0 +1,1056 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var zod = require('zod');
|
|
4
|
+
var async_hooks = require('async_hooks');
|
|
5
|
+
var crypto = require('crypto');
|
|
6
|
+
var pino = require('pino');
|
|
7
|
+
|
|
8
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
|
|
10
|
+
var pino__default = /*#__PURE__*/_interopDefault(pino);
|
|
11
|
+
|
|
12
|
+
// src/testing/TestClient.ts
|
|
13
|
+
|
|
14
|
+
// src/errors/McpError.ts
|
|
15
|
+
var McpError = class _McpError extends Error {
|
|
16
|
+
code;
|
|
17
|
+
details;
|
|
18
|
+
timestamp;
|
|
19
|
+
constructor(options) {
|
|
20
|
+
super(options.message);
|
|
21
|
+
this.name = "McpError";
|
|
22
|
+
this.code = options.code;
|
|
23
|
+
this.details = options.details;
|
|
24
|
+
this.timestamp = /* @__PURE__ */ new Date();
|
|
25
|
+
if (options.cause) {
|
|
26
|
+
this.cause = options.cause;
|
|
27
|
+
}
|
|
28
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Convert error to a JSON-serializable object
|
|
32
|
+
*/
|
|
33
|
+
toJSON() {
|
|
34
|
+
return {
|
|
35
|
+
name: this.name,
|
|
36
|
+
code: this.code,
|
|
37
|
+
message: this.message,
|
|
38
|
+
details: this.details,
|
|
39
|
+
timestamp: this.timestamp.toISOString(),
|
|
40
|
+
stack: this.stack
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Create an error for tool not found
|
|
45
|
+
*/
|
|
46
|
+
static toolNotFound(toolName) {
|
|
47
|
+
return new _McpError({
|
|
48
|
+
code: "TOOL_NOT_FOUND" /* TOOL_NOT_FOUND */,
|
|
49
|
+
message: `Tool '${toolName}' not found`,
|
|
50
|
+
details: { toolName }
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create an error for resource not found
|
|
55
|
+
*/
|
|
56
|
+
static resourceNotFound(uri) {
|
|
57
|
+
return new _McpError({
|
|
58
|
+
code: "RESOURCE_NOT_FOUND" /* RESOURCE_NOT_FOUND */,
|
|
59
|
+
message: `Resource '${uri}' not found`,
|
|
60
|
+
details: { uri }
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Create an error for prompt not found
|
|
65
|
+
*/
|
|
66
|
+
static promptNotFound(promptName) {
|
|
67
|
+
return new _McpError({
|
|
68
|
+
code: "PROMPT_NOT_FOUND" /* PROMPT_NOT_FOUND */,
|
|
69
|
+
message: `Prompt '${promptName}' not found`,
|
|
70
|
+
details: { promptName }
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create a validation error
|
|
75
|
+
*/
|
|
76
|
+
static validationError(message, details) {
|
|
77
|
+
return new _McpError({
|
|
78
|
+
code: "VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
79
|
+
message,
|
|
80
|
+
details
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Create an authentication error
|
|
85
|
+
*/
|
|
86
|
+
static authError(code, message) {
|
|
87
|
+
return new _McpError({
|
|
88
|
+
code,
|
|
89
|
+
message
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Create an internal error
|
|
94
|
+
*/
|
|
95
|
+
static internal(message, cause) {
|
|
96
|
+
return new _McpError({
|
|
97
|
+
code: "INTERNAL_ERROR" /* INTERNAL_ERROR */,
|
|
98
|
+
message,
|
|
99
|
+
cause
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// src/logging/types.ts
|
|
105
|
+
var LOG_LEVEL_PRIORITY = {
|
|
106
|
+
debug: 0,
|
|
107
|
+
info: 1,
|
|
108
|
+
warn: 2,
|
|
109
|
+
error: 3
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// src/logging/Logger.ts
|
|
113
|
+
var Logger = class _Logger {
|
|
114
|
+
pino;
|
|
115
|
+
config;
|
|
116
|
+
defaultContext;
|
|
117
|
+
constructor(config = {}) {
|
|
118
|
+
this.config = {
|
|
119
|
+
level: config.level ?? "info",
|
|
120
|
+
json: config.json ?? process.env["NODE_ENV"] === "production",
|
|
121
|
+
timestamps: config.timestamps ?? true,
|
|
122
|
+
pretty: config.pretty ?? process.env["NODE_ENV"] !== "production",
|
|
123
|
+
defaultContext: config.defaultContext ?? {}
|
|
124
|
+
};
|
|
125
|
+
this.defaultContext = this.config.defaultContext ?? {};
|
|
126
|
+
this.pino = pino__default.default({
|
|
127
|
+
level: this.config.level,
|
|
128
|
+
transport: this.config.pretty ? {
|
|
129
|
+
target: "pino-pretty",
|
|
130
|
+
options: {
|
|
131
|
+
colorize: true,
|
|
132
|
+
translateTime: "SYS:standard",
|
|
133
|
+
ignore: "pid,hostname"
|
|
134
|
+
}
|
|
135
|
+
} : void 0,
|
|
136
|
+
formatters: {
|
|
137
|
+
level: (label) => ({ level: label })
|
|
138
|
+
},
|
|
139
|
+
timestamp: this.config.timestamps ? pino__default.default.stdTimeFunctions.isoTime : false
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Check if a log level should be output
|
|
144
|
+
*/
|
|
145
|
+
shouldLog(level) {
|
|
146
|
+
return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.config.level];
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Create a child logger with additional context
|
|
150
|
+
*/
|
|
151
|
+
child(context) {
|
|
152
|
+
const child = new _Logger(this.config);
|
|
153
|
+
child.defaultContext = { ...this.defaultContext, ...context };
|
|
154
|
+
child.pino = this.pino.child(context);
|
|
155
|
+
return child;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Log a debug message
|
|
159
|
+
*/
|
|
160
|
+
debug(message, context) {
|
|
161
|
+
if (this.shouldLog("debug")) {
|
|
162
|
+
this.pino.debug({ ...this.defaultContext, ...context }, message);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Log an info message
|
|
167
|
+
*/
|
|
168
|
+
info(message, context) {
|
|
169
|
+
if (this.shouldLog("info")) {
|
|
170
|
+
this.pino.info({ ...this.defaultContext, ...context }, message);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Log a warning message
|
|
175
|
+
*/
|
|
176
|
+
warn(message, context) {
|
|
177
|
+
if (this.shouldLog("warn")) {
|
|
178
|
+
this.pino.warn({ ...this.defaultContext, ...context }, message);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Log an error message
|
|
183
|
+
*/
|
|
184
|
+
error(message, error) {
|
|
185
|
+
if (this.shouldLog("error")) {
|
|
186
|
+
if (error instanceof Error) {
|
|
187
|
+
this.pino.error(
|
|
188
|
+
{
|
|
189
|
+
...this.defaultContext,
|
|
190
|
+
err: {
|
|
191
|
+
message: error.message,
|
|
192
|
+
name: error.name,
|
|
193
|
+
stack: error.stack
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
message
|
|
197
|
+
);
|
|
198
|
+
} else {
|
|
199
|
+
this.pino.error({ ...this.defaultContext, ...error }, message);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Log with correlation ID for request tracing
|
|
205
|
+
*/
|
|
206
|
+
withCorrelationId(correlationId) {
|
|
207
|
+
return this.child({ correlationId });
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Set the log level
|
|
211
|
+
*/
|
|
212
|
+
setLevel(level) {
|
|
213
|
+
this.config.level = level;
|
|
214
|
+
this.pino.level = level;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get the current log level
|
|
218
|
+
*/
|
|
219
|
+
getLevel() {
|
|
220
|
+
return this.config.level;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
var logger = new Logger();
|
|
224
|
+
|
|
225
|
+
// src/context/RequestContext.ts
|
|
226
|
+
var asyncLocalStorage = new async_hooks.AsyncLocalStorage();
|
|
227
|
+
function createRequestContext(correlationId, logger2) {
|
|
228
|
+
const id = crypto.randomUUID();
|
|
229
|
+
const contextLogger = (logger2 ?? logger).withCorrelationId(id);
|
|
230
|
+
return {
|
|
231
|
+
correlationId: id,
|
|
232
|
+
startTime: /* @__PURE__ */ new Date(),
|
|
233
|
+
logger: contextLogger,
|
|
234
|
+
custom: /* @__PURE__ */ new Map()
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
async function runInRequestContextAsync(fn, context) {
|
|
238
|
+
const ctx = context ?? createRequestContext();
|
|
239
|
+
return asyncLocalStorage.run(ctx, fn);
|
|
240
|
+
}
|
|
241
|
+
function getRequestContext() {
|
|
242
|
+
return asyncLocalStorage.getStore();
|
|
243
|
+
}
|
|
244
|
+
function requireRequestContext() {
|
|
245
|
+
const context = getRequestContext();
|
|
246
|
+
if (!context) {
|
|
247
|
+
throw new Error("No request context available. Ensure code is running within runInRequestContext.");
|
|
248
|
+
}
|
|
249
|
+
return context;
|
|
250
|
+
}
|
|
251
|
+
function getContextLogger() {
|
|
252
|
+
return getRequestContext()?.logger ?? logger;
|
|
253
|
+
}
|
|
254
|
+
function setAuthContext(auth) {
|
|
255
|
+
const context = requireRequestContext();
|
|
256
|
+
context.auth = auth;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/tools/BaseTool.ts
|
|
260
|
+
var BaseTool = class {
|
|
261
|
+
/** Required scopes for this tool (optional) */
|
|
262
|
+
scopes = [];
|
|
263
|
+
/**
|
|
264
|
+
* Validate and execute the tool
|
|
265
|
+
*/
|
|
266
|
+
async run(input) {
|
|
267
|
+
const logger2 = getContextLogger();
|
|
268
|
+
const parseResult = this.schema.safeParse(input);
|
|
269
|
+
if (!parseResult.success) {
|
|
270
|
+
const errors = parseResult.error.errors.map((e) => ({
|
|
271
|
+
path: e.path.join("."),
|
|
272
|
+
message: e.message
|
|
273
|
+
}));
|
|
274
|
+
logger2.warn("Tool input validation failed", {
|
|
275
|
+
tool: this.name,
|
|
276
|
+
errors
|
|
277
|
+
});
|
|
278
|
+
throw new McpError({
|
|
279
|
+
code: "TOOL_VALIDATION_ERROR" /* TOOL_VALIDATION_ERROR */,
|
|
280
|
+
message: `Invalid input for tool '${this.name}'`,
|
|
281
|
+
details: { errors }
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
logger2.debug("Executing tool", { tool: this.name });
|
|
286
|
+
const result = await this.execute(parseResult.data);
|
|
287
|
+
logger2.debug("Tool executed successfully", { tool: this.name });
|
|
288
|
+
return result;
|
|
289
|
+
} catch (error) {
|
|
290
|
+
if (error instanceof McpError) {
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
293
|
+
logger2.error("Tool execution failed", error instanceof Error ? error : void 0);
|
|
294
|
+
throw new McpError({
|
|
295
|
+
code: "TOOL_EXECUTION_ERROR" /* TOOL_EXECUTION_ERROR */,
|
|
296
|
+
message: `Tool '${this.name}' execution failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
297
|
+
cause: error instanceof Error ? error : void 0
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Get tool metadata for registration
|
|
303
|
+
*/
|
|
304
|
+
getMetadata() {
|
|
305
|
+
return {
|
|
306
|
+
name: this.name,
|
|
307
|
+
description: this.description,
|
|
308
|
+
inputSchema: this.schema,
|
|
309
|
+
scopes: this.scopes
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Convert schema to JSON Schema format for MCP protocol
|
|
314
|
+
*/
|
|
315
|
+
getJsonSchema() {
|
|
316
|
+
return zodToJsonSchema(this.schema);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
function zodToJsonSchema(schema) {
|
|
320
|
+
const def = schema._def;
|
|
321
|
+
const typeName = def.typeName;
|
|
322
|
+
if (typeName === "ZodObject") {
|
|
323
|
+
const shape = schema.shape;
|
|
324
|
+
const properties = {};
|
|
325
|
+
const required = [];
|
|
326
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
327
|
+
const zodValue = value;
|
|
328
|
+
properties[key] = zodToJsonSchema(zodValue);
|
|
329
|
+
if (!zodValue.isOptional()) {
|
|
330
|
+
required.push(key);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
type: "object",
|
|
335
|
+
properties,
|
|
336
|
+
required: required.length > 0 ? required : void 0
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
if (typeName === "ZodString") {
|
|
340
|
+
const result = { type: "string" };
|
|
341
|
+
if (def.description) result["description"] = def.description;
|
|
342
|
+
return result;
|
|
343
|
+
}
|
|
344
|
+
if (typeName === "ZodNumber") {
|
|
345
|
+
const result = { type: "number" };
|
|
346
|
+
if (def.description) result["description"] = def.description;
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
if (typeName === "ZodBoolean") {
|
|
350
|
+
const result = { type: "boolean" };
|
|
351
|
+
if (def.description) result["description"] = def.description;
|
|
352
|
+
return result;
|
|
353
|
+
}
|
|
354
|
+
if (typeName === "ZodArray") {
|
|
355
|
+
return {
|
|
356
|
+
type: "array",
|
|
357
|
+
items: zodToJsonSchema(def.type)
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
if (typeName === "ZodOptional") {
|
|
361
|
+
return zodToJsonSchema(def.innerType);
|
|
362
|
+
}
|
|
363
|
+
if (typeName === "ZodDefault") {
|
|
364
|
+
const inner = zodToJsonSchema(def.innerType);
|
|
365
|
+
return { ...inner, default: def.defaultValue() };
|
|
366
|
+
}
|
|
367
|
+
return {};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// src/tools/registry.ts
|
|
371
|
+
var InlineTool = class extends BaseTool {
|
|
372
|
+
name;
|
|
373
|
+
description;
|
|
374
|
+
schema;
|
|
375
|
+
scopes;
|
|
376
|
+
handler;
|
|
377
|
+
constructor(name, definition) {
|
|
378
|
+
super();
|
|
379
|
+
this.name = name;
|
|
380
|
+
this.description = definition.description;
|
|
381
|
+
this.schema = definition.schema;
|
|
382
|
+
this.scopes = definition.scopes ?? [];
|
|
383
|
+
this.handler = definition.handler;
|
|
384
|
+
}
|
|
385
|
+
async execute(input) {
|
|
386
|
+
return this.handler(input);
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
var ToolRegistry = class {
|
|
390
|
+
tools = /* @__PURE__ */ new Map();
|
|
391
|
+
/**
|
|
392
|
+
* Register a tool instance
|
|
393
|
+
*/
|
|
394
|
+
register(tool) {
|
|
395
|
+
if (this.tools.has(tool.name)) {
|
|
396
|
+
throw new McpError({
|
|
397
|
+
code: "TOOL_ALREADY_REGISTERED" /* TOOL_ALREADY_REGISTERED */,
|
|
398
|
+
message: `Tool '${tool.name}' is already registered`,
|
|
399
|
+
details: { toolName: tool.name }
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
this.tools.set(tool.name, tool);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Register an inline tool definition
|
|
406
|
+
*/
|
|
407
|
+
registerInline(name, definition) {
|
|
408
|
+
const tool = new InlineTool(name, definition);
|
|
409
|
+
this.register(tool);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Get a tool by name
|
|
413
|
+
*/
|
|
414
|
+
get(name) {
|
|
415
|
+
return this.tools.get(name);
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Get a tool by name or throw if not found
|
|
419
|
+
*/
|
|
420
|
+
getOrThrow(name) {
|
|
421
|
+
const tool = this.tools.get(name);
|
|
422
|
+
if (!tool) {
|
|
423
|
+
throw McpError.toolNotFound(name);
|
|
424
|
+
}
|
|
425
|
+
return tool;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Check if a tool exists
|
|
429
|
+
*/
|
|
430
|
+
has(name) {
|
|
431
|
+
return this.tools.has(name);
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Remove a tool
|
|
435
|
+
*/
|
|
436
|
+
remove(name) {
|
|
437
|
+
return this.tools.delete(name);
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Get all registered tools
|
|
441
|
+
*/
|
|
442
|
+
getAll() {
|
|
443
|
+
return Array.from(this.tools.values());
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Get all tool names
|
|
447
|
+
*/
|
|
448
|
+
getNames() {
|
|
449
|
+
return Array.from(this.tools.keys());
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Get the number of registered tools
|
|
453
|
+
*/
|
|
454
|
+
get size() {
|
|
455
|
+
return this.tools.size;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Clear all tools
|
|
459
|
+
*/
|
|
460
|
+
clear() {
|
|
461
|
+
this.tools.clear();
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// src/resources/BaseResource.ts
|
|
466
|
+
var BaseResource = class {
|
|
467
|
+
/** Description of the resource */
|
|
468
|
+
description;
|
|
469
|
+
/** MIME type of the resource content */
|
|
470
|
+
mimeType = "text/plain";
|
|
471
|
+
/** Required scopes for this resource (optional) */
|
|
472
|
+
scopes = [];
|
|
473
|
+
/**
|
|
474
|
+
* Safely read the resource with error handling
|
|
475
|
+
*/
|
|
476
|
+
async safeRead() {
|
|
477
|
+
const logger2 = getContextLogger();
|
|
478
|
+
try {
|
|
479
|
+
logger2.debug("Reading resource", { uri: this.uri });
|
|
480
|
+
const result = await this.read();
|
|
481
|
+
logger2.debug("Resource read successfully", { uri: this.uri });
|
|
482
|
+
return result;
|
|
483
|
+
} catch (error) {
|
|
484
|
+
if (error instanceof McpError) {
|
|
485
|
+
throw error;
|
|
486
|
+
}
|
|
487
|
+
logger2.error("Resource read failed", error instanceof Error ? error : void 0);
|
|
488
|
+
throw new McpError({
|
|
489
|
+
code: "RESOURCE_READ_ERROR" /* RESOURCE_READ_ERROR */,
|
|
490
|
+
message: `Failed to read resource '${this.uri}': ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
491
|
+
cause: error instanceof Error ? error : void 0,
|
|
492
|
+
details: { uri: this.uri }
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Get resource metadata for registration
|
|
498
|
+
*/
|
|
499
|
+
getMetadata() {
|
|
500
|
+
return {
|
|
501
|
+
uri: this.uri,
|
|
502
|
+
name: this.name,
|
|
503
|
+
description: this.description,
|
|
504
|
+
mimeType: this.mimeType,
|
|
505
|
+
scopes: this.scopes
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Helper to create text content
|
|
510
|
+
*/
|
|
511
|
+
text(content) {
|
|
512
|
+
return { type: "text", text: content };
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Helper to create JSON content
|
|
516
|
+
*/
|
|
517
|
+
json(data) {
|
|
518
|
+
return { type: "text", text: JSON.stringify(data, null, 2) };
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Helper to create blob content
|
|
522
|
+
*/
|
|
523
|
+
blob(data, mimeType) {
|
|
524
|
+
return { type: "blob", data, mimeType };
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
// src/resources/registry.ts
|
|
529
|
+
var InlineResource = class extends BaseResource {
|
|
530
|
+
uri;
|
|
531
|
+
name;
|
|
532
|
+
description;
|
|
533
|
+
mimeType;
|
|
534
|
+
scopes;
|
|
535
|
+
handler;
|
|
536
|
+
constructor(uri, definition) {
|
|
537
|
+
super();
|
|
538
|
+
this.uri = uri;
|
|
539
|
+
this.name = definition.name;
|
|
540
|
+
this.description = definition.description;
|
|
541
|
+
this.mimeType = definition.mimeType;
|
|
542
|
+
this.scopes = definition.scopes ?? [];
|
|
543
|
+
this.handler = definition.read;
|
|
544
|
+
}
|
|
545
|
+
async read() {
|
|
546
|
+
return this.handler();
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
var ResourceRegistry = class {
|
|
550
|
+
resources = /* @__PURE__ */ new Map();
|
|
551
|
+
/**
|
|
552
|
+
* Register a resource instance
|
|
553
|
+
*/
|
|
554
|
+
register(resource) {
|
|
555
|
+
if (this.resources.has(resource.uri)) {
|
|
556
|
+
throw new McpError({
|
|
557
|
+
code: "RESOURCE_ALREADY_REGISTERED" /* RESOURCE_ALREADY_REGISTERED */,
|
|
558
|
+
message: `Resource '${resource.uri}' is already registered`,
|
|
559
|
+
details: { uri: resource.uri }
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
this.resources.set(resource.uri, resource);
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Register an inline resource definition
|
|
566
|
+
*/
|
|
567
|
+
registerInline(uri, definition) {
|
|
568
|
+
const resource = new InlineResource(uri, definition);
|
|
569
|
+
this.register(resource);
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Get a resource by URI
|
|
573
|
+
*/
|
|
574
|
+
get(uri) {
|
|
575
|
+
return this.resources.get(uri);
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Get a resource by URI or throw if not found
|
|
579
|
+
*/
|
|
580
|
+
getOrThrow(uri) {
|
|
581
|
+
const resource = this.resources.get(uri);
|
|
582
|
+
if (!resource) {
|
|
583
|
+
throw McpError.resourceNotFound(uri);
|
|
584
|
+
}
|
|
585
|
+
return resource;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Check if a resource exists
|
|
589
|
+
*/
|
|
590
|
+
has(uri) {
|
|
591
|
+
return this.resources.has(uri);
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Remove a resource
|
|
595
|
+
*/
|
|
596
|
+
remove(uri) {
|
|
597
|
+
return this.resources.delete(uri);
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Get all registered resources
|
|
601
|
+
*/
|
|
602
|
+
getAll() {
|
|
603
|
+
return Array.from(this.resources.values());
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Get all resource URIs
|
|
607
|
+
*/
|
|
608
|
+
getUris() {
|
|
609
|
+
return Array.from(this.resources.keys());
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Get the number of registered resources
|
|
613
|
+
*/
|
|
614
|
+
get size() {
|
|
615
|
+
return this.resources.size;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Clear all resources
|
|
619
|
+
*/
|
|
620
|
+
clear() {
|
|
621
|
+
this.resources.clear();
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
// src/prompts/BasePrompt.ts
|
|
626
|
+
var BasePrompt = class {
|
|
627
|
+
/** Human-readable description */
|
|
628
|
+
description;
|
|
629
|
+
/** Zod schema for arguments validation */
|
|
630
|
+
arguments;
|
|
631
|
+
/** Required scopes for this prompt (optional) */
|
|
632
|
+
scopes = [];
|
|
633
|
+
/**
|
|
634
|
+
* Validate arguments and render the prompt
|
|
635
|
+
*/
|
|
636
|
+
async safeRender(args) {
|
|
637
|
+
const logger2 = getContextLogger();
|
|
638
|
+
if (this.arguments) {
|
|
639
|
+
const parseResult = this.arguments.safeParse(args);
|
|
640
|
+
if (!parseResult.success) {
|
|
641
|
+
const errors = parseResult.error.errors.map((e) => ({
|
|
642
|
+
path: e.path.join("."),
|
|
643
|
+
message: e.message
|
|
644
|
+
}));
|
|
645
|
+
logger2.warn("Prompt argument validation failed", {
|
|
646
|
+
prompt: this.name,
|
|
647
|
+
errors
|
|
648
|
+
});
|
|
649
|
+
throw new McpError({
|
|
650
|
+
code: "VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
651
|
+
message: `Invalid arguments for prompt '${this.name}'`,
|
|
652
|
+
details: { errors }
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
args = parseResult.data;
|
|
656
|
+
}
|
|
657
|
+
try {
|
|
658
|
+
logger2.debug("Rendering prompt", { prompt: this.name });
|
|
659
|
+
const result = await this.render(args);
|
|
660
|
+
logger2.debug("Prompt rendered successfully", { prompt: this.name });
|
|
661
|
+
return result;
|
|
662
|
+
} catch (error) {
|
|
663
|
+
if (error instanceof McpError) {
|
|
664
|
+
throw error;
|
|
665
|
+
}
|
|
666
|
+
logger2.error("Prompt render failed", error instanceof Error ? error : void 0);
|
|
667
|
+
throw new McpError({
|
|
668
|
+
code: "PROMPT_RENDER_ERROR" /* PROMPT_RENDER_ERROR */,
|
|
669
|
+
message: `Failed to render prompt '${this.name}': ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
670
|
+
cause: error instanceof Error ? error : void 0,
|
|
671
|
+
details: { promptName: this.name }
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Get prompt metadata for registration
|
|
677
|
+
*/
|
|
678
|
+
getMetadata() {
|
|
679
|
+
return {
|
|
680
|
+
name: this.name,
|
|
681
|
+
description: this.description,
|
|
682
|
+
arguments: this.getArgumentsMetadata(),
|
|
683
|
+
scopes: this.scopes
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Get arguments metadata from schema
|
|
688
|
+
*/
|
|
689
|
+
getArgumentsMetadata() {
|
|
690
|
+
if (!this.arguments) return [];
|
|
691
|
+
const def = this.arguments._def;
|
|
692
|
+
if (def.typeName !== "ZodObject") return [];
|
|
693
|
+
const shape = this.arguments.shape;
|
|
694
|
+
const result = [];
|
|
695
|
+
for (const [name, value] of Object.entries(shape)) {
|
|
696
|
+
const zodValue = value;
|
|
697
|
+
const zodDef = zodValue._def;
|
|
698
|
+
result.push({
|
|
699
|
+
name,
|
|
700
|
+
description: zodDef.description,
|
|
701
|
+
required: !zodValue.isOptional()
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
return result;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Helper to create a user message
|
|
708
|
+
*/
|
|
709
|
+
user(content) {
|
|
710
|
+
return { role: "user", content };
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Helper to create an assistant message
|
|
714
|
+
*/
|
|
715
|
+
assistant(content) {
|
|
716
|
+
return { role: "assistant", content };
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
// src/prompts/registry.ts
|
|
721
|
+
var InlinePrompt = class extends BasePrompt {
|
|
722
|
+
name;
|
|
723
|
+
description;
|
|
724
|
+
arguments;
|
|
725
|
+
scopes;
|
|
726
|
+
handler;
|
|
727
|
+
constructor(name, definition) {
|
|
728
|
+
super();
|
|
729
|
+
this.name = name;
|
|
730
|
+
this.description = definition.description;
|
|
731
|
+
this.arguments = definition.arguments;
|
|
732
|
+
this.scopes = definition.scopes ?? [];
|
|
733
|
+
this.handler = definition.render;
|
|
734
|
+
}
|
|
735
|
+
async render(args) {
|
|
736
|
+
return this.handler(args);
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
var PromptRegistry = class {
|
|
740
|
+
prompts = /* @__PURE__ */ new Map();
|
|
741
|
+
/**
|
|
742
|
+
* Register a prompt instance
|
|
743
|
+
*/
|
|
744
|
+
register(prompt) {
|
|
745
|
+
if (this.prompts.has(prompt.name)) {
|
|
746
|
+
throw new McpError({
|
|
747
|
+
code: "PROMPT_ALREADY_REGISTERED" /* PROMPT_ALREADY_REGISTERED */,
|
|
748
|
+
message: `Prompt '${prompt.name}' is already registered`,
|
|
749
|
+
details: { promptName: prompt.name }
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
this.prompts.set(prompt.name, prompt);
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Register an inline prompt definition
|
|
756
|
+
*/
|
|
757
|
+
registerInline(name, definition) {
|
|
758
|
+
const prompt = new InlinePrompt(name, definition);
|
|
759
|
+
this.register(prompt);
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Get a prompt by name
|
|
763
|
+
*/
|
|
764
|
+
get(name) {
|
|
765
|
+
return this.prompts.get(name);
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Get a prompt by name or throw if not found
|
|
769
|
+
*/
|
|
770
|
+
getOrThrow(name) {
|
|
771
|
+
const prompt = this.prompts.get(name);
|
|
772
|
+
if (!prompt) {
|
|
773
|
+
throw McpError.promptNotFound(name);
|
|
774
|
+
}
|
|
775
|
+
return prompt;
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Check if a prompt exists
|
|
779
|
+
*/
|
|
780
|
+
has(name) {
|
|
781
|
+
return this.prompts.has(name);
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Remove a prompt
|
|
785
|
+
*/
|
|
786
|
+
remove(name) {
|
|
787
|
+
return this.prompts.delete(name);
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Get all registered prompts
|
|
791
|
+
*/
|
|
792
|
+
getAll() {
|
|
793
|
+
return Array.from(this.prompts.values());
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Get all prompt names
|
|
797
|
+
*/
|
|
798
|
+
getNames() {
|
|
799
|
+
return Array.from(this.prompts.keys());
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Get the number of registered prompts
|
|
803
|
+
*/
|
|
804
|
+
get size() {
|
|
805
|
+
return this.prompts.size;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Clear all prompts
|
|
809
|
+
*/
|
|
810
|
+
clear() {
|
|
811
|
+
this.prompts.clear();
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
// src/testing/TestClient.ts
|
|
816
|
+
var MockTool = class extends BaseTool {
|
|
817
|
+
name;
|
|
818
|
+
description;
|
|
819
|
+
schema = zod.z.object({}).passthrough();
|
|
820
|
+
response;
|
|
821
|
+
error;
|
|
822
|
+
customHandler;
|
|
823
|
+
constructor(config) {
|
|
824
|
+
super();
|
|
825
|
+
this.name = config.name;
|
|
826
|
+
this.description = config.description ?? `Mock tool: ${config.name}`;
|
|
827
|
+
this.response = config.response;
|
|
828
|
+
this.error = config.error;
|
|
829
|
+
this.customHandler = config.handler;
|
|
830
|
+
}
|
|
831
|
+
async execute(input) {
|
|
832
|
+
if (this.error) {
|
|
833
|
+
throw this.error;
|
|
834
|
+
}
|
|
835
|
+
if (this.customHandler) {
|
|
836
|
+
const result = await this.customHandler(input);
|
|
837
|
+
return { content: result };
|
|
838
|
+
}
|
|
839
|
+
return { content: this.response ?? { success: true } };
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
var MockResource = class extends BaseResource {
|
|
843
|
+
uri;
|
|
844
|
+
name;
|
|
845
|
+
content;
|
|
846
|
+
error;
|
|
847
|
+
customHandler;
|
|
848
|
+
constructor(config) {
|
|
849
|
+
super();
|
|
850
|
+
this.uri = config.uri;
|
|
851
|
+
this.name = config.name ?? `Mock resource: ${config.uri}`;
|
|
852
|
+
this.content = config.content;
|
|
853
|
+
this.error = config.error;
|
|
854
|
+
this.customHandler = config.handler;
|
|
855
|
+
}
|
|
856
|
+
async read() {
|
|
857
|
+
if (this.error) {
|
|
858
|
+
throw this.error;
|
|
859
|
+
}
|
|
860
|
+
if (this.customHandler) {
|
|
861
|
+
const result = await this.customHandler();
|
|
862
|
+
const text2 = typeof result === "string" ? result : JSON.stringify(result);
|
|
863
|
+
return { contents: [{ type: "text", text: text2 }] };
|
|
864
|
+
}
|
|
865
|
+
const text = typeof this.content === "string" ? this.content : JSON.stringify(this.content ?? {});
|
|
866
|
+
return { contents: [{ type: "text", text }] };
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
var MockPrompt = class extends BasePrompt {
|
|
870
|
+
name;
|
|
871
|
+
description;
|
|
872
|
+
arguments = zod.z.object({}).passthrough();
|
|
873
|
+
messages;
|
|
874
|
+
error;
|
|
875
|
+
customHandler;
|
|
876
|
+
constructor(config) {
|
|
877
|
+
super();
|
|
878
|
+
this.name = config.name;
|
|
879
|
+
this.description = config.description;
|
|
880
|
+
this.messages = config.messages;
|
|
881
|
+
this.error = config.error;
|
|
882
|
+
this.customHandler = config.handler;
|
|
883
|
+
}
|
|
884
|
+
async render(args) {
|
|
885
|
+
if (this.error) {
|
|
886
|
+
throw this.error;
|
|
887
|
+
}
|
|
888
|
+
if (this.customHandler) {
|
|
889
|
+
return this.customHandler(args);
|
|
890
|
+
}
|
|
891
|
+
return {
|
|
892
|
+
messages: this.messages ?? [{ role: "user", content: "Mock prompt" }]
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
var TestClient = class _TestClient {
|
|
897
|
+
toolRegistry = new ToolRegistry();
|
|
898
|
+
resourceRegistry = new ResourceRegistry();
|
|
899
|
+
promptRegistry = new PromptRegistry();
|
|
900
|
+
config;
|
|
901
|
+
logger;
|
|
902
|
+
constructor(config = {}) {
|
|
903
|
+
this.config = config;
|
|
904
|
+
this.logger = new Logger({
|
|
905
|
+
level: config.debug ? "debug" : "error",
|
|
906
|
+
pretty: true
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Create a test client from an existing McpServer
|
|
911
|
+
*/
|
|
912
|
+
static fromServer(server, config) {
|
|
913
|
+
const client = new _TestClient(config);
|
|
914
|
+
for (const tool of server.getToolRegistry().getAll()) {
|
|
915
|
+
client.toolRegistry.register(tool);
|
|
916
|
+
}
|
|
917
|
+
for (const resource of server.getResourceRegistry().getAll()) {
|
|
918
|
+
client.resourceRegistry.register(resource);
|
|
919
|
+
}
|
|
920
|
+
for (const prompt of server.getPromptRegistry().getAll()) {
|
|
921
|
+
client.promptRegistry.register(prompt);
|
|
922
|
+
}
|
|
923
|
+
return client;
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Register a tool for testing
|
|
927
|
+
*/
|
|
928
|
+
registerTool(tool) {
|
|
929
|
+
this.toolRegistry.register(tool);
|
|
930
|
+
return this;
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Register a resource for testing
|
|
934
|
+
*/
|
|
935
|
+
registerResource(resource) {
|
|
936
|
+
this.resourceRegistry.register(resource);
|
|
937
|
+
return this;
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Register a prompt for testing
|
|
941
|
+
*/
|
|
942
|
+
registerPrompt(prompt) {
|
|
943
|
+
this.promptRegistry.register(prompt);
|
|
944
|
+
return this;
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Register a mock tool
|
|
948
|
+
*/
|
|
949
|
+
mockTool(config) {
|
|
950
|
+
this.toolRegistry.register(new MockTool(config));
|
|
951
|
+
return this;
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Register a mock resource
|
|
955
|
+
*/
|
|
956
|
+
mockResource(config) {
|
|
957
|
+
this.resourceRegistry.register(new MockResource(config));
|
|
958
|
+
return this;
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Register a mock prompt
|
|
962
|
+
*/
|
|
963
|
+
mockPrompt(config) {
|
|
964
|
+
this.promptRegistry.register(new MockPrompt(config));
|
|
965
|
+
return this;
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Call a tool
|
|
969
|
+
*/
|
|
970
|
+
async callTool(name, input) {
|
|
971
|
+
return runInRequestContextAsync(async () => {
|
|
972
|
+
if (this.config.auth) {
|
|
973
|
+
setAuthContext(this.config.auth);
|
|
974
|
+
}
|
|
975
|
+
const tool = this.toolRegistry.getOrThrow(name);
|
|
976
|
+
return tool.run(input ?? {});
|
|
977
|
+
}, createRequestContext(void 0, this.logger));
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Read a resource
|
|
981
|
+
*/
|
|
982
|
+
async readResource(uri) {
|
|
983
|
+
return runInRequestContextAsync(async () => {
|
|
984
|
+
if (this.config.auth) {
|
|
985
|
+
setAuthContext(this.config.auth);
|
|
986
|
+
}
|
|
987
|
+
const resource = this.resourceRegistry.getOrThrow(uri);
|
|
988
|
+
return resource.safeRead();
|
|
989
|
+
}, createRequestContext(void 0, this.logger));
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Render a prompt
|
|
993
|
+
*/
|
|
994
|
+
async renderPrompt(name, args) {
|
|
995
|
+
return runInRequestContextAsync(async () => {
|
|
996
|
+
if (this.config.auth) {
|
|
997
|
+
setAuthContext(this.config.auth);
|
|
998
|
+
}
|
|
999
|
+
const prompt = this.promptRegistry.getOrThrow(name);
|
|
1000
|
+
return prompt.safeRender(args ?? {});
|
|
1001
|
+
}, createRequestContext(void 0, this.logger));
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* List all registered tools
|
|
1005
|
+
*/
|
|
1006
|
+
listTools() {
|
|
1007
|
+
return this.toolRegistry.getNames();
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* List all registered resources
|
|
1011
|
+
*/
|
|
1012
|
+
listResources() {
|
|
1013
|
+
return this.resourceRegistry.getUris();
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* List all registered prompts
|
|
1017
|
+
*/
|
|
1018
|
+
listPrompts() {
|
|
1019
|
+
return this.promptRegistry.getNames();
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Check if a tool exists
|
|
1023
|
+
*/
|
|
1024
|
+
hasTool(name) {
|
|
1025
|
+
return this.toolRegistry.has(name);
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Check if a resource exists
|
|
1029
|
+
*/
|
|
1030
|
+
hasResource(uri) {
|
|
1031
|
+
return this.resourceRegistry.has(uri);
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Check if a prompt exists
|
|
1035
|
+
*/
|
|
1036
|
+
hasPrompt(name) {
|
|
1037
|
+
return this.promptRegistry.has(name);
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Clear all registered items
|
|
1041
|
+
*/
|
|
1042
|
+
clear() {
|
|
1043
|
+
this.toolRegistry.clear();
|
|
1044
|
+
this.resourceRegistry.clear();
|
|
1045
|
+
this.promptRegistry.clear();
|
|
1046
|
+
return this;
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
function createTestClient(config) {
|
|
1050
|
+
return new TestClient(config);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
exports.TestClient = TestClient;
|
|
1054
|
+
exports.createTestClient = createTestClient;
|
|
1055
|
+
//# sourceMappingURL=index.cjs.map
|
|
1056
|
+
//# sourceMappingURL=index.cjs.map
|