@fresho/router 0.5.0 → 0.6.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 +67 -2
- package/dist/auth/index.d.mts +2 -2
- package/dist/auth/index.mjs +2 -2
- package/dist/docs-B1IFBS_w.mjs +724 -0
- package/dist/docs-B1IFBS_w.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 +329 -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-zRMBpTLe.d.mts} +23 -2
- package/dist/middleware-zRMBpTLe.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
package/README.md
CHANGED
|
@@ -45,6 +45,7 @@ export default { fetch: api.handler() };
|
|
|
45
45
|
- **Typed HTTP client** — call your API with full type safety
|
|
46
46
|
- **Typed local client** — test handlers directly without HTTP
|
|
47
47
|
- **OpenAPI generation** — auto-generate docs from your routes
|
|
48
|
+
- **MCP server** — expose routes as tools for AI agents
|
|
48
49
|
- **Middleware** — cors, auth, rate limiting, and more
|
|
49
50
|
- **Streaming** — SSE and JSON lines helpers
|
|
50
51
|
- **HTTP-compliant** — automatic HEAD support for GET routes
|
|
@@ -56,6 +57,16 @@ export default { fetch: api.handler() };
|
|
|
56
57
|
npm install @fresho/router
|
|
57
58
|
```
|
|
58
59
|
|
|
60
|
+
```bash
|
|
61
|
+
bun add @fresho/router
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Runtime Support
|
|
65
|
+
|
|
66
|
+
The full test suite runs in Node.js 22/24 and Bun 1.3.14. CI also deploys the built
|
|
67
|
+
package to a local Cloudflare `workerd` runtime and verifies routing, validation,
|
|
68
|
+
middleware, and streaming behavior.
|
|
69
|
+
|
|
59
70
|
## Path Convention
|
|
60
71
|
|
|
61
72
|
Property names become URL path segments:
|
|
@@ -219,7 +230,7 @@ See **[Middleware Documentation](docs/middleware.md)** for detailed usage.
|
|
|
219
230
|
For standalone auth utilities (JWT signing/verification, Basic auth, OAuth), import from `@fresho/router/auth`:
|
|
220
231
|
|
|
221
232
|
```typescript
|
|
222
|
-
import { jwtSign, jwtVerify, parseBasicAuth } from "@fresho/router/auth";
|
|
233
|
+
import { createJwks, jwtSign, jwtVerify, parseBasicAuth } from "@fresho/router/auth";
|
|
223
234
|
|
|
224
235
|
// Sign a JWT
|
|
225
236
|
const token = await jwtSign({ uid: "user-123" }, secret, { expiresIn: "1h" });
|
|
@@ -227,12 +238,20 @@ const token = await jwtSign({ uid: "user-123" }, secret, { expiresIn: "1h" });
|
|
|
227
238
|
// Verify a JWT
|
|
228
239
|
const payload = await jwtVerify(token, secret);
|
|
229
240
|
|
|
241
|
+
// Verify a third-party OAuth/OIDC token against the provider's JWKS
|
|
242
|
+
const jwks = createJwks("https://example.auth0.com/.well-known/jwks.json");
|
|
243
|
+
const claims = await jwtVerify(accessToken, jwks, {
|
|
244
|
+
algorithms: ["RS256"],
|
|
245
|
+
issuer: "https://example.auth0.com/",
|
|
246
|
+
audience: "https://api.example.com",
|
|
247
|
+
});
|
|
248
|
+
|
|
230
249
|
// Parse Basic auth header
|
|
231
250
|
const creds = parseBasicAuth(request.headers.get("Authorization"));
|
|
232
251
|
```
|
|
233
252
|
|
|
234
253
|
Available utilities:
|
|
235
|
-
- **JWT**: `jwtSign`, `jwtVerify`
|
|
254
|
+
- **JWT**: `jwtSign`, `jwtVerify`, `createJwks` (HS*, RS*, PS*, ES256/384)
|
|
236
255
|
- **Basic Auth**: `parseBasicAuth`, `encodeBasicAuth`, `extractBasicAuthToken`
|
|
237
256
|
- **OAuth**: `encodeOAuthState`, `decodeOAuthState`, `exchangeCode`, `refreshAccessToken`, `revokeToken`, `buildAuthorizationUrl`, `OAUTH_PROVIDERS`
|
|
238
257
|
|
|
@@ -370,6 +389,52 @@ const docs = router({
|
|
|
370
389
|
});
|
|
371
390
|
```
|
|
372
391
|
|
|
392
|
+
## MCP Server
|
|
393
|
+
|
|
394
|
+
Expose routes as [Model Context Protocol](https://modelcontextprotocol.io) tools, so agents can use the same API as your other clients. Requires the optional peer dependency `@modelcontextprotocol/sdk`:
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
npm install @modelcontextprotocol/sdk
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
import { router } from "@fresho/router";
|
|
402
|
+
import { mcp } from "@fresho/router/mcp";
|
|
403
|
+
|
|
404
|
+
const api = router({
|
|
405
|
+
users,
|
|
406
|
+
billing,
|
|
407
|
+
mcp: mcp({ users }, { name: "my-api", version: "1.0.0" }, bearerAuth({ verify })),
|
|
408
|
+
});
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
`mcp()` requires endpoint middleware (usually auth). Pass `public: true` instead when the endpoint is open, or already guarded by middleware on its parent routers.
|
|
412
|
+
|
|
413
|
+
- **Tools** — one per route, named from the method and path (`get_users`, `post_users`, `get_users_by_id`, ...) unless the route sets `operationId` (also used in OpenAPI). Descriptions come from `route.description`.
|
|
414
|
+
- **Arguments** — flat: path params, query fields and body fields side by side (`{ id, text }`), as JSON Schema derived from the route's schemas. Names must be unique across the three; a clash throws when `mcp()` is called. `descriptions: { field: '...', 'address.city': '...' }` on a route documents fields for agents and OpenAPI. Array query params are left out, since the router can't parse them from query strings. Without a body schema, `post`/`put`/`patch` tools send any other arguments as the body.
|
|
415
|
+
- **Annotations** — `get` tools are marked `readOnlyHint`, `delete` tools `destructiveHint` and `idempotentHint`, and `put` tools `idempotentHint`, so clients can decide when to ask for confirmation. Override per route with `mcp: { annotations: { ... } }`.
|
|
416
|
+
- **Calls** — each tool call is dispatched as an HTTP request through the given routers with the caller's headers forwarded. Middleware on the MCP endpoint (passed to `mcp()` or on its parent routers) runs once per MCP request, and what it adds to the context (e.g. `c.user`) reaches handlers; middleware on the given routers and below runs on every call. Middleware on the given routers' *own* parents elsewhere in your app does **not** run, so guard the endpoint or the routers themselves. Responses become text content, plus `structuredContent` when the body is a JSON object; a non-2xx status sets `isError`, and streaming (SSE, NDJSON) responses are rejected.
|
|
417
|
+
- **Safety** — requests with an `Origin` other than the endpoint's get 403 (as the MCP spec requires against DNS rebinding), so browser-hosted clients on other origins can't connect. Path parameters can't be `.` or `..`, and handler exceptions become a generic `Internal error`.
|
|
418
|
+
- **Transport** — stateless Streamable HTTP with JSON responses; `GET` and `DELETE` return 405.
|
|
419
|
+
- Pass a router to expose all of its routes, or a record of routers to mount them under their keys.
|
|
420
|
+
|
|
421
|
+
### OAuth
|
|
422
|
+
|
|
423
|
+
Put auth middleware on the endpoint so unauthenticated clients get an HTTP 401, and set `oauth` to advertise your authorization server (RFC 9728):
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
const oauth = { authorizationServers: ["https://auth.example.com"], scopes: ["users:read"] };
|
|
427
|
+
|
|
428
|
+
const app = router({
|
|
429
|
+
users,
|
|
430
|
+
mcp: mcp({ users }, { name: "my-api", version: "1.0.0", oauth }, jwtAuth({ ... })),
|
|
431
|
+
// Optional: metadata at the origin root, for clients that probe it before calling the endpoint.
|
|
432
|
+
".well-known": oauthProtectedResource(oauth),
|
|
433
|
+
});
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
401s from the endpoint gain `WWW-Authenticate: Bearer resource_metadata="https://host/mcp/.well-known/oauth-protected-resource"`, and that URL serves the metadata without the endpoint's middleware (keep auth off the endpoint's parent routers, or the metadata is guarded too). `oauthProtectedResource` only handles single-segment endpoint paths such as `/mcp`. Validate that tokens were issued for this server (the `aud` claim), not just by your authorization server.
|
|
437
|
+
|
|
373
438
|
## Streaming
|
|
374
439
|
|
|
375
440
|
### Server-Sent Events
|
package/dist/auth/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
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
2
|
|
|
3
3
|
//#region src/auth/basic.d.ts
|
|
4
4
|
|
|
@@ -323,5 +323,5 @@ interface AuthorizationUrlOptions {
|
|
|
323
323
|
*/
|
|
324
324
|
declare function buildAuthorizationUrl(options: AuthorizationUrlOptions): string;
|
|
325
325
|
//#endregion
|
|
326
|
-
export { type AuthorizationUrlOptions, type BasicCredentials, type ExchangeCodeOptions, type JwtAlgorithm, type JwtPayload, type JwtSecret, OAUTH_PROVIDERS, type OAuthProvider, type OAuthTokenResponse, type RevokeTokenOptions, type SignJwtOptions, type TokenAuthMethod, type VerifyJwtOptions, buildAuthorizationUrl, decodeOAuthState, encodeBasicAuth, encodeOAuthState, exchangeCode, extractBasicAuthToken, jwtSign, jwtVerify, parseBasicAuth, refreshAccessToken, revokeToken };
|
|
326
|
+
export { type AuthorizationUrlOptions, type BasicCredentials, type ExchangeCodeOptions, type Jwks, type JwtAlgorithm, type JwtPayload, type JwtSecret, OAUTH_PROVIDERS, type OAuthProvider, type OAuthTokenResponse, type RevokeTokenOptions, type SignJwtOptions, type TokenAuthMethod, type VerifyJwtOptions, buildAuthorizationUrl, createJwks, decodeOAuthState, encodeBasicAuth, encodeOAuthState, exchangeCode, extractBasicAuthToken, jwtSign, jwtVerify, parseBasicAuth, refreshAccessToken, revokeToken };
|
|
327
327
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/auth/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as extractBasicAuthToken, i as encodeBasicAuth, n as jwtSign, o as parseBasicAuth, r as jwtVerify, t as createJwks } from "../jwt-ikNuUBcp.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/auth/oauth.ts
|
|
4
4
|
/** Common OAuth 2.0 providers with their endpoints. */
|
|
@@ -275,5 +275,5 @@ function buildAuthorizationUrl(options) {
|
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
//#endregion
|
|
278
|
-
export { OAUTH_PROVIDERS, buildAuthorizationUrl, decodeOAuthState, encodeBasicAuth, encodeOAuthState, exchangeCode, extractBasicAuthToken, jwtSign, jwtVerify, parseBasicAuth, refreshAccessToken, revokeToken };
|
|
278
|
+
export { OAUTH_PROVIDERS, buildAuthorizationUrl, createJwks, decodeOAuthState, encodeBasicAuth, encodeOAuthState, exchangeCode, extractBasicAuthToken, jwtSign, jwtVerify, parseBasicAuth, refreshAccessToken, revokeToken };
|
|
279
279
|
//# sourceMappingURL=index.mjs.map
|