@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
package/dist/index.js
ADDED
|
@@ -0,0 +1,1481 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
5
|
+
import { randomUUID } from 'crypto';
|
|
6
|
+
import pino from 'pino';
|
|
7
|
+
import { createServer } from 'http';
|
|
8
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
9
|
+
export { z } from 'zod';
|
|
10
|
+
|
|
11
|
+
// src/server/McpServer.ts
|
|
12
|
+
|
|
13
|
+
// src/errors/types.ts
|
|
14
|
+
var McpErrorCode = /* @__PURE__ */ ((McpErrorCode2) => {
|
|
15
|
+
McpErrorCode2["SERVER_NOT_INITIALIZED"] = "SERVER_NOT_INITIALIZED";
|
|
16
|
+
McpErrorCode2["SERVER_ALREADY_RUNNING"] = "SERVER_ALREADY_RUNNING";
|
|
17
|
+
McpErrorCode2["SERVER_SHUTDOWN_ERROR"] = "SERVER_SHUTDOWN_ERROR";
|
|
18
|
+
McpErrorCode2["TOOL_NOT_FOUND"] = "TOOL_NOT_FOUND";
|
|
19
|
+
McpErrorCode2["TOOL_EXECUTION_ERROR"] = "TOOL_EXECUTION_ERROR";
|
|
20
|
+
McpErrorCode2["TOOL_VALIDATION_ERROR"] = "TOOL_VALIDATION_ERROR";
|
|
21
|
+
McpErrorCode2["TOOL_ALREADY_REGISTERED"] = "TOOL_ALREADY_REGISTERED";
|
|
22
|
+
McpErrorCode2["RESOURCE_NOT_FOUND"] = "RESOURCE_NOT_FOUND";
|
|
23
|
+
McpErrorCode2["RESOURCE_READ_ERROR"] = "RESOURCE_READ_ERROR";
|
|
24
|
+
McpErrorCode2["RESOURCE_ALREADY_REGISTERED"] = "RESOURCE_ALREADY_REGISTERED";
|
|
25
|
+
McpErrorCode2["PROMPT_NOT_FOUND"] = "PROMPT_NOT_FOUND";
|
|
26
|
+
McpErrorCode2["PROMPT_RENDER_ERROR"] = "PROMPT_RENDER_ERROR";
|
|
27
|
+
McpErrorCode2["PROMPT_ALREADY_REGISTERED"] = "PROMPT_ALREADY_REGISTERED";
|
|
28
|
+
McpErrorCode2["AUTH_INVALID_TOKEN"] = "AUTH_INVALID_TOKEN";
|
|
29
|
+
McpErrorCode2["AUTH_TOKEN_EXPIRED"] = "AUTH_TOKEN_EXPIRED";
|
|
30
|
+
McpErrorCode2["AUTH_INSUFFICIENT_SCOPE"] = "AUTH_INSUFFICIENT_SCOPE";
|
|
31
|
+
McpErrorCode2["AUTH_MISSING_CREDENTIALS"] = "AUTH_MISSING_CREDENTIALS";
|
|
32
|
+
McpErrorCode2["TRANSPORT_ERROR"] = "TRANSPORT_ERROR";
|
|
33
|
+
McpErrorCode2["TRANSPORT_CONNECTION_CLOSED"] = "TRANSPORT_CONNECTION_CLOSED";
|
|
34
|
+
McpErrorCode2["VALIDATION_ERROR"] = "VALIDATION_ERROR";
|
|
35
|
+
McpErrorCode2["SCHEMA_ERROR"] = "SCHEMA_ERROR";
|
|
36
|
+
McpErrorCode2["INTERNAL_ERROR"] = "INTERNAL_ERROR";
|
|
37
|
+
McpErrorCode2["NOT_IMPLEMENTED"] = "NOT_IMPLEMENTED";
|
|
38
|
+
return McpErrorCode2;
|
|
39
|
+
})(McpErrorCode || {});
|
|
40
|
+
|
|
41
|
+
// src/errors/McpError.ts
|
|
42
|
+
var McpError = class _McpError extends Error {
|
|
43
|
+
code;
|
|
44
|
+
details;
|
|
45
|
+
timestamp;
|
|
46
|
+
constructor(options) {
|
|
47
|
+
super(options.message);
|
|
48
|
+
this.name = "McpError";
|
|
49
|
+
this.code = options.code;
|
|
50
|
+
this.details = options.details;
|
|
51
|
+
this.timestamp = /* @__PURE__ */ new Date();
|
|
52
|
+
if (options.cause) {
|
|
53
|
+
this.cause = options.cause;
|
|
54
|
+
}
|
|
55
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Convert error to a JSON-serializable object
|
|
59
|
+
*/
|
|
60
|
+
toJSON() {
|
|
61
|
+
return {
|
|
62
|
+
name: this.name,
|
|
63
|
+
code: this.code,
|
|
64
|
+
message: this.message,
|
|
65
|
+
details: this.details,
|
|
66
|
+
timestamp: this.timestamp.toISOString(),
|
|
67
|
+
stack: this.stack
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Create an error for tool not found
|
|
72
|
+
*/
|
|
73
|
+
static toolNotFound(toolName) {
|
|
74
|
+
return new _McpError({
|
|
75
|
+
code: "TOOL_NOT_FOUND" /* TOOL_NOT_FOUND */,
|
|
76
|
+
message: `Tool '${toolName}' not found`,
|
|
77
|
+
details: { toolName }
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create an error for resource not found
|
|
82
|
+
*/
|
|
83
|
+
static resourceNotFound(uri) {
|
|
84
|
+
return new _McpError({
|
|
85
|
+
code: "RESOURCE_NOT_FOUND" /* RESOURCE_NOT_FOUND */,
|
|
86
|
+
message: `Resource '${uri}' not found`,
|
|
87
|
+
details: { uri }
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Create an error for prompt not found
|
|
92
|
+
*/
|
|
93
|
+
static promptNotFound(promptName) {
|
|
94
|
+
return new _McpError({
|
|
95
|
+
code: "PROMPT_NOT_FOUND" /* PROMPT_NOT_FOUND */,
|
|
96
|
+
message: `Prompt '${promptName}' not found`,
|
|
97
|
+
details: { promptName }
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Create a validation error
|
|
102
|
+
*/
|
|
103
|
+
static validationError(message, details) {
|
|
104
|
+
return new _McpError({
|
|
105
|
+
code: "VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
106
|
+
message,
|
|
107
|
+
details
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Create an authentication error
|
|
112
|
+
*/
|
|
113
|
+
static authError(code, message) {
|
|
114
|
+
return new _McpError({
|
|
115
|
+
code,
|
|
116
|
+
message
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Create an internal error
|
|
121
|
+
*/
|
|
122
|
+
static internal(message, cause) {
|
|
123
|
+
return new _McpError({
|
|
124
|
+
code: "INTERNAL_ERROR" /* INTERNAL_ERROR */,
|
|
125
|
+
message,
|
|
126
|
+
cause
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// src/logging/types.ts
|
|
132
|
+
var LOG_LEVEL_PRIORITY = {
|
|
133
|
+
debug: 0,
|
|
134
|
+
info: 1,
|
|
135
|
+
warn: 2,
|
|
136
|
+
error: 3
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// src/logging/Logger.ts
|
|
140
|
+
var Logger = class _Logger {
|
|
141
|
+
pino;
|
|
142
|
+
config;
|
|
143
|
+
defaultContext;
|
|
144
|
+
constructor(config = {}) {
|
|
145
|
+
this.config = {
|
|
146
|
+
level: config.level ?? "info",
|
|
147
|
+
json: config.json ?? process.env["NODE_ENV"] === "production",
|
|
148
|
+
timestamps: config.timestamps ?? true,
|
|
149
|
+
pretty: config.pretty ?? process.env["NODE_ENV"] !== "production",
|
|
150
|
+
defaultContext: config.defaultContext ?? {}
|
|
151
|
+
};
|
|
152
|
+
this.defaultContext = this.config.defaultContext ?? {};
|
|
153
|
+
this.pino = pino({
|
|
154
|
+
level: this.config.level,
|
|
155
|
+
transport: this.config.pretty ? {
|
|
156
|
+
target: "pino-pretty",
|
|
157
|
+
options: {
|
|
158
|
+
colorize: true,
|
|
159
|
+
translateTime: "SYS:standard",
|
|
160
|
+
ignore: "pid,hostname"
|
|
161
|
+
}
|
|
162
|
+
} : void 0,
|
|
163
|
+
formatters: {
|
|
164
|
+
level: (label) => ({ level: label })
|
|
165
|
+
},
|
|
166
|
+
timestamp: this.config.timestamps ? pino.stdTimeFunctions.isoTime : false
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Check if a log level should be output
|
|
171
|
+
*/
|
|
172
|
+
shouldLog(level) {
|
|
173
|
+
return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.config.level];
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Create a child logger with additional context
|
|
177
|
+
*/
|
|
178
|
+
child(context) {
|
|
179
|
+
const child = new _Logger(this.config);
|
|
180
|
+
child.defaultContext = { ...this.defaultContext, ...context };
|
|
181
|
+
child.pino = this.pino.child(context);
|
|
182
|
+
return child;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Log a debug message
|
|
186
|
+
*/
|
|
187
|
+
debug(message, context) {
|
|
188
|
+
if (this.shouldLog("debug")) {
|
|
189
|
+
this.pino.debug({ ...this.defaultContext, ...context }, message);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Log an info message
|
|
194
|
+
*/
|
|
195
|
+
info(message, context) {
|
|
196
|
+
if (this.shouldLog("info")) {
|
|
197
|
+
this.pino.info({ ...this.defaultContext, ...context }, message);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Log a warning message
|
|
202
|
+
*/
|
|
203
|
+
warn(message, context) {
|
|
204
|
+
if (this.shouldLog("warn")) {
|
|
205
|
+
this.pino.warn({ ...this.defaultContext, ...context }, message);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Log an error message
|
|
210
|
+
*/
|
|
211
|
+
error(message, error) {
|
|
212
|
+
if (this.shouldLog("error")) {
|
|
213
|
+
if (error instanceof Error) {
|
|
214
|
+
this.pino.error(
|
|
215
|
+
{
|
|
216
|
+
...this.defaultContext,
|
|
217
|
+
err: {
|
|
218
|
+
message: error.message,
|
|
219
|
+
name: error.name,
|
|
220
|
+
stack: error.stack
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
message
|
|
224
|
+
);
|
|
225
|
+
} else {
|
|
226
|
+
this.pino.error({ ...this.defaultContext, ...error }, message);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Log with correlation ID for request tracing
|
|
232
|
+
*/
|
|
233
|
+
withCorrelationId(correlationId) {
|
|
234
|
+
return this.child({ correlationId });
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Set the log level
|
|
238
|
+
*/
|
|
239
|
+
setLevel(level) {
|
|
240
|
+
this.config.level = level;
|
|
241
|
+
this.pino.level = level;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Get the current log level
|
|
245
|
+
*/
|
|
246
|
+
getLevel() {
|
|
247
|
+
return this.config.level;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
var logger = new Logger();
|
|
251
|
+
|
|
252
|
+
// src/context/RequestContext.ts
|
|
253
|
+
var asyncLocalStorage = new AsyncLocalStorage();
|
|
254
|
+
function createRequestContext(correlationId, logger2) {
|
|
255
|
+
const id = correlationId ?? randomUUID();
|
|
256
|
+
const contextLogger = (logger2 ?? logger).withCorrelationId(id);
|
|
257
|
+
return {
|
|
258
|
+
correlationId: id,
|
|
259
|
+
startTime: /* @__PURE__ */ new Date(),
|
|
260
|
+
logger: contextLogger,
|
|
261
|
+
custom: /* @__PURE__ */ new Map()
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function runInRequestContext(fn, context) {
|
|
265
|
+
const ctx = context ?? createRequestContext();
|
|
266
|
+
return asyncLocalStorage.run(ctx, fn);
|
|
267
|
+
}
|
|
268
|
+
async function runInRequestContextAsync(fn, context) {
|
|
269
|
+
const ctx = context ?? createRequestContext();
|
|
270
|
+
return asyncLocalStorage.run(ctx, fn);
|
|
271
|
+
}
|
|
272
|
+
function getRequestContext() {
|
|
273
|
+
return asyncLocalStorage.getStore();
|
|
274
|
+
}
|
|
275
|
+
function requireRequestContext() {
|
|
276
|
+
const context = getRequestContext();
|
|
277
|
+
if (!context) {
|
|
278
|
+
throw new Error("No request context available. Ensure code is running within runInRequestContext.");
|
|
279
|
+
}
|
|
280
|
+
return context;
|
|
281
|
+
}
|
|
282
|
+
function getCorrelationId() {
|
|
283
|
+
return getRequestContext()?.correlationId;
|
|
284
|
+
}
|
|
285
|
+
function getContextLogger() {
|
|
286
|
+
return getRequestContext()?.logger ?? logger;
|
|
287
|
+
}
|
|
288
|
+
function setAuthContext(auth) {
|
|
289
|
+
const context = requireRequestContext();
|
|
290
|
+
context.auth = auth;
|
|
291
|
+
}
|
|
292
|
+
function getAuthContext() {
|
|
293
|
+
return getRequestContext()?.auth;
|
|
294
|
+
}
|
|
295
|
+
function setContextValue(key, value) {
|
|
296
|
+
const context = requireRequestContext();
|
|
297
|
+
context.custom.set(key, value);
|
|
298
|
+
}
|
|
299
|
+
function getContextValue(key) {
|
|
300
|
+
return getRequestContext()?.custom.get(key);
|
|
301
|
+
}
|
|
302
|
+
function getRequestDuration() {
|
|
303
|
+
const context = getRequestContext();
|
|
304
|
+
if (!context) return void 0;
|
|
305
|
+
return Date.now() - context.startTime.getTime();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/tools/BaseTool.ts
|
|
309
|
+
var BaseTool = class {
|
|
310
|
+
/** Required scopes for this tool (optional) */
|
|
311
|
+
scopes = [];
|
|
312
|
+
/**
|
|
313
|
+
* Validate and execute the tool
|
|
314
|
+
*/
|
|
315
|
+
async run(input) {
|
|
316
|
+
const logger2 = getContextLogger();
|
|
317
|
+
const parseResult = this.schema.safeParse(input);
|
|
318
|
+
if (!parseResult.success) {
|
|
319
|
+
const errors = parseResult.error.errors.map((e) => ({
|
|
320
|
+
path: e.path.join("."),
|
|
321
|
+
message: e.message
|
|
322
|
+
}));
|
|
323
|
+
logger2.warn("Tool input validation failed", {
|
|
324
|
+
tool: this.name,
|
|
325
|
+
errors
|
|
326
|
+
});
|
|
327
|
+
throw new McpError({
|
|
328
|
+
code: "TOOL_VALIDATION_ERROR" /* TOOL_VALIDATION_ERROR */,
|
|
329
|
+
message: `Invalid input for tool '${this.name}'`,
|
|
330
|
+
details: { errors }
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
logger2.debug("Executing tool", { tool: this.name });
|
|
335
|
+
const result = await this.execute(parseResult.data);
|
|
336
|
+
logger2.debug("Tool executed successfully", { tool: this.name });
|
|
337
|
+
return result;
|
|
338
|
+
} catch (error) {
|
|
339
|
+
if (error instanceof McpError) {
|
|
340
|
+
throw error;
|
|
341
|
+
}
|
|
342
|
+
logger2.error("Tool execution failed", error instanceof Error ? error : void 0);
|
|
343
|
+
throw new McpError({
|
|
344
|
+
code: "TOOL_EXECUTION_ERROR" /* TOOL_EXECUTION_ERROR */,
|
|
345
|
+
message: `Tool '${this.name}' execution failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
346
|
+
cause: error instanceof Error ? error : void 0
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Get tool metadata for registration
|
|
352
|
+
*/
|
|
353
|
+
getMetadata() {
|
|
354
|
+
return {
|
|
355
|
+
name: this.name,
|
|
356
|
+
description: this.description,
|
|
357
|
+
inputSchema: this.schema,
|
|
358
|
+
scopes: this.scopes
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Convert schema to JSON Schema format for MCP protocol
|
|
363
|
+
*/
|
|
364
|
+
getJsonSchema() {
|
|
365
|
+
return zodToJsonSchema(this.schema);
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
function zodToJsonSchema(schema) {
|
|
369
|
+
const def = schema._def;
|
|
370
|
+
const typeName = def.typeName;
|
|
371
|
+
if (typeName === "ZodObject") {
|
|
372
|
+
const shape = schema.shape;
|
|
373
|
+
const properties = {};
|
|
374
|
+
const required = [];
|
|
375
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
376
|
+
const zodValue = value;
|
|
377
|
+
properties[key] = zodToJsonSchema(zodValue);
|
|
378
|
+
if (!zodValue.isOptional()) {
|
|
379
|
+
required.push(key);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
type: "object",
|
|
384
|
+
properties,
|
|
385
|
+
required: required.length > 0 ? required : void 0
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
if (typeName === "ZodString") {
|
|
389
|
+
const result = { type: "string" };
|
|
390
|
+
if (def.description) result["description"] = def.description;
|
|
391
|
+
return result;
|
|
392
|
+
}
|
|
393
|
+
if (typeName === "ZodNumber") {
|
|
394
|
+
const result = { type: "number" };
|
|
395
|
+
if (def.description) result["description"] = def.description;
|
|
396
|
+
return result;
|
|
397
|
+
}
|
|
398
|
+
if (typeName === "ZodBoolean") {
|
|
399
|
+
const result = { type: "boolean" };
|
|
400
|
+
if (def.description) result["description"] = def.description;
|
|
401
|
+
return result;
|
|
402
|
+
}
|
|
403
|
+
if (typeName === "ZodArray") {
|
|
404
|
+
return {
|
|
405
|
+
type: "array",
|
|
406
|
+
items: zodToJsonSchema(def.type)
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
if (typeName === "ZodOptional") {
|
|
410
|
+
return zodToJsonSchema(def.innerType);
|
|
411
|
+
}
|
|
412
|
+
if (typeName === "ZodDefault") {
|
|
413
|
+
const inner = zodToJsonSchema(def.innerType);
|
|
414
|
+
return { ...inner, default: def.defaultValue() };
|
|
415
|
+
}
|
|
416
|
+
return {};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// src/tools/registry.ts
|
|
420
|
+
var InlineTool = class extends BaseTool {
|
|
421
|
+
name;
|
|
422
|
+
description;
|
|
423
|
+
schema;
|
|
424
|
+
scopes;
|
|
425
|
+
handler;
|
|
426
|
+
constructor(name, definition) {
|
|
427
|
+
super();
|
|
428
|
+
this.name = name;
|
|
429
|
+
this.description = definition.description;
|
|
430
|
+
this.schema = definition.schema;
|
|
431
|
+
this.scopes = definition.scopes ?? [];
|
|
432
|
+
this.handler = definition.handler;
|
|
433
|
+
}
|
|
434
|
+
async execute(input) {
|
|
435
|
+
return this.handler(input);
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
var ToolRegistry = class {
|
|
439
|
+
tools = /* @__PURE__ */ new Map();
|
|
440
|
+
/**
|
|
441
|
+
* Register a tool instance
|
|
442
|
+
*/
|
|
443
|
+
register(tool) {
|
|
444
|
+
if (this.tools.has(tool.name)) {
|
|
445
|
+
throw new McpError({
|
|
446
|
+
code: "TOOL_ALREADY_REGISTERED" /* TOOL_ALREADY_REGISTERED */,
|
|
447
|
+
message: `Tool '${tool.name}' is already registered`,
|
|
448
|
+
details: { toolName: tool.name }
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
this.tools.set(tool.name, tool);
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Register an inline tool definition
|
|
455
|
+
*/
|
|
456
|
+
registerInline(name, definition) {
|
|
457
|
+
const tool = new InlineTool(name, definition);
|
|
458
|
+
this.register(tool);
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Get a tool by name
|
|
462
|
+
*/
|
|
463
|
+
get(name) {
|
|
464
|
+
return this.tools.get(name);
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Get a tool by name or throw if not found
|
|
468
|
+
*/
|
|
469
|
+
getOrThrow(name) {
|
|
470
|
+
const tool = this.tools.get(name);
|
|
471
|
+
if (!tool) {
|
|
472
|
+
throw McpError.toolNotFound(name);
|
|
473
|
+
}
|
|
474
|
+
return tool;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Check if a tool exists
|
|
478
|
+
*/
|
|
479
|
+
has(name) {
|
|
480
|
+
return this.tools.has(name);
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Remove a tool
|
|
484
|
+
*/
|
|
485
|
+
remove(name) {
|
|
486
|
+
return this.tools.delete(name);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Get all registered tools
|
|
490
|
+
*/
|
|
491
|
+
getAll() {
|
|
492
|
+
return Array.from(this.tools.values());
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Get all tool names
|
|
496
|
+
*/
|
|
497
|
+
getNames() {
|
|
498
|
+
return Array.from(this.tools.keys());
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Get the number of registered tools
|
|
502
|
+
*/
|
|
503
|
+
get size() {
|
|
504
|
+
return this.tools.size;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Clear all tools
|
|
508
|
+
*/
|
|
509
|
+
clear() {
|
|
510
|
+
this.tools.clear();
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
// src/resources/BaseResource.ts
|
|
515
|
+
var BaseResource = class {
|
|
516
|
+
/** Description of the resource */
|
|
517
|
+
description;
|
|
518
|
+
/** MIME type of the resource content */
|
|
519
|
+
mimeType = "text/plain";
|
|
520
|
+
/** Required scopes for this resource (optional) */
|
|
521
|
+
scopes = [];
|
|
522
|
+
/**
|
|
523
|
+
* Safely read the resource with error handling
|
|
524
|
+
*/
|
|
525
|
+
async safeRead() {
|
|
526
|
+
const logger2 = getContextLogger();
|
|
527
|
+
try {
|
|
528
|
+
logger2.debug("Reading resource", { uri: this.uri });
|
|
529
|
+
const result = await this.read();
|
|
530
|
+
logger2.debug("Resource read successfully", { uri: this.uri });
|
|
531
|
+
return result;
|
|
532
|
+
} catch (error) {
|
|
533
|
+
if (error instanceof McpError) {
|
|
534
|
+
throw error;
|
|
535
|
+
}
|
|
536
|
+
logger2.error("Resource read failed", error instanceof Error ? error : void 0);
|
|
537
|
+
throw new McpError({
|
|
538
|
+
code: "RESOURCE_READ_ERROR" /* RESOURCE_READ_ERROR */,
|
|
539
|
+
message: `Failed to read resource '${this.uri}': ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
540
|
+
cause: error instanceof Error ? error : void 0,
|
|
541
|
+
details: { uri: this.uri }
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Get resource metadata for registration
|
|
547
|
+
*/
|
|
548
|
+
getMetadata() {
|
|
549
|
+
return {
|
|
550
|
+
uri: this.uri,
|
|
551
|
+
name: this.name,
|
|
552
|
+
description: this.description,
|
|
553
|
+
mimeType: this.mimeType,
|
|
554
|
+
scopes: this.scopes
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Helper to create text content
|
|
559
|
+
*/
|
|
560
|
+
text(content) {
|
|
561
|
+
return { type: "text", text: content };
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Helper to create JSON content
|
|
565
|
+
*/
|
|
566
|
+
json(data) {
|
|
567
|
+
return { type: "text", text: JSON.stringify(data, null, 2) };
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Helper to create blob content
|
|
571
|
+
*/
|
|
572
|
+
blob(data, mimeType) {
|
|
573
|
+
return { type: "blob", data, mimeType };
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
// src/resources/registry.ts
|
|
578
|
+
var InlineResource = class extends BaseResource {
|
|
579
|
+
uri;
|
|
580
|
+
name;
|
|
581
|
+
description;
|
|
582
|
+
mimeType;
|
|
583
|
+
scopes;
|
|
584
|
+
handler;
|
|
585
|
+
constructor(uri, definition) {
|
|
586
|
+
super();
|
|
587
|
+
this.uri = uri;
|
|
588
|
+
this.name = definition.name;
|
|
589
|
+
this.description = definition.description;
|
|
590
|
+
this.mimeType = definition.mimeType;
|
|
591
|
+
this.scopes = definition.scopes ?? [];
|
|
592
|
+
this.handler = definition.read;
|
|
593
|
+
}
|
|
594
|
+
async read() {
|
|
595
|
+
return this.handler();
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
var ResourceRegistry = class {
|
|
599
|
+
resources = /* @__PURE__ */ new Map();
|
|
600
|
+
/**
|
|
601
|
+
* Register a resource instance
|
|
602
|
+
*/
|
|
603
|
+
register(resource) {
|
|
604
|
+
if (this.resources.has(resource.uri)) {
|
|
605
|
+
throw new McpError({
|
|
606
|
+
code: "RESOURCE_ALREADY_REGISTERED" /* RESOURCE_ALREADY_REGISTERED */,
|
|
607
|
+
message: `Resource '${resource.uri}' is already registered`,
|
|
608
|
+
details: { uri: resource.uri }
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
this.resources.set(resource.uri, resource);
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Register an inline resource definition
|
|
615
|
+
*/
|
|
616
|
+
registerInline(uri, definition) {
|
|
617
|
+
const resource = new InlineResource(uri, definition);
|
|
618
|
+
this.register(resource);
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Get a resource by URI
|
|
622
|
+
*/
|
|
623
|
+
get(uri) {
|
|
624
|
+
return this.resources.get(uri);
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Get a resource by URI or throw if not found
|
|
628
|
+
*/
|
|
629
|
+
getOrThrow(uri) {
|
|
630
|
+
const resource = this.resources.get(uri);
|
|
631
|
+
if (!resource) {
|
|
632
|
+
throw McpError.resourceNotFound(uri);
|
|
633
|
+
}
|
|
634
|
+
return resource;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Check if a resource exists
|
|
638
|
+
*/
|
|
639
|
+
has(uri) {
|
|
640
|
+
return this.resources.has(uri);
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Remove a resource
|
|
644
|
+
*/
|
|
645
|
+
remove(uri) {
|
|
646
|
+
return this.resources.delete(uri);
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Get all registered resources
|
|
650
|
+
*/
|
|
651
|
+
getAll() {
|
|
652
|
+
return Array.from(this.resources.values());
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Get all resource URIs
|
|
656
|
+
*/
|
|
657
|
+
getUris() {
|
|
658
|
+
return Array.from(this.resources.keys());
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Get the number of registered resources
|
|
662
|
+
*/
|
|
663
|
+
get size() {
|
|
664
|
+
return this.resources.size;
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Clear all resources
|
|
668
|
+
*/
|
|
669
|
+
clear() {
|
|
670
|
+
this.resources.clear();
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
// src/prompts/BasePrompt.ts
|
|
675
|
+
var BasePrompt = class {
|
|
676
|
+
/** Human-readable description */
|
|
677
|
+
description;
|
|
678
|
+
/** Zod schema for arguments validation */
|
|
679
|
+
arguments;
|
|
680
|
+
/** Required scopes for this prompt (optional) */
|
|
681
|
+
scopes = [];
|
|
682
|
+
/**
|
|
683
|
+
* Validate arguments and render the prompt
|
|
684
|
+
*/
|
|
685
|
+
async safeRender(args) {
|
|
686
|
+
const logger2 = getContextLogger();
|
|
687
|
+
if (this.arguments) {
|
|
688
|
+
const parseResult = this.arguments.safeParse(args);
|
|
689
|
+
if (!parseResult.success) {
|
|
690
|
+
const errors = parseResult.error.errors.map((e) => ({
|
|
691
|
+
path: e.path.join("."),
|
|
692
|
+
message: e.message
|
|
693
|
+
}));
|
|
694
|
+
logger2.warn("Prompt argument validation failed", {
|
|
695
|
+
prompt: this.name,
|
|
696
|
+
errors
|
|
697
|
+
});
|
|
698
|
+
throw new McpError({
|
|
699
|
+
code: "VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
700
|
+
message: `Invalid arguments for prompt '${this.name}'`,
|
|
701
|
+
details: { errors }
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
args = parseResult.data;
|
|
705
|
+
}
|
|
706
|
+
try {
|
|
707
|
+
logger2.debug("Rendering prompt", { prompt: this.name });
|
|
708
|
+
const result = await this.render(args);
|
|
709
|
+
logger2.debug("Prompt rendered successfully", { prompt: this.name });
|
|
710
|
+
return result;
|
|
711
|
+
} catch (error) {
|
|
712
|
+
if (error instanceof McpError) {
|
|
713
|
+
throw error;
|
|
714
|
+
}
|
|
715
|
+
logger2.error("Prompt render failed", error instanceof Error ? error : void 0);
|
|
716
|
+
throw new McpError({
|
|
717
|
+
code: "PROMPT_RENDER_ERROR" /* PROMPT_RENDER_ERROR */,
|
|
718
|
+
message: `Failed to render prompt '${this.name}': ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
719
|
+
cause: error instanceof Error ? error : void 0,
|
|
720
|
+
details: { promptName: this.name }
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Get prompt metadata for registration
|
|
726
|
+
*/
|
|
727
|
+
getMetadata() {
|
|
728
|
+
return {
|
|
729
|
+
name: this.name,
|
|
730
|
+
description: this.description,
|
|
731
|
+
arguments: this.getArgumentsMetadata(),
|
|
732
|
+
scopes: this.scopes
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Get arguments metadata from schema
|
|
737
|
+
*/
|
|
738
|
+
getArgumentsMetadata() {
|
|
739
|
+
if (!this.arguments) return [];
|
|
740
|
+
const def = this.arguments._def;
|
|
741
|
+
if (def.typeName !== "ZodObject") return [];
|
|
742
|
+
const shape = this.arguments.shape;
|
|
743
|
+
const result = [];
|
|
744
|
+
for (const [name, value] of Object.entries(shape)) {
|
|
745
|
+
const zodValue = value;
|
|
746
|
+
const zodDef = zodValue._def;
|
|
747
|
+
result.push({
|
|
748
|
+
name,
|
|
749
|
+
description: zodDef.description,
|
|
750
|
+
required: !zodValue.isOptional()
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
return result;
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Helper to create a user message
|
|
757
|
+
*/
|
|
758
|
+
user(content) {
|
|
759
|
+
return { role: "user", content };
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Helper to create an assistant message
|
|
763
|
+
*/
|
|
764
|
+
assistant(content) {
|
|
765
|
+
return { role: "assistant", content };
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
// src/prompts/registry.ts
|
|
770
|
+
var InlinePrompt = class extends BasePrompt {
|
|
771
|
+
name;
|
|
772
|
+
description;
|
|
773
|
+
arguments;
|
|
774
|
+
scopes;
|
|
775
|
+
handler;
|
|
776
|
+
constructor(name, definition) {
|
|
777
|
+
super();
|
|
778
|
+
this.name = name;
|
|
779
|
+
this.description = definition.description;
|
|
780
|
+
this.arguments = definition.arguments;
|
|
781
|
+
this.scopes = definition.scopes ?? [];
|
|
782
|
+
this.handler = definition.render;
|
|
783
|
+
}
|
|
784
|
+
async render(args) {
|
|
785
|
+
return this.handler(args);
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
var PromptRegistry = class {
|
|
789
|
+
prompts = /* @__PURE__ */ new Map();
|
|
790
|
+
/**
|
|
791
|
+
* Register a prompt instance
|
|
792
|
+
*/
|
|
793
|
+
register(prompt) {
|
|
794
|
+
if (this.prompts.has(prompt.name)) {
|
|
795
|
+
throw new McpError({
|
|
796
|
+
code: "PROMPT_ALREADY_REGISTERED" /* PROMPT_ALREADY_REGISTERED */,
|
|
797
|
+
message: `Prompt '${prompt.name}' is already registered`,
|
|
798
|
+
details: { promptName: prompt.name }
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
this.prompts.set(prompt.name, prompt);
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Register an inline prompt definition
|
|
805
|
+
*/
|
|
806
|
+
registerInline(name, definition) {
|
|
807
|
+
const prompt = new InlinePrompt(name, definition);
|
|
808
|
+
this.register(prompt);
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Get a prompt by name
|
|
812
|
+
*/
|
|
813
|
+
get(name) {
|
|
814
|
+
return this.prompts.get(name);
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Get a prompt by name or throw if not found
|
|
818
|
+
*/
|
|
819
|
+
getOrThrow(name) {
|
|
820
|
+
const prompt = this.prompts.get(name);
|
|
821
|
+
if (!prompt) {
|
|
822
|
+
throw McpError.promptNotFound(name);
|
|
823
|
+
}
|
|
824
|
+
return prompt;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Check if a prompt exists
|
|
828
|
+
*/
|
|
829
|
+
has(name) {
|
|
830
|
+
return this.prompts.has(name);
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Remove a prompt
|
|
834
|
+
*/
|
|
835
|
+
remove(name) {
|
|
836
|
+
return this.prompts.delete(name);
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Get all registered prompts
|
|
840
|
+
*/
|
|
841
|
+
getAll() {
|
|
842
|
+
return Array.from(this.prompts.values());
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Get all prompt names
|
|
846
|
+
*/
|
|
847
|
+
getNames() {
|
|
848
|
+
return Array.from(this.prompts.keys());
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Get the number of registered prompts
|
|
852
|
+
*/
|
|
853
|
+
get size() {
|
|
854
|
+
return this.prompts.size;
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Clear all prompts
|
|
858
|
+
*/
|
|
859
|
+
clear() {
|
|
860
|
+
this.prompts.clear();
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
var DEFAULT_CORS = {
|
|
864
|
+
origin: "*",
|
|
865
|
+
methods: ["GET", "POST", "OPTIONS"],
|
|
866
|
+
allowedHeaders: ["Content-Type", "Authorization", "Mcp-Session-Id"],
|
|
867
|
+
exposedHeaders: ["Mcp-Session-Id"],
|
|
868
|
+
credentials: false,
|
|
869
|
+
maxAge: 86400
|
|
870
|
+
// 24 hours
|
|
871
|
+
};
|
|
872
|
+
var HttpTransport = class {
|
|
873
|
+
config;
|
|
874
|
+
logger;
|
|
875
|
+
httpServer = null;
|
|
876
|
+
mcpTransport = null;
|
|
877
|
+
running = false;
|
|
878
|
+
constructor(config, logger2) {
|
|
879
|
+
this.config = {
|
|
880
|
+
...config,
|
|
881
|
+
host: config.host ?? "0.0.0.0",
|
|
882
|
+
basePath: config.basePath ?? "/mcp",
|
|
883
|
+
cors: { ...DEFAULT_CORS, ...config.cors }
|
|
884
|
+
};
|
|
885
|
+
this.logger = logger2 ?? new Logger({ level: "info" });
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Start the HTTP transport
|
|
889
|
+
*/
|
|
890
|
+
async start(mcpServer) {
|
|
891
|
+
if (this.running) {
|
|
892
|
+
throw new Error("Transport is already running");
|
|
893
|
+
}
|
|
894
|
+
this.mcpTransport = new StreamableHTTPServerTransport({
|
|
895
|
+
sessionIdGenerator: () => randomUUID()
|
|
896
|
+
});
|
|
897
|
+
if (mcpServer) {
|
|
898
|
+
await mcpServer.connect(this.mcpTransport);
|
|
899
|
+
}
|
|
900
|
+
this.httpServer = createServer((req, res) => {
|
|
901
|
+
this.handleRequest(req, res);
|
|
902
|
+
});
|
|
903
|
+
return new Promise((resolve, reject) => {
|
|
904
|
+
const server = this.httpServer;
|
|
905
|
+
server.on("error", (error) => {
|
|
906
|
+
this.logger.error("HTTP server error", error);
|
|
907
|
+
reject(error);
|
|
908
|
+
});
|
|
909
|
+
server.listen(this.config.port, this.config.host, () => {
|
|
910
|
+
this.running = true;
|
|
911
|
+
this.logger.info("HTTP transport started", {
|
|
912
|
+
host: this.config.host,
|
|
913
|
+
port: this.config.port,
|
|
914
|
+
basePath: this.config.basePath
|
|
915
|
+
});
|
|
916
|
+
resolve();
|
|
917
|
+
});
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Stop the HTTP transport
|
|
922
|
+
*/
|
|
923
|
+
async stop() {
|
|
924
|
+
if (!this.running) {
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
if (this.mcpTransport) {
|
|
928
|
+
await this.mcpTransport.close();
|
|
929
|
+
this.mcpTransport = null;
|
|
930
|
+
}
|
|
931
|
+
if (this.httpServer) {
|
|
932
|
+
return new Promise((resolve, reject) => {
|
|
933
|
+
this.httpServer.close((error) => {
|
|
934
|
+
if (error) {
|
|
935
|
+
this.logger.error("Error closing HTTP server", error);
|
|
936
|
+
reject(error);
|
|
937
|
+
} else {
|
|
938
|
+
this.running = false;
|
|
939
|
+
this.httpServer = null;
|
|
940
|
+
this.logger.info("HTTP transport stopped");
|
|
941
|
+
resolve();
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Check if transport is running
|
|
949
|
+
*/
|
|
950
|
+
isRunning() {
|
|
951
|
+
return this.running;
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Get the underlying MCP transport (for advanced use cases)
|
|
955
|
+
*/
|
|
956
|
+
getMcpTransport() {
|
|
957
|
+
return this.mcpTransport;
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Handle incoming HTTP request
|
|
961
|
+
*/
|
|
962
|
+
async handleRequest(req, res) {
|
|
963
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
964
|
+
const path = url.pathname;
|
|
965
|
+
if (req.method === "OPTIONS") {
|
|
966
|
+
this.setCorsHeaders(res);
|
|
967
|
+
res.writeHead(204);
|
|
968
|
+
res.end();
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
this.setCorsHeaders(res);
|
|
972
|
+
if (path === "/health" || path === `${this.config.basePath}/health`) {
|
|
973
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
974
|
+
res.end(JSON.stringify({ status: "healthy" }));
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
if (path === this.config.basePath || path === `${this.config.basePath}/`) {
|
|
978
|
+
if (!this.mcpTransport) {
|
|
979
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
980
|
+
res.end(JSON.stringify({ error: "Transport not initialized" }));
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
try {
|
|
984
|
+
let body = void 0;
|
|
985
|
+
if (req.method === "POST") {
|
|
986
|
+
body = await this.parseBody(req);
|
|
987
|
+
}
|
|
988
|
+
await this.mcpTransport.handleRequest(req, res, body);
|
|
989
|
+
} catch (error) {
|
|
990
|
+
this.logger.error("Error handling MCP request", error instanceof Error ? error : void 0);
|
|
991
|
+
if (!res.headersSent) {
|
|
992
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
993
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
999
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Set CORS headers on response
|
|
1003
|
+
*/
|
|
1004
|
+
setCorsHeaders(res) {
|
|
1005
|
+
const cors = this.config.cors;
|
|
1006
|
+
if (cors.origin === true) {
|
|
1007
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1008
|
+
} else if (cors.origin === false) ; else if (typeof cors.origin === "string") {
|
|
1009
|
+
res.setHeader("Access-Control-Allow-Origin", cors.origin);
|
|
1010
|
+
} else if (Array.isArray(cors.origin)) {
|
|
1011
|
+
res.setHeader("Access-Control-Allow-Origin", cors.origin.join(", "));
|
|
1012
|
+
}
|
|
1013
|
+
if (cors.methods) {
|
|
1014
|
+
res.setHeader("Access-Control-Allow-Methods", cors.methods.join(", "));
|
|
1015
|
+
}
|
|
1016
|
+
if (cors.allowedHeaders) {
|
|
1017
|
+
res.setHeader("Access-Control-Allow-Headers", cors.allowedHeaders.join(", "));
|
|
1018
|
+
}
|
|
1019
|
+
if (cors.exposedHeaders) {
|
|
1020
|
+
res.setHeader("Access-Control-Expose-Headers", cors.exposedHeaders.join(", "));
|
|
1021
|
+
}
|
|
1022
|
+
if (cors.credentials) {
|
|
1023
|
+
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
1024
|
+
}
|
|
1025
|
+
if (cors.maxAge) {
|
|
1026
|
+
res.setHeader("Access-Control-Max-Age", cors.maxAge.toString());
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Parse request body as JSON
|
|
1031
|
+
*/
|
|
1032
|
+
parseBody(req) {
|
|
1033
|
+
return new Promise((resolve, reject) => {
|
|
1034
|
+
const chunks = [];
|
|
1035
|
+
req.on("data", (chunk) => {
|
|
1036
|
+
chunks.push(chunk);
|
|
1037
|
+
});
|
|
1038
|
+
req.on("end", () => {
|
|
1039
|
+
try {
|
|
1040
|
+
const body = Buffer.concat(chunks).toString("utf8");
|
|
1041
|
+
if (!body) {
|
|
1042
|
+
resolve(void 0);
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
resolve(JSON.parse(body));
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
reject(new Error("Invalid JSON body"));
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
1050
|
+
req.on("error", reject);
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1054
|
+
|
|
1055
|
+
// src/server/McpServer.ts
|
|
1056
|
+
var McpServer = class {
|
|
1057
|
+
server;
|
|
1058
|
+
config;
|
|
1059
|
+
logger;
|
|
1060
|
+
running = false;
|
|
1061
|
+
httpTransport = null;
|
|
1062
|
+
toolRegistry = new ToolRegistry();
|
|
1063
|
+
resourceRegistry = new ResourceRegistry();
|
|
1064
|
+
promptRegistry = new PromptRegistry();
|
|
1065
|
+
hooks = {};
|
|
1066
|
+
constructor(config) {
|
|
1067
|
+
this.config = config;
|
|
1068
|
+
this.logger = new Logger({
|
|
1069
|
+
level: config.logging?.level ?? "info",
|
|
1070
|
+
pretty: config.logging?.pretty,
|
|
1071
|
+
defaultContext: { server: config.name }
|
|
1072
|
+
});
|
|
1073
|
+
this.server = new Server(
|
|
1074
|
+
{
|
|
1075
|
+
name: config.name,
|
|
1076
|
+
version: config.version
|
|
1077
|
+
},
|
|
1078
|
+
{
|
|
1079
|
+
capabilities: this.getCapabilities()
|
|
1080
|
+
}
|
|
1081
|
+
);
|
|
1082
|
+
this.setupHandlers();
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Get server info
|
|
1086
|
+
*/
|
|
1087
|
+
getInfo() {
|
|
1088
|
+
return {
|
|
1089
|
+
name: this.config.name,
|
|
1090
|
+
version: this.config.version,
|
|
1091
|
+
description: this.config.description
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Get server capabilities
|
|
1096
|
+
*/
|
|
1097
|
+
getCapabilities() {
|
|
1098
|
+
return {
|
|
1099
|
+
tools: {},
|
|
1100
|
+
resources: {},
|
|
1101
|
+
prompts: {}
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Set up MCP request handlers
|
|
1106
|
+
*/
|
|
1107
|
+
setupHandlers() {
|
|
1108
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1109
|
+
return runInRequestContextAsync(async () => {
|
|
1110
|
+
const tools = this.toolRegistry.getAll();
|
|
1111
|
+
return {
|
|
1112
|
+
tools: tools.map((tool) => ({
|
|
1113
|
+
name: tool.name,
|
|
1114
|
+
description: tool.description,
|
|
1115
|
+
inputSchema: tool.getJsonSchema()
|
|
1116
|
+
}))
|
|
1117
|
+
};
|
|
1118
|
+
});
|
|
1119
|
+
});
|
|
1120
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1121
|
+
return runInRequestContextAsync(async () => {
|
|
1122
|
+
const { name, arguments: args } = request.params;
|
|
1123
|
+
const tool = this.toolRegistry.get(name);
|
|
1124
|
+
if (!tool) {
|
|
1125
|
+
throw McpError.toolNotFound(name);
|
|
1126
|
+
}
|
|
1127
|
+
const result = await tool.run(args);
|
|
1128
|
+
return {
|
|
1129
|
+
content: [
|
|
1130
|
+
{
|
|
1131
|
+
type: "text",
|
|
1132
|
+
text: typeof result.content === "string" ? result.content : JSON.stringify(result.content)
|
|
1133
|
+
}
|
|
1134
|
+
],
|
|
1135
|
+
isError: result.isError
|
|
1136
|
+
};
|
|
1137
|
+
});
|
|
1138
|
+
});
|
|
1139
|
+
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
1140
|
+
return runInRequestContextAsync(async () => {
|
|
1141
|
+
const resources = this.resourceRegistry.getAll();
|
|
1142
|
+
return {
|
|
1143
|
+
resources: resources.map((resource) => ({
|
|
1144
|
+
uri: resource.uri,
|
|
1145
|
+
name: resource.name,
|
|
1146
|
+
description: resource.description,
|
|
1147
|
+
mimeType: resource.mimeType
|
|
1148
|
+
}))
|
|
1149
|
+
};
|
|
1150
|
+
});
|
|
1151
|
+
});
|
|
1152
|
+
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
1153
|
+
return runInRequestContextAsync(async () => {
|
|
1154
|
+
const { uri } = request.params;
|
|
1155
|
+
const resource = this.resourceRegistry.get(uri);
|
|
1156
|
+
if (!resource) {
|
|
1157
|
+
throw McpError.resourceNotFound(uri);
|
|
1158
|
+
}
|
|
1159
|
+
const result = await resource.safeRead();
|
|
1160
|
+
return {
|
|
1161
|
+
contents: result.contents.map((content) => ({
|
|
1162
|
+
uri,
|
|
1163
|
+
...content
|
|
1164
|
+
}))
|
|
1165
|
+
};
|
|
1166
|
+
});
|
|
1167
|
+
});
|
|
1168
|
+
this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
1169
|
+
return runInRequestContextAsync(async () => {
|
|
1170
|
+
const prompts = this.promptRegistry.getAll();
|
|
1171
|
+
return {
|
|
1172
|
+
prompts: prompts.map((prompt) => {
|
|
1173
|
+
const metadata = prompt.getMetadata();
|
|
1174
|
+
return {
|
|
1175
|
+
name: prompt.name,
|
|
1176
|
+
description: metadata.description,
|
|
1177
|
+
arguments: metadata.arguments.map((arg) => ({
|
|
1178
|
+
name: arg.name,
|
|
1179
|
+
description: arg.description,
|
|
1180
|
+
required: arg.required
|
|
1181
|
+
}))
|
|
1182
|
+
};
|
|
1183
|
+
})
|
|
1184
|
+
};
|
|
1185
|
+
});
|
|
1186
|
+
});
|
|
1187
|
+
this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
1188
|
+
return runInRequestContextAsync(async () => {
|
|
1189
|
+
const { name, arguments: args } = request.params;
|
|
1190
|
+
const prompt = this.promptRegistry.get(name);
|
|
1191
|
+
if (!prompt) {
|
|
1192
|
+
throw McpError.promptNotFound(name);
|
|
1193
|
+
}
|
|
1194
|
+
const result = await prompt.safeRender(args);
|
|
1195
|
+
return {
|
|
1196
|
+
description: result.description,
|
|
1197
|
+
messages: result.messages.map((msg) => ({
|
|
1198
|
+
role: msg.role,
|
|
1199
|
+
content: { type: "text", text: msg.content }
|
|
1200
|
+
}))
|
|
1201
|
+
};
|
|
1202
|
+
});
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
tool(toolOrName, definition) {
|
|
1206
|
+
if (typeof toolOrName === "string" && definition) {
|
|
1207
|
+
this.toolRegistry.registerInline(toolOrName, definition);
|
|
1208
|
+
} else if (toolOrName instanceof BaseTool) {
|
|
1209
|
+
this.toolRegistry.register(toolOrName);
|
|
1210
|
+
}
|
|
1211
|
+
return this;
|
|
1212
|
+
}
|
|
1213
|
+
resource(resourceOrUri, definition) {
|
|
1214
|
+
if (typeof resourceOrUri === "string" && definition) {
|
|
1215
|
+
this.resourceRegistry.registerInline(resourceOrUri, definition);
|
|
1216
|
+
} else if (resourceOrUri instanceof BaseResource) {
|
|
1217
|
+
this.resourceRegistry.register(resourceOrUri);
|
|
1218
|
+
}
|
|
1219
|
+
return this;
|
|
1220
|
+
}
|
|
1221
|
+
prompt(promptOrName, definition) {
|
|
1222
|
+
if (typeof promptOrName === "string" && definition) {
|
|
1223
|
+
this.promptRegistry.registerInline(promptOrName, definition);
|
|
1224
|
+
} else if (promptOrName instanceof BasePrompt) {
|
|
1225
|
+
this.promptRegistry.register(promptOrName);
|
|
1226
|
+
}
|
|
1227
|
+
return this;
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Set lifecycle hooks
|
|
1231
|
+
*/
|
|
1232
|
+
setHooks(hooks) {
|
|
1233
|
+
this.hooks = { ...this.hooks, ...hooks };
|
|
1234
|
+
return this;
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Start the server
|
|
1238
|
+
*/
|
|
1239
|
+
async start() {
|
|
1240
|
+
if (this.running) {
|
|
1241
|
+
throw new McpError({
|
|
1242
|
+
code: "SERVER_ALREADY_RUNNING" /* SERVER_ALREADY_RUNNING */,
|
|
1243
|
+
message: "Server is already running"
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
const transportConfig = this.config.transport ?? "stdio";
|
|
1247
|
+
const transportType = typeof transportConfig === "string" ? transportConfig : transportConfig.type;
|
|
1248
|
+
this.logger.info("Starting MCP server", {
|
|
1249
|
+
name: this.config.name,
|
|
1250
|
+
version: this.config.version,
|
|
1251
|
+
transport: transportType,
|
|
1252
|
+
tools: this.toolRegistry.size,
|
|
1253
|
+
resources: this.resourceRegistry.size,
|
|
1254
|
+
prompts: this.promptRegistry.size
|
|
1255
|
+
});
|
|
1256
|
+
try {
|
|
1257
|
+
await this.hooks.onBeforeStart?.();
|
|
1258
|
+
if (transportType === "stdio") {
|
|
1259
|
+
const transport = new StdioServerTransport();
|
|
1260
|
+
await this.server.connect(transport);
|
|
1261
|
+
} else if (transportType === "http") {
|
|
1262
|
+
const httpConfig = transportConfig;
|
|
1263
|
+
this.httpTransport = new HttpTransport(httpConfig, this.logger);
|
|
1264
|
+
await this.httpTransport.start(this.server);
|
|
1265
|
+
} else if (transportType === "sse") {
|
|
1266
|
+
throw new McpError({
|
|
1267
|
+
code: "NOT_IMPLEMENTED" /* NOT_IMPLEMENTED */,
|
|
1268
|
+
message: "SSE transport not yet implemented. Use HTTP transport instead."
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
this.running = true;
|
|
1272
|
+
await this.hooks.onAfterStart?.();
|
|
1273
|
+
this.logger.info("MCP server started successfully", {
|
|
1274
|
+
transport: transportType,
|
|
1275
|
+
...transportType === "http" && {
|
|
1276
|
+
port: transportConfig.port,
|
|
1277
|
+
host: transportConfig.host ?? "0.0.0.0"
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
} catch (error) {
|
|
1281
|
+
this.logger.error("Failed to start server", error instanceof Error ? error : void 0);
|
|
1282
|
+
await this.hooks.onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
1283
|
+
throw error;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Stop the server
|
|
1288
|
+
*/
|
|
1289
|
+
async stop() {
|
|
1290
|
+
if (!this.running) {
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
this.logger.info("Stopping MCP server");
|
|
1294
|
+
try {
|
|
1295
|
+
await this.hooks.onBeforeStop?.();
|
|
1296
|
+
if (this.httpTransport) {
|
|
1297
|
+
await this.httpTransport.stop();
|
|
1298
|
+
this.httpTransport = null;
|
|
1299
|
+
}
|
|
1300
|
+
await this.server.close();
|
|
1301
|
+
this.running = false;
|
|
1302
|
+
await this.hooks.onAfterStop?.();
|
|
1303
|
+
this.logger.info("MCP server stopped");
|
|
1304
|
+
} catch (error) {
|
|
1305
|
+
this.logger.error("Error stopping server", error instanceof Error ? error : void 0);
|
|
1306
|
+
await this.hooks.onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
1307
|
+
throw error;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
/**
|
|
1311
|
+
* Check if server is running
|
|
1312
|
+
*/
|
|
1313
|
+
isRunning() {
|
|
1314
|
+
return this.running;
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Get the logger instance
|
|
1318
|
+
*/
|
|
1319
|
+
getLogger() {
|
|
1320
|
+
return this.logger;
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Get tool registry (for testing)
|
|
1324
|
+
*/
|
|
1325
|
+
getToolRegistry() {
|
|
1326
|
+
return this.toolRegistry;
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Get resource registry (for testing)
|
|
1330
|
+
*/
|
|
1331
|
+
getResourceRegistry() {
|
|
1332
|
+
return this.resourceRegistry;
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Get prompt registry (for testing)
|
|
1336
|
+
*/
|
|
1337
|
+
getPromptRegistry() {
|
|
1338
|
+
return this.promptRegistry;
|
|
1339
|
+
}
|
|
1340
|
+
};
|
|
1341
|
+
|
|
1342
|
+
// src/server/ServerBuilder.ts
|
|
1343
|
+
var ServerBuilder = class {
|
|
1344
|
+
config;
|
|
1345
|
+
tools = [];
|
|
1346
|
+
resources = [];
|
|
1347
|
+
prompts = [];
|
|
1348
|
+
hooks = {};
|
|
1349
|
+
constructor(config) {
|
|
1350
|
+
this.config = {
|
|
1351
|
+
name: config.name,
|
|
1352
|
+
version: config.version,
|
|
1353
|
+
description: config.description
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Add a tool using inline definition
|
|
1358
|
+
*/
|
|
1359
|
+
tool(name, definition) {
|
|
1360
|
+
this.tools.push({ name, definition });
|
|
1361
|
+
return this;
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Add a resource using inline definition
|
|
1365
|
+
*/
|
|
1366
|
+
resource(uri, definition) {
|
|
1367
|
+
this.resources.push({ uri, definition });
|
|
1368
|
+
return this;
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Add a prompt using inline definition
|
|
1372
|
+
*/
|
|
1373
|
+
prompt(name, definition) {
|
|
1374
|
+
this.prompts.push({ name, definition });
|
|
1375
|
+
return this;
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Configure transport
|
|
1379
|
+
*/
|
|
1380
|
+
withTransport(transport) {
|
|
1381
|
+
this.config.transport = transport;
|
|
1382
|
+
return this;
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Configure authentication
|
|
1386
|
+
*/
|
|
1387
|
+
withAuth(auth) {
|
|
1388
|
+
this.config.auth = auth;
|
|
1389
|
+
return this;
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Configure logging
|
|
1393
|
+
*/
|
|
1394
|
+
withLogging(options) {
|
|
1395
|
+
this.config.logging = options;
|
|
1396
|
+
return this;
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Configure auto-discovery
|
|
1400
|
+
*/
|
|
1401
|
+
withDiscovery(options) {
|
|
1402
|
+
this.config.discovery = options;
|
|
1403
|
+
return this;
|
|
1404
|
+
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Add lifecycle hooks
|
|
1407
|
+
*/
|
|
1408
|
+
withHooks(hooks) {
|
|
1409
|
+
this.hooks = { ...this.hooks, ...hooks };
|
|
1410
|
+
return this;
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Add a hook for before start
|
|
1414
|
+
*/
|
|
1415
|
+
onBeforeStart(fn) {
|
|
1416
|
+
this.hooks.onBeforeStart = fn;
|
|
1417
|
+
return this;
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Add a hook for after start
|
|
1421
|
+
*/
|
|
1422
|
+
onAfterStart(fn) {
|
|
1423
|
+
this.hooks.onAfterStart = fn;
|
|
1424
|
+
return this;
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Add a hook for before stop
|
|
1428
|
+
*/
|
|
1429
|
+
onBeforeStop(fn) {
|
|
1430
|
+
this.hooks.onBeforeStop = fn;
|
|
1431
|
+
return this;
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Add a hook for after stop
|
|
1435
|
+
*/
|
|
1436
|
+
onAfterStop(fn) {
|
|
1437
|
+
this.hooks.onAfterStop = fn;
|
|
1438
|
+
return this;
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Add an error handler
|
|
1442
|
+
*/
|
|
1443
|
+
onError(fn) {
|
|
1444
|
+
this.hooks.onError = fn;
|
|
1445
|
+
return this;
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Build the server instance
|
|
1449
|
+
*/
|
|
1450
|
+
build() {
|
|
1451
|
+
const server = new McpServer(this.config);
|
|
1452
|
+
for (const { name, definition } of this.tools) {
|
|
1453
|
+
server.tool(name, definition);
|
|
1454
|
+
}
|
|
1455
|
+
for (const { uri, definition } of this.resources) {
|
|
1456
|
+
server.resource(uri, definition);
|
|
1457
|
+
}
|
|
1458
|
+
for (const { name, definition } of this.prompts) {
|
|
1459
|
+
server.prompt(name, definition);
|
|
1460
|
+
}
|
|
1461
|
+
if (Object.keys(this.hooks).length > 0) {
|
|
1462
|
+
server.setHooks(this.hooks);
|
|
1463
|
+
}
|
|
1464
|
+
return server;
|
|
1465
|
+
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Build and start the server
|
|
1468
|
+
*/
|
|
1469
|
+
async start() {
|
|
1470
|
+
const server = this.build();
|
|
1471
|
+
await server.start();
|
|
1472
|
+
return server;
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
function createServer2(config) {
|
|
1476
|
+
return new ServerBuilder(config);
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
export { BasePrompt, BaseResource, BaseTool, HttpTransport, Logger, McpError, McpErrorCode, McpServer, PromptRegistry, ResourceRegistry, ServerBuilder, ToolRegistry, createRequestContext, createServer2 as createServer, getAuthContext, getContextLogger, getContextValue, getCorrelationId, getRequestContext, getRequestDuration, logger, requireRequestContext, runInRequestContext, runInRequestContextAsync, setAuthContext, setContextValue };
|
|
1480
|
+
//# sourceMappingURL=index.js.map
|
|
1481
|
+
//# sourceMappingURL=index.js.map
|