@fresho/router 0.5.0 → 0.6.1
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 +70 -2
- package/dist/auth/index.d.mts +2 -2
- package/dist/auth/index.mjs +2 -2
- package/dist/docs-C77XQzF3.mjs +738 -0
- package/dist/docs-C77XQzF3.mjs.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -679
- package/dist/index.mjs.map +1 -1
- package/dist/{jwt-DHM_nSwL.d.mts → jwt-B-Fqwu4E.d.mts} +52 -9
- package/dist/jwt-B-Fqwu4E.d.mts.map +1 -0
- package/dist/jwt-ikNuUBcp.mjs +373 -0
- package/dist/jwt-ikNuUBcp.mjs.map +1 -0
- package/dist/mcp/index.d.mts +63 -0
- package/dist/mcp/index.d.mts.map +1 -0
- package/dist/mcp/index.mjs +333 -0
- package/dist/mcp/index.mjs.map +1 -0
- package/dist/middleware/index.d.mts +10 -4
- package/dist/middleware/index.d.mts.map +1 -1
- package/dist/middleware/index.mjs +10 -5
- package/dist/middleware/index.mjs.map +1 -1
- package/dist/{middleware-XMbIevSv.mjs → middleware-B-_0PLA6.mjs} +1 -1
- package/dist/{middleware-XMbIevSv.mjs.map → middleware-B-_0PLA6.mjs.map} +1 -1
- package/dist/{middleware-Bxq0LHLS.d.mts → middleware-Cj1yHSJF.d.mts} +31 -9
- package/dist/middleware-Cj1yHSJF.d.mts.map +1 -0
- package/docs/authentication.md +89 -5
- package/package.json +21 -4
- package/dist/jwt-DHM_nSwL.d.mts.map +0 -1
- package/dist/jwt-Dab9kOC2.mjs +0 -208
- package/dist/jwt-Dab9kOC2.mjs.map +0 -1
- package/dist/middleware-Bxq0LHLS.d.mts.map +0 -1
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { n as runMiddleware } from "../middleware-B-_0PLA6.mjs";
|
|
2
|
+
import { d as unpackField, i as router, l as isNestedSchema, s as isRouter, t as collectRoutes, u as toJsonSchema } from "../docs-C77XQzF3.mjs";
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
5
|
+
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
+
|
|
7
|
+
//#region src/mcp/index.ts
|
|
8
|
+
/**
|
|
9
|
+
* @fileoverview MCP server adapter for @fresho/router.
|
|
10
|
+
*
|
|
11
|
+
* Exposes routes as Model Context Protocol tools over stateless Streamable HTTP. Each tool call
|
|
12
|
+
* is dispatched as a regular HTTP request through the router, so route middleware (auth, rate
|
|
13
|
+
* limits, logging) applies to agents exactly as it does to other clients.
|
|
14
|
+
*
|
|
15
|
+
* Requires the optional peer dependency `@modelcontextprotocol/sdk`.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import { router } from '@fresho/router';
|
|
20
|
+
* import { mcp } from '@fresho/router/mcp';
|
|
21
|
+
* import { bearerAuth } from '@fresho/router/middleware';
|
|
22
|
+
*
|
|
23
|
+
* const api = router({ users, billing });
|
|
24
|
+
*
|
|
25
|
+
* export default {
|
|
26
|
+
* fetch: router({
|
|
27
|
+
* ...api.routes,
|
|
28
|
+
* mcp: mcp({ users }, { name: 'my-api', version: '1.0.0' }, bearerAuth({ verify })),
|
|
29
|
+
* }).handler(),
|
|
30
|
+
* };
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
const METHODS_WITH_BODY = [
|
|
34
|
+
"post",
|
|
35
|
+
"put",
|
|
36
|
+
"patch"
|
|
37
|
+
];
|
|
38
|
+
const CONTEXT_KEYS = new Set([
|
|
39
|
+
"request",
|
|
40
|
+
"path",
|
|
41
|
+
"query",
|
|
42
|
+
"body",
|
|
43
|
+
"env",
|
|
44
|
+
"executionCtx"
|
|
45
|
+
]);
|
|
46
|
+
const DROPPED_HEADERS = [
|
|
47
|
+
"Connection",
|
|
48
|
+
"Content-Encoding",
|
|
49
|
+
"Content-Length",
|
|
50
|
+
"Content-Type",
|
|
51
|
+
"Keep-Alive",
|
|
52
|
+
"Mcp-Protocol-Version",
|
|
53
|
+
"Mcp-Session-Id",
|
|
54
|
+
"Proxy-Connection",
|
|
55
|
+
"TE",
|
|
56
|
+
"Trailer",
|
|
57
|
+
"Transfer-Encoding",
|
|
58
|
+
"Upgrade"
|
|
59
|
+
];
|
|
60
|
+
const STREAMING_TYPES = /^(text\/event-stream|application\/x-ndjson)/i;
|
|
61
|
+
const TOOL_NAME = /^[A-Za-z0-9_-]{1,128}$/;
|
|
62
|
+
const DEFAULT_ANNOTATIONS = {
|
|
63
|
+
get: { readOnlyHint: true },
|
|
64
|
+
put: { idempotentHint: true },
|
|
65
|
+
delete: {
|
|
66
|
+
destructiveHint: true,
|
|
67
|
+
idempotentHint: true
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const TOOL_METHODS = new Set([
|
|
71
|
+
"get",
|
|
72
|
+
"delete",
|
|
73
|
+
...METHODS_WITH_BODY
|
|
74
|
+
]);
|
|
75
|
+
const METADATA_PATH = "/.well-known/oauth-protected-resource";
|
|
76
|
+
/** Parses JSON, returning undefined for invalid input. */
|
|
77
|
+
function parseJson(text) {
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(text);
|
|
80
|
+
} catch {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Builds a tool name such as `get_users_by_id` from a method and `{param}` path. */
|
|
85
|
+
function toolName(method, path) {
|
|
86
|
+
return [method, ...path.split("/").filter(Boolean).map((s) => s.startsWith("{") ? `by_${s.slice(1, -1)}` : s)].join("_").replace(/[^A-Za-z0-9_-]/g, "_");
|
|
87
|
+
}
|
|
88
|
+
/** Derives one tool per route in the tree, with path, query and body fields as flat arguments. */
|
|
89
|
+
function collectTools(root) {
|
|
90
|
+
const tools = /* @__PURE__ */ new Map();
|
|
91
|
+
for (const { path, method, route } of collectRoutes(root)) {
|
|
92
|
+
if (!TOOL_METHODS.has(method)) continue;
|
|
93
|
+
const name = route?.operationId ?? toolName(method, path);
|
|
94
|
+
if (!TOOL_NAME.test(name)) throw new Error(`Invalid tool name: ${name}`);
|
|
95
|
+
if (tools.has(name)) throw new Error(`Duplicate tool name: ${name}`);
|
|
96
|
+
const properties = {};
|
|
97
|
+
const required = [];
|
|
98
|
+
const sources = /* @__PURE__ */ new Map();
|
|
99
|
+
const add = (source, schema) => {
|
|
100
|
+
const json = toJsonSchema(schema);
|
|
101
|
+
for (const [key, property] of Object.entries(json.properties)) {
|
|
102
|
+
if (sources.has(key)) throw new Error(`Duplicate argument ${key} in tool ${name}`);
|
|
103
|
+
sources.set(key, source);
|
|
104
|
+
properties[key] = property;
|
|
105
|
+
}
|
|
106
|
+
required.push(...json.required ?? []);
|
|
107
|
+
};
|
|
108
|
+
const params = [...path.matchAll(/\{(\w+)\}/g)].map((m) => m[1]);
|
|
109
|
+
add("path", Object.fromEntries(params.map((p) => [p, "string"])));
|
|
110
|
+
if (route?.query) {
|
|
111
|
+
const scalars = Object.entries(route.query).filter(([, field]) => {
|
|
112
|
+
if (isNestedSchema(field)) return false;
|
|
113
|
+
const [type] = unpackField(field);
|
|
114
|
+
return typeof type === "string" && !type.includes("[]");
|
|
115
|
+
});
|
|
116
|
+
add("query", Object.fromEntries(scalars));
|
|
117
|
+
}
|
|
118
|
+
const hasBodySchema = !!route?.body && METHODS_WITH_BODY.includes(method);
|
|
119
|
+
if (hasBodySchema) add("body", route?.body);
|
|
120
|
+
tools.set(name, {
|
|
121
|
+
tool: {
|
|
122
|
+
name,
|
|
123
|
+
description: route?.description ?? `${method.toUpperCase()} ${path}`,
|
|
124
|
+
inputSchema: {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties,
|
|
127
|
+
...required.length > 0 && { required }
|
|
128
|
+
},
|
|
129
|
+
annotations: {
|
|
130
|
+
...DEFAULT_ANNOTATIONS[method],
|
|
131
|
+
...route?.mcp?.annotations
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
method,
|
|
135
|
+
path,
|
|
136
|
+
sources,
|
|
137
|
+
hasBodySchema
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return tools;
|
|
141
|
+
}
|
|
142
|
+
/** Runs a tool as an HTTP request through the router, so its middleware applies. */
|
|
143
|
+
async function callTool(dispatch, target, args, c) {
|
|
144
|
+
const fail = (text$1) => ({
|
|
145
|
+
content: [{
|
|
146
|
+
type: "text",
|
|
147
|
+
text: text$1
|
|
148
|
+
}],
|
|
149
|
+
isError: true
|
|
150
|
+
});
|
|
151
|
+
const pathArgs = {};
|
|
152
|
+
const queryArgs = {};
|
|
153
|
+
const bodyArgs = {};
|
|
154
|
+
for (const [key, value] of Object.entries(args)) {
|
|
155
|
+
const source = target.sources.get(key) ?? (target.hasBodySchema ? void 0 : "body");
|
|
156
|
+
if (source === "path") pathArgs[key] = value;
|
|
157
|
+
else if (source === "query") queryArgs[key] = value;
|
|
158
|
+
else if (source === "body") bodyArgs[key] = value;
|
|
159
|
+
}
|
|
160
|
+
let missing;
|
|
161
|
+
let invalid;
|
|
162
|
+
const path = target.path.replace(/\{(\w+)\}/g, (_, name) => {
|
|
163
|
+
const value = pathArgs[name];
|
|
164
|
+
if (value === void 0 || value === null || value === "") missing ??= name;
|
|
165
|
+
if (value === "." || value === "..") invalid ??= name;
|
|
166
|
+
return encodeURIComponent(String(value ?? ""));
|
|
167
|
+
});
|
|
168
|
+
if (missing) return fail(`Missing path parameter: ${missing}`);
|
|
169
|
+
if (invalid) return fail(`Invalid path parameter: ${invalid}`);
|
|
170
|
+
const url = new URL(path, c.request.url);
|
|
171
|
+
for (const [key, value] of Object.entries(queryArgs)) {
|
|
172
|
+
if (typeof value === "object" && value !== null) return fail(`Invalid query parameter: ${key}`);
|
|
173
|
+
if (value !== void 0 && value !== null) url.searchParams.set(key, String(value));
|
|
174
|
+
}
|
|
175
|
+
const headers = new Headers(c.request.headers);
|
|
176
|
+
for (const name of DROPPED_HEADERS) headers.delete(name);
|
|
177
|
+
headers.set("Accept", "application/json");
|
|
178
|
+
const hasBody = METHODS_WITH_BODY.includes(target.method);
|
|
179
|
+
if (hasBody) headers.set("Content-Type", "application/json");
|
|
180
|
+
let response;
|
|
181
|
+
try {
|
|
182
|
+
response = await dispatch(new Request(url, {
|
|
183
|
+
method: target.method.toUpperCase(),
|
|
184
|
+
headers,
|
|
185
|
+
body: hasBody ? JSON.stringify(bodyArgs) : void 0
|
|
186
|
+
}), c);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error(`Error in MCP tool ${target.tool.name}:`, error);
|
|
189
|
+
return fail("Internal error");
|
|
190
|
+
}
|
|
191
|
+
if (STREAMING_TYPES.test(response.headers.get("Content-Type") ?? "")) {
|
|
192
|
+
await response.body?.cancel();
|
|
193
|
+
return fail("Streaming responses are not supported");
|
|
194
|
+
}
|
|
195
|
+
const text = await response.text();
|
|
196
|
+
const result = {
|
|
197
|
+
content: [{
|
|
198
|
+
type: "text",
|
|
199
|
+
text
|
|
200
|
+
}],
|
|
201
|
+
isError: !response.ok
|
|
202
|
+
};
|
|
203
|
+
if (/^application\/json/i.test(response.headers.get("Content-Type") ?? "")) {
|
|
204
|
+
const value = parseJson(text);
|
|
205
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) result.structuredContent = value;
|
|
206
|
+
}
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
/** Handles one MCP request with a fresh stateless server. */
|
|
210
|
+
async function serve(tools, dispatch, options, c) {
|
|
211
|
+
const server = new Server({
|
|
212
|
+
name: options.name,
|
|
213
|
+
version: options.version
|
|
214
|
+
}, {
|
|
215
|
+
capabilities: { tools: {} },
|
|
216
|
+
instructions: options.instructions
|
|
217
|
+
});
|
|
218
|
+
server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: [...tools.values()].map((t) => t.tool) }));
|
|
219
|
+
server.setRequestHandler(CallToolRequestSchema, (request) => {
|
|
220
|
+
const target = tools.get(request.params.name);
|
|
221
|
+
if (!target) throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${request.params.name}`);
|
|
222
|
+
return callTool(dispatch, target, request.params.arguments ?? {}, c);
|
|
223
|
+
});
|
|
224
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
225
|
+
sessionIdGenerator: void 0,
|
|
226
|
+
enableJsonResponse: true
|
|
227
|
+
});
|
|
228
|
+
await server.connect(transport);
|
|
229
|
+
return transport.handleRequest(c.request);
|
|
230
|
+
}
|
|
231
|
+
/** Adds the RFC 9728 `resource_metadata` parameter to a 401's Bearer challenge. */
|
|
232
|
+
function challenge(oauth) {
|
|
233
|
+
return async (c, next) => {
|
|
234
|
+
const response = await next();
|
|
235
|
+
if (response.status !== 401) return response;
|
|
236
|
+
const url = new URL(c.request.url);
|
|
237
|
+
const params = [`resource_metadata="${url.origin}${url.pathname}${METADATA_PATH}"`];
|
|
238
|
+
if (oauth.scopes?.length) params.push(`scope="${oauth.scopes.join(" ")}"`);
|
|
239
|
+
const existing = response.headers.get("WWW-Authenticate");
|
|
240
|
+
const value = existing?.startsWith("Bearer ") ? `${existing}, ${params.join(", ")}` : `Bearer ${params.join(", ")}`;
|
|
241
|
+
const challenged = new Response(response.body, response);
|
|
242
|
+
challenged.headers.set("WWW-Authenticate", value);
|
|
243
|
+
return challenged;
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/** Serves protected resource metadata for the resource named by the request path. */
|
|
247
|
+
function metadata(oauth) {
|
|
248
|
+
return (c) => {
|
|
249
|
+
const url = new URL(c.request.url);
|
|
250
|
+
const resource = url.pathname.replace(METADATA_PATH, "");
|
|
251
|
+
return Response.json({
|
|
252
|
+
resource: `${url.origin}${resource}`,
|
|
253
|
+
authorization_servers: oauth.authorizationServers,
|
|
254
|
+
...oauth.scopes && { scopes_supported: oauth.scopes },
|
|
255
|
+
bearer_methods_supported: ["header"]
|
|
256
|
+
});
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Creates an MCP endpoint exposing routes as tools.
|
|
261
|
+
*
|
|
262
|
+
* Mount the result in a router (e.g. `mcp: mcp({ users }, options, auth)`). Tool names come from
|
|
263
|
+
* `route.operationId` or the method and path (`get_users`, `delete_users_by_id`), descriptions
|
|
264
|
+
* from `route.description`, and flat inputs from the route's path params and query/body schemas.
|
|
265
|
+
*
|
|
266
|
+
* Middleware passed after `options` guards the endpoint itself, and is required unless
|
|
267
|
+
* `options.public` is set; put auth there so unauthenticated
|
|
268
|
+
* clients get an HTTP 401 (with an OAuth challenge when `oauth` is set). Context it adds reaches
|
|
269
|
+
* handlers. Middleware on the given routers runs on every tool call, with the caller's headers
|
|
270
|
+
* forwarded; middleware on their parents elsewhere in the app does not.
|
|
271
|
+
*
|
|
272
|
+
* With `oauth`, metadata is served at `<endpoint>/.well-known/oauth-protected-resource` and
|
|
273
|
+
* referenced from the 401 challenge. Mount {@link oauthProtectedResource} at the origin root as
|
|
274
|
+
* well for clients that probe `/.well-known/oauth-protected-resource/<endpoint>` first.
|
|
275
|
+
*
|
|
276
|
+
* @param routes - A router, or a record of routers mounted under their keys
|
|
277
|
+
* @param options - Server info and optional OAuth settings
|
|
278
|
+
* @param middleware - Middleware for the MCP endpoint
|
|
279
|
+
*/
|
|
280
|
+
function mcp(routes, options, ...middleware) {
|
|
281
|
+
if (middleware.length === 0 && options.public !== true) throw new Error("mcp() needs endpoint middleware such as auth, or public: true");
|
|
282
|
+
const base = isRouter(routes) ? routes : router(routes);
|
|
283
|
+
const tools = collectTools(base);
|
|
284
|
+
const inherited = /* @__PURE__ */ new WeakMap();
|
|
285
|
+
const inherit = (c, next) => {
|
|
286
|
+
Object.assign(c, inherited.get(c.request));
|
|
287
|
+
return next();
|
|
288
|
+
};
|
|
289
|
+
const fetchRoute = router(base.routes, inherit, ...base.middleware ?? []).handler();
|
|
290
|
+
const dispatch = (request, c) => {
|
|
291
|
+
inherited.set(request, Object.fromEntries(Object.entries(c).filter(([key]) => !CONTEXT_KEYS.has(key))));
|
|
292
|
+
return fetchRoute(request, c.env, c.executionCtx);
|
|
293
|
+
};
|
|
294
|
+
const chain = options.oauth ? [challenge(options.oauth), ...middleware] : middleware;
|
|
295
|
+
const notAllowed = () => new Response(null, {
|
|
296
|
+
status: 405,
|
|
297
|
+
headers: { Allow: "POST" }
|
|
298
|
+
});
|
|
299
|
+
return router({
|
|
300
|
+
post: (c) => {
|
|
301
|
+
const origin = c.request.headers.get("Origin");
|
|
302
|
+
if (origin && origin !== new URL(c.request.url).origin) return new Response("Forbidden origin", { status: 403 });
|
|
303
|
+
return runMiddleware(chain, c, () => serve(tools, dispatch, options, c));
|
|
304
|
+
},
|
|
305
|
+
get: notAllowed,
|
|
306
|
+
delete: notAllowed,
|
|
307
|
+
...options.oauth && { ".well-known": router({ "oauth-protected-resource": router({ get: metadata(options.oauth) }) }) }
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Creates RFC 9728 protected resource metadata routes to mount as `'.well-known'` at the origin
|
|
312
|
+
* root, serving `/.well-known/oauth-protected-resource` and
|
|
313
|
+
* `/.well-known/oauth-protected-resource/<resource>` for single-segment resource paths like `/mcp`.
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* ```typescript
|
|
317
|
+
* router({
|
|
318
|
+
* mcp: mcp({ users }, { name: 'api', version: '1.0.0', oauth }, jwtAuth({ ... })),
|
|
319
|
+
* '.well-known': oauthProtectedResource(oauth),
|
|
320
|
+
* });
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
function oauthProtectedResource(oauth) {
|
|
324
|
+
const get = metadata(oauth);
|
|
325
|
+
return router({ "oauth-protected-resource": router({
|
|
326
|
+
get,
|
|
327
|
+
$resource: router({ get })
|
|
328
|
+
}) });
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
//#endregion
|
|
332
|
+
export { mcp, oauthProtectedResource };
|
|
333
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["DEFAULT_ANNOTATIONS: Record<string, ToolAnnotations>","properties: Record<string, object>","required: string[]","pathArgs: Record<string, unknown>","queryArgs: Record<string, unknown>","bodyArgs: Record<string, unknown>","missing: string | undefined","invalid: string | undefined","response: Response","result: CallToolResult","inherit: Middleware","dispatch: Dispatch"],"sources":["../../src/mcp/index.ts"],"sourcesContent":["/**\n * @fileoverview MCP server adapter for @fresho/router.\n *\n * Exposes routes as Model Context Protocol tools over stateless Streamable HTTP. Each tool call\n * is dispatched as a regular HTTP request through the router, so route middleware (auth, rate\n * limits, logging) applies to agents exactly as it does to other clients.\n *\n * Requires the optional peer dependency `@modelcontextprotocol/sdk`.\n *\n * @example\n * ```typescript\n * import { router } from '@fresho/router';\n * import { mcp } from '@fresho/router/mcp';\n * import { bearerAuth } from '@fresho/router/middleware';\n *\n * const api = router({ users, billing });\n *\n * export default {\n * fetch: router({\n * ...api.routes,\n * mcp: mcp({ users }, { name: 'my-api', version: '1.0.0' }, bearerAuth({ verify })),\n * }).handler(),\n * };\n * ```\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';\nimport {\n CallToolRequestSchema,\n type CallToolResult,\n ErrorCode,\n ListToolsRequestSchema,\n McpError,\n type Tool,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { router } from '../core.js';\nimport { collectRoutes } from '../docs.js';\nimport { type Middleware, type MiddlewareContext, runMiddleware } from '../middleware.js';\nimport {\n isNestedSchema,\n type SchemaDefinition,\n type SchemaField,\n toJsonSchema,\n unpackField,\n} from '../schema.js';\nimport type { Context, Router, RouterRoutes, ToolAnnotations } from '../types.js';\nimport { isRouter } from '../types.js';\n\n/** OAuth 2.0 protected resource settings (RFC 9728). */\nexport interface McpOAuthOptions {\n /** Issuer URLs of the authorization servers that issue tokens for this server. */\n authorizationServers: string[];\n /** Scopes advertised to clients. */\n scopes?: string[];\n}\n\n/** MCP server options. */\nexport interface McpOptions {\n /** Server name reported to clients. */\n name: string;\n /** Server version reported to clients. */\n version: string;\n /** Instructions for agents on how to use the tools. */\n instructions?: string;\n /** Advertise OAuth: 401s gain a `resource_metadata` challenge and metadata is served. */\n oauth?: McpOAuthOptions;\n /** Allow an endpoint without middleware: it is open, or already guarded by parent routers. */\n public?: boolean;\n}\n\n/** Where a flat tool argument goes in the HTTP request. */\ntype ArgumentSource = 'path' | 'query' | 'body';\n\n/** A tool and the route it calls. */\ninterface ToolRoute {\n tool: Tool;\n method: string;\n path: string;\n sources: Map<string, ArgumentSource>;\n /** Without a body schema, arguments that aren't path or query fields form the body. */\n hasBodySchema: boolean;\n}\n\nconst METHODS_WITH_BODY = ['post', 'put', 'patch'];\nconst CONTEXT_KEYS = new Set(['request', 'path', 'query', 'body', 'env', 'executionCtx']);\n// Describe the outer MCP request or connection, not the dispatched tool call.\nconst DROPPED_HEADERS = [\n 'Connection',\n 'Content-Encoding',\n 'Content-Length',\n 'Content-Type',\n 'Keep-Alive',\n 'Mcp-Protocol-Version',\n 'Mcp-Session-Id',\n 'Proxy-Connection',\n 'TE',\n 'Trailer',\n 'Transfer-Encoding',\n 'Upgrade',\n];\nconst STREAMING_TYPES = /^(text\\/event-stream|application\\/x-ndjson)/i;\nconst TOOL_NAME = /^[A-Za-z0-9_-]{1,128}$/;\nconst DEFAULT_ANNOTATIONS: Record<string, ToolAnnotations> = {\n get: { readOnlyHint: true },\n put: { idempotentHint: true },\n delete: { destructiveHint: true, idempotentHint: true },\n};\nconst TOOL_METHODS = new Set(['get', 'delete', ...METHODS_WITH_BODY]);\nconst METADATA_PATH = '/.well-known/oauth-protected-resource';\n\n/** Parses JSON, returning undefined for invalid input. */\nfunction parseJson(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/** Builds a tool name such as `get_users_by_id` from a method and `{param}` path. */\nfunction toolName(method: string, path: string): string {\n const segments = path\n .split('/')\n .filter(Boolean)\n .map((s) => (s.startsWith('{') ? `by_${s.slice(1, -1)}` : s));\n return [method, ...segments].join('_').replace(/[^A-Za-z0-9_-]/g, '_');\n}\n\n/** Derives one tool per route in the tree, with path, query and body fields as flat arguments. */\nfunction collectTools(root: Router<RouterRoutes>): Map<string, ToolRoute> {\n const tools = new Map<string, ToolRoute>();\n for (const { path, method, route } of collectRoutes(root)) {\n if (!TOOL_METHODS.has(method)) continue;\n const name = route?.operationId ?? toolName(method, path);\n // MCP allows up to 128 characters; some clients reject other characters.\n if (!TOOL_NAME.test(name)) throw new Error(`Invalid tool name: ${name}`);\n if (tools.has(name)) throw new Error(`Duplicate tool name: ${name}`);\n\n const properties: Record<string, object> = {};\n const required: string[] = [];\n const sources = new Map<string, ArgumentSource>();\n const add = (source: ArgumentSource, schema: SchemaDefinition) => {\n const json = toJsonSchema(schema);\n for (const [key, property] of Object.entries(json.properties)) {\n if (sources.has(key)) throw new Error(`Duplicate argument ${key} in tool ${name}`);\n sources.set(key, source);\n properties[key] = property as object;\n }\n required.push(...(json.required ?? []));\n };\n const params = [...path.matchAll(/\\{(\\w+)\\}/g)].map((m) => m[1]);\n add('path', Object.fromEntries(params.map((p) => [p, 'string'])));\n if (route?.query) {\n // Query strings only carry scalars.\n const scalars = Object.entries(route.query as SchemaDefinition).filter(([, field]) => {\n if (isNestedSchema(field)) return false;\n const [type] = unpackField(field as SchemaField);\n return typeof type === 'string' && !type.includes('[]');\n });\n add('query', Object.fromEntries(scalars));\n }\n const hasBodySchema = !!route?.body && METHODS_WITH_BODY.includes(method);\n if (hasBodySchema) add('body', route?.body as SchemaDefinition);\n\n tools.set(name, {\n tool: {\n name,\n description: route?.description ?? `${method.toUpperCase()} ${path}`,\n inputSchema: { type: 'object', properties, ...(required.length > 0 && { required }) },\n annotations: { ...DEFAULT_ANNOTATIONS[method], ...route?.mcp?.annotations },\n },\n method,\n path,\n sources,\n hasBodySchema,\n });\n }\n return tools;\n}\n\n/** Sends a tool's request through the routes on behalf of the MCP request context `c`. */\ntype Dispatch = (request: Request, c: Context) => Response | Promise<Response>;\n\n/** Runs a tool as an HTTP request through the router, so its middleware applies. */\nasync function callTool(\n dispatch: Dispatch,\n target: ToolRoute,\n args: Record<string, unknown>,\n c: Context,\n): Promise<CallToolResult> {\n const fail = (text: string): CallToolResult => ({\n content: [{ type: 'text', text }],\n isError: true,\n });\n const pathArgs: Record<string, unknown> = {};\n const queryArgs: Record<string, unknown> = {};\n const bodyArgs: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(args)) {\n const source = target.sources.get(key) ?? (target.hasBodySchema ? undefined : 'body');\n if (source === 'path') pathArgs[key] = value;\n else if (source === 'query') queryArgs[key] = value;\n else if (source === 'body') bodyArgs[key] = value;\n }\n let missing: string | undefined;\n let invalid: string | undefined;\n const path = target.path.replace(/\\{(\\w+)\\}/g, (_, name: string) => {\n const value = pathArgs[name];\n if (value === undefined || value === null || value === '') missing ??= name;\n // URL parsing resolves dot segments, which would reach a different route.\n if (value === '.' || value === '..') invalid ??= name;\n return encodeURIComponent(String(value ?? ''));\n });\n if (missing) return fail(`Missing path parameter: ${missing}`);\n if (invalid) return fail(`Invalid path parameter: ${invalid}`);\n\n const url = new URL(path, c.request.url);\n for (const [key, value] of Object.entries(queryArgs)) {\n if (typeof value === 'object' && value !== null) return fail(`Invalid query parameter: ${key}`);\n if (value !== undefined && value !== null) url.searchParams.set(key, String(value));\n }\n\n // Forward the caller's headers (Authorization, cookies, ...) but describe the new request.\n const headers = new Headers(c.request.headers);\n for (const name of DROPPED_HEADERS) headers.delete(name);\n headers.set('Accept', 'application/json');\n const hasBody = METHODS_WITH_BODY.includes(target.method);\n if (hasBody) headers.set('Content-Type', 'application/json');\n\n let response: Response;\n try {\n response = await dispatch(\n new Request(url, {\n method: target.method.toUpperCase(),\n headers,\n body: hasBody ? JSON.stringify(bodyArgs) : undefined,\n }),\n c,\n );\n } catch (error) {\n console.error(`Error in MCP tool ${target.tool.name}:`, error);\n return fail('Internal error');\n }\n if (STREAMING_TYPES.test(response.headers.get('Content-Type') ?? '')) {\n await response.body?.cancel();\n return fail('Streaming responses are not supported');\n }\n const text = await response.text();\n const result: CallToolResult = { content: [{ type: 'text', text }], isError: !response.ok };\n if (/^application\\/json/i.test(response.headers.get('Content-Type') ?? '')) {\n const value = parseJson(text);\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n result.structuredContent = value as Record<string, unknown>;\n }\n }\n return result;\n}\n\n/** Handles one MCP request with a fresh stateless server. */\nasync function serve(\n tools: Map<string, ToolRoute>,\n dispatch: Dispatch,\n options: McpOptions,\n c: Context,\n): Promise<Response> {\n const server = new Server(\n { name: options.name, version: options.version },\n { capabilities: { tools: {} }, instructions: options.instructions },\n );\n server.setRequestHandler(ListToolsRequestSchema, () => ({\n tools: [...tools.values()].map((t) => t.tool),\n }));\n server.setRequestHandler(CallToolRequestSchema, (request) => {\n const target = tools.get(request.params.name);\n if (!target) {\n throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${request.params.name}`);\n }\n return callTool(dispatch, target, request.params.arguments ?? {}, c);\n });\n const transport = new WebStandardStreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: true,\n });\n await server.connect(transport);\n return transport.handleRequest(c.request);\n}\n\n/** Adds the RFC 9728 `resource_metadata` parameter to a 401's Bearer challenge. */\nfunction challenge(oauth: McpOAuthOptions): Middleware {\n return async (c, next) => {\n const response = await next();\n if (response.status !== 401) return response;\n const url = new URL(c.request.url);\n const params = [`resource_metadata=\"${url.origin}${url.pathname}${METADATA_PATH}\"`];\n if (oauth.scopes?.length) params.push(`scope=\"${oauth.scopes.join(' ')}\"`);\n const existing = response.headers.get('WWW-Authenticate');\n const value = existing?.startsWith('Bearer ')\n ? `${existing}, ${params.join(', ')}`\n : `Bearer ${params.join(', ')}`;\n const challenged = new Response(response.body, response);\n challenged.headers.set('WWW-Authenticate', value);\n return challenged;\n };\n}\n\n/** Serves protected resource metadata for the resource named by the request path. */\nfunction metadata(oauth: McpOAuthOptions) {\n return (c: Context) => {\n const url = new URL(c.request.url);\n const resource = url.pathname.replace(METADATA_PATH, '');\n return Response.json({\n resource: `${url.origin}${resource}`,\n authorization_servers: oauth.authorizationServers,\n ...(oauth.scopes && { scopes_supported: oauth.scopes }),\n bearer_methods_supported: ['header'],\n });\n };\n}\n\n/**\n * Creates an MCP endpoint exposing routes as tools.\n *\n * Mount the result in a router (e.g. `mcp: mcp({ users }, options, auth)`). Tool names come from\n * `route.operationId` or the method and path (`get_users`, `delete_users_by_id`), descriptions\n * from `route.description`, and flat inputs from the route's path params and query/body schemas.\n *\n * Middleware passed after `options` guards the endpoint itself, and is required unless\n * `options.public` is set; put auth there so unauthenticated\n * clients get an HTTP 401 (with an OAuth challenge when `oauth` is set). Context it adds reaches\n * handlers. Middleware on the given routers runs on every tool call, with the caller's headers\n * forwarded; middleware on their parents elsewhere in the app does not.\n *\n * With `oauth`, metadata is served at `<endpoint>/.well-known/oauth-protected-resource` and\n * referenced from the 401 challenge. Mount {@link oauthProtectedResource} at the origin root as\n * well for clients that probe `/.well-known/oauth-protected-resource/<endpoint>` first.\n *\n * @param routes - A router, or a record of routers mounted under their keys\n * @param options - Server info and optional OAuth settings\n * @param middleware - Middleware for the MCP endpoint\n */\nexport function mcp(\n routes: Router<RouterRoutes> | Record<string, Router<RouterRoutes>>,\n options: McpOptions,\n ...middleware: Middleware[]\n): Router<RouterRoutes> {\n if (middleware.length === 0 && options.public !== true) {\n throw new Error('mcp() needs endpoint middleware such as auth, or public: true');\n }\n const base = isRouter(routes) ? routes : router(routes as RouterRoutes);\n const tools = collectTools(base);\n\n // Context set by middleware on the endpoint and its parents, keyed by dispatched request.\n const inherited = new WeakMap<Request, Record<string, unknown>>();\n const inherit: Middleware = (c, next) => {\n Object.assign(c, inherited.get(c.request));\n return next();\n };\n const fetchRoute = router(base.routes, inherit, ...(base.middleware ?? [])).handler();\n const dispatch: Dispatch = (request, c) => {\n inherited.set(\n request,\n Object.fromEntries(Object.entries(c).filter(([key]) => !CONTEXT_KEYS.has(key))),\n );\n return fetchRoute(request, c.env, c.executionCtx);\n };\n\n const chain = options.oauth ? [challenge(options.oauth), ...middleware] : middleware;\n const notAllowed = () => new Response(null, { status: 405, headers: { Allow: 'POST' } });\n\n return router({\n post: (c) => {\n // The MCP spec requires Origin validation against DNS rebinding.\n const origin = c.request.headers.get('Origin');\n if (origin && origin !== new URL(c.request.url).origin) {\n return new Response('Forbidden origin', { status: 403 });\n }\n return runMiddleware(chain, c as MiddlewareContext, () => serve(tools, dispatch, options, c));\n },\n get: notAllowed,\n delete: notAllowed,\n ...(options.oauth && {\n '.well-known': router({\n 'oauth-protected-resource': router({ get: metadata(options.oauth) }),\n }),\n }),\n });\n}\n\n/**\n * Creates RFC 9728 protected resource metadata routes to mount as `'.well-known'` at the origin\n * root, serving `/.well-known/oauth-protected-resource` and\n * `/.well-known/oauth-protected-resource/<resource>` for single-segment resource paths like `/mcp`.\n *\n * @example\n * ```typescript\n * router({\n * mcp: mcp({ users }, { name: 'api', version: '1.0.0', oauth }, jwtAuth({ ... })),\n * '.well-known': oauthProtectedResource(oauth),\n * });\n * ```\n */\nexport function oauthProtectedResource(oauth: McpOAuthOptions): Router<RouterRoutes> {\n const get = metadata(oauth);\n return router({ 'oauth-protected-resource': router({ get, $resource: router({ get }) }) });\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { i as compose, n as MiddlewareContext, t as Middleware } from "../middleware-
|
|
1
|
+
import { a as SignJwtOptions, c as jwtSign, i as JwtSecret, l as jwtVerify, n as JwtAlgorithm, o as VerifyJwtOptions, r as JwtPayload, s as createJwks, t as Jwks } from "../jwt-B-Fqwu4E.mjs";
|
|
2
|
+
import { i as compose, n as MiddlewareContext, t as Middleware } from "../middleware-Cj1yHSJF.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/middleware/auth.d.ts
|
|
5
5
|
|
|
@@ -37,8 +37,14 @@ interface JwtAuthOptions<Ctx = {}> {
|
|
|
37
37
|
* The returned object is merged into the middleware context.
|
|
38
38
|
*/
|
|
39
39
|
claims: (payload: JwtPayload) => Record<string, unknown> | null;
|
|
40
|
-
/** Allowed algorithms (default: ['HS256']). */
|
|
40
|
+
/** Allowed algorithms (default: ['HS256'] for HMAC secrets, ['RS256'] otherwise). */
|
|
41
41
|
algorithms?: JwtAlgorithm[];
|
|
42
|
+
/** Required issuer (`iss`). */
|
|
43
|
+
issuer?: string | string[];
|
|
44
|
+
/** Required audience (`aud`); the token must contain at least one of the given values. */
|
|
45
|
+
audience?: string | string[];
|
|
46
|
+
/** Seconds of clock skew allowed when checking `exp` and `nbf` (default: 0). */
|
|
47
|
+
clockTolerance?: number;
|
|
42
48
|
/** Function to extract token from request (default: Authorization header or 'token' cookie). */
|
|
43
49
|
getToken?: (request: Request) => string | null;
|
|
44
50
|
}
|
|
@@ -344,5 +350,5 @@ interface CorsOptions {
|
|
|
344
350
|
*/
|
|
345
351
|
declare function cors(options?: CorsOptions): Middleware;
|
|
346
352
|
//#endregion
|
|
347
|
-
export { type BasicAuthOptions, type BearerAuthOptions, type ContentTypeOptions, type CookieOptions, type CorsOptions, type ErrorHandlerOptions, HttpError, type JwtAlgorithm, type JwtAuthOptions, type JwtPayload, type LogInfo, type LoggerOptions, MemoryRateLimitStore, type RateLimitOptions, type RateLimitStore, type RequestIdOptions, type SetCookie, type SignJwtOptions, type TimeoutOptions, type VerifyJwtOptions, basicAuth, bearerAuth, compose, contentType, cookies, cors, errorHandler, jwtAuth, jwtSign, jwtVerify, logger, parseCookies, rateLimit, requestId, serializeCookie, timeout };
|
|
353
|
+
export { type BasicAuthOptions, type BearerAuthOptions, type ContentTypeOptions, type CookieOptions, type CorsOptions, type ErrorHandlerOptions, HttpError, type Jwks, type JwtAlgorithm, type JwtAuthOptions, type JwtPayload, type LogInfo, type LoggerOptions, MemoryRateLimitStore, type RateLimitOptions, type RateLimitStore, type RequestIdOptions, type SetCookie, type SignJwtOptions, type TimeoutOptions, type VerifyJwtOptions, basicAuth, bearerAuth, compose, contentType, cookies, cors, createJwks, errorHandler, jwtAuth, jwtSign, jwtVerify, logger, parseCookies, rateLimit, requestId, serializeCookie, timeout };
|
|
348
354
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/middleware/auth.ts","../../src/middleware/common.ts","../../src/middleware/cookies.ts","../../src/middleware/cors.ts"],"sourcesContent":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/middleware/auth.ts","../../src/middleware/common.ts","../../src/middleware/cookies.ts","../../src/middleware/cors.ts"],"sourcesContent":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as compose } from "../middleware-
|
|
2
|
-
import {
|
|
1
|
+
import { t as compose } from "../middleware-B-_0PLA6.mjs";
|
|
2
|
+
import { n as jwtSign, o as parseBasicAuth, r as jwtVerify, t as createJwks } from "../jwt-ikNuUBcp.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/middleware/auth.ts
|
|
5
5
|
/**
|
|
@@ -94,7 +94,12 @@ function jwtAuth(options) {
|
|
|
94
94
|
headers: { "WWW-Authenticate": "Bearer" }
|
|
95
95
|
});
|
|
96
96
|
try {
|
|
97
|
-
const payload = await jwtVerify(token, typeof options.secret === "function" ? options.secret(context) : options.secret, {
|
|
97
|
+
const payload = await jwtVerify(token, typeof options.secret === "function" ? options.secret(context) : options.secret, {
|
|
98
|
+
algorithms: options.algorithms,
|
|
99
|
+
issuer: options.issuer,
|
|
100
|
+
audience: options.audience,
|
|
101
|
+
clockTolerance: options.clockTolerance
|
|
102
|
+
});
|
|
98
103
|
const claims = options.claims(payload);
|
|
99
104
|
if (claims === null) throw new Error("Token validation failed");
|
|
100
105
|
Object.assign(context, claims);
|
|
@@ -103,7 +108,7 @@ function jwtAuth(options) {
|
|
|
103
108
|
const message = error instanceof Error ? error.message : "Invalid token";
|
|
104
109
|
return new Response(message, {
|
|
105
110
|
status: 401,
|
|
106
|
-
headers: { "WWW-Authenticate":
|
|
111
|
+
headers: { "WWW-Authenticate": "Bearer error=\"invalid_token\"" }
|
|
107
112
|
});
|
|
108
113
|
}
|
|
109
114
|
};
|
|
@@ -613,5 +618,5 @@ function cors(options = {}) {
|
|
|
613
618
|
}
|
|
614
619
|
|
|
615
620
|
//#endregion
|
|
616
|
-
export { HttpError, MemoryRateLimitStore, basicAuth, bearerAuth, compose, contentType, cookies, cors, errorHandler, jwtAuth, jwtSign, jwtVerify, logger, parseCookies, rateLimit, requestId, serializeCookie, timeout };
|
|
621
|
+
export { HttpError, MemoryRateLimitStore, basicAuth, bearerAuth, compose, contentType, cookies, cors, createJwks, errorHandler, jwtAuth, jwtSign, jwtVerify, logger, parseCookies, rateLimit, requestId, serializeCookie, timeout };
|
|
617
622
|
//# sourceMappingURL=index.mjs.map
|