mcp-trellis 1.0.0 → 2.2.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 (301) hide show
  1. package/README.md +65 -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/auth/signed-token.d.ts +26 -0
  31. package/dist/auth/signed-token.d.ts.map +1 -0
  32. package/dist/auth/signed-token.js +96 -0
  33. package/dist/auth/signed-token.js.map +1 -0
  34. package/dist/clients.js +1 -1
  35. package/dist/clients.js.map +1 -1
  36. package/dist/deprecated.d.ts +42 -0
  37. package/dist/deprecated.d.ts.map +1 -0
  38. package/dist/deprecated.js +35 -0
  39. package/dist/deprecated.js.map +1 -0
  40. package/dist/{body.d.ts → http/body.d.ts} +27 -6
  41. package/dist/http/body.d.ts.map +1 -0
  42. package/dist/{body.js → http/body.js} +33 -13
  43. package/dist/http/body.js.map +1 -0
  44. package/dist/http/http.d.ts +42 -0
  45. package/dist/http/http.d.ts.map +1 -0
  46. package/dist/http/http.js +100 -0
  47. package/dist/http/http.js.map +1 -0
  48. package/dist/http/origins.d.ts +37 -0
  49. package/dist/http/origins.d.ts.map +1 -0
  50. package/dist/http/origins.js +87 -0
  51. package/dist/http/origins.js.map +1 -0
  52. package/dist/{dispatch.d.ts → mcp/dispatch.d.ts} +1 -1
  53. package/dist/mcp/dispatch.d.ts.map +1 -0
  54. package/dist/mcp/dispatch.js +99 -0
  55. package/dist/mcp/dispatch.js.map +1 -0
  56. package/dist/{jsonrpc.d.ts → mcp/jsonrpc.d.ts} +9 -0
  57. package/dist/mcp/jsonrpc.d.ts.map +1 -0
  58. package/dist/mcp/jsonrpc.js +88 -0
  59. package/dist/mcp/jsonrpc.js.map +1 -0
  60. package/dist/{methods.d.ts → mcp/methods.d.ts} +60 -11
  61. package/dist/mcp/methods.d.ts.map +1 -0
  62. package/dist/mcp/methods.js +84 -0
  63. package/dist/mcp/methods.js.map +1 -0
  64. package/dist/{protocol.d.ts → mcp/protocol.d.ts} +1 -3
  65. package/dist/mcp/protocol.d.ts.map +1 -0
  66. package/dist/{protocol.js → mcp/protocol.js} +1 -3
  67. package/dist/mcp/protocol.js.map +1 -0
  68. package/dist/mcp/registry.d.ts +58 -0
  69. package/dist/mcp/registry.d.ts.map +1 -0
  70. package/dist/mcp/registry.js +81 -0
  71. package/dist/mcp/registry.js.map +1 -0
  72. package/dist/mcp/rpc-handlers.d.ts +11 -0
  73. package/dist/mcp/rpc-handlers.d.ts.map +1 -0
  74. package/dist/mcp/rpc-handlers.js +133 -0
  75. package/dist/mcp/rpc-handlers.js.map +1 -0
  76. package/dist/{tools.d.ts → mcp/tools.d.ts} +20 -1
  77. package/dist/mcp/tools.d.ts.map +1 -0
  78. package/dist/mcp/tools.js +166 -0
  79. package/dist/mcp/tools.js.map +1 -0
  80. package/dist/{validate.d.ts → mcp/validate.d.ts} +18 -21
  81. package/dist/mcp/validate.d.ts.map +1 -0
  82. package/dist/{validate.js → mcp/validate.js} +59 -41
  83. package/dist/mcp/validate.js.map +1 -0
  84. package/dist/oauth/audit.d.ts +20 -0
  85. package/dist/oauth/audit.d.ts.map +1 -0
  86. package/dist/oauth/audit.js +29 -0
  87. package/dist/oauth/audit.js.map +1 -0
  88. package/dist/oauth/cimd/cimd-parse.d.ts +4 -0
  89. package/dist/oauth/cimd/cimd-parse.d.ts.map +1 -0
  90. package/dist/oauth/cimd/cimd-parse.js +70 -0
  91. package/dist/oauth/cimd/cimd-parse.js.map +1 -0
  92. package/dist/oauth/cimd/cimd-ssrf.d.ts +8 -0
  93. package/dist/oauth/cimd/cimd-ssrf.d.ts.map +1 -0
  94. package/dist/oauth/cimd/cimd-ssrf.js +193 -0
  95. package/dist/oauth/cimd/cimd-ssrf.js.map +1 -0
  96. package/dist/oauth/cimd/cimd.d.ts +36 -0
  97. package/dist/oauth/cimd/cimd.d.ts.map +1 -0
  98. package/dist/oauth/cimd/cimd.js +132 -0
  99. package/dist/oauth/cimd/cimd.js.map +1 -0
  100. package/dist/oauth/cimd/types.d.ts +12 -0
  101. package/dist/oauth/cimd/types.d.ts.map +1 -0
  102. package/dist/oauth/cimd/types.js +3 -0
  103. package/dist/oauth/cimd/types.js.map +1 -0
  104. package/dist/oauth/config.d.ts +51 -0
  105. package/dist/oauth/config.d.ts.map +1 -0
  106. package/dist/oauth/config.js +108 -0
  107. package/dist/oauth/config.js.map +1 -0
  108. package/dist/oauth/constants.d.ts +11 -0
  109. package/dist/oauth/constants.d.ts.map +1 -1
  110. package/dist/oauth/constants.js +11 -0
  111. package/dist/oauth/constants.js.map +1 -1
  112. package/dist/oauth/{base64url.d.ts → crypto/base64url.d.ts} +0 -1
  113. package/dist/oauth/crypto/base64url.d.ts.map +1 -0
  114. package/dist/oauth/{base64url.js → crypto/base64url.js} +0 -1
  115. package/dist/oauth/crypto/base64url.js.map +1 -0
  116. package/dist/oauth/crypto/clientauth.d.ts +13 -0
  117. package/dist/oauth/crypto/clientauth.d.ts.map +1 -0
  118. package/dist/oauth/crypto/clientauth.js +195 -0
  119. package/dist/oauth/crypto/clientauth.js.map +1 -0
  120. package/dist/oauth/crypto/codes.d.ts +65 -0
  121. package/dist/oauth/crypto/codes.d.ts.map +1 -0
  122. package/dist/oauth/crypto/codes.js +121 -0
  123. package/dist/oauth/crypto/codes.js.map +1 -0
  124. package/dist/oauth/crypto/hkdf.d.ts +13 -0
  125. package/dist/oauth/crypto/hkdf.d.ts.map +1 -0
  126. package/dist/oauth/crypto/hkdf.js +30 -0
  127. package/dist/oauth/crypto/hkdf.js.map +1 -0
  128. package/dist/oauth/crypto/keycache.d.ts +19 -0
  129. package/dist/oauth/crypto/keycache.d.ts.map +1 -0
  130. package/dist/oauth/crypto/keycache.js +44 -0
  131. package/dist/oauth/crypto/keycache.js.map +1 -0
  132. package/dist/oauth/crypto/pkce.d.ts.map +1 -0
  133. package/dist/oauth/{pkce.js → crypto/pkce.js} +1 -1
  134. package/dist/oauth/crypto/pkce.js.map +1 -0
  135. package/dist/oauth/{sealed.d.ts → crypto/sealed.d.ts} +12 -3
  136. package/dist/oauth/crypto/sealed.d.ts.map +1 -0
  137. package/dist/oauth/{sealed.js → crypto/sealed.js} +22 -14
  138. package/dist/oauth/crypto/sealed.js.map +1 -0
  139. package/dist/oauth/{secrethash.d.ts → crypto/secrethash.d.ts} +14 -0
  140. package/dist/oauth/crypto/secrethash.d.ts.map +1 -0
  141. package/dist/oauth/{secrethash.js → crypto/secrethash.js} +27 -11
  142. package/dist/oauth/crypto/secrethash.js.map +1 -0
  143. package/dist/oauth/deprecated.d.ts +52 -0
  144. package/dist/oauth/deprecated.d.ts.map +1 -0
  145. package/dist/oauth/deprecated.js +49 -0
  146. package/dist/oauth/deprecated.js.map +1 -0
  147. package/dist/oauth/endpoints/authorize.d.ts +3 -0
  148. package/dist/oauth/endpoints/authorize.d.ts.map +1 -0
  149. package/dist/oauth/endpoints/authorize.js +209 -0
  150. package/dist/oauth/endpoints/authorize.js.map +1 -0
  151. package/dist/oauth/endpoints/callback.d.ts +20 -0
  152. package/dist/oauth/endpoints/callback.d.ts.map +1 -0
  153. package/dist/oauth/endpoints/callback.js +31 -0
  154. package/dist/oauth/endpoints/callback.js.map +1 -0
  155. package/dist/oauth/endpoints/consent-page.d.ts +5 -0
  156. package/dist/oauth/endpoints/consent-page.d.ts.map +1 -0
  157. package/dist/oauth/endpoints/consent-page.js +63 -0
  158. package/dist/oauth/endpoints/consent-page.js.map +1 -0
  159. package/dist/oauth/endpoints/consent.d.ts +29 -0
  160. package/dist/oauth/endpoints/consent.d.ts.map +1 -0
  161. package/dist/oauth/endpoints/consent.js +98 -0
  162. package/dist/oauth/endpoints/consent.js.map +1 -0
  163. package/dist/oauth/{metadata.d.ts → endpoints/metadata.d.ts} +8 -2
  164. package/dist/oauth/endpoints/metadata.d.ts.map +1 -0
  165. package/dist/oauth/{metadata.js → endpoints/metadata.js} +10 -4
  166. package/dist/oauth/endpoints/metadata.js.map +1 -0
  167. package/dist/oauth/endpoints/register.d.ts +10 -0
  168. package/dist/oauth/endpoints/register.d.ts.map +1 -0
  169. package/dist/oauth/endpoints/register.js +96 -0
  170. package/dist/oauth/endpoints/register.js.map +1 -0
  171. package/dist/oauth/{revoke.d.ts → endpoints/revoke.d.ts} +1 -1
  172. package/dist/oauth/endpoints/revoke.d.ts.map +1 -0
  173. package/dist/oauth/{revoke.js → endpoints/revoke.js} +8 -18
  174. package/dist/oauth/endpoints/revoke.js.map +1 -0
  175. package/dist/oauth/endpoints/token.d.ts +3 -0
  176. package/dist/oauth/endpoints/token.d.ts.map +1 -0
  177. package/dist/oauth/{token.js → endpoints/token.js} +32 -25
  178. package/dist/oauth/endpoints/token.js.map +1 -0
  179. package/dist/oauth/{wellknown.d.ts → endpoints/wellknown.d.ts} +1 -1
  180. package/dist/oauth/endpoints/wellknown.d.ts.map +1 -0
  181. package/dist/oauth/{wellknown.js → endpoints/wellknown.js} +7 -9
  182. package/dist/oauth/endpoints/wellknown.js.map +1 -0
  183. package/dist/oauth/policy/redirect.d.ts.map +1 -0
  184. package/dist/oauth/policy/redirect.js.map +1 -0
  185. package/dist/oauth/policy/reqbody.d.ts +18 -0
  186. package/dist/oauth/policy/reqbody.d.ts.map +1 -0
  187. package/dist/oauth/policy/reqbody.js +68 -0
  188. package/dist/oauth/policy/reqbody.js.map +1 -0
  189. package/dist/oauth/{resource.d.ts → policy/resource.d.ts} +1 -1
  190. package/dist/oauth/policy/resource.d.ts.map +1 -0
  191. package/dist/oauth/{resource.js → policy/resource.js} +2 -2
  192. package/dist/oauth/policy/resource.js.map +1 -0
  193. package/dist/oauth/{scope.d.ts → policy/scope.d.ts} +2 -2
  194. package/dist/oauth/policy/scope.d.ts.map +1 -0
  195. package/dist/oauth/{scope.js → policy/scope.js} +3 -3
  196. package/dist/oauth/policy/scope.js.map +1 -0
  197. package/dist/oauth/router.d.ts +10 -13
  198. package/dist/oauth/router.d.ts.map +1 -1
  199. package/dist/oauth/router.js +93 -115
  200. package/dist/oauth/router.js.map +1 -1
  201. package/dist/oauth/types.d.ts +99 -45
  202. package/dist/oauth/types.d.ts.map +1 -1
  203. package/dist/oauth/types.js +1 -90
  204. package/dist/oauth/types.js.map +1 -1
  205. package/dist/server.d.ts +13 -12
  206. package/dist/server.d.ts.map +1 -1
  207. package/dist/server.js +10 -8
  208. package/dist/server.js.map +1 -1
  209. package/dist/util/defined.d.ts +11 -0
  210. package/dist/util/defined.d.ts.map +1 -0
  211. package/dist/util/defined.js +15 -0
  212. package/dist/util/defined.js.map +1 -0
  213. package/dist/util/json.d.ts +3 -0
  214. package/dist/util/json.d.ts.map +1 -0
  215. package/dist/util/json.js +3 -0
  216. package/dist/util/json.js.map +1 -0
  217. package/docs/ROADMAP.md +37 -47
  218. package/docs/compatibility.md +86 -0
  219. package/docs/diagrams/demo-tool-result.png +0 -0
  220. package/docs/diagrams/mcp-trellis-architecture.html +305 -0
  221. package/docs/guide.md +46 -16
  222. package/docs/reference.md +55 -22
  223. package/docs/security.md +58 -9
  224. package/docs/troubleshooting.md +19 -0
  225. package/package.json +27 -10
  226. package/dist/adapters/origins.d.ts +0 -19
  227. package/dist/adapters/origins.d.ts.map +0 -1
  228. package/dist/adapters/origins.js +0 -52
  229. package/dist/adapters/origins.js.map +0 -1
  230. package/dist/body.d.ts.map +0 -1
  231. package/dist/body.js.map +0 -1
  232. package/dist/dispatch.d.ts.map +0 -1
  233. package/dist/dispatch.js +0 -119
  234. package/dist/dispatch.js.map +0 -1
  235. package/dist/http.d.ts +0 -17
  236. package/dist/http.d.ts.map +0 -1
  237. package/dist/http.js +0 -38
  238. package/dist/http.js.map +0 -1
  239. package/dist/jsonrpc.d.ts.map +0 -1
  240. package/dist/jsonrpc.js +0 -18
  241. package/dist/jsonrpc.js.map +0 -1
  242. package/dist/methods.d.ts.map +0 -1
  243. package/dist/methods.js +0 -130
  244. package/dist/methods.js.map +0 -1
  245. package/dist/oauth/authorize.d.ts +0 -3
  246. package/dist/oauth/authorize.d.ts.map +0 -1
  247. package/dist/oauth/authorize.js +0 -141
  248. package/dist/oauth/authorize.js.map +0 -1
  249. package/dist/oauth/base64url.d.ts.map +0 -1
  250. package/dist/oauth/base64url.js.map +0 -1
  251. package/dist/oauth/clientauth.d.ts +0 -24
  252. package/dist/oauth/clientauth.d.ts.map +0 -1
  253. package/dist/oauth/clientauth.js +0 -119
  254. package/dist/oauth/clientauth.js.map +0 -1
  255. package/dist/oauth/codes.d.ts +0 -30
  256. package/dist/oauth/codes.d.ts.map +0 -1
  257. package/dist/oauth/codes.js +0 -85
  258. package/dist/oauth/codes.js.map +0 -1
  259. package/dist/oauth/consent.d.ts +0 -65
  260. package/dist/oauth/consent.d.ts.map +0 -1
  261. package/dist/oauth/consent.js +0 -173
  262. package/dist/oauth/consent.js.map +0 -1
  263. package/dist/oauth/metadata.d.ts.map +0 -1
  264. package/dist/oauth/metadata.js.map +0 -1
  265. package/dist/oauth/pkce.d.ts.map +0 -1
  266. package/dist/oauth/pkce.js.map +0 -1
  267. package/dist/oauth/redirect.d.ts.map +0 -1
  268. package/dist/oauth/redirect.js.map +0 -1
  269. package/dist/oauth/reqbody.d.ts +0 -10
  270. package/dist/oauth/reqbody.d.ts.map +0 -1
  271. package/dist/oauth/reqbody.js +0 -29
  272. package/dist/oauth/reqbody.js.map +0 -1
  273. package/dist/oauth/resource.d.ts.map +0 -1
  274. package/dist/oauth/resource.js.map +0 -1
  275. package/dist/oauth/revoke.d.ts.map +0 -1
  276. package/dist/oauth/revoke.js.map +0 -1
  277. package/dist/oauth/scope.d.ts.map +0 -1
  278. package/dist/oauth/scope.js.map +0 -1
  279. package/dist/oauth/sealed.d.ts.map +0 -1
  280. package/dist/oauth/sealed.js.map +0 -1
  281. package/dist/oauth/secrethash.d.ts.map +0 -1
  282. package/dist/oauth/secrethash.js.map +0 -1
  283. package/dist/oauth/token.d.ts +0 -3
  284. package/dist/oauth/token.d.ts.map +0 -1
  285. package/dist/oauth/token.js.map +0 -1
  286. package/dist/oauth/wellknown.d.ts.map +0 -1
  287. package/dist/oauth/wellknown.js.map +0 -1
  288. package/dist/protocol.d.ts.map +0 -1
  289. package/dist/protocol.js.map +0 -1
  290. package/dist/registry.d.ts +0 -37
  291. package/dist/registry.d.ts.map +0 -1
  292. package/dist/registry.js +0 -77
  293. package/dist/registry.js.map +0 -1
  294. package/dist/tools.d.ts.map +0 -1
  295. package/dist/tools.js +0 -98
  296. package/dist/tools.js.map +0 -1
  297. package/dist/validate.d.ts.map +0 -1
  298. package/dist/validate.js.map +0 -1
  299. /package/dist/oauth/{pkce.d.ts → crypto/pkce.d.ts} +0 -0
  300. /package/dist/oauth/{redirect.d.ts → policy/redirect.d.ts} +0 -0
  301. /package/dist/oauth/{redirect.js → policy/redirect.js} +0 -0
