mcp-trellis 1.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -93
- package/dist/adapters/node-request.d.ts +53 -0
- package/dist/adapters/node-request.d.ts.map +1 -0
- package/dist/adapters/node-request.js +165 -0
- package/dist/adapters/node-request.js.map +1 -0
- package/dist/adapters/node.d.ts +13 -30
- package/dist/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js +55 -101
- package/dist/adapters/node.js.map +1 -1
- package/dist/advanced.d.ts +18 -0
- package/dist/advanced.d.ts.map +1 -0
- package/dist/advanced.js +18 -0
- package/dist/advanced.js.map +1 -0
- package/dist/app-options.d.ts +132 -0
- package/dist/app-options.d.ts.map +1 -0
- package/dist/app-options.js +2 -0
- package/dist/app-options.js.map +1 -0
- package/dist/app.d.ts +2 -89
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +143 -63
- package/dist/app.js.map +1 -1
- package/dist/audit.d.ts +5 -5
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +19 -8
- package/dist/audit.js.map +1 -1
- package/dist/auth/bearer.d.ts +4 -1
- package/dist/auth/bearer.d.ts.map +1 -1
- package/dist/auth/bearer.js +19 -4
- package/dist/auth/bearer.js.map +1 -1
- package/dist/clients.js +1 -1
- package/dist/clients.js.map +1 -1
- package/dist/deprecated.d.ts +42 -0
- package/dist/deprecated.d.ts.map +1 -0
- package/dist/deprecated.js +35 -0
- package/dist/deprecated.js.map +1 -0
- package/dist/{body.d.ts → http/body.d.ts} +27 -6
- package/dist/http/body.d.ts.map +1 -0
- package/dist/{body.js → http/body.js} +33 -13
- package/dist/http/body.js.map +1 -0
- package/dist/http/http.d.ts +42 -0
- package/dist/http/http.d.ts.map +1 -0
- package/dist/http/http.js +100 -0
- package/dist/http/http.js.map +1 -0
- package/dist/http/origins.d.ts +37 -0
- package/dist/http/origins.d.ts.map +1 -0
- package/dist/http/origins.js +87 -0
- package/dist/http/origins.js.map +1 -0
- package/dist/{dispatch.d.ts → mcp/dispatch.d.ts} +1 -1
- package/dist/mcp/dispatch.d.ts.map +1 -0
- package/dist/mcp/dispatch.js +99 -0
- package/dist/mcp/dispatch.js.map +1 -0
- package/dist/{jsonrpc.d.ts → mcp/jsonrpc.d.ts} +9 -0
- package/dist/mcp/jsonrpc.d.ts.map +1 -0
- package/dist/mcp/jsonrpc.js +88 -0
- package/dist/mcp/jsonrpc.js.map +1 -0
- package/dist/{methods.d.ts → mcp/methods.d.ts} +60 -11
- package/dist/mcp/methods.d.ts.map +1 -0
- package/dist/mcp/methods.js +84 -0
- package/dist/mcp/methods.js.map +1 -0
- package/dist/{protocol.d.ts → mcp/protocol.d.ts} +1 -3
- package/dist/mcp/protocol.d.ts.map +1 -0
- package/dist/{protocol.js → mcp/protocol.js} +1 -3
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/registry.d.ts +58 -0
- package/dist/mcp/registry.d.ts.map +1 -0
- package/dist/mcp/registry.js +81 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/mcp/rpc-handlers.d.ts +11 -0
- package/dist/mcp/rpc-handlers.d.ts.map +1 -0
- package/dist/mcp/rpc-handlers.js +133 -0
- package/dist/mcp/rpc-handlers.js.map +1 -0
- package/dist/{tools.d.ts → mcp/tools.d.ts} +20 -1
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +166 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/{validate.d.ts → mcp/validate.d.ts} +18 -21
- package/dist/mcp/validate.d.ts.map +1 -0
- package/dist/{validate.js → mcp/validate.js} +59 -41
- package/dist/mcp/validate.js.map +1 -0
- package/dist/oauth/audit.d.ts +20 -0
- package/dist/oauth/audit.d.ts.map +1 -0
- package/dist/oauth/audit.js +29 -0
- package/dist/oauth/audit.js.map +1 -0
- package/dist/oauth/cimd/cimd-parse.d.ts +4 -0
- package/dist/oauth/cimd/cimd-parse.d.ts.map +1 -0
- package/dist/oauth/cimd/cimd-parse.js +70 -0
- package/dist/oauth/cimd/cimd-parse.js.map +1 -0
- package/dist/oauth/cimd/cimd-ssrf.d.ts +8 -0
- package/dist/oauth/cimd/cimd-ssrf.d.ts.map +1 -0
- package/dist/oauth/cimd/cimd-ssrf.js +193 -0
- package/dist/oauth/cimd/cimd-ssrf.js.map +1 -0
- package/dist/oauth/cimd/cimd.d.ts +36 -0
- package/dist/oauth/cimd/cimd.d.ts.map +1 -0
- package/dist/oauth/cimd/cimd.js +132 -0
- package/dist/oauth/cimd/cimd.js.map +1 -0
- package/dist/oauth/cimd/types.d.ts +12 -0
- package/dist/oauth/cimd/types.d.ts.map +1 -0
- package/dist/oauth/cimd/types.js +3 -0
- package/dist/oauth/cimd/types.js.map +1 -0
- package/dist/oauth/config.d.ts +51 -0
- package/dist/oauth/config.d.ts.map +1 -0
- package/dist/oauth/config.js +108 -0
- package/dist/oauth/config.js.map +1 -0
- package/dist/oauth/constants.d.ts +11 -0
- package/dist/oauth/constants.d.ts.map +1 -1
- package/dist/oauth/constants.js +11 -0
- package/dist/oauth/constants.js.map +1 -1
- package/dist/oauth/{base64url.d.ts → crypto/base64url.d.ts} +0 -1
- package/dist/oauth/crypto/base64url.d.ts.map +1 -0
- package/dist/oauth/{base64url.js → crypto/base64url.js} +0 -1
- package/dist/oauth/crypto/base64url.js.map +1 -0
- package/dist/oauth/crypto/clientauth.d.ts +13 -0
- package/dist/oauth/crypto/clientauth.d.ts.map +1 -0
- package/dist/oauth/crypto/clientauth.js +195 -0
- package/dist/oauth/crypto/clientauth.js.map +1 -0
- package/dist/oauth/crypto/codes.d.ts +65 -0
- package/dist/oauth/crypto/codes.d.ts.map +1 -0
- package/dist/oauth/crypto/codes.js +121 -0
- package/dist/oauth/crypto/codes.js.map +1 -0
- package/dist/oauth/crypto/hkdf.d.ts +13 -0
- package/dist/oauth/crypto/hkdf.d.ts.map +1 -0
- package/dist/oauth/crypto/hkdf.js +30 -0
- package/dist/oauth/crypto/hkdf.js.map +1 -0
- package/dist/oauth/crypto/keycache.d.ts +19 -0
- package/dist/oauth/crypto/keycache.d.ts.map +1 -0
- package/dist/oauth/crypto/keycache.js +44 -0
- package/dist/oauth/crypto/keycache.js.map +1 -0
- package/dist/oauth/crypto/pkce.d.ts.map +1 -0
- package/dist/oauth/{pkce.js → crypto/pkce.js} +1 -1
- package/dist/oauth/crypto/pkce.js.map +1 -0
- package/dist/oauth/{sealed.d.ts → crypto/sealed.d.ts} +12 -3
- package/dist/oauth/crypto/sealed.d.ts.map +1 -0
- package/dist/oauth/{sealed.js → crypto/sealed.js} +22 -14
- package/dist/oauth/crypto/sealed.js.map +1 -0
- package/dist/oauth/{secrethash.d.ts → crypto/secrethash.d.ts} +14 -0
- package/dist/oauth/crypto/secrethash.d.ts.map +1 -0
- package/dist/oauth/{secrethash.js → crypto/secrethash.js} +27 -11
- package/dist/oauth/crypto/secrethash.js.map +1 -0
- package/dist/oauth/deprecated.d.ts +52 -0
- package/dist/oauth/deprecated.d.ts.map +1 -0
- package/dist/oauth/deprecated.js +49 -0
- package/dist/oauth/deprecated.js.map +1 -0
- package/dist/oauth/endpoints/authorize.d.ts +3 -0
- package/dist/oauth/endpoints/authorize.d.ts.map +1 -0
- package/dist/oauth/endpoints/authorize.js +209 -0
- package/dist/oauth/endpoints/authorize.js.map +1 -0
- package/dist/oauth/endpoints/callback.d.ts +20 -0
- package/dist/oauth/endpoints/callback.d.ts.map +1 -0
- package/dist/oauth/endpoints/callback.js +31 -0
- package/dist/oauth/endpoints/callback.js.map +1 -0
- package/dist/oauth/endpoints/consent-page.d.ts +5 -0
- package/dist/oauth/endpoints/consent-page.d.ts.map +1 -0
- package/dist/oauth/endpoints/consent-page.js +63 -0
- package/dist/oauth/endpoints/consent-page.js.map +1 -0
- package/dist/oauth/endpoints/consent.d.ts +29 -0
- package/dist/oauth/endpoints/consent.d.ts.map +1 -0
- package/dist/oauth/endpoints/consent.js +98 -0
- package/dist/oauth/endpoints/consent.js.map +1 -0
- package/dist/oauth/{metadata.d.ts → endpoints/metadata.d.ts} +8 -2
- package/dist/oauth/endpoints/metadata.d.ts.map +1 -0
- package/dist/oauth/{metadata.js → endpoints/metadata.js} +10 -4
- package/dist/oauth/endpoints/metadata.js.map +1 -0
- package/dist/oauth/endpoints/register.d.ts +10 -0
- package/dist/oauth/endpoints/register.d.ts.map +1 -0
- package/dist/oauth/endpoints/register.js +96 -0
- package/dist/oauth/endpoints/register.js.map +1 -0
- package/dist/oauth/{revoke.d.ts → endpoints/revoke.d.ts} +1 -1
- package/dist/oauth/endpoints/revoke.d.ts.map +1 -0
- package/dist/oauth/{revoke.js → endpoints/revoke.js} +8 -18
- package/dist/oauth/endpoints/revoke.js.map +1 -0
- package/dist/oauth/endpoints/token.d.ts +3 -0
- package/dist/oauth/endpoints/token.d.ts.map +1 -0
- package/dist/oauth/{token.js → endpoints/token.js} +32 -25
- package/dist/oauth/endpoints/token.js.map +1 -0
- package/dist/oauth/{wellknown.d.ts → endpoints/wellknown.d.ts} +1 -1
- package/dist/oauth/endpoints/wellknown.d.ts.map +1 -0
- package/dist/oauth/{wellknown.js → endpoints/wellknown.js} +7 -9
- package/dist/oauth/endpoints/wellknown.js.map +1 -0
- package/dist/oauth/policy/redirect.d.ts.map +1 -0
- package/dist/oauth/policy/redirect.js.map +1 -0
- package/dist/oauth/policy/reqbody.d.ts +18 -0
- package/dist/oauth/policy/reqbody.d.ts.map +1 -0
- package/dist/oauth/policy/reqbody.js +68 -0
- package/dist/oauth/policy/reqbody.js.map +1 -0
- package/dist/oauth/{resource.d.ts → policy/resource.d.ts} +1 -1
- package/dist/oauth/policy/resource.d.ts.map +1 -0
- package/dist/oauth/{resource.js → policy/resource.js} +2 -2
- package/dist/oauth/policy/resource.js.map +1 -0
- package/dist/oauth/{scope.d.ts → policy/scope.d.ts} +2 -2
- package/dist/oauth/policy/scope.d.ts.map +1 -0
- package/dist/oauth/{scope.js → policy/scope.js} +3 -3
- package/dist/oauth/policy/scope.js.map +1 -0
- package/dist/oauth/router.d.ts +10 -13
- package/dist/oauth/router.d.ts.map +1 -1
- package/dist/oauth/router.js +93 -115
- package/dist/oauth/router.js.map +1 -1
- package/dist/oauth/types.d.ts +99 -45
- package/dist/oauth/types.d.ts.map +1 -1
- package/dist/oauth/types.js +1 -90
- package/dist/oauth/types.js.map +1 -1
- package/dist/server.d.ts +12 -12
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +9 -8
- package/dist/server.js.map +1 -1
- package/dist/util/defined.d.ts +11 -0
- package/dist/util/defined.d.ts.map +1 -0
- package/dist/util/defined.js +15 -0
- package/dist/util/defined.js.map +1 -0
- package/dist/util/json.d.ts +3 -0
- package/dist/util/json.d.ts.map +1 -0
- package/dist/util/json.js +3 -0
- package/dist/util/json.js.map +1 -0
- package/docs/ROADMAP.md +35 -47
- package/docs/compatibility.md +84 -0
- package/docs/diagrams/mcp-trellis-architecture.html +305 -0
- package/docs/guide.md +37 -13
- package/docs/reference.md +54 -22
- package/docs/security.md +58 -9
- package/docs/troubleshooting.md +19 -0
- package/package.json +23 -10
- package/dist/adapters/origins.d.ts +0 -19
- package/dist/adapters/origins.d.ts.map +0 -1
- package/dist/adapters/origins.js +0 -52
- package/dist/adapters/origins.js.map +0 -1
- package/dist/body.d.ts.map +0 -1
- package/dist/body.js.map +0 -1
- package/dist/dispatch.d.ts.map +0 -1
- package/dist/dispatch.js +0 -119
- package/dist/dispatch.js.map +0 -1
- package/dist/http.d.ts +0 -17
- package/dist/http.d.ts.map +0 -1
- package/dist/http.js +0 -38
- package/dist/http.js.map +0 -1
- package/dist/jsonrpc.d.ts.map +0 -1
- package/dist/jsonrpc.js +0 -18
- package/dist/jsonrpc.js.map +0 -1
- package/dist/methods.d.ts.map +0 -1
- package/dist/methods.js +0 -130
- package/dist/methods.js.map +0 -1
- package/dist/oauth/authorize.d.ts +0 -3
- package/dist/oauth/authorize.d.ts.map +0 -1
- package/dist/oauth/authorize.js +0 -141
- package/dist/oauth/authorize.js.map +0 -1
- package/dist/oauth/base64url.d.ts.map +0 -1
- package/dist/oauth/base64url.js.map +0 -1
- package/dist/oauth/clientauth.d.ts +0 -24
- package/dist/oauth/clientauth.d.ts.map +0 -1
- package/dist/oauth/clientauth.js +0 -119
- package/dist/oauth/clientauth.js.map +0 -1
- package/dist/oauth/codes.d.ts +0 -30
- package/dist/oauth/codes.d.ts.map +0 -1
- package/dist/oauth/codes.js +0 -85
- package/dist/oauth/codes.js.map +0 -1
- package/dist/oauth/consent.d.ts +0 -65
- package/dist/oauth/consent.d.ts.map +0 -1
- package/dist/oauth/consent.js +0 -173
- package/dist/oauth/consent.js.map +0 -1
- package/dist/oauth/metadata.d.ts.map +0 -1
- package/dist/oauth/metadata.js.map +0 -1
- package/dist/oauth/pkce.d.ts.map +0 -1
- package/dist/oauth/pkce.js.map +0 -1
- package/dist/oauth/redirect.d.ts.map +0 -1
- package/dist/oauth/redirect.js.map +0 -1
- package/dist/oauth/reqbody.d.ts +0 -10
- package/dist/oauth/reqbody.d.ts.map +0 -1
- package/dist/oauth/reqbody.js +0 -29
- package/dist/oauth/reqbody.js.map +0 -1
- package/dist/oauth/resource.d.ts.map +0 -1
- package/dist/oauth/resource.js.map +0 -1
- package/dist/oauth/revoke.d.ts.map +0 -1
- package/dist/oauth/revoke.js.map +0 -1
- package/dist/oauth/scope.d.ts.map +0 -1
- package/dist/oauth/scope.js.map +0 -1
- package/dist/oauth/sealed.d.ts.map +0 -1
- package/dist/oauth/sealed.js.map +0 -1
- package/dist/oauth/secrethash.d.ts.map +0 -1
- package/dist/oauth/secrethash.js.map +0 -1
- package/dist/oauth/token.d.ts +0 -3
- package/dist/oauth/token.d.ts.map +0 -1
- package/dist/oauth/token.js.map +0 -1
- package/dist/oauth/wellknown.d.ts.map +0 -1
- package/dist/oauth/wellknown.js.map +0 -1
- package/dist/protocol.d.ts.map +0 -1
- package/dist/protocol.js.map +0 -1
- package/dist/registry.d.ts +0 -37
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js +0 -77
- package/dist/registry.js.map +0 -1
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js +0 -98
- package/dist/tools.js.map +0 -1
- package/dist/validate.d.ts.map +0 -1
- package/dist/validate.js.map +0 -1
- /package/dist/oauth/{pkce.d.ts → crypto/pkce.d.ts} +0 -0
- /package/dist/oauth/{redirect.d.ts → policy/redirect.d.ts} +0 -0
- /package/dist/oauth/{redirect.js → policy/redirect.js} +0 -0
package/README.md
CHANGED
|
@@ -1,122 +1,92 @@
|
|
|
1
1
|
# mcp-trellis
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](LICENSE)
|
|
5
|
-
|
|
6
|
-
**Host-agnostic MCP + OAuth ports** — you bring the runtime and IdP; the library owns the connector protocol.
|
|
7
|
-
|
|
8
|
-
Web-standard `Request` → `Response`. Same handler on Cloudflare Workers, Next.js App Router, Deno, Bun, and Node HTTP via `mcp-trellis/node`. Zero runtime dependencies.
|
|
9
|
-
|
|
10
|
-
## Why mcp-trellis
|
|
11
|
-
|
|
12
|
-
The whole connector stack in one package — MCP handler **and** OAuth 2.1 authorization server — with no database and no vendor signup. You own login, token minting, and storage; the library owns the protocol.
|
|
3
|
+
**Existing SaaS → authenticated AI connector SDK.**
|
|
13
4
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
| MCP handler | ✅ | ✅ | ❌ | ❌ | ❌ |
|
|
17
|
-
| OAuth 2.1 authorization server | ✅ | ❌ bring your own | ✅ | ✅ | ✅ |
|
|
18
|
-
| Runtime | any Web-standard | any Web-standard | Workers only | Node | — |
|
|
19
|
-
| Database | none | — | KV (optional) | **required** | — |
|
|
20
|
-
| Runtime dependencies | **zero** | several | several | several | — |
|
|
21
|
-
| Self-hosted | ✅ | ✅ | ✅ | ✅ | ❌ SaaS |
|
|
22
|
-
| Named connector profiles (Claude / Gemini / Codex), enforced | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
5
|
+
Claude, ChatGPT and Gemini against the login and data you already have. No new
|
|
6
|
+
database. No auth vendor. One TypeScript package.
|
|
23
7
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
8
|
+
mcp-trellis is the **vendor-neutral embedded BYO-auth** path: MCP handler and
|
|
9
|
+
self-hosted OAuth AS in one npm install. Contrast composition stacks such as
|
|
10
|
+
**xmcp + Scalekit** (or WorkOS / Descope connectors) that split the MCP runtime
|
|
11
|
+
and the identity product across 2–3 packages.
|
|
27
12
|
|
|
28
|
-
|
|
13
|
+
Your app already knows who the user is. Connecting an AI client still means
|
|
14
|
+
wiring OAuth discovery, registration, consent, PKCE and token exchange.
|
|
15
|
+
**mcp-trellis handles that protocol work**, while your app keeps login, token
|
|
16
|
+
verification and data permissions.
|
|
29
17
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
(`Request`/`Response`) and not tied to a particular MCP framework.
|
|
36
|
-
|
|
37
|
-
## Requirements
|
|
38
|
-
|
|
39
|
-
- **Node ≥ 20** for Node hosts (global WebCrypto in ESM)
|
|
40
|
-
- Or any runtime with WebCrypto + `fetch` (Workers, Deno, Bun)
|
|
18
|
+
```mermaid
|
|
19
|
+
flowchart LR
|
|
20
|
+
A[Existing app: login + user data] --> B[mcp-trellis: OAuth + MCP]
|
|
21
|
+
B --> C[AI client: user-scoped tools]
|
|
22
|
+
```
|
|
41
23
|
|
|
42
|
-
##
|
|
24
|
+
## The 30-second example
|
|
43
25
|
|
|
44
26
|
```bash
|
|
45
27
|
npm install mcp-trellis
|
|
46
28
|
```
|
|
47
29
|
|
|
48
|
-
## Quick start
|
|
49
|
-
|
|
50
|
-
One call mounts the MCP endpoint, the OAuth authorization server, and both discovery documents:
|
|
51
|
-
|
|
52
30
|
```ts
|
|
53
31
|
import { createMcpApp } from "mcp-trellis";
|
|
32
|
+
import { existingAuth, projectsForUser } from "./your-app.js";
|
|
54
33
|
|
|
55
|
-
const
|
|
56
|
-
serverInfo: { name: "
|
|
34
|
+
export const mcp = createMcpApp<{ userId: string }>({
|
|
35
|
+
serverInfo: { name: "my-saas", version: "1.0.0" },
|
|
57
36
|
clients: ["claude"],
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
handler: (_ctx, args) => String(args.text ?? ""),
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
auth: {
|
|
72
|
-
codeSecret: process.env.OAUTH_CODE_SECRET!,
|
|
73
|
-
resolveUser: async (req) => getSession(req),
|
|
74
|
-
loginUrl: (_req, next) => `/login?next=${encodeURIComponent(next)}`,
|
|
75
|
-
mintAccessToken: async ({ userId, scope, resource }) => ({
|
|
76
|
-
// Embed `resource` as the token audience (RFC 8707).
|
|
77
|
-
accessToken: await issueUserToken(userId, { aud: resource, scope }),
|
|
78
|
-
expiresIn: 3600,
|
|
79
|
-
}),
|
|
80
|
-
verifyToken: async (token) => {
|
|
81
|
-
const claims = await readUserToken(token);
|
|
82
|
-
if (!claims) return null;
|
|
83
|
-
return {
|
|
84
|
-
userId: claims.sub,
|
|
85
|
-
scopes: claims.scope.split(" "),
|
|
86
|
-
audience: claims.aud,
|
|
87
|
-
};
|
|
88
|
-
},
|
|
89
|
-
},
|
|
37
|
+
// existingAuth must include codeStore (required since 2.0) plus the usual ports.
|
|
38
|
+
auth: existingAuth,
|
|
39
|
+
context: (_request, principal) => ({ userId: principal!.id }),
|
|
40
|
+
tools: [{
|
|
41
|
+
name: "list_my_projects",
|
|
42
|
+
description: "List the signed-in user's projects",
|
|
43
|
+
inputSchema: { type: "object", properties: {} },
|
|
44
|
+
scope: "mcp",
|
|
45
|
+
handler: async ({ userId }) => JSON.stringify(await projectsForUser(userId)),
|
|
46
|
+
}],
|
|
90
47
|
});
|
|
91
|
-
|
|
92
|
-
export default { fetch: (req: Request) => app.fetch(req) };
|
|
93
48
|
```
|
|
94
49
|
|
|
95
|
-
|
|
50
|
+
This shows the integration shape: `your-app.js` is your application's auth/data adapter, not a supplied module. Production needs a shared `codeStore` (see [examples/stores.ts](examples/stores.ts)). **[Run the complete Node demo](examples/saas-demo/README.md)** for working login, token handling and two users with different projects. Node ≥20 is required.
|
|
96
51
|
|
|
97
|
-
|
|
52
|
+
[](https://www.npmjs.com/package/mcp-trellis)
|
|
53
|
+
[](https://github.com/amir1824/mcp-trellis/actions/workflows/ci.yml)
|
|
54
|
+
[](LICENSE)
|
|
98
55
|
|
|
99
|
-
|
|
56
|
+
## When should I use this?
|
|
100
57
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}'
|
|
105
|
-
```
|
|
58
|
+
- Your TypeScript app already has login and user data, and you want to expose user-scoped tools through remote MCP.
|
|
59
|
+
- You want the MCP handler and OAuth authorization endpoints in one package, with zero runtime dependencies.
|
|
60
|
+
- You want to reuse your runtime and existing storage. The library does not require a new database; production deployments still need appropriate replay, token and session storage.
|
|
106
61
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
62
|
+
## When should I not use this?
|
|
63
|
+
|
|
64
|
+
- You need a managed identity provider, a user database, or a login system built for you.
|
|
65
|
+
- You already have an MCP server and authorization server that meet your needs.
|
|
66
|
+
- You need protocol features or client combinations absent from the [verified support table](docs/compatibility.md).
|
|
67
|
+
- You only need a local stdio tool with no remote authorization flow.
|
|
68
|
+
|
|
69
|
+
## Run a real tool
|
|
70
|
+
|
|
71
|
+
The [Project desk demo](examples/saas-demo/README.md) proves the local flow: app login → OAuth consent → access token → `list_my_projects`. Alice sees her two projects; Bob sees his own. It uses fictional data and an independent app session.
|
|
72
|
+
|
|
73
|
+
**Live Claude / ChatGPT recordings are still pending.** Automated OAuth tests are not evidence that a current vendor client has connected successfully.
|
|
74
|
+
|
|
75
|
+
## Clients and compatibility
|
|
76
|
+
|
|
77
|
+
| Profile | Implemented server flow | Live client evidence |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| Claude (`claude`) | Dynamic registration, public client, PKCE | Pending |
|
|
80
|
+
| ChatGPT / Codex (`codex`) | Public client, PKCE; configure exact hosted callback as needed | Pending for each product |
|
|
81
|
+
| Gemini Enterprise (`gemini`) | Pre-registered client; secret basic/post; requires `clientStore` | Pending; does not imply every Gemini product |
|
|
82
|
+
|
|
83
|
+
Protocol versions implemented: `2024-11-05`, `2025-03-26`, `2025-06-18`; default `2025-06-18`. See [test evidence and verification checklist](docs/compatibility.md), [client configuration](docs/guide.md#clients) and [troubleshooting](docs/troubleshooting.md).
|
|
110
84
|
|
|
111
|
-
##
|
|
85
|
+
## Integrate with your app
|
|
112
86
|
|
|
113
|
-
|
|
114
|
-
|--------|--------------|---------------------|-------|
|
|
115
|
-
| `claude` | Dynamic (DCR), public + PKCE | `none` | Claude Custom Connectors callback allowlisted |
|
|
116
|
-
| `gemini` | **Pre-registered**, confidential | `client_secret_basic`, `client_secret_post` | Supply `auth.clientStore` |
|
|
117
|
-
| `codex` | OAuth 2.1 per MCP auth spec, public + PKCE | `none` | ChatGPT / Codex share one contract |
|
|
87
|
+
Implement `resolveUser`, `loginUrl`, `mintAccessToken` and `verifyToken` using your app's existing auth. Mint tokens for the requested MCP resource and return their verified `audience`; the library rejects an audience mismatch before executing tools. A logged-in user still sees an OAuth consent screen. Your data layer must enforce ownership and tenant permissions. Browser callers that send an `Origin` header need `allowedRequestOrigins` (fail-closed by default) — see [security.md](docs/security.md).
|
|
118
88
|
|
|
119
|
-
|
|
89
|
+
[Node demo](examples/saas-demo/README.md) · [Host recipes](docs/guide.md) · [Security responsibilities](docs/security.md) · [npm](https://www.npmjs.com/package/mcp-trellis)
|
|
120
90
|
|
|
121
91
|
## Architecture
|
|
122
92
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node request-side plumbing for the `(req, res)` adapter: origin
|
|
3
|
+
* resolution, Web `Request` construction, and bounded body reading.
|
|
4
|
+
* Response-side glue (`sendWebResponse`, `asNodeHandler`) lives in `node.ts`.
|
|
5
|
+
*/
|
|
6
|
+
export type NodeRequestLike = {
|
|
7
|
+
method?: string | undefined;
|
|
8
|
+
url?: string | undefined;
|
|
9
|
+
headers: Record<string, string | string[] | undefined>;
|
|
10
|
+
body?: unknown;
|
|
11
|
+
/** Destroy the underlying socket — called only after a 413 has flushed. */
|
|
12
|
+
destroy?: (() => void) | undefined;
|
|
13
|
+
[Symbol.asyncIterator]?: () => AsyncIterator<Uint8Array | string>;
|
|
14
|
+
};
|
|
15
|
+
export type ToWebRequestOptions = {
|
|
16
|
+
/** Absolute origin, e.g. https://app.example.com. */
|
|
17
|
+
origin: string;
|
|
18
|
+
/** Pre-read body. Overrides `req.body` when supplied. */
|
|
19
|
+
body?: unknown;
|
|
20
|
+
};
|
|
21
|
+
export type ResolveOriginOptions = {
|
|
22
|
+
/** Trust last `X-Forwarded-Host` / `X-Forwarded-Proto` hop. */
|
|
23
|
+
trustProxy?: boolean;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* A Host (or `X-Forwarded-*`) header that can't safely become an origin —
|
|
27
|
+
* missing, carrying a path/query/fragment/credentials, malformed, or a
|
|
28
|
+
* non-`http(s)` scheme. Distinct from a generic `Error` so `asNodeHandler`
|
|
29
|
+
* can answer this with a client-facing 400 instead of the same 500 it gives
|
|
30
|
+
* an actual unexpected failure.
|
|
31
|
+
*/
|
|
32
|
+
export declare class InvalidOriginError extends Error {
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Derive origin from Host, or from forwarded headers when `trustProxy`.
|
|
36
|
+
* Always parsed through `URL` and rejected unless it is a bare `host[:port]`
|
|
37
|
+
* over http(s): a Host carrying a path (`evil.test/../trusted.test`) or
|
|
38
|
+
* credentials (`user:pass@evil.test`) must not become an origin-shaped string.
|
|
39
|
+
*/
|
|
40
|
+
export declare const resolveOrigin: (req: NodeRequestLike, options?: ResolveOriginOptions) => string;
|
|
41
|
+
export declare const toWebRequest: (req: NodeRequestLike, options: ToWebRequestOptions) => Request;
|
|
42
|
+
export declare const requestPath: (req: NodeRequestLike) => string;
|
|
43
|
+
/** True for a path under an `…/oauth` segment — the default OAuth mount. */
|
|
44
|
+
export declare const isOAuthPath: (path: string) => boolean;
|
|
45
|
+
/** OAuth routes under `…/oauth/…` get the 64 KiB cap; everything else gets 1 MiB. */
|
|
46
|
+
export declare const bodyLimitForPath: (path: string) => number;
|
|
47
|
+
/**
|
|
48
|
+
* Prefer `req.body`; else drain the stream (raw `http.createServer`), capped
|
|
49
|
+
* at `maxBytes`. Throws `BodyTooLargeError` past the cap without destroying
|
|
50
|
+
* the socket — `asNodeHandler` writes 413 first, then destroys after flush.
|
|
51
|
+
*/
|
|
52
|
+
export declare const readNodeBody: (req: NodeRequestLike, maxBytes?: number) => Promise<unknown>;
|
|
53
|
+
//# sourceMappingURL=node-request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-request.d.ts","sourceRoot":"","sources":["../../src/adapters/node-request.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,2EAA2E;IAC3E,OAAO,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IACnC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,MAAM,aAAa,CAAC,UAAU,GAAG,MAAM,CAAC,CAAC;CACnE,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,+DAA+D;IAC/D,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAQF;;;;;;GAMG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;CAAG;AAOhD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,KAAK,eAAe,EAAE,UAAS,oBAAyB,KAAG,MA+BxF,CAAC;AA6CF,eAAO,MAAM,YAAY,GAAI,KAAK,eAAe,EAAE,SAAS,mBAAmB,KAAG,OAkCjF,CAAC;AAMF,eAAO,MAAM,WAAW,GAAI,KAAK,eAAe,KAAG,MAA+C,CAAC;AAEnG,4EAA4E;AAC5E,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,OAAuC,CAAC;AAEnF,qFAAqF;AACrF,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,MACuB,CAAC;AAqBxE;;;;GAIG;AACH,eAAO,MAAM,YAAY,GACvB,KAAK,eAAe,EACpB,WAAU,MAA+B,KACxC,OAAO,CAAC,OAAO,CAcjB,CAAC"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node request-side plumbing for the `(req, res)` adapter: origin
|
|
3
|
+
* resolution, Web `Request` construction, and bounded body reading.
|
|
4
|
+
* Response-side glue (`sendWebResponse`, `asNodeHandler`) lives in `node.ts`.
|
|
5
|
+
*/
|
|
6
|
+
import { BodyTooLargeError, DEFAULT_MCP_BODY_LIMIT, DEFAULT_OAUTH_BODY_LIMIT, isNodeReadable, readBoundedNodeBody, } from "../http/body.js";
|
|
7
|
+
import { isJsonObject } from "../util/json.js";
|
|
8
|
+
const headerValue = (value) => Array.isArray(value) ? value[0] : value;
|
|
9
|
+
const lastForwarded = (value) => value?.split(",").pop()?.trim();
|
|
10
|
+
/**
|
|
11
|
+
* A Host (or `X-Forwarded-*`) header that can't safely become an origin —
|
|
12
|
+
* missing, carrying a path/query/fragment/credentials, malformed, or a
|
|
13
|
+
* non-`http(s)` scheme. Distinct from a generic `Error` so `asNodeHandler`
|
|
14
|
+
* can answer this with a client-facing 400 instead of the same 500 it gives
|
|
15
|
+
* an actual unexpected failure.
|
|
16
|
+
*/
|
|
17
|
+
export class InvalidOriginError extends Error {
|
|
18
|
+
}
|
|
19
|
+
/** A bare `host[:port]` must not carry any of these — they'd mean a path, query, fragment, or credentials smuggled into what should be just the authority. */
|
|
20
|
+
const HOST_HEADER_STRUCTURAL_CHARS = /[/?#@\\]/;
|
|
21
|
+
const ALLOWED_ORIGIN_PROTOCOLS = new Set(["http:", "https:"]);
|
|
22
|
+
/**
|
|
23
|
+
* Derive origin from Host, or from forwarded headers when `trustProxy`.
|
|
24
|
+
* Always parsed through `URL` and rejected unless it is a bare `host[:port]`
|
|
25
|
+
* over http(s): a Host carrying a path (`evil.test/../trusted.test`) or
|
|
26
|
+
* credentials (`user:pass@evil.test`) must not become an origin-shaped string.
|
|
27
|
+
*/
|
|
28
|
+
export const resolveOrigin = (req, options = {}) => {
|
|
29
|
+
const trustProxy = options.trustProxy === true;
|
|
30
|
+
const proto = trustProxy
|
|
31
|
+
? lastForwarded(headerValue(req.headers["x-forwarded-proto"])) || "https"
|
|
32
|
+
: "https";
|
|
33
|
+
const host = trustProxy
|
|
34
|
+
? lastForwarded(headerValue(req.headers["x-forwarded-host"])) || headerValue(req.headers.host)
|
|
35
|
+
: headerValue(req.headers.host);
|
|
36
|
+
if (!host) {
|
|
37
|
+
throw new InvalidOriginError("Cannot resolve origin: pass options.origin or set Host" +
|
|
38
|
+
(trustProxy ? " / X-Forwarded-Host" : ""));
|
|
39
|
+
}
|
|
40
|
+
if (HOST_HEADER_STRUCTURAL_CHARS.test(host)) {
|
|
41
|
+
throw new InvalidOriginError(`Cannot resolve origin: Host header "${host}" must be a bare host[:port], not a full URL`);
|
|
42
|
+
}
|
|
43
|
+
let parsed;
|
|
44
|
+
try {
|
|
45
|
+
parsed = new URL(`${proto}://${host}`);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
throw new InvalidOriginError(`Cannot resolve origin: Host header "${host}" is not valid`);
|
|
49
|
+
}
|
|
50
|
+
if (!ALLOWED_ORIGIN_PROTOCOLS.has(parsed.protocol)) {
|
|
51
|
+
throw new InvalidOriginError(`Cannot resolve origin: unsupported protocol "${parsed.protocol}"`);
|
|
52
|
+
}
|
|
53
|
+
return parsed.origin;
|
|
54
|
+
};
|
|
55
|
+
const isPlainRecord = (value) => isJsonObject(value) && !(value instanceof Uint8Array);
|
|
56
|
+
const isFormContentType = (contentType) => (contentType ?? "").toLowerCase().includes("application/x-www-form-urlencoded");
|
|
57
|
+
const appendFormValue = (params, key, value) => {
|
|
58
|
+
if (Array.isArray(value)) {
|
|
59
|
+
for (const entry of value)
|
|
60
|
+
appendFormValue(params, key, entry);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (value === undefined || value === null)
|
|
64
|
+
return;
|
|
65
|
+
params.append(key, typeof value === "string" ? value : String(value));
|
|
66
|
+
};
|
|
67
|
+
/** Re-serialize a pre-parsed `express.urlencoded()`-shaped body back to `a=1&b=2`. */
|
|
68
|
+
const objectToUrlEncoded = (body) => {
|
|
69
|
+
const params = new URLSearchParams();
|
|
70
|
+
for (const [key, value] of Object.entries(body))
|
|
71
|
+
appendFormValue(params, key, value);
|
|
72
|
+
return params.toString();
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* `req.body` from a body-parsing middleware (Express `json()`/`urlencoded()`,
|
|
76
|
+
* multer, …) arrives as an already-parsed object, not raw bytes — the
|
|
77
|
+
* `Content-Type` header says what shape it round-trips back to. A form body
|
|
78
|
+
* must be re-encoded as a form, or `/token` and `/consent` behind
|
|
79
|
+
* `express.urlencoded()` receive one unparseable JSON-shaped key.
|
|
80
|
+
*/
|
|
81
|
+
const toBodyInit = (body, contentType) => {
|
|
82
|
+
if (body === undefined || body === null)
|
|
83
|
+
return undefined;
|
|
84
|
+
if (typeof body === "string")
|
|
85
|
+
return body;
|
|
86
|
+
if (body instanceof Uint8Array) {
|
|
87
|
+
return body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength);
|
|
88
|
+
}
|
|
89
|
+
if (isPlainRecord(body) && isFormContentType(contentType)) {
|
|
90
|
+
return objectToUrlEncoded(body);
|
|
91
|
+
}
|
|
92
|
+
return JSON.stringify(body);
|
|
93
|
+
};
|
|
94
|
+
const BODYLESS_METHODS = new Set(["GET", "HEAD"]);
|
|
95
|
+
export const toWebRequest = (req, options) => {
|
|
96
|
+
// Never trust an absolute-form request target — rebase onto the validated origin.
|
|
97
|
+
const target = new URL(req.url ?? "/", options.origin);
|
|
98
|
+
const url = `${options.origin}${target.pathname}${target.search}`;
|
|
99
|
+
const headers = new Headers(Object.entries(req.headers)
|
|
100
|
+
.map(([key, value]) => {
|
|
101
|
+
const resolved = headerValue(value);
|
|
102
|
+
return resolved === undefined ? null : [key, resolved];
|
|
103
|
+
})
|
|
104
|
+
.filter((entry) => entry !== null));
|
|
105
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
106
|
+
if (BODYLESS_METHODS.has(method)) {
|
|
107
|
+
return new Request(url, { method, headers });
|
|
108
|
+
}
|
|
109
|
+
const body = options.body !== undefined ? options.body : req.body;
|
|
110
|
+
const bodyInit = toBodyInit(body, headers.get("content-type") ?? undefined);
|
|
111
|
+
if (isPlainRecord(body)) {
|
|
112
|
+
// The re-serialized body's byte length no longer matches whatever
|
|
113
|
+
// Content-Length the original (already-parsed) request declared — a
|
|
114
|
+
// stale header here can make the body-size precheck reject a body well
|
|
115
|
+
// within the cap, or let an oversized one slip past it (still caught by
|
|
116
|
+
// the streaming fallback either way).
|
|
117
|
+
headers.delete("content-length");
|
|
118
|
+
headers.delete("transfer-encoding");
|
|
119
|
+
}
|
|
120
|
+
return new Request(url, {
|
|
121
|
+
method,
|
|
122
|
+
headers,
|
|
123
|
+
...(bodyInit !== undefined ? { body: bodyInit } : {}),
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
const isAsyncIterable = (value) => typeof value?.[Symbol.asyncIterator] ===
|
|
127
|
+
"function";
|
|
128
|
+
export const requestPath = (req) => (req.url ?? "/").split("?")[0] ?? "/";
|
|
129
|
+
/** True for a path under an `…/oauth` segment — the default OAuth mount. */
|
|
130
|
+
export const isOAuthPath = (path) => /\/oauth(?:\/|$)/.test(path);
|
|
131
|
+
/** OAuth routes under `…/oauth/…` get the 64 KiB cap; everything else gets 1 MiB. */
|
|
132
|
+
export const bodyLimitForPath = (path) => isOAuthPath(path) ? DEFAULT_OAUTH_BODY_LIMIT : DEFAULT_MCP_BODY_LIMIT;
|
|
133
|
+
const enforcePresetBodySize = (body, maxBytes) => {
|
|
134
|
+
if (typeof body === "string" && new TextEncoder().encode(body).byteLength > maxBytes) {
|
|
135
|
+
throw new BodyTooLargeError(maxBytes);
|
|
136
|
+
}
|
|
137
|
+
if (body instanceof Uint8Array && body.byteLength > maxBytes) {
|
|
138
|
+
throw new BodyTooLargeError(maxBytes);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const emptyToUndefined = (bytes) => bytes.byteLength > 0 ? bytes : undefined;
|
|
142
|
+
const asBodyStream = (req) => {
|
|
143
|
+
if (isNodeReadable(req) || isAsyncIterable(req)) {
|
|
144
|
+
return req;
|
|
145
|
+
}
|
|
146
|
+
return undefined;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Prefer `req.body`; else drain the stream (raw `http.createServer`), capped
|
|
150
|
+
* at `maxBytes`. Throws `BodyTooLargeError` past the cap without destroying
|
|
151
|
+
* the socket — `asNodeHandler` writes 413 first, then destroys after flush.
|
|
152
|
+
*/
|
|
153
|
+
export const readNodeBody = async (req, maxBytes = DEFAULT_MCP_BODY_LIMIT) => {
|
|
154
|
+
if (req.body !== undefined && req.body !== null) {
|
|
155
|
+
enforcePresetBodySize(req.body, maxBytes);
|
|
156
|
+
return req.body;
|
|
157
|
+
}
|
|
158
|
+
const stream = asBodyStream(req);
|
|
159
|
+
if (!stream)
|
|
160
|
+
return undefined;
|
|
161
|
+
return emptyToUndefined(await readBoundedNodeBody(stream, maxBytes, {
|
|
162
|
+
contentLength: headerValue(req.headers["content-length"]),
|
|
163
|
+
}));
|
|
164
|
+
};
|
|
165
|
+
//# sourceMappingURL=node-request.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-request.js","sourceRoot":"","sources":["../../src/adapters/node-request.ts"],"names":[],"mappings":"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"}
|
package/dist/adapters/node.d.ts
CHANGED
|
@@ -1,33 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Node HTTP `(req, res)` adapter — Express, Cloud Functions, `http.createServer`, etc.
|
|
3
3
|
* Bridges duck-typed Node request/response objects to Web `Request`/`Response`.
|
|
4
|
+
* Request-side plumbing (origin resolution, `toWebRequest`, body reading)
|
|
5
|
+
* lives in `node-request.ts` and is re-exported below unchanged.
|
|
4
6
|
*/
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
export type
|
|
8
|
-
|
|
9
|
-
url?: string | undefined;
|
|
10
|
-
headers: Record<string, string | string[] | undefined>;
|
|
11
|
-
body?: unknown;
|
|
12
|
-
/** Destroy the underlying socket — called only after a 413 has flushed. */
|
|
13
|
-
destroy?: (() => void) | undefined;
|
|
14
|
-
[Symbol.asyncIterator]?: () => AsyncIterator<Uint8Array | string>;
|
|
15
|
-
};
|
|
7
|
+
import { type OriginAllowlistOptions } from "../http/origins.js";
|
|
8
|
+
import { type NodeRequestLike } from "./node-request.js";
|
|
9
|
+
export { isAllowedOrigin, isAllowedRequestOrigin, type OriginAllowlistOptions, } from "../http/origins.js";
|
|
10
|
+
export { bodyLimitForPath, InvalidOriginError, type NodeRequestLike, type ResolveOriginOptions, readNodeBody, resolveOrigin, type ToWebRequestOptions, toWebRequest, } from "./node-request.js";
|
|
16
11
|
export type NodeResponseLike = {
|
|
17
12
|
statusCode: number;
|
|
18
13
|
setHeader: (name: string, value: string | number | readonly string[]) => void;
|
|
19
14
|
end: (chunk?: string | Uint8Array, callback?: () => void) => void;
|
|
20
15
|
};
|
|
21
|
-
export type ToWebRequestOptions = {
|
|
22
|
-
/** Absolute origin, e.g. https://app.example.com. */
|
|
23
|
-
origin: string;
|
|
24
|
-
/** Pre-read body. Overrides `req.body` when supplied. */
|
|
25
|
-
body?: unknown;
|
|
26
|
-
};
|
|
27
|
-
export type ResolveOriginOptions = {
|
|
28
|
-
/** Trust last `X-Forwarded-Host` / `X-Forwarded-Proto` hop. */
|
|
29
|
-
trustProxy?: boolean;
|
|
30
|
-
};
|
|
31
16
|
export type AsNodeHandlerOptions = OriginAllowlistOptions & {
|
|
32
17
|
/**
|
|
33
18
|
* Absolute origin. Required unless `trustProxy: true`.
|
|
@@ -37,17 +22,15 @@ export type AsNodeHandlerOptions = OriginAllowlistOptions & {
|
|
|
37
22
|
origin?: string;
|
|
38
23
|
trustProxy?: boolean;
|
|
39
24
|
};
|
|
40
|
-
/** Derive origin from Host, or from forwarded headers when trustProxy. */
|
|
41
|
-
export declare const resolveOrigin: (req: NodeRequestLike, options?: ResolveOriginOptions) => string;
|
|
42
|
-
export declare const toWebRequest: (req: NodeRequestLike, options: ToWebRequestOptions) => Request;
|
|
43
|
-
/** OAuth routes under `…/oauth/…` get the 64 KiB cap; everything else gets 1 MiB. */
|
|
44
|
-
export declare const bodyLimitForPath: (path: string) => number;
|
|
45
25
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
26
|
+
* Unlike every other header, `Set-Cookie` cannot be folded into one
|
|
27
|
+
* comma-joined value — a cookie's own `Expires` attribute contains a comma.
|
|
28
|
+
* The Fetch `Headers` object accordingly keeps duplicates apart internally
|
|
29
|
+
* and exposes them via `getSetCookie()`, but `forEach`/`entries()` still
|
|
30
|
+
* yields one `"set-cookie"` pair per cookie — calling `res.setHeader` once
|
|
31
|
+
* per pair (as for any other header) makes each call overwrite the last,
|
|
32
|
+
* silently dropping every cookie but the final one.
|
|
49
33
|
*/
|
|
50
|
-
export declare const readNodeBody: (req: NodeRequestLike, maxBytes?: number) => Promise<unknown>;
|
|
51
34
|
export declare const sendWebResponse: (res: NodeResponseLike, response: Response) => Promise<void>;
|
|
52
35
|
/** One-liner: Node `(req, res)` → MCP / OAuth Web handler. */
|
|
53
36
|
export declare const asNodeHandler: (mcp: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/adapters/node.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/adapters/node.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAmB,KAAK,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAElF,OAAO,EAIL,KAAK,eAAe,EAKrB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,KAAK,sBAAsB,GAC5B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,YAAY,EACZ,aAAa,EACb,KAAK,mBAAmB,EACxB,YAAY,GACb,MAAM,mBAAmB,CAAC;AAE3B,MAAM,MAAM,gBAAgB,GAAG;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,KAAK,IAAI,CAAC;IAC9E,GAAG,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,UAAU,EAAE,QAAQ,CAAC,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CACnE,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,sBAAsB,GAAG;IAC1D;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAU,KAAK,gBAAgB,EAAE,UAAU,QAAQ,KAAG,OAAO,CAAC,IAAI,CAS7F,CAAC;AAkDF,8DAA8D;AAC9D,eAAO,MAAM,aAAa,GACxB,KAAK;IAAE,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAAE,EACvD,UAAU,oBAAoB,MAIhB,KAAK,eAAe,EAAE,KAAK,gBAAgB,KAAG,OAAO,CAAC,IAAI,CA2BzE,CAAC"}
|