edge.libx.js 0.2.4 → 0.3.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 CHANGED
@@ -1,33 +1,108 @@
1
1
  # 🗡️ edge.libx.js
2
2
 
3
3
  Provider-agnostic, edge-compatible, [itty-router](https://itty.dev/itty-router/) microrouter wrapper.
4
- Allowing you to crete multi-route endpoints that can work with Cloudflare, Vercel, Google Cloud Functions, Netlify Functions, and any other provider.
4
+ Allowing you to create multi-route endpoints that can work with Cloudflare, Vercel, Google Cloud Functions, Netlify Functions, and any other provider.
5
5
 
6
6
  ## Get Started:
7
7
 
8
- ```ts:
9
- import { Route, Router, RouterType, error, json, cors, text } from 'itty-router';
10
- import { RouterWrapper } from 'edge.libx.js';
8
+ ```ts
9
+ import { RouterWrapper, cors } from 'edge.libx.js';
10
+ import { json, error } from 'itty-router';
11
11
 
12
- const routerWrapper = RouterWrapper.getNew('/v1');
12
+ // Create router with base path and optional CORS configuration
13
+ const routerWrapper = RouterWrapper.getNew('/v1', {
14
+ origin: '*',
15
+ allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],
16
+ allowHeaders: ['Content-Type', 'Authorization']
17
+ });
18
+
19
+ // Add routes
20
+ routerWrapper.router.get('/ping', async (req) => {
21
+ return json({ message: 'pong', timestamp: Date.now() });
22
+ });
13
23
 