package/README.md CHANGED
@@ -1,122 +1,94 @@
1
1
  # mcp-trellis
2
2
 
3
+ Add authenticated AI connectors to your existing SaaS.
4
+ Reuse your users, login and permissions. Trellis handles MCP + OAuth.
5
+
6
+ ```
7
+ Existing SaaS
8
+ users + login + data
9
+ ↓
10
+ mcp-trellis
11
+ ↓
12
+ Claude / ChatGPT / Gemini
13
+ ```
14
+
15
+ [![npm](https://img.shields.io/npm/v/mcp-trellis)](https://www.npmjs.com/package/mcp-trellis)
3
16
  [![CI](https://github.com/amir1824/mcp-trellis/actions/workflows/ci.yml/badge.svg)](https://github.com/amir1824/mcp-trellis/actions/workflows/ci.yml)
4
17
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
18
 
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.
19
+ ## Demo
9
20
 
10
- ## Why mcp-trellis
21
+ ![Project desk local demo — login, consent, user-scoped tool result](docs/diagrams/demo-tool-result.png)
11
22
 
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.
23
+ ```bash
24
+ npm install mcp-trellis
25
+ ```
13
26
 
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 | ✅ | ❌ | ❌ | ❌ | ❌ |
27
+ ## The 30-second example
23
28
 
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.
29
+ ```ts
30
+ import { createMcpApp, signedTokenAuth } from "mcp-trellis";
31
+ import { session, projectsForUser, codeStore } from "./your-app.js";
32
+
33
+ export const mcp = createMcpApp<{ userId: string }>({
34
+ serverInfo: { name: "my-saas", version: "1.0.0" },
35
+ auth: signedTokenAuth({
36
+ secret: process.env.MCP_SECRET!,
37
+ resolveUser: (req) => session(req),
38
+ loginUrl: (_r, n) => `/login?next=${encodeURIComponent(n)}`,
39
+ codeStore,
40
+ }),
41
+ context: (_r, p) => ({ userId: p!.id }),
42
+ tools: [{ name: "list_my_projects", description: "List projects",
43
+ inputSchema: { type: "object", properties: {} }, scope: "mcp",
44
+ handler: async ({ userId }) => JSON.stringify(await projectsForUser(userId)) }],
45
+ });
46
+ ```
27
47
 
28
- Named alternatives in the same problem space:
48
+ `codeStore` must be a shared single-use store (Redis/KV — see [examples/stores.ts](examples/stores.ts)). Advanced ports (`mintAccessToken`, `verifyToken`, refresh, revoke) remain available when you outgrow the helper. Node ≥20.
29
49
 
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.
50
+ **[View the complete SaaS demo](examples/saas-demo/README.md)** — working login, consent, two users with different projects.
36
51
 
37
- ## Requirements
52
+ Also listed on the Official MCP Registry as **Project desk** (reference remote server; not the SDK) — see [`examples/project-desk`](examples/project-desk/).
38
53
 
39
- - **Node ≥ 20** for Node hosts (global WebCrypto in ESM)
40
- - Or any runtime with WebCrypto + `fetch` (Workers, Deno, Bun)
54
+ ## When should I use this?
41
55
 
42
- ## Install
56
+ - Your TypeScript app already has login and user data, and you want to expose user-scoped tools through remote MCP.
57
+ - You want the MCP handler and OAuth authorization endpoints in one package, with zero runtime dependencies.
58
+ - 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.
43
59
 
44
- ```bash
45
- npm install mcp-trellis
46
- ```
60
+ ## When should I not use this?
47
61
 
48
- ## Quick start
62
+ - You need a managed identity provider, a user database, or a login system built for you.
63
+ - You already have an MCP server and authorization server that meet your needs.
64
+ - You need protocol features or client combinations absent from the [verified support table](docs/compatibility.md).
65
+ - You only need a local stdio tool with no remote authorization flow.
49
66
 
50
- One call mounts the MCP endpoint, the OAuth authorization server, and both discovery documents:
67
+ ## Run a real tool
51
68
 
52
- ```ts
53
- import { createMcpApp } from "mcp-trellis";
54
-
55
- const app = createMcpApp({
56
- serverInfo: { name: "demo", version: "1.0.0" },
57
- 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
- },
90
- });
69
+ The [Project desk demo](examples/saas-demo/README.md) is the canonical example ([`examples/project-desk`](examples/project-desk/) implementation): 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.
91
70
 
92
- export default { fetch: (req: Request) => app.fetch(req) };
93
- ```
71
+ **Live Claude / ChatGPT recordings are still pending.** Automated OAuth tests are not evidence that a current vendor client has connected successfully.
94
72
 
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).
73
+ ## Clients and compatibility
96
74
 
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).
75
+ | Profile | Implemented server flow | Live client evidence |
76
+ |---|---|---|
77
+ | Claude (`claude`) | Dynamic registration, public client, PKCE | Pending |
78
+ | ChatGPT / Codex (`codex`) | Public client, PKCE; configure exact hosted callback as needed | Pending for each product |
79
+ | Gemini Enterprise (`gemini`) | Pre-registered client; secret basic/post; requires `clientStore` | Pending; does not imply every Gemini product |
98
80
 
99
- Smoke-test with `initialize` (public — no Bearer needed):
81
+ 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).
100
82
 
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
- ```
83
+ ## Integrate with your app
106
84
 
107
- ```json
108
- {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{}},"serverInfo":{"name":"demo","version":"1.0.0"},"instructions":""}}
109
- ```
85
+ Prefer `signedTokenAuth` for a one-secret start. For refresh, revoke, or your own JWT/JWKS, implement `resolveUser`, `loginUrl`, `mintAccessToken` and `verifyToken` yourself. 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).
110
86
 
111
- ## Clients
87
+ [SaaS demo](examples/saas-demo/README.md) · [Next.js starter](examples/nextjs-saas/README.md) · [Host recipes](docs/guide.md) · [Security](docs/security.md) · [npm](https://www.npmjs.com/package/mcp-trellis)
112
88
 
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 |
89
+ ## How it compares
118
90
 
119
- Pre-registered clients, DCR enforcement, and `clientStore` wiring: [docs/guide.md#clients](docs/guide.md#clients).
91
+ mcp-trellis is the **vendor-neutral embedded BYO-auth** path: MCP handler and self-hosted OAuth AS in one npm install. Contrast composition stacks such as **xmcp + Scalekit** (or WorkOS / Descope connectors) that split the MCP runtime and the identity product across 2–3 packages. Full comparison table: [docs/ROADMAP.md](docs/ROADMAP.md).
120
92
 
121
93
  ## Architecture
122
94
 
@@ -137,7 +109,7 @@ Host recipes, ports, tools, and multi-tenant: [docs/guide.md](docs/guide.md).
137
109
  | [docs/security.md](docs/security.md) | Protocol promises, threat model, not in scope |
138
110
  | [docs/ROADMAP.md](docs/ROADMAP.md) | What's next |
139
111
 
140
- Examples: [`examples/`](examples/) — HTTP server, Worker, multi-tenant, stores, audit.
112
+ Canonical example: [`examples/project-desk`](examples/project-desk/) ([local docs](examples/saas-demo/)). Recipes: [`examples/`](examples/).
141
113
 
142
114
  ## Contributing
143
115
 
@@ -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"}