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 +88 -13
- package/build/main.d.ts +1 -0
- package/build/main.js +3 -1
- package/build/main.js.map +1 -1
- package/build/modules/MCPAdapter.d.ts +45 -0
- package/build/modules/MCPAdapter.js +291 -0
- package/build/modules/MCPAdapter.js.map +1 -0
- package/build/modules/RouterWrapper.d.ts +10 -0
- package/build/modules/RouterWrapper.js +9 -0
- package/build/modules/RouterWrapper.js.map +1 -1
- package/package.json +1 -1
- package/src/main.ts +1 -0
- package/src/modules/MCPAdapter.ts +356 -0
- package/src/modules/RouterWrapper.ts +17 -0
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
|
|
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 {
|
|
10
|
-
import {
|
|
8
|
+
```ts
|
|
9
|
+
import { RouterWrapper, cors } from 'edge.libx.js';
|
|
10
|
+
import { json, error } from 'itty-router';
|
|
11
11
|
|
|
12
|
-
|
|
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.
|
|
15
|
-
|
|
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
|
-
"
|
|
25
|
-
"
|
|
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
|
-
|
|
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
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":";;;AAAA,uEAA2D;AAC3D,6CAAwH;AACxH,gDAA0D;
|
|
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
package/src/main.ts
CHANGED
|
@@ -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
|
}
|