14
- routerWrapper.router.all('/ping', async (req) => {
15
- return json({ message: 'pong' });
24
+ routerWrapper.router.post('/users', async (req) => {
25
+ const body = await req.json();
26
+ return json({ success: true, user: body });
16
27
  });
28
+
29
+ // Catch unmatched routes
30
+ routerWrapper.catchNotFound();
31
+
32
+ // Export for edge runtime (Cloudflare Workers, Vercel Edge, etc.)
33
+ export default {
34
+ fetch: routerWrapper.fetchHandler.bind(routerWrapper)
35
+ };
17
36
  ```
18
37
 
19
38
  Useful package.json scripts:
20
- ```json:
21
- ...
39
+ ```json
40
+ {
22
41
  "scripts": {
23
42
  "build": "tsc",
24
- "debug": "tsx --inspect node_modules/edge.libx.js/src/helpers/localServer.ts src/index.ts",
25
- "debug:watch": "nodemon --watch 'src/**/*.ts' --exec tsx --inspect node_modules/edge.libx.js/src/helpers/localServer.ts src/index.ts",
43
+ "watch": "tsc -w",
44
+ "test": "jest",
45
+ "format": "prettier --config .prettierrc 'src/**/*.ts' 'tests/**/*.ts' --write"
46
+ }
47
+ }
48
+ ```
49
+
50
+ ## Features:
26
51
 
27
- "set-secrets": "set-secrets .env.preview preview \"vercel env add\" remove",
52
+ ### CORS Support
53
+ Built-in CORS handling with configurable options:
54
+ ```ts
55
+ const routerWrapper = RouterWrapper.getNew('/api', {
56
+ origin: ['https://example.com', 'https://app.example.com'],
57
+ allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
58
+ allowHeaders: ['Content-Type', 'Authorization', 'X-API-Key']
59
+ });
60
+ ```
28
61
 
62
+ ### Error Handling
63
+ Automatic error handling with proper HTTP status codes:
64
+ ```ts
65
+ routerWrapper.router.get('/error-example', async () => {
66
+ throw new Error('Something went wrong'); // Returns 500 with error message
67
+ });
68
+ ```
69
+
70
+ ### Local Development
71
+ For local development with Express.js:
72
+ ```ts
73
+ // Create server adapter for local development
74
+ const server = routerWrapper.createServerAdapter();
75
+
76
+ // Start local server (HTTP)
77
+ tsx node_modules/edge.libx.js/build/helpers/localServer.js src/index.js
78
+
79
+ // Start local server with HTTPS (self-signed certificate)
80
+ tsx node_modules/edge.libx.js/build/helpers/localServer.js src/index.js --https
81
+
82
+ // Or set environment variable
83
+ useHttps=true tsx node_modules/edge.libx.js/build/helpers/localServer.js src/index.js
84
+ ```
85
+
86
+ Add these scripts to your `package.json`:
87
+ ```json
88
+ {
89
+ "scripts": {
90
+ "dev": "tsx node_modules/edge.libx.js/build/helpers/localServer.js src/index.js",
91
+ "dev:https": "tsx node_modules/edge.libx.js/build/helpers/localServer.js src/index.js --https",
92
+ "dev:watch": "nodemon --watch 'src/**/*.ts' --exec tsx node_modules/edge.libx.js/build/helpers/localServer.js src/index.js"
29
93
  }
30
- ...
94
+ }
95
+ ```
96
+
97
+ ### Route Registration
98
+ Register sub-routers for modular organization:
99
+ ```ts
100
+ const subRouter = RouterWrapper.getNew('/users');
101
+ subRouter.router.get('/:id', async (req) => {
102
+ return json({ userId: req.params.id });
103
+ });
104
+
105
+ routerWrapper.registerRoute('/users', () => subRouter);
31
106
  ```
32
107
 
33
108
  ## Contribute:
package/build/main.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { RouterWrapper } from "./modules/RouterWrapper";
2
+ export { MCPAdapter } from "./modules/MCPAdapter";
2
3
  export * as cors from "./modules/cors";
package/build/main.js CHANGED
@@ -33,8 +33,10 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.cors = exports.RouterWrapper = void 0;
36
+ exports.cors = exports.MCPAdapter = exports.RouterWrapper = void 0;
37
37
  var RouterWrapper_1 = require("./modules/RouterWrapper");
38
38
  Object.defineProperty(exports, "RouterWrapper", { enumerable: true, get: function () { return RouterWrapper_1.RouterWrapper; } });
39
+ var MCPAdapter_1 = require("./modules/MCPAdapter");
40
+ Object.defineProperty(exports, "MCPAdapter", { enumerable: true, get: function () { return MCPAdapter_1.MCPAdapter; } });
39
41
  exports.cors = __importStar(require("./modules/cors"));
40
42
  //# sourceMappingURL=main.js.map
package/build/main.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,yDAAwD;AAA/C,8GAAA,aAAa,OAAA;AACtB,uDAAuC","sourcesContent":["export { RouterWrapper } from \"./modules/RouterWrapper\";\nexport * as cors from \"./modules/cors\";"]}
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,yDAAwD;AAA/C,8GAAA,aAAa,OAAA;AACtB,mDAAkD;AAAzC,wGAAA,UAAU,OAAA;AACnB,uDAAuC","sourcesContent":["export { RouterWrapper } from \"./modules/RouterWrapper\";\nexport { MCPAdapter } from \"./modules/MCPAdapter\";\nexport * as cors from \"./modules/cors\";"]}
@@ -0,0 +1,45 @@
1
+ import { RouterType, IRequest } from 'itty-router';
2
+ export interface MCPOptions {
3
+ name?: string;
4
+ version?: string;
5
+ }
6
+ interface ToolMeta {
7
+ description?: string;
8
+ params?: Record<string, {
9
+ description?: string;
10
+ type?: string;
11
+ }>;
12
+ }
13
+ interface ToolDefinition {
14
+ name: string;
15
+ description: string;
16
+ inputSchema: {
17
+ type: 'object';
18
+ properties: Record<string, any>;
19
+ required: string[];
20
+ };
21
+ }
22
+ interface JsonRpcRequest {
23
+ jsonrpc: '2.0';
24
+ id?: string | number;
25
+ method: string;
26
+ params?: any;
27
+ }
28
+ export declare class MCPAdapter {
29
+ private router;
30
+ private base;
31
+ private fetchHandler;
32
+ private serverName;
33
+ private serverVersion;
34
+ private mcpMeta;
35
+ constructor(router: RouterType<any, any[], any>, base: string, fetchHandler: (request: IRequest, ctx?: any) => Promise<Response>, mcpMeta: Map<string, ToolMeta>, options?: MCPOptions);
36
+ toolNameFromRoute(method: string, path: string): string;
37
+ inferQueryParams(handlers: Function[]): string[];
38
+ introspectRoutes(): ToolDefinition[];
39
+ private findRoute;
40
+ callTool(name: string, args?: Record<string, any>): Promise<any>;
41
+ handleJsonRpc(message: JsonRpcRequest): Promise<any>;
42
+ httpHandler: (request: Request) => Promise<Response>;
43
+ serveStdio(): Promise<void>;
44
+ }
45
+ export {};
@@ -0,0 +1,291 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.MCPAdapter = void 0;
13
+ class MCPAdapter {
14
+ constructor(router, base, fetchHandler, mcpMeta, options) {
15
+ var _a, _b;
16
+ this.httpHandler = (request) => __awaiter(this, void 0, void 0, function* () {
17
+ if (request.method === 'GET') {
18
+ const stream = new ReadableStream({
19
+ start(controller) {
20
+ const encoder = new TextEncoder();
21
+ controller.enqueue(encoder.encode(`event: endpoint\ndata: /mcp\n\n`));
22
+ },
23
+ });
24
+ return new Response(stream, {
25
+ headers: {
26
+ 'Content-Type': 'text/event-stream',
27
+ 'Cache-Control': 'no-cache',
28
+ 'Connection': 'keep-alive',
29
+ },
30
+ });
31
+ }
32
+ try {
33
+ const body = yield request.json();
34
+ const result = yield this.handleJsonRpc(body);
35
+ if (result === null) {
36
+ return new Response(null, { status: 204 });
37
+ }
38
+ return new Response(JSON.stringify(result), {
39
+ headers: { 'Content-Type': 'application/json' },
40
+ });
41
+ }
42
+ catch (err) {
43
+ return new Response(JSON.stringify({
44
+ jsonrpc: '2.0',
45
+ id: null,
46
+ error: { code: -32700, message: 'Parse error' },
47
+ }), {
48
+ status: 400,
49
+ headers: { 'Content-Type': 'application/json' },
50
+ });
51
+ }
52
+ });
53
+ this.router = router;
54
+ this.base = base;
55
+ this.fetchHandler = fetchHandler;
56
+ this.mcpMeta = mcpMeta;
57
+ this.serverName = (_a = options === null || options === void 0 ? void 0 : options.name) !== null && _a !== void 0 ? _a : 'MCP Server';
58
+ this.serverVersion = (_b = options === null || options === void 0 ? void 0 : options.version) !== null && _b !== void 0 ? _b : '1.0.0';
59
+ }
60
+ toolNameFromRoute(method, path) {
61
+ let name = path;
62
+ if (this.base && name.startsWith(this.base)) {
63
+ name = name.slice(this.base.length);
64
+ }
65
+ name = name.replace(/^\//, '');
66
+ name = name.replace(/:(\w+)/g, 'by_$1');
67
+ name = name.replace(/\//g, '_');
68
+ name = `${method.toLowerCase()}_${name}`;
69
+ name = name.replace(/_+/g, '_').replace(/_$/, '');
70
+ return name;
71
+ }
72
+ inferQueryParams(handlers) {
73
+ const params = new Set();
74
+ for (const handler of handlers) {
75
+ try {
76
+ const src = handler.toString();
77
+ const dotPattern = /(?:req(?:uest)?\.)?query\.(\w+)/g;
78
+ let match;
79
+ while ((match = dotPattern.exec(src)) !== null) {
80
+ params.add(match[1]);
81
+ }
82
+ const bracketPattern = /(?:req(?:uest)?\.)?query\[['"](\w+)['"]\]/g;
83
+ while ((match = bracketPattern.exec(src)) !== null) {
84
+ params.add(match[1]);
85
+ }
86
+ }
87
+ catch (_a) {
88
+ }
89
+ }
90
+ return Array.from(params);
91
+ }
92
+ introspectRoutes() {
93
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
94
+ const routes = (_a = this.router.routes) !== null && _a !== void 0 ? _a : [];
95
+ const tools = [];
96
+ for (const route of routes) {
97
+ const [method, , handlers, originalPath] = route;
98
+ if (!originalPath || originalPath === '*' || originalPath.endsWith('/*'))
99
+ continue;
100
+ if (method === 'OPTIONS' || method === 'ALL')
101
+ continue;
102
+ const toolName = this.toolNameFromRoute(method, originalPath);
103
+ const metaKey = `${method}:${originalPath}`;
104
+ const meta = this.mcpMeta.get(metaKey);
105
+ const properties = {};
106
+ const required = [];
107
+ const paramPattern = /:(\w+)/g;
108
+ let match;
109
+ while ((match = paramPattern.exec(originalPath)) !== null) {
110
+ const paramName = match[1];
111
+ properties[paramName] = Object.assign({ type: (_d = (_c = (_b = meta === null || meta === void 0 ? void 0 : meta.params) === null || _b === void 0 ? void 0 : _b[paramName]) === null || _c === void 0 ? void 0 : _c.type) !== null && _d !== void 0 ? _d : 'string' }, (((_f = (_e = meta === null || meta === void 0 ? void 0 : meta.params) === null || _e === void 0 ? void 0 : _e[paramName]) === null || _f === void 0 ? void 0 : _f.description) && { description: meta.params[paramName].description }));
112
+ required.push(paramName);
113
+ }
114
+ if (['POST', 'PUT', 'PATCH'].includes(method)) {
115
+ properties['body'] = {
116
+ type: 'object',
117
+ description: (_j = (_h = (_g = meta === null || meta === void 0 ? void 0 : meta.params) === null || _g === void 0 ? void 0 : _g['body']) === null || _h === void 0 ? void 0 : _h.description) !== null && _j !== void 0 ? _j : 'Request body',
118
+ };
119
+ }
120
+ if (['GET', 'DELETE'].includes(method)) {
121
+ const queryParams = this.inferQueryParams(handlers);
122
+ for (const qp of queryParams) {
123
+ if (!properties[qp]) {
124
+ properties[qp] = Object.assign({ type: (_m = (_l = (_k = meta === null || meta === void 0 ? void 0 : meta.params) === null || _k === void 0 ? void 0 : _k[qp]) === null || _l === void 0 ? void 0 : _l.type) !== null && _m !== void 0 ? _m : 'string' }, (((_p = (_o = meta === null || meta === void 0 ? void 0 : meta.params) === null || _o === void 0 ? void 0 : _o[qp]) === null || _p === void 0 ? void 0 : _p.description) && { description: meta.params[qp].description }));
125
+ }
126
+ }
127
+ }
128
+ const description = (_q = meta === null || meta === void 0 ? void 0 : meta.description) !== null && _q !== void 0 ? _q : `${method} ${originalPath}`;
129
+ tools.push({
130
+ name: toolName,
131
+ description,
132
+ inputSchema: {
133
+ type: 'object',
134
+ properties,
135
+ required,
136
+ },
137
+ });
138
+ }
139
+ return tools;
140
+ }
141
+ findRoute(toolName) {
142
+ var _a;
143
+ const routes = (_a = this.router.routes) !== null && _a !== void 0 ? _a : [];
144
+ for (const route of routes) {
145
+ const [method, , handlers, originalPath] = route;
146
+ if (!originalPath || originalPath === '*' || originalPath.endsWith('/*'))
147
+ continue;
148
+ if (method === 'OPTIONS' || method === 'ALL')
149
+ continue;
150
+ if (this.toolNameFromRoute(method, originalPath) === toolName) {
151
+ return { method, path: originalPath, handlers };
152
+ }
153
+ }
154
+ return null;
155
+ }
156
+ callTool(name_1) {
157
+ return __awaiter(this, arguments, void 0, function* (name, args = {}) {
158
+ var _a;
159
+ const route = this.findRoute(name);
160
+ if (!route) {
161
+ return { isError: true, content: [{ type: 'text', text: `Unknown tool: ${name}` }] };
162
+ }
163
+ const { method, path } = route;
164
+ let url = path;
165
+ const pathParamPattern = /:(\w+)/g;
166
+ let match;
167
+ while ((match = pathParamPattern.exec(path)) !== null) {
168
+ const paramName = match[1];
169
+ if (args[paramName] != null) {
170
+ url = url.replace(`:${paramName}`, encodeURIComponent(String(args[paramName])));
171
+ }
172
+ }
173
+ const pathParams = new Set();
174
+ const ppPattern = /:(\w+)/g;
175
+ let m;
176
+ while ((m = ppPattern.exec(path)) !== null)
177
+ pathParams.add(m[1]);
178
+ const queryParams = [];
179
+ for (const [key, value] of Object.entries(args)) {
180
+ if (key === 'body' || pathParams.has(key))
181
+ continue;
182
+ queryParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
183
+ }
184
+ if (queryParams.length > 0) {
185
+ url += '?' + queryParams.join('&');
186
+ }
187
+ const fullUrl = `http://localhost${url}`;
188
+ const requestInit = { method };
189
+ if (args.body && ['POST', 'PUT', 'PATCH'].includes(method)) {
190
+ requestInit.body = JSON.stringify(args.body);
191
+ requestInit.headers = { 'Content-Type': 'application/json' };
192
+ }
193
+ const request = new Request(fullUrl, requestInit);
194
+ try {
195
+ const response = yield this.fetchHandler(request);
196
+ const text = yield response.text();
197
+ let content;
198
+ try {
199
+ content = JSON.parse(text);
200
+ }
201
+ catch (_b) {
202
+ content = text;
203
+ }
204
+ return {
205
+ content: [{ type: 'text', text: typeof content === 'string' ? content : JSON.stringify(content) }],
206
+ };
207
+ }
208
+ catch (err) {
209
+ return {
210
+ isError: true,
211
+ content: [{ type: 'text', text: (_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : String(err) }],
212
+ };
213
+ }
214
+ });
215
+ }
216
+ handleJsonRpc(message) {
217
+ return __awaiter(this, void 0, void 0, function* () {
218
+ var _a;
219
+ const { method, id, params } = message;
220
+ switch (method) {
221
+ case 'initialize':
222
+ return {
223
+ jsonrpc: '2.0',
224
+ id,
225
+ result: {
226
+ protocolVersion: '2024-11-05',
227
+ capabilities: { tools: { listChanged: true } },
228
+ serverInfo: { name: this.serverName, version: this.serverVersion },
229
+ },
230
+ };
231
+ case 'notifications/initialized':
232
+ return null;
233
+ case 'tools/list':
234
+ return {
235
+ jsonrpc: '2.0',
236
+ id,
237
+ result: { tools: this.introspectRoutes() },
238
+ };
239
+ case 'tools/call': {
240
+ const result = yield this.callTool(params === null || params === void 0 ? void 0 : params.name, (_a = params === null || params === void 0 ? void 0 : params.arguments) !== null && _a !== void 0 ? _a : {});
241
+ return {
242
+ jsonrpc: '2.0',
243
+ id,
244
+ result,
245
+ };
246
+ }
247
+ default:
248
+ return {
249
+ jsonrpc: '2.0',
250
+ id,
251
+ error: { code: -32601, message: `Method not found: ${method}` },
252
+ };
253
+ }
254
+ });
255
+ }
256
+ serveStdio() {
257
+ return __awaiter(this, void 0, void 0, function* () {
258
+ const stdin = globalThis.process.stdin;
259
+ const stdout = globalThis.process.stdout;
260
+ stdin.setEncoding('utf8');
261
+ let buffer = '';
262
+ stdin.on('data', (chunk) => __awaiter(this, void 0, void 0, function* () {
263
+ var _a;
264
+ buffer += chunk;
265
+ const lines = buffer.split('\n');
266
+ buffer = (_a = lines.pop()) !== null && _a !== void 0 ? _a : '';
267
+ for (const line of lines) {
268
+ const trimmed = line.trim();
269
+ if (!trimmed)
270
+ continue;
271
+ try {
272
+ const message = JSON.parse(trimmed);
273
+ const result = yield this.handleJsonRpc(message);
274
+ if (result !== null) {
275
+ stdout.write(JSON.stringify(result) + '\n');
276
+ }
277
+ }
278
+ catch (_b) {
279
+ stdout.write(JSON.stringify({
280
+ jsonrpc: '2.0',
281
+ id: null,
282
+ error: { code: -32700, message: 'Parse error' },
283
+ }) + '\n');
284
+ }
285
+ }
286
+ }));
287
+ });
288
+ }
289
+ }
290
+ exports.MCPAdapter = MCPAdapter;
291
+ //# sourceMappingURL=MCPAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MCPAdapter.js","sourceRoot":"","sources":["../../src/modules/MCPAdapter.ts"],"names":[],"mappings":"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","sourcesContent":["import { RouterType, IRequest } from 'itty-router';\n\nexport interface MCPOptions {\n\tname?: string;\n\tversion?: string;\n}\n\ninterface ToolMeta {\n\tdescription?: string;\n\tparams?: Record<string, { description?: string; type?: string }>;\n}\n\ninterface ToolDefinition {\n\tname: string;\n\tdescription: string;\n\tinputSchema: {\n\t\ttype: 'object';\n\t\tproperties: Record<string, any>;\n\t\trequired: string[];\n\t};\n}\n\ninterface JsonRpcRequest {\n\tjsonrpc: '2.0';\n\tid?: string | number;\n\tmethod: string;\n\tparams?: any;\n}\n\nexport class MCPAdapter {\n\tprivate router: RouterType<any, any[], any>;\n\tprivate base: string;\n\tprivate fetchHandler: (request: IRequest, ctx?: any) => Promise<Response>;\n\tprivate serverName: string;\n\tprivate serverVersion: string;\n\tprivate mcpMeta: Map<string, ToolMeta>;\n\n\tconstructor(\n\t\trouter: RouterType<any, any[], any>,\n\t\tbase: string,\n\t\tfetchHandler: (request: IRequest, ctx?: any) => Promise<Response>,\n\t\tmcpMeta: Map<string, ToolMeta>,\n\t\toptions?: MCPOptions,\n\t) {\n\t\tthis.router = router;\n\t\tthis.base = base;\n\t\tthis.fetchHandler = fetchHandler;\n\t\tthis.mcpMeta = mcpMeta;\n\t\tthis.serverName = options?.name ?? 'MCP Server';\n\t\tthis.serverVersion = options?.version ?? '1.0.0';\n\t}\n\n\tpublic toolNameFromRoute(method: string, path: string): string {\n\t\tlet name = path;\n\t\t// Strip base path\n\t\tif (this.base && name.startsWith(this.base)) {\n\t\t\tname = name.slice(this.base.length);\n\t\t}\n\t\t// Remove leading slash\n\t\tname = name.replace(/^\\//, '');\n\t\t// Replace :param with \"by_param\"\n\t\tname = name.replace(/:(\\w+)/g, 'by_$1');\n\t\t// Replace slashes with underscores\n\t\tname = name.replace(/\\//g, '_');\n\t\t// Prepend method\n\t\tname = `${method.toLowerCase()}_${name}`;\n\t\t// Clean up double underscores\n\t\tname = name.replace(/_+/g, '_').replace(/_$/, '');\n\t\treturn name;\n\t}\n\n\tpublic inferQueryParams(handlers: Function[]): string[] {\n\t\tconst params = new Set<string>();\n\t\tfor (const handler of handlers) {\n\t\t\ttry {\n\t\t\t\tconst src = handler.toString();\n\t\t\t\t// Match req.query.paramName, request.query.paramName, query.paramName\n\t\t\t\tconst dotPattern = /(?:req(?:uest)?\\.)?query\\.(\\w+)/g;\n\t\t\t\tlet match: RegExpExecArray | null;\n\t\t\t\twhile ((match = dotPattern.exec(src)) !== null) {\n\t\t\t\t\tparams.add(match[1]);\n\t\t\t\t}\n\t\t\t\t// Match req.query['paramName'] or req.query[\"paramName\"]\n\t\t\t\tconst bracketPattern = /(?:req(?:uest)?\\.)?query\\[['\"](\\w+)['\"]\\]/g;\n\t\t\t\twhile ((match = bracketPattern.exec(src)) !== null) {\n\t\t\t\t\tparams.add(match[1]);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// handler.toString() may fail for native code\n\t\t\t}\n\t\t}\n\t\treturn Array.from(params);\n\t}\n\n\tpublic introspectRoutes(): ToolDefinition[] {\n\t\tconst routes: any[] = (this.router as any).routes ?? [];\n\t\tconst tools: ToolDefinition[] = [];\n\n\t\tfor (const route of routes) {\n\t\t\t// itty-router v5 stores routes as [method, regex, handlers[], path]\n\t\t\tconst [method, , handlers, originalPath] = route;\n\n\t\t\t// Skip wildcard catch-alls and OPTIONS\n\t\t\tif (!originalPath || originalPath === '*' || originalPath.endsWith('/*')) continue;\n\t\t\tif (method === 'OPTIONS' || method === 'ALL') continue;\n\n\t\t\tconst toolName = this.toolNameFromRoute(method, originalPath);\n\t\t\tconst metaKey = `${method}:${originalPath}`;\n\t\t\tconst meta = this.mcpMeta.get(metaKey);\n\n\t\t\tconst properties: Record<string, any> = {};\n\t\t\tconst required: string[] = [];\n\n\t\t\t// Extract path params\n\t\t\tconst paramPattern = /:(\\w+)/g;\n\t\t\tlet match: RegExpExecArray | null;\n\t\t\twhile ((match = paramPattern.exec(originalPath)) !== null) {\n\t\t\t\tconst paramName = match[1];\n\t\t\t\tproperties[paramName] = {\n\t\t\t\t\ttype: meta?.params?.[paramName]?.type ?? 'string',\n\t\t\t\t\t...(meta?.params?.[paramName]?.description && { description: meta.params[paramName].description }),\n\t\t\t\t};\n\t\t\t\trequired.push(paramName);\n\t\t\t}\n\n\t\t\t// POST/PUT/PATCH get a body property\n\t\t\tif (['POST', 'PUT', 'PATCH'].includes(method)) {\n\t\t\t\tproperties['body'] = {\n\t\t\t\t\ttype: 'object',\n\t\t\t\t\tdescription: meta?.params?.['body']?.description ?? 'Request body',\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Infer query params for GET/DELETE\n\t\t\tif (['GET', 'DELETE'].includes(method)) {\n\t\t\t\tconst queryParams = this.inferQueryParams(handlers);\n\t\t\t\tfor (const qp of queryParams) {\n\t\t\t\t\tif (!properties[qp]) {\n\t\t\t\t\t\tproperties[qp] = {\n\t\t\t\t\t\t\ttype: meta?.params?.[qp]?.type ?? 'string',\n\t\t\t\t\t\t\t...(meta?.params?.[qp]?.description && { description: meta.params[qp].description }),\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst description = meta?.description ?? `${method} ${originalPath}`;\n\n\t\t\ttools.push({\n\t\t\t\tname: toolName,\n\t\t\t\tdescription,\n\t\t\t\tinputSchema: {\n\t\t\t\t\ttype: 'object',\n\t\t\t\t\tproperties,\n\t\t\t\t\trequired,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\treturn tools;\n\t}\n\n\tprivate findRoute(toolName: string): { method: string; path: string; handlers: Function[] } | null {\n\t\tconst routes: any[] = (this.router as any).routes ?? [];\n\t\tfor (const route of routes) {\n\t\t\tconst [method, , handlers, originalPath] = route;\n\t\t\tif (!originalPath || originalPath === '*' || originalPath.endsWith('/*')) continue;\n\t\t\tif (method === 'OPTIONS' || method === 'ALL') continue;\n\t\t\tif (this.toolNameFromRoute(method, originalPath) === toolName) {\n\t\t\t\treturn { method, path: originalPath, handlers };\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tpublic async callTool(name: string, args: Record<string, any> = {}): Promise<any> {\n\t\tconst route = this.findRoute(name);\n\t\tif (!route) {\n\t\t\treturn { isError: true, content: [{ type: 'text', text: `Unknown tool: ${name}` }] };\n\t\t}\n\n\t\tconst { method, path } = route;\n\n\t\t// Build URL with path params substituted\n\t\tlet url = path;\n\t\tconst pathParamPattern = /:(\\w+)/g;\n\t\tlet match: RegExpExecArray | null;\n\t\twhile ((match = pathParamPattern.exec(path)) !== null) {\n\t\t\tconst paramName = match[1];\n\t\t\tif (args[paramName] != null) {\n\t\t\t\turl = url.replace(`:${paramName}`, encodeURIComponent(String(args[paramName])));\n\t\t\t}\n\t\t}\n\n\t\t// Build query string for non-path, non-body params\n\t\tconst pathParams = new Set<string>();\n\t\tconst ppPattern = /:(\\w+)/g;\n\t\tlet m: RegExpExecArray | null;\n\t\twhile ((m = ppPattern.exec(path)) !== null) pathParams.add(m[1]);\n\n\t\tconst queryParams: string[] = [];\n\t\tfor (const [key, value] of Object.entries(args)) {\n\t\t\tif (key === 'body' || pathParams.has(key)) continue;\n\t\t\tqueryParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);\n\t\t}\n\t\tif (queryParams.length > 0) {\n\t\t\turl += '?' + queryParams.join('&');\n\t\t}\n\n\t\tconst fullUrl = `http://localhost${url}`;\n\t\tconst requestInit: RequestInit = { method };\n\t\tif (args.body && ['POST', 'PUT', 'PATCH'].includes(method)) {\n\t\t\trequestInit.body = JSON.stringify(args.body);\n\t\t\trequestInit.headers = { 'Content-Type': 'application/json' };\n\t\t}\n\n\t\tconst request = new Request(fullUrl, requestInit);\n\n\t\ttry {\n\t\t\tconst response = await this.fetchHandler(request as any);\n\t\t\tconst text = await response.text();\n\t\t\tlet content: any;\n\t\t\ttry {\n\t\t\t\tcontent = JSON.parse(text);\n\t\t\t} catch {\n\t\t\t\tcontent = text;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: 'text', text: typeof content === 'string' ? content : JSON.stringify(content) }],\n\t\t\t};\n\t\t} catch (err: any) {\n\t\t\treturn {\n\t\t\t\tisError: true,\n\t\t\t\tcontent: [{ type: 'text', text: err?.message ?? String(err) }],\n\t\t\t};\n\t\t}\n\t}\n\n\tpublic async handleJsonRpc(message: JsonRpcRequest): Promise<any> {\n\t\tconst { method, id, params } = message;\n\n\t\tswitch (method) {\n\t\t\tcase 'initialize':\n\t\t\t\treturn {\n\t\t\t\t\tjsonrpc: '2.0',\n\t\t\t\t\tid,\n\t\t\t\t\tresult: {\n\t\t\t\t\t\tprotocolVersion: '2024-11-05',\n\t\t\t\t\t\tcapabilities: { tools: { listChanged: true } },\n\t\t\t\t\t\tserverInfo: { name: this.serverName, version: this.serverVersion },\n\t\t\t\t\t},\n\t\t\t\t};\n\n\t\t\tcase 'notifications/initialized':\n\t\t\t\t// No response needed for notifications\n\t\t\t\treturn null;\n\n\t\t\tcase 'tools/list':\n\t\t\t\treturn {\n\t\t\t\t\tjsonrpc: '2.0',\n\t\t\t\t\tid,\n\t\t\t\t\tresult: { tools: this.introspectRoutes() },\n\t\t\t\t};\n\n\t\t\tcase 'tools/call': {\n\t\t\t\tconst result = await this.callTool(params?.name, params?.arguments ?? {});\n\t\t\t\treturn {\n\t\t\t\t\tjsonrpc: '2.0',\n\t\t\t\t\tid,\n\t\t\t\t\tresult,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t\treturn {\n\t\t\t\t\tjsonrpc: '2.0',\n\t\t\t\t\tid,\n\t\t\t\t\terror: { code: -32601, message: `Method not found: ${method}` },\n\t\t\t\t};\n\t\t}\n\t}\n\n\tpublic httpHandler = async (request: Request): Promise<Response> => {\n\t\tif (request.method === 'GET') {\n\t\t\t// SSE endpoint for server-sent events (Streamable HTTP)\n\t\t\tconst stream = new ReadableStream({\n\t\t\t\tstart(controller) {\n\t\t\t\t\tconst encoder = new TextEncoder();\n\t\t\t\t\tcontroller.enqueue(encoder.encode(`event: endpoint\\ndata: /mcp\\n\\n`));\n\t\t\t\t\t// Keep connection open - client will close\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn new Response(stream, {\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'text/event-stream',\n\t\t\t\t\t'Cache-Control': 'no-cache',\n\t\t\t\t\t'Connection': 'keep-alive',\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\t// POST: JSON-RPC request\n\t\ttry {\n\t\t\tconst body = await request.json() as JsonRpcRequest;\n\t\t\tconst result = await this.handleJsonRpc(body);\n\t\t\tif (result === null) {\n\t\t\t\treturn new Response(null, { status: 204 });\n\t\t\t}\n\t\t\treturn new Response(JSON.stringify(result), {\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t});\n\t\t} catch (err: any) {\n\t\t\treturn new Response(JSON.stringify({\n\t\t\t\tjsonrpc: '2.0',\n\t\t\t\tid: null,\n\t\t\t\terror: { code: -32700, message: 'Parse error' },\n\t\t\t}), {\n\t\t\t\tstatus: 400,\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t});\n\t\t}\n\t};\n\n\tpublic async serveStdio(): Promise<void> {\n\t\tconst stdin = (globalThis as any).process.stdin;\n\t\tconst stdout = (globalThis as any).process.stdout;\n\n\t\tstdin.setEncoding('utf8');\n\n\t\tlet buffer = '';\n\t\tstdin.on('data', async (chunk: string) => {\n\t\t\tbuffer += chunk;\n\t\t\t// Process complete lines\n\t\t\tconst lines = buffer.split('\\n');\n\t\t\tbuffer = lines.pop() ?? '';\n\n\t\t\tfor (const line of lines) {\n\t\t\t\tconst trimmed = line.trim();\n\t\t\t\tif (!trimmed) continue;\n\t\t\t\ttry {\n\t\t\t\t\tconst message = JSON.parse(trimmed) as JsonRpcRequest;\n\t\t\t\t\tconst result = await this.handleJsonRpc(message);\n\t\t\t\t\tif (result !== null) {\n\t\t\t\t\t\tstdout.write(JSON.stringify(result) + '\\n');\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\tstdout.write(JSON.stringify({\n\t\t\t\t\t\tjsonrpc: '2.0',\n\t\t\t\t\t\tid: null,\n\t\t\t\t\t\terror: { code: -32700, message: 'Parse error' },\n\t\t\t\t\t}) + '\\n');\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import { RouterType, cors, IRequest } from 'itty-router';
2
+ import { MCPAdapter, MCPOptions } from './MCPAdapter';
2
3
  type BaseRouterInitializer = (string: any) => {
3
4
  base: string;
4
5
  router: RouterType<IRequest, any[]>;
@@ -10,6 +11,7 @@ export declare class RouterWrapper<TCtx = any> {
10
11
  private cors;
11
12
  private preflight;
12
13
  private corsify;
14
+ private mcpMeta;
13
15
  constructor(base: string, router: RouterType<Request, [], any>, corsOptions?: CorsOptions);
14
16
  static getNew(base: string, corsOptions?: CorsOptions): RouterWrapper<any>;
15
17
  static errorHandler(error: any): Response;
@@ -17,5 +19,13 @@ export declare class RouterWrapper<TCtx = any> {
17
19
  fetchHandler(request: IRequest, ctx: TCtx): Promise<Response>;
18
20
  catchNotFound(): void;
19
21
  createServerAdapter(): import("@whatwg-node/server").ServerAdapter<{}, import("@whatwg-node/server").ServerAdapterBaseObject<{}, any>>;
22
+ describeMCP(path: string, method: string, meta: {
23
+ description?: string;
24
+ params?: Record<string, {
25
+ description?: string;
26
+ type?: string;
27
+ }>;
28
+ }): void;
29
+ asMCP(options?: MCPOptions): MCPAdapter;
20
30
  }
21
31
  export {};
@@ -4,10 +4,12 @@ exports.RouterWrapper = void 0;
4
4
  const essentials_js_1 = require("libx.js/build/bundles/essentials.js");
5
5
  const itty_router_1 = require("itty-router");
6
6
  const server_1 = require("@whatwg-node/server");
7
+ const MCPAdapter_1 = require("./MCPAdapter");
7
8
  class RouterWrapper {
8
9
  constructor(base, router, corsOptions) {
9
10
  this.base = base;
10
11
  this.router = router;
12
+ this.mcpMeta = new Map();
11
13
  this.cors = (0, itty_router_1.cors)(corsOptions !== null && corsOptions !== void 0 ? corsOptions : {
12
14
  origin: '*',
13
15
  allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],
@@ -55,6 +57,13 @@ class RouterWrapper {
55
57
  const ittyServer = (0, server_1.createServerAdapter)(this.fetchHandler.bind(this));
56
58
  return ittyServer;
57
59
  }
60
+ describeMCP(path, method, meta) {
61
+ const key = `${method.toUpperCase()}:${this.base}${path}`;
62
+ this.mcpMeta.set(key, meta);
63
+ }
64
+ asMCP(options) {
65
+ return new MCPAdapter_1.MCPAdapter(this.router, this.base, this.fetchHandler.bind(this), this.mcpMeta, options);
66
+ }
58
67
  }
59
68
  exports.RouterWrapper = RouterWrapper;
60
69
  //# sourceMappingURL=RouterWrapper.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"RouterWrapper.js","sourceRoot":"","sources":["../../src/modules/RouterWrapper.ts"],"names":[],"mappings":"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","sourcesContent":["import { libx } from 'libx.js/build/bundles/essentials.js';\nimport { Route, Router, RouterType, error, json, cors, withParams, IRequest, text, ResponseHandler } from 'itty-router';\nimport { createServerAdapter } from '@whatwg-node/server';\n\ntype BaseRouterInitializer = (string) => { base: string; router: RouterType<IRequest, any[]> };\ntype CorsOptions = Parameters<typeof cors>[0];\n\nexport class RouterWrapper<TCtx = any> {\n\tprivate cors: ReturnType<typeof cors>;\n\tprivate preflight: (request: Request) => Response | Promise<Response>;\n\tprivate corsify: (response: Response, request: Request) => Response | Promise<Response>;\n\n\tpublic constructor(\n\t\tpublic base: string,\n\t\tpublic router: RouterType<Request, [], any>,\n\t\tcorsOptions?: CorsOptions\n\t) {\n\t\tthis.cors = cors(corsOptions ?? {\n\t\t\torigin: '*',\n\t\t\tallowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],\n\t\t\tallowHeaders: ['Content-Type', 'Authorization'],\n\t\t\texposeHeaders: ['Content-Type', 'Authorization'],\n\t\t});\n\t\tthis.preflight = this.cors.preflight;\n\t\tthis.corsify = this.cors.corsify;\n\t}\n\n\tpublic static getNew(base: string, corsOptions?: CorsOptions) {\n\t\tconst temp = new RouterWrapper(base, Router({ base }), corsOptions);\n\t\tconst router = Router({\n\t\t\tbase,\n\t\t\tbefore: [temp.preflight],\n\t\t\tcatch: RouterWrapper.errorHandler,\n\t\t});\n\t\trouter.all('*', withParams);\n\t\treturn new RouterWrapper(base, router, corsOptions);\n\t}\n\n\tpublic static errorHandler(error) {\n\t\tconst isObject = libx.isObject(error);\n\t\tconst errMessage = error?.message ?? error;\n\t\tconst msg = 'Error: ' + (!isObject ? (errMessage ?? 'Server Error') : JSON.stringify(error));\n\t\tconst status = parseInt(error?.status ?? error?.statusCode ?? error?.code ?? error?.error?.code) || 500;\n\t\tconsole.error('Server error: ', error, status);\n\t\treturn new Response(msg, { status })\n\t}\n\n\tpublic registerRoute(newBase: string, baseRouterInitializer: BaseRouterInitializer) {\n\t\tconst route = baseRouterInitializer(`${this.base}${newBase}`);\n\t\tthis.router.all(newBase + '/*', route.router.fetch);\n\t\treturn this.router;\n\t};\n\n\tpublic fetchHandler(request: IRequest, ctx: TCtx) {\n\t\treturn this.router.fetch(request, ctx)\n\t\t\t.then(json)\n\t\t\t.catch(RouterWrapper.errorHandler)\n\t\t\t.then((res) => this.corsify(res, request));\n\t}\n\n\tpublic catchNotFound() {\n\t\tthis.router.all('*', () => error(404));\n\t}\n\n\tpublic createServerAdapter() {\n\t\tconst ittyServer = createServerAdapter(this.fetchHandler.bind(this));\n\t\treturn ittyServer;\n\t}\n}"]}
1
+ {"version":3,"file":"RouterWrapper.js","sourceRoot":"","sources":["../../src/modules/RouterWrapper.ts"],"names":[],"mappings":"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","sourcesContent":["import { libx } from 'libx.js/build/bundles/essentials.js';\nimport { Route, Router, RouterType, error, json, cors, withParams, IRequest, text, ResponseHandler } from 'itty-router';\nimport { createServerAdapter } from '@whatwg-node/server';\nimport { MCPAdapter, MCPOptions } from './MCPAdapter';\n\ntype BaseRouterInitializer = (string) => { base: string; router: RouterType<IRequest, any[]> };\ntype CorsOptions = Parameters<typeof cors>[0];\n\nexport class RouterWrapper<TCtx = any> {\n\tprivate cors: ReturnType<typeof cors>;\n\tprivate preflight: (request: Request) => Response | Promise<Response>;\n\tprivate corsify: (response: Response, request: Request) => Response | Promise<Response>;\n\tprivate mcpMeta: Map<string, { description?: string; params?: Record<string, { description?: string; type?: string }> }> = new Map();\n\n\tpublic constructor(\n\t\tpublic base: string,\n\t\tpublic router: RouterType<Request, [], any>,\n\t\tcorsOptions?: CorsOptions\n\t) {\n\t\tthis.cors = cors(corsOptions ?? {\n\t\t\torigin: '*',\n\t\t\tallowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],\n\t\t\tallowHeaders: ['Content-Type', 'Authorization'],\n\t\t\texposeHeaders: ['Content-Type', 'Authorization'],\n\t\t});\n\t\tthis.preflight = this.cors.preflight;\n\t\tthis.corsify = this.cors.corsify;\n\t}\n\n\tpublic static getNew(base: string, corsOptions?: CorsOptions) {\n\t\tconst temp = new RouterWrapper(base, Router({ base }), corsOptions);\n\t\tconst router = Router({\n\t\t\tbase,\n\t\t\tbefore: [temp.preflight],\n\t\t\tcatch: RouterWrapper.errorHandler,\n\t\t});\n\t\trouter.all('*', withParams);\n\t\treturn new RouterWrapper(base, router, corsOptions);\n\t}\n\n\tpublic static errorHandler(error) {\n\t\tconst isObject = libx.isObject(error);\n\t\tconst errMessage = error?.message ?? error;\n\t\tconst msg = 'Error: ' + (!isObject ? (errMessage ?? 'Server Error') : JSON.stringify(error));\n\t\tconst status = parseInt(error?.status ?? error?.statusCode ?? error?.code ?? error?.error?.code) || 500;\n\t\tconsole.error('Server error: ', error, status);\n\t\treturn new Response(msg, { status })\n\t}\n\n\tpublic registerRoute(newBase: string, baseRouterInitializer: BaseRouterInitializer) {\n\t\tconst route = baseRouterInitializer(`${this.base}${newBase}`);\n\t\tthis.router.all(newBase + '/*', route.router.fetch);\n\t\treturn this.router;\n\t};\n\n\tpublic fetchHandler(request: IRequest, ctx: TCtx) {\n\t\treturn this.router.fetch(request, ctx)\n\t\t\t.then(json)\n\t\t\t.catch(RouterWrapper.errorHandler)\n\t\t\t.then((res) => this.corsify(res, request));\n\t}\n\n\tpublic catchNotFound() {\n\t\tthis.router.all('*', () => error(404));\n\t}\n\n\tpublic createServerAdapter() {\n\t\tconst ittyServer = createServerAdapter(this.fetchHandler.bind(this));\n\t\treturn ittyServer;\n\t}\n\n\tpublic describeMCP(path: string, method: string, meta: { description?: string; params?: Record<string, { description?: string; type?: string }> }) {\n\t\tconst key = `${method.toUpperCase()}:${this.base}${path}`;\n\t\tthis.mcpMeta.set(key, meta);\n\t}\n\n\tpublic asMCP(options?: MCPOptions): MCPAdapter {\n\t\treturn new MCPAdapter(\n\t\t\tthis.router,\n\t\t\tthis.base,\n\t\t\tthis.fetchHandler.bind(this),\n\t\t\tthis.mcpMeta,\n\t\t\toptions,\n\t\t);\n\t}\n}"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edge.libx.js",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "main": "build/main.js",
5
5
  "license": "MIT",
6
6
  "author": "Elya Livshitz",
package/src/main.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { RouterWrapper } from "./modules/RouterWrapper";
2
+ export { MCPAdapter } from "./modules/MCPAdapter";
2
3
  export * as cors from "./modules/cors";
@@ -0,0 +1,356 @@
1
+ import { RouterType, IRequest } from 'itty-router';
2
+
3
+ export interface MCPOptions {
4
+ name?: string;
5
+ version?: string;
6
+ }
7
+
8
+ interface ToolMeta {
9
+ description?: string;
10
+ params?: Record<string, { description?: string; type?: string }>;
11
+ }
12
+
13
+ interface ToolDefinition {
14
+ name: string;
15
+ description: string;
16
+ inputSchema: {
17
+ type: 'object';
18
+ properties: Record<string, any>;
19
+ required: string[];
20
+ };
21
+ }
22
+
23
+ interface JsonRpcRequest {
24
+ jsonrpc: '2.0';
25
+ id?: string | number;
26
+ method: string;
27
+ params?: any;
28
+ }
29
+
30
+ export class MCPAdapter {
31
+ private router: RouterType<any, any[], any>;
32
+ private base: string;
33
+ private fetchHandler: (request: IRequest, ctx?: any) => Promise<Response>;
34
+ private serverName: string;
35
+ private serverVersion: string;
36
+ private mcpMeta: Map<string, ToolMeta>;
37
+
38
+ constructor(
39
+ router: RouterType<any, any[], any>,
40
+ base: string,
41
+ fetchHandler: (request: IRequest, ctx?: any) => Promise<Response>,
42
+ mcpMeta: Map<string, ToolMeta>,
43
+ options?: MCPOptions,
44
+ ) {
45
+ this.router = router;
46
+ this.base = base;
47
+ this.fetchHandler = fetchHandler;
48
+ this.mcpMeta = mcpMeta;
49
+ this.serverName = options?.name ?? 'MCP Server';
50
+ this.serverVersion = options?.version ?? '1.0.0';
51
+ }
52
+
53
+ public toolNameFromRoute(method: string, path: string): string {
54
+ let name = path;
55
+ // Strip base path
56
+ if (this.base && name.startsWith(this.base)) {
57
+ name = name.slice(this.base.length);
58
+ }
59
+ // Remove leading slash
60
+ name = name.replace(/^\//, '');
61
+ // Replace :param with "by_param"
62
+ name = name.replace(/:(\w+)/g, 'by_$1');
63
+ // Replace slashes with underscores
64
+ name = name.replace(/\//g, '_');
65
+ // Prepend method
66
+ name = `${method.toLowerCase()}_${name}`;
67
+ // Clean up double underscores
68
+ name = name.replace(/_+/g, '_').replace(/_$/, '');
69
+ return name;
70
+ }
71
+
72
+ public inferQueryParams(handlers: Function[]): string[] {
73
+ const params = new Set<string>();
74
+ for (const handler of handlers) {
75
+ try {
76
+ const src = handler.toString();
77
+ // Match req.query.paramName, request.query.paramName, query.paramName
78
+ const dotPattern = /(?:req(?:uest)?\.)?query\.(\w+)/g;
79
+ let match: RegExpExecArray | null;
80
+ while ((match = dotPattern.exec(src)) !== null) {
81
+ params.add(match[1]);
82
+ }
83
+ // Match req.query['paramName'] or req.query["paramName"]
84
+ const bracketPattern = /(?:req(?:uest)?\.)?query\[['"](\w+)['"]\]/g;
85
+ while ((match = bracketPattern.exec(src)) !== null) {
86
+ params.add(match[1]);
87
+ }
88
+ } catch {
89
+ // handler.toString() may fail for native code
90
+ }
91
+ }
92
+ return Array.from(params);
93
+ }
94
+
95
+ public introspectRoutes(): ToolDefinition[] {
96
+ const routes: any[] = (this.router as any).routes ?? [];
97
+ const tools: ToolDefinition[] = [];
98
+
99
+ for (const route of routes) {
100
+ // itty-router v5 stores routes as [method, regex, handlers[], path]
101
+ const [method, , handlers, originalPath] = route;
102
+
103
+ // Skip wildcard catch-alls and OPTIONS
104
+ if (!originalPath || originalPath === '*' || originalPath.endsWith('/*')) continue;
105
+ if (method === 'OPTIONS' || method === 'ALL') continue;
106
+
107
+ const toolName = this.toolNameFromRoute(method, originalPath);
108
+ const metaKey = `${method}:${originalPath}`;
109
+ const meta = this.mcpMeta.get(metaKey);
110
+
111
+ const properties: Record<string, any> = {};
112
+ const required: string[] = [];
113
+
114
+ // Extract path params
115
+ const paramPattern = /:(\w+)/g;
116
+ let match: RegExpExecArray | null;
117
+ while ((match = paramPattern.exec(originalPath)) !== null) {
118
+ const paramName = match[1];
119
+ properties[paramName] = {
120
+ type: meta?.params?.[paramName]?.type ?? 'string',
121
+ ...(meta?.params?.[paramName]?.description && { description: meta.params[paramName].description }),
122
+ };
123
+ required.push(paramName);
124
+ }
125
+
126
+ // POST/PUT/PATCH get a body property
127
+ if (['POST', 'PUT', 'PATCH'].includes(method)) {
128
+ properties['body'] = {
129
+ type: 'object',
130
+ description: meta?.params?.['body']?.description ?? 'Request body',
131
+ };
132
+ }
133
+
134
+ // Infer query params for GET/DELETE
135
+ if (['GET', 'DELETE'].includes(method)) {
136
+ const queryParams = this.inferQueryParams(handlers);
137
+ for (const qp of queryParams) {
138
+ if (!properties[qp]) {
139
+ properties[qp] = {
140
+ type: meta?.params?.[qp]?.type ?? 'string',
141
+ ...(meta?.params?.[qp]?.description && { description: meta.params[qp].description }),
142
+ };
143
+ }
144
+ }
145
+ }
146
+
147
+ const description = meta?.description ?? `${method} ${originalPath}`;
148
+
149
+ tools.push({
150
+ name: toolName,
151
+ description,
152
+ inputSchema: {
153
+ type: 'object',
154
+ properties,
155
+ required,
156
+ },
157
+ });
158
+ }
159
+
160
+ return tools;
161
+ }
162
+
163
+ private findRoute(toolName: string): { method: string; path: string; handlers: Function[] } | null {
164
+ const routes: any[] = (this.router as any).routes ?? [];
165
+ for (const route of routes) {
166
+ const [method, , handlers, originalPath] = route;
167
+ if (!originalPath || originalPath === '*' || originalPath.endsWith('/*')) continue;
168
+ if (method === 'OPTIONS' || method === 'ALL') continue;
169
+ if (this.toolNameFromRoute(method, originalPath) === toolName) {
170
+ return { method, path: originalPath, handlers };
171
+ }
172
+ }
173
+ return null;
174
+ }
175
+
176
+ public async callTool(name: string, args: Record<string, any> = {}): Promise<any> {
177
+ const route = this.findRoute(name);
178
+ if (!route) {
179
+ return { isError: true, content: [{ type: 'text', text: `Unknown tool: ${name}` }] };
180
+ }
181
+
182
+ const { method, path } = route;
183
+
184
+ // Build URL with path params substituted
185
+ let url = path;
186
+ const pathParamPattern = /:(\w+)/g;
187
+ let match: RegExpExecArray | null;
188
+ while ((match = pathParamPattern.exec(path)) !== null) {
189
+ const paramName = match[1];
190
+ if (args[paramName] != null) {
191
+ url = url.replace(`:${paramName}`, encodeURIComponent(String(args[paramName])));
192
+ }
193
+ }
194
+
195
+ // Build query string for non-path, non-body params
196
+ const pathParams = new Set<string>();
197
+ const ppPattern = /:(\w+)/g;
198
+ let m: RegExpExecArray | null;
199
+ while ((m = ppPattern.exec(path)) !== null) pathParams.add(m[1]);
200
+
201
+ const queryParams: string[] = [];
202
+ for (const [key, value] of Object.entries(args)) {
203
+ if (key === 'body' || pathParams.has(key)) continue;
204
+ queryParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
205
+ }
206
+ if (queryParams.length > 0) {
207
+ url += '?' + queryParams.join('&');
208
+ }
209
+
210
+ const fullUrl = `http://localhost${url}`;
211
+ const requestInit: RequestInit = { method };
212
+ if (args.body && ['POST', 'PUT', 'PATCH'].includes(method)) {
213
+ requestInit.body = JSON.stringify(args.body);
214
+ requestInit.headers = { 'Content-Type': 'application/json' };
215
+ }
216
+
217
+ const request = new Request(fullUrl, requestInit);
218
+
219
+ try {
220
+ const response = await this.fetchHandler(request as any);
221
+ const text = await response.text();
222
+ let content: any;
223
+ try {
224
+ content = JSON.parse(text);
225
+ } catch {
226
+ content = text;
227
+ }
228
+ return {
229
+ content: [{ type: 'text', text: typeof content === 'string' ? content : JSON.stringify(content) }],
230
+ };
231
+ } catch (err: any) {
232
+ return {
233
+ isError: true,
234
+ content: [{ type: 'text', text: err?.message ?? String(err) }],
235
+ };
236
+ }
237
+ }
238
+
239
+ public async handleJsonRpc(message: JsonRpcRequest): Promise<any> {
240
+ const { method, id, params } = message;
241
+
242
+ switch (method) {
243
+ case 'initialize':
244
+ return {
245
+ jsonrpc: '2.0',
246
+ id,
247
+ result: {
248
+ protocolVersion: '2024-11-05',
249
+ capabilities: { tools: { listChanged: true } },
250
+ serverInfo: { name: this.serverName, version: this.serverVersion },
251
+ },
252
+ };
253
+
254
+ case 'notifications/initialized':
255
+ // No response needed for notifications
256
+ return null;
257
+
258
+ case 'tools/list':
259
+ return {
260
+ jsonrpc: '2.0',
261
+ id,
262
+ result: { tools: this.introspectRoutes() },
263
+ };
264
+
265
+ case 'tools/call': {
266
+ const result = await this.callTool(params?.name, params?.arguments ?? {});
267
+ return {
268
+ jsonrpc: '2.0',
269
+ id,
270
+ result,
271
+ };
272
+ }
273
+
274
+ default:
275
+ return {
276
+ jsonrpc: '2.0',
277
+ id,
278
+ error: { code: -32601, message: `Method not found: ${method}` },
279
+ };
280
+ }
281
+ }
282
+
283
+ public httpHandler = async (request: Request): Promise<Response> => {
284
+ if (request.method === 'GET') {
285
+ // SSE endpoint for server-sent events (Streamable HTTP)
286
+ const stream = new ReadableStream({
287
+ start(controller) {
288
+ const encoder = new TextEncoder();
289
+ controller.enqueue(encoder.encode(`event: endpoint\ndata: /mcp\n\n`));
290
+ // Keep connection open - client will close
291
+ },
292
+ });
293
+ return new Response(stream, {
294
+ headers: {
295
+ 'Content-Type': 'text/event-stream',
296
+ 'Cache-Control': 'no-cache',
297
+ 'Connection': 'keep-alive',
298
+ },
299
+ });
300
+ }
301
+
302
+ // POST: JSON-RPC request
303
+ try {
304
+ const body = await request.json() as JsonRpcRequest;
305
+ const result = await this.handleJsonRpc(body);
306
+ if (result === null) {
307
+ return new Response(null, { status: 204 });
308
+ }
309
+ return new Response(JSON.stringify(result), {
310
+ headers: { 'Content-Type': 'application/json' },
311
+ });
312
+ } catch (err: any) {
313
+ return new Response(JSON.stringify({
314
+ jsonrpc: '2.0',
315
+ id: null,
316
+ error: { code: -32700, message: 'Parse error' },
317
+ }), {
318
+ status: 400,
319
+ headers: { 'Content-Type': 'application/json' },
320
+ });
321
+ }
322
+ };
323
+
324
+ public async serveStdio(): Promise<void> {
325
+ const stdin = (globalThis as any).process.stdin;
326
+ const stdout = (globalThis as any).process.stdout;
327
+
328
+ stdin.setEncoding('utf8');
329
+
330
+ let buffer = '';
331
+ stdin.on('data', async (chunk: string) => {
332
+ buffer += chunk;
333
+ // Process complete lines
334
+ const lines = buffer.split('\n');
335
+ buffer = lines.pop() ?? '';
336
+
337
+ for (const line of lines) {
338
+ const trimmed = line.trim();
339
+ if (!trimmed) continue;
340
+ try {
341
+ const message = JSON.parse(trimmed) as JsonRpcRequest;
342
+ const result = await this.handleJsonRpc(message);
343
+ if (result !== null) {
344
+ stdout.write(JSON.stringify(result) + '\n');
345
+ }
346
+ } catch {
347
+ stdout.write(JSON.stringify({
348
+ jsonrpc: '2.0',
349
+ id: null,
350
+ error: { code: -32700, message: 'Parse error' },
351
+ }) + '\n');
352
+ }
353
+ }
354
+ });
355
+ }
356
+ }
@@ -1,6 +1,7 @@
1
1
  import { libx } from 'libx.js/build/bundles/essentials.js';
2
2
  import { Route, Router, RouterType, error, json, cors, withParams, IRequest, text, ResponseHandler } from 'itty-router';
3
3
  import { createServerAdapter } from '@whatwg-node/server';
4
+ import { MCPAdapter, MCPOptions } from './MCPAdapter';
4
5
 
5
6
  type BaseRouterInitializer = (string) => { base: string; router: RouterType<IRequest, any[]> };
6
7
  type CorsOptions = Parameters<typeof cors>[0];
@@ -9,6 +10,7 @@ export class RouterWrapper<TCtx = any> {
9
10
  private cors: ReturnType<typeof cors>;
10
11
  private preflight: (request: Request) => Response | Promise<Response>;
11
12
  private corsify: (response: Response, request: Request) => Response | Promise<Response>;
13
+ private mcpMeta: Map<string, { description?: string; params?: Record<string, { description?: string; type?: string }> }> = new Map();
12
14
 
13
15
  public constructor(
14
16
  public base: string,
@@ -66,4 +68,19 @@ export class RouterWrapper<TCtx = any> {
66
68
  const ittyServer = createServerAdapter(this.fetchHandler.bind(this));
67
69
  return ittyServer;
68
70
  }
71
+
72
+ public describeMCP(path: string, method: string, meta: { description?: string; params?: Record<string, { description?: string; type?: string }> }) {
73
+ const key = `${method.toUpperCase()}:${this.base}${path}`;
74
+ this.mcpMeta.set(key, meta);
75
+ }
76
+
77
+ public asMCP(options?: MCPOptions): MCPAdapter {
78
+ return new MCPAdapter(
79
+ this.router,
80
+ this.base,
81
+ this.fetchHandler.bind(this),
82
+ this.mcpMeta,
83
+ options,
84
+ );
85
+ }
69
86
  }