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.
Files changed (296) hide show
  1. package/README.md +63 -93
  2. package/dist/adapters/node-request.d.ts +53 -0
  3. package/dist/adapters/node-request.d.ts.map +1 -0
  4. package/dist/adapters/node-request.js +165 -0
  5. package/dist/adapters/node-request.js.map +1 -0
  6. package/dist/adapters/node.d.ts +13 -30
  7. package/dist/adapters/node.d.ts.map +1 -1
  8. package/dist/adapters/node.js +55 -101
  9. package/dist/adapters/node.js.map +1 -1
  10. package/dist/advanced.d.ts +18 -0
  11. package/dist/advanced.d.ts.map +1 -0
  12. package/dist/advanced.js +18 -0
  13. package/dist/advanced.js.map +1 -0
  14. package/dist/app-options.d.ts +132 -0
  15. package/dist/app-options.d.ts.map +1 -0
  16. package/dist/app-options.js +2 -0
  17. package/dist/app-options.js.map +1 -0
  18. package/dist/app.d.ts +2 -89
  19. package/dist/app.d.ts.map +1 -1
  20. package/dist/app.js +143 -63
  21. package/dist/app.js.map +1 -1
  22. package/dist/audit.d.ts +5 -5
  23. package/dist/audit.d.ts.map +1 -1
  24. package/dist/audit.js +19 -8
  25. package/dist/audit.js.map +1 -1
  26. package/dist/auth/bearer.d.ts +4 -1
  27. package/dist/auth/bearer.d.ts.map +1 -1
  28. package/dist/auth/bearer.js +19 -4
  29. package/dist/auth/bearer.js.map +1 -1
  30. package/dist/clients.js +1 -1
  31. package/dist/clients.js.map +1 -1
  32. package/dist/deprecated.d.ts +42 -0
  33. package/dist/deprecated.d.ts.map +1 -0
  34. package/dist/deprecated.js +35 -0
  35. package/dist/deprecated.js.map +1 -0
  36. package/dist/{body.d.ts → http/body.d.ts} +27 -6
  37. package/dist/http/body.d.ts.map +1 -0
  38. package/dist/{body.js → http/body.js} +33 -13
  39. package/dist/http/body.js.map +1 -0
  40. package/dist/http/http.d.ts +42 -0
  41. package/dist/http/http.d.ts.map +1 -0
  42. package/dist/http/http.js +100 -0
  43. package/dist/http/http.js.map +1 -0
  44. package/dist/http/origins.d.ts +37 -0
  45. package/dist/http/origins.d.ts.map +1 -0
  46. package/dist/http/origins.js +87 -0
  47. package/dist/http/origins.js.map +1 -0
  48. package/dist/{dispatch.d.ts → mcp/dispatch.d.ts} +1 -1
  49. package/dist/mcp/dispatch.d.ts.map +1 -0
  50. package/dist/mcp/dispatch.js +99 -0
  51. package/dist/mcp/dispatch.js.map +1 -0
  52. package/dist/{jsonrpc.d.ts → mcp/jsonrpc.d.ts} +9 -0
  53. package/dist/mcp/jsonrpc.d.ts.map +1 -0
  54. package/dist/mcp/jsonrpc.js +88 -0
  55. package/dist/mcp/jsonrpc.js.map +1 -0
  56. package/dist/{methods.d.ts → mcp/methods.d.ts} +60 -11
  57. package/dist/mcp/methods.d.ts.map +1 -0
  58. package/dist/mcp/methods.js +84 -0
  59. package/dist/mcp/methods.js.map +1 -0
  60. package/dist/{protocol.d.ts → mcp/protocol.d.ts} +1 -3
  61. package/dist/mcp/protocol.d.ts.map +1 -0
  62. package/dist/{protocol.js → mcp/protocol.js} +1 -3
  63. package/dist/mcp/protocol.js.map +1 -0
  64. package/dist/mcp/registry.d.ts +58 -0
  65. package/dist/mcp/registry.d.ts.map +1 -0
  66. package/dist/mcp/registry.js +81 -0
  67. package/dist/mcp/registry.js.map +1 -0
  68. package/dist/mcp/rpc-handlers.d.ts +11 -0
  69. package/dist/mcp/rpc-handlers.d.ts.map +1 -0
  70. package/dist/mcp/rpc-handlers.js +133 -0
  71. package/dist/mcp/rpc-handlers.js.map +1 -0
  72. package/dist/{tools.d.ts → mcp/tools.d.ts} +20 -1
  73. package/dist/mcp/tools.d.ts.map +1 -0
  74. package/dist/mcp/tools.js +166 -0
  75. package/dist/mcp/tools.js.map +1 -0
  76. package/dist/{validate.d.ts → mcp/validate.d.ts} +18 -21
  77. package/dist/mcp/validate.d.ts.map +1 -0
  78. package/dist/{validate.js → mcp/validate.js} +59 -41
  79. package/dist/mcp/validate.js.map +1 -0
  80. package/dist/oauth/audit.d.ts +20 -0
  81. package/dist/oauth/audit.d.ts.map +1 -0
  82. package/dist/oauth/audit.js +29 -0
  83. package/dist/oauth/audit.js.map +1 -0
  84. package/dist/oauth/cimd/cimd-parse.d.ts +4 -0
  85. package/dist/oauth/cimd/cimd-parse.d.ts.map +1 -0
  86. package/dist/oauth/cimd/cimd-parse.js +70 -0
  87. package/dist/oauth/cimd/cimd-parse.js.map +1 -0
  88. package/dist/oauth/cimd/cimd-ssrf.d.ts +8 -0
  89. package/dist/oauth/cimd/cimd-ssrf.d.ts.map +1 -0
  90. package/dist/oauth/cimd/cimd-ssrf.js +193 -0
  91. package/dist/oauth/cimd/cimd-ssrf.js.map +1 -0
  92. package/dist/oauth/cimd/cimd.d.ts +36 -0
  93. package/dist/oauth/cimd/cimd.d.ts.map +1 -0
  94. package/dist/oauth/cimd/cimd.js +132 -0
  95. package/dist/oauth/cimd/cimd.js.map +1 -0
  96. package/dist/oauth/cimd/types.d.ts +12 -0
  97. package/dist/oauth/cimd/types.d.ts.map +1 -0
  98. package/dist/oauth/cimd/types.js +3 -0
  99. package/dist/oauth/cimd/types.js.map +1 -0
  100. package/dist/oauth/config.d.ts +51 -0
  101. package/dist/oauth/config.d.ts.map +1 -0
  102. package/dist/oauth/config.js +108 -0
  103. package/dist/oauth/config.js.map +1 -0
  104. package/dist/oauth/constants.d.ts +11 -0
  105. package/dist/oauth/constants.d.ts.map +1 -1
  106. package/dist/oauth/constants.js +11 -0
  107. package/dist/oauth/constants.js.map +1 -1
  108. package/dist/oauth/{base64url.d.ts → crypto/base64url.d.ts} +0 -1
  109. package/dist/oauth/crypto/base64url.d.ts.map +1 -0
  110. package/dist/oauth/{base64url.js → crypto/base64url.js} +0 -1
  111. package/dist/oauth/crypto/base64url.js.map +1 -0
  112. package/dist/oauth/crypto/clientauth.d.ts +13 -0
  113. package/dist/oauth/crypto/clientauth.d.ts.map +1 -0
  114. package/dist/oauth/crypto/clientauth.js +195 -0
  115. package/dist/oauth/crypto/clientauth.js.map +1 -0
  116. package/dist/oauth/crypto/codes.d.ts +65 -0
  117. package/dist/oauth/crypto/codes.d.ts.map +1 -0
  118. package/dist/oauth/crypto/codes.js +121 -0
  119. package/dist/oauth/crypto/codes.js.map +1 -0
  120. package/dist/oauth/crypto/hkdf.d.ts +13 -0
  121. package/dist/oauth/crypto/hkdf.d.ts.map +1 -0
  122. package/dist/oauth/crypto/hkdf.js +30 -0
  123. package/dist/oauth/crypto/hkdf.js.map +1 -0
  124. package/dist/oauth/crypto/keycache.d.ts +19 -0
  125. package/dist/oauth/crypto/keycache.d.ts.map +1 -0
  126. package/dist/oauth/crypto/keycache.js +44 -0
  127. package/dist/oauth/crypto/keycache.js.map +1 -0
  128. package/dist/oauth/crypto/pkce.d.ts.map +1 -0
  129. package/dist/oauth/{pkce.js → crypto/pkce.js} +1 -1
  130. package/dist/oauth/crypto/pkce.js.map +1 -0
  131. package/dist/oauth/{sealed.d.ts → crypto/sealed.d.ts} +12 -3
  132. package/dist/oauth/crypto/sealed.d.ts.map +1 -0
  133. package/dist/oauth/{sealed.js → crypto/sealed.js} +22 -14
  134. package/dist/oauth/crypto/sealed.js.map +1 -0
  135. package/dist/oauth/{secrethash.d.ts → crypto/secrethash.d.ts} +14 -0
  136. package/dist/oauth/crypto/secrethash.d.ts.map +1 -0
  137. package/dist/oauth/{secrethash.js → crypto/secrethash.js} +27 -11
  138. package/dist/oauth/crypto/secrethash.js.map +1 -0
  139. package/dist/oauth/deprecated.d.ts +52 -0
  140. package/dist/oauth/deprecated.d.ts.map +1 -0
  141. package/dist/oauth/deprecated.js +49 -0
  142. package/dist/oauth/deprecated.js.map +1 -0
  143. package/dist/oauth/endpoints/authorize.d.ts +3 -0
  144. package/dist/oauth/endpoints/authorize.d.ts.map +1 -0
  145. package/dist/oauth/endpoints/authorize.js +209 -0
  146. package/dist/oauth/endpoints/authorize.js.map +1 -0
  147. package/dist/oauth/endpoints/callback.d.ts +20 -0
  148. package/dist/oauth/endpoints/callback.d.ts.map +1 -0
  149. package/dist/oauth/endpoints/callback.js +31 -0
  150. package/dist/oauth/endpoints/callback.js.map +1 -0
  151. package/dist/oauth/endpoints/consent-page.d.ts +5 -0
  152. package/dist/oauth/endpoints/consent-page.d.ts.map +1 -0
  153. package/dist/oauth/endpoints/consent-page.js +63 -0
  154. package/dist/oauth/endpoints/consent-page.js.map +1 -0
  155. package/dist/oauth/endpoints/consent.d.ts +29 -0
  156. package/dist/oauth/endpoints/consent.d.ts.map +1 -0
  157. package/dist/oauth/endpoints/consent.js +98 -0
  158. package/dist/oauth/endpoints/consent.js.map +1 -0
  159. package/dist/oauth/{metadata.d.ts → endpoints/metadata.d.ts} +8 -2
  160. package/dist/oauth/endpoints/metadata.d.ts.map +1 -0
  161. package/dist/oauth/{metadata.js → endpoints/metadata.js} +10 -4
  162. package/dist/oauth/endpoints/metadata.js.map +1 -0
  163. package/dist/oauth/endpoints/register.d.ts +10 -0
  164. package/dist/oauth/endpoints/register.d.ts.map +1 -0
  165. package/dist/oauth/endpoints/register.js +96 -0
  166. package/dist/oauth/endpoints/register.js.map +1 -0
  167. package/dist/oauth/{revoke.d.ts → endpoints/revoke.d.ts} +1 -1
  168. package/dist/oauth/endpoints/revoke.d.ts.map +1 -0
  169. package/dist/oauth/{revoke.js → endpoints/revoke.js} +8 -18
  170. package/dist/oauth/endpoints/revoke.js.map +1 -0
  171. package/dist/oauth/endpoints/token.d.ts +3 -0
  172. package/dist/oauth/endpoints/token.d.ts.map +1 -0
  173. package/dist/oauth/{token.js → endpoints/token.js} +32 -25
  174. package/dist/oauth/endpoints/token.js.map +1 -0
  175. package/dist/oauth/{wellknown.d.ts → endpoints/wellknown.d.ts} +1 -1
  176. package/dist/oauth/endpoints/wellknown.d.ts.map +1 -0
  177. package/dist/oauth/{wellknown.js → endpoints/wellknown.js} +7 -9
  178. package/dist/oauth/endpoints/wellknown.js.map +1 -0
  179. package/dist/oauth/policy/redirect.d.ts.map +1 -0
  180. package/dist/oauth/policy/redirect.js.map +1 -0
  181. package/dist/oauth/policy/reqbody.d.ts +18 -0
  182. package/dist/oauth/policy/reqbody.d.ts.map +1 -0
  183. package/dist/oauth/policy/reqbody.js +68 -0
  184. package/dist/oauth/policy/reqbody.js.map +1 -0
  185. package/dist/oauth/{resource.d.ts → policy/resource.d.ts} +1 -1
  186. package/dist/oauth/policy/resource.d.ts.map +1 -0
  187. package/dist/oauth/{resource.js → policy/resource.js} +2 -2
  188. package/dist/oauth/policy/resource.js.map +1 -0
  189. package/dist/oauth/{scope.d.ts → policy/scope.d.ts} +2 -2
  190. package/dist/oauth/policy/scope.d.ts.map +1 -0
  191. package/dist/oauth/{scope.js → policy/scope.js} +3 -3
  192. package/dist/oauth/policy/scope.js.map +1 -0
  193. package/dist/oauth/router.d.ts +10 -13
  194. package/dist/oauth/router.d.ts.map +1 -1
  195. package/dist/oauth/router.js +93 -115
  196. package/dist/oauth/router.js.map +1 -1
  197. package/dist/oauth/types.d.ts +99 -45
  198. package/dist/oauth/types.d.ts.map +1 -1
  199. package/dist/oauth/types.js +1 -90
  200. package/dist/oauth/types.js.map +1 -1
  201. package/dist/server.d.ts +12 -12
  202. package/dist/server.d.ts.map +1 -1
  203. package/dist/server.js +9 -8
  204. package/dist/server.js.map +1 -1
  205. package/dist/util/defined.d.ts +11 -0
  206. package/dist/util/defined.d.ts.map +1 -0
  207. package/dist/util/defined.js +15 -0
  208. package/dist/util/defined.js.map +1 -0
  209. package/dist/util/json.d.ts +3 -0
  210. package/dist/util/json.d.ts.map +1 -0
  211. package/dist/util/json.js +3 -0
  212. package/dist/util/json.js.map +1 -0
  213. package/docs/ROADMAP.md +35 -47
  214. package/docs/compatibility.md +84 -0
  215. package/docs/diagrams/mcp-trellis-architecture.html +305 -0
  216. package/docs/guide.md +37 -13
  217. package/docs/reference.md +54 -22
  218. package/docs/security.md +58 -9
  219. package/docs/troubleshooting.md +19 -0
  220. package/package.json +23 -10
  221. package/dist/adapters/origins.d.ts +0 -19
  222. package/dist/adapters/origins.d.ts.map +0 -1
  223. package/dist/adapters/origins.js +0 -52
  224. package/dist/adapters/origins.js.map +0 -1
  225. package/dist/body.d.ts.map +0 -1
  226. package/dist/body.js.map +0 -1
  227. package/dist/dispatch.d.ts.map +0 -1
  228. package/dist/dispatch.js +0 -119
  229. package/dist/dispatch.js.map +0 -1
  230. package/dist/http.d.ts +0 -17
  231. package/dist/http.d.ts.map +0 -1
  232. package/dist/http.js +0 -38
  233. package/dist/http.js.map +0 -1
  234. package/dist/jsonrpc.d.ts.map +0 -1
  235. package/dist/jsonrpc.js +0 -18
  236. package/dist/jsonrpc.js.map +0 -1
  237. package/dist/methods.d.ts.map +0 -1
  238. package/dist/methods.js +0 -130
  239. package/dist/methods.js.map +0 -1
  240. package/dist/oauth/authorize.d.ts +0 -3
  241. package/dist/oauth/authorize.d.ts.map +0 -1
  242. package/dist/oauth/authorize.js +0 -141
  243. package/dist/oauth/authorize.js.map +0 -1
  244. package/dist/oauth/base64url.d.ts.map +0 -1
  245. package/dist/oauth/base64url.js.map +0 -1
  246. package/dist/oauth/clientauth.d.ts +0 -24
  247. package/dist/oauth/clientauth.d.ts.map +0 -1
  248. package/dist/oauth/clientauth.js +0 -119
  249. package/dist/oauth/clientauth.js.map +0 -1
  250. package/dist/oauth/codes.d.ts +0 -30
  251. package/dist/oauth/codes.d.ts.map +0 -1
  252. package/dist/oauth/codes.js +0 -85
  253. package/dist/oauth/codes.js.map +0 -1
  254. package/dist/oauth/consent.d.ts +0 -65
  255. package/dist/oauth/consent.d.ts.map +0 -1
  256. package/dist/oauth/consent.js +0 -173
  257. package/dist/oauth/consent.js.map +0 -1
  258. package/dist/oauth/metadata.d.ts.map +0 -1
  259. package/dist/oauth/metadata.js.map +0 -1
  260. package/dist/oauth/pkce.d.ts.map +0 -1
  261. package/dist/oauth/pkce.js.map +0 -1
  262. package/dist/oauth/redirect.d.ts.map +0 -1
  263. package/dist/oauth/redirect.js.map +0 -1
  264. package/dist/oauth/reqbody.d.ts +0 -10
  265. package/dist/oauth/reqbody.d.ts.map +0 -1
  266. package/dist/oauth/reqbody.js +0 -29
  267. package/dist/oauth/reqbody.js.map +0 -1
  268. package/dist/oauth/resource.d.ts.map +0 -1
  269. package/dist/oauth/resource.js.map +0 -1
  270. package/dist/oauth/revoke.d.ts.map +0 -1
  271. package/dist/oauth/revoke.js.map +0 -1
  272. package/dist/oauth/scope.d.ts.map +0 -1
  273. package/dist/oauth/scope.js.map +0 -1
  274. package/dist/oauth/sealed.d.ts.map +0 -1
  275. package/dist/oauth/sealed.js.map +0 -1
  276. package/dist/oauth/secrethash.d.ts.map +0 -1
  277. package/dist/oauth/secrethash.js.map +0 -1
  278. package/dist/oauth/token.d.ts +0 -3
  279. package/dist/oauth/token.d.ts.map +0 -1
  280. package/dist/oauth/token.js.map +0 -1
  281. package/dist/oauth/wellknown.d.ts.map +0 -1
  282. package/dist/oauth/wellknown.js.map +0 -1
  283. package/dist/protocol.d.ts.map +0 -1
  284. package/dist/protocol.js.map +0 -1
  285. package/dist/registry.d.ts +0 -37
  286. package/dist/registry.d.ts.map +0 -1
  287. package/dist/registry.js +0 -77
  288. package/dist/registry.js.map +0 -1
  289. package/dist/tools.d.ts.map +0 -1
  290. package/dist/tools.js +0 -98
  291. package/dist/tools.js.map +0 -1
  292. package/dist/validate.d.ts.map +0 -1
  293. package/dist/validate.js.map +0 -1
  294. /package/dist/oauth/{pkce.d.ts → crypto/pkce.d.ts} +0 -0
  295. /package/dist/oauth/{redirect.d.ts → policy/redirect.d.ts} +0 -0
  296. /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
- [![CI](https://github.com/amir1824/mcp-trellis/actions/workflows/ci.yml/badge.svg)](https://github.com/amir1824/mcp-trellis/actions/workflows/ci.yml)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
- | | mcp-trellis | Official MCP SDK | `workers-oauth-provider` | `@mcpauth/auth` | Auth0 / Clerk / Authlete |
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
- Prefer the official SDK when you already have a separate AS. Prefer
25
- `workers-oauth-provider` when you want Cloudflare's Workers-only
26
- implementation. Prefer a managed IdP when you'd rather pay than operate one.
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
- Named alternatives in the same problem space:
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
- - **`@mcpauth/auth` / `getmcpauth` / `mcp-auth`** — OAuth for MCP, typically
31
- with a DB or a different runtime/stack assumption. mcp-trellis is the
32
- **zero-dependency** option that ships the MCP handler **and** the OAuth 2.1
33
- AS in one package.
34
- - **`fastmcp-oauth`** — OAuth helpers around FastMCP. mcp-trellis is host-agnostic
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
- ## Install
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 app = createMcpApp({
56
- serverInfo: { name: "demo", version: "1.0.0" },
34
+ export const mcp = createMcpApp<{ userId: string }>({
35
+ serverInfo: { name: "my-saas", version: "1.0.0" },
57
36
  clients: ["claude"],
58
- tools: [
59
- {
60
- name: "echo",
61
- description: "Echo text back",
62
- inputSchema: {
63
- type: "object",
64
- properties: { text: { type: "string" } },
65
- required: ["text"],
66
- },
67
- scope: "mcp",
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
- You return the token's `audience`; **the library rejects tokens minted for a different resource** before any tool runs. Details: [docs/security.md](docs/security.md).
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
- A real `/authorize` walk includes an approval step: a resolved session doesn't redirect straight back with a code, it renders a consent screen first (built in, or your own via `consent`). First-time integrators clicking through by hand should expect an HTML page there, not an immediate redirect — see [Consent](docs/guide.md#consent).
52
+ [![npm](https://img.shields.io/npm/v/mcp-trellis)](https://www.npmjs.com/package/mcp-trellis)
53
+ [![CI](https://github.com/amir1824/mcp-trellis/actions/workflows/ci.yml/badge.svg)](https://github.com/amir1824/mcp-trellis/actions/workflows/ci.yml)
54
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
98
55
 
99
- Smoke-test with `initialize` (public — no Bearer needed):
56
+ ## When should I use this?
100
57
 
101
- ```bash
102
- curl -s http://127.0.0.1:8787/mcp \
103
- -H 'content-type: application/json' \
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
- ```json
108
- {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{}},"serverInfo":{"name":"demo","version":"1.0.0"},"instructions":""}}
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
- ## Clients
85
+ ## Integrate with your app
112
86
 
113
- | Client | Registration | Token endpoint auth | Notes |
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
- Pre-registered clients, DCR enforcement, and `clientStore` wiring: [docs/guide.md#clients](docs/guide.md#clients).
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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"}
@@ -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 { isAllowedOrigin, type OriginAllowlistOptions } from "./origins.js";
6
- export { isAllowedOrigin, type OriginAllowlistOptions };
7
- export type NodeRequestLike = {
8
- method?: string | undefined;
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
- * Prefer `req.body`; else drain the stream (raw `http.createServer`), capped
47
- * at `maxBytes`. Throws `BodyTooLargeError` past the cap without destroying
48
- * the socket — `asNodeHandler` writes 413 first, then destroys after flush.
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;;;GAGG;AAWH,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAE5E,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,CAAC;AAExD,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,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,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;AAEF,MAAM,MAAM,oBAAoB,GAAG,sBAAsB,GAAG;IAC1D;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAQF,0EAA0E;AAC1E,eAAO,MAAM,aAAa,GAAI,KAAK,eAAe,EAAE,UAAS,oBAAyB,KAAG,MAexF,CAAC;AAWF,eAAO,MAAM,YAAY,GAAI,KAAK,eAAe,EAAE,SAAS,mBAAmB,KAAG,OAyBjF,CAAC;AAQF,qFAAqF;AACrF,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,MACkC,CAAC;AAWnF;;;;GAIG;AACH,eAAO,MAAM,YAAY,GACvB,KAAK,eAAe,EACpB,WAAU,MAA+B,KACxC,OAAO,CAAC,OAAO,CAmBjB,CAAC;AAEF,eAAO,MAAM,eAAe,GAAU,KAAK,gBAAgB,EAAE,UAAU,QAAQ,KAAG,OAAO,CAAC,IAAI,CAM7F,CAAC;AAoBF,8DAA8D;AAC9D,eAAO,MAAM,aAAa,GACxB,KAAK;IAAE,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAAE,EACvD,UAAU,oBAAoB,MAqBhB,KAAK,eAAe,EAAE,KAAK,gBAAgB,KAAG,OAAO,CAAC,IAAI,CAqBzE,CAAC"}
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"}