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/docs/reference.md CHANGED
@@ -7,9 +7,10 @@ Ports and recipes: [guide.md](guide.md). Security: [security.md](security.md).
7
7
 
8
8
  | Import | What you get |
9
9
  |--------|----------------|
10
- | `mcp-trellis` | `createMcpApp`, client profiles, MCP handler, tool registry, bearer helpers, HTTP utils |
11
- | `mcp-trellis/oauth` | OAuth 2.1 AS router, PKCE, auth codes, client auth, metadata |
10
+ | `mcp-trellis` | `createMcpApp`, client profiles, MCP handler, tool registry, bearer helpers, JSON-RPC helpers |
11
+ | `mcp-trellis/oauth` | OAuth 2.1 AS router, discovery metadata, resource and redirect policy, client-secret hashing |
12
12
  | `mcp-trellis/node` | `asNodeHandler` + `resolveOrigin` for Node `(req, res)` |
13
+ | `mcp-trellis/advanced` | Low-level primitives: PKCE, auth codes, client auth, scope/resource parsing, schema validation, HTTP utils. Less stable — may change in a minor release |
13
14
 
14
15
  `createMcpApp` is the batteries-included layer. `createMcpHandler` and
15
16
  `createOAuthRouter` remain exported and unchanged — reach for them when you want
@@ -24,7 +25,7 @@ With defaults `resourcePath: "/mcp"` and `oauthPath: "/mcp/oauth"`:
24
25
  |------|---------|
25
26
  | `/.well-known/oauth-protected-resource` (+ `/mcp`) | Protected resource metadata |
26
27
  | `/.well-known/oauth-authorization-server` (+ `/mcp`) | Authorization server metadata |
27
- | `/mcp/oauth/register` | Dynamic client registration (unmounted when DCR is off) |
28
+ | `/mcp/oauth/register` | Dynamic client registration (unmounted when DCR is off). `redirect_uris`, if supplied, must be an array of at most 10 entries — a malformed body, a non-object body, a non-array `redirect_uris`, or too many entries all return **400** `invalid_client_metadata` (RFC 7591 §3.2.2) rather than silently falling back to the default callback. `redirect_uris` omitted entirely still falls back as documented below |
28
29
  | `/mcp/oauth/authorize` | Authorization endpoint — GET only. Renders a consent interstitial rather than redirecting directly; see `/consent` below and [security.md](security.md). Once `redirect_uri` is validated, every remaining error redirects to it with `?error=…` (RFC 6749 §4.1.2.1) rather than returning JSON; an invalid `redirect_uri` itself, and an oversized `state`, are the two exceptions and stay direct 400s |
29
30
  | `/mcp/oauth/consent` | POST only. Approves or denies a consent ticket issued by `/authorize` and, on approval, issues the code and redirects to `redirect_uri` |
30
31
  | `/mcp/oauth/token` | Token endpoint. JSON or form body |
@@ -37,7 +38,7 @@ With defaults `resourcePath: "/mcp"` and `oauthPath: "/mcp/oauth"`:
37
38
  | `initialize` | public | Negotiates protocol version; returns `capabilities.tools` |
38
39
  | `ping` | public | Empty result |
39
40
  | `tools/list` | Bearer | Lists registry entries |
40
- | `tools/call` | Bearer + scope | Runs the tool; missing scope → 401 |
41
+ | `tools/call` | Bearer + scope | Runs the tool; no Bearer at all → 401, authenticated but missing scope → 403, `params.name` naming no registered tool → `-32602` (protocol error, not a tool result — see `docs/guide.md`) |
41
42
  | `notifications/*` | public | HTTP 202 empty body |
42
43
 
43
44
  Anything else → JSON-RPC `-32601`. Capabilities advertise **tools only**.
@@ -49,6 +50,7 @@ Anything else → JSON-RPC `-32601`. Capabilities advertise **tools only**.
49
50
  | **202** | Notification (empty body) |
50
51
  | **400** | Parse error, batch array, invalid request, `jsonrpc` not `"2.0"`, unsupported `MCP-Protocol-Version` |
51
52
  | **401** | Missing/invalid Bearer; includes `WWW-Authenticate`; echoes the real request id, not `null` |
53
+ | **403** | Authenticated, but the token's scopes don't include the tool's `scope`; `WWW-Authenticate` carries `error="insufficient_scope"` and `scope="<required>"` (RFC 6750 §3.1) |
52
54
  | **405** | Wrong HTTP verb on MCP |
53
55
  | **413** | Request body over the cap — `/mcp` (1 MiB default), `/token`/`/revoke`/`/register` (64 KiB default) |
54
56
 
@@ -57,7 +59,7 @@ Anything else → JSON-RPC `-32601`. Capabilities advertise **tools only**.
57
59
  | `-32700` | `JSONRPC_PARSE_ERROR` |
58
60
  | `-32600` | `JSONRPC_INVALID_REQUEST` (batches) |
59
61
  | `-32601` | `JSONRPC_METHOD_NOT_FOUND` |
60
- | `-32602` | `JSONRPC_INVALID_PARAMS` |
62
+ | `-32602` | `JSONRPC_INVALID_PARAMS` (unrecognized `tools/call` `params.name`) |
61
63
  | `-32603` | `JSONRPC_INTERNAL_ERROR` |
62
64
  | `-32001` | `JSONRPC_UNAUTHORIZED` |
63
65
 
@@ -72,9 +74,11 @@ Anything else → JSON-RPC `-32601`. Capabilities advertise **tools only**.
72
74
  | `instructions` | no | Returned on `initialize` |
73
75
  | `publicMethods` | no | Default: `initialize`, `ping`, notifications |
74
76
  | `auditTimeoutMs` | no | Max time to wait for `ports.audit` before responding anyway. Default 1000ms — see [security.md](security.md) |
77
+ | `allowedRequestOrigins` | no | Browser `Origin` allowlist. Omitted → reject any present `Origin`; no header → allow; `"*"` opts out. Distinct from Node Host `allowedOrigins` |
75
78
 
76
79
  `ports.audit` fires on tool results and on auth denials (bad token, missing
77
- scope, query-string token), plus the 500 path when a host port throws.
80
+ scope, query-string token, rejected browser Origin as `origin_not_allowed`),
81
+ plus the 500 path when a host port throws.
78
82
  Malformed JSON, bad `jsonrpc`, unsupported `MCP-Protocol-Version`, oversized
79
83
  bodies, and batch requests return JSON-RPC client errors without an audit
80
84
  entry — those are protocol errors, not auth denials.
@@ -91,9 +95,10 @@ entry — those are protocol errors, not auth denials.
91
95
  | `defaultScopes` | full `scopes` | Scopes granted when a client omits `scope` entirely. **Required at construction** once `scopes` has more than one entry — the router throws rather than silently granting everything advertised. Must be a subset of `scopes` |
92
96
  | `tokenEndpointAuthMethods` | `["none"]` | Advertised client auth methods |
93
97
  | `allowUnregisteredClients` | `true` | `false` requires `clientStore`, unmounts DCR, and rejects unknown `client_id`s (`unauthorized_client` / `invalid_client`) |
94
- | `requireRegisteredClients` | `true` | Reject any `client_id` that isn't `clientStore`-resolved or self-sealed via this server's own `/register` (see [security.md](security.md)). DCR stays mounted, unlike `allowUnregisteredClients: false` — a client just can't invent an id out of thin air |
98
+ | `requireRegisteredClients` | `true` | Reject any `client_id` that isn't `clientStore`-resolved or self-sealed via this server's own `/register` (see [security.md](security.md)). DCR stays mounted, unlike `allowUnregisteredClients: false` — a client just can't invent an id out of thin air. Enforced at `/authorize`, and at `/token`'s `refresh_token` grant and `/revoke` (the `authorization_code` grant's `client_id` is already bound into the sealed code at `/authorize` time, so it's covered indirectly there) |
95
99
  | `redirect` | see below | Redirect URI allowlist |
96
100
  | `consent` | see below | Consent/approval policy for `/authorize` |
101
+ | `auditTimeoutMs` | `1000` | Max time to wait for `ports.audit` before responding anyway — same as MCP |
97
102
 
98
103
  **`redirect` (`RedirectAllowlistOptions`):**
99
104
 
@@ -129,7 +134,17 @@ validated against their own bound `redirectUris`.
129
134
  | `allowLoopback` | `true` | Allow loopback redirects — see the narrowed `http:`-only, no-`localhost` behavior in [security.md](security.md) |
130
135
  | `consent` | built-in interstitial | Same as `OAuthRouterOptions.consent` — override the approval page or pre-approve specific client ids |
131
136
  | `requireRegisteredClients` | `true` | Same as `OAuthRouterOptions.requireRegisteredClients` — inventing a public `client_id` is rejected unless it comes from `clientStore` or this server's own `/register` |
132
- | `instructions`, `validateArgs`, `onToolError`, `context`, `audit`, `auditTimeoutMs` | — | Passed through. `audit`/`auditTimeoutMs` are the MCP-side (tool-call) hook — the OAuth side has its own, separate `auth.audit` (see [Ports](guide.md#ports--what-you-implement)) |
137
+ | `instructions`, `validateArgs`, `onToolError`, `context`, `audit`, `auditTimeoutMs`, `allowedRequestOrigins` | — | Passed through to the MCP handler. Top-level `audit` is a unified MCP+OAuth sink (`source`); `auth.audit` overrides OAuth only. `auditTimeoutMs` applies to both |
138
+ | `hideToolsOutsideScope` | `false` | Filter `tools/list` to tools the calling principal's scopes actually satisfy (an unscoped tool, or one with `scope: null`, is always listed). `tools/call` already enforces scope on its own (403 `insufficient_scope`) regardless of this setting — turning it on only changes what's discoverable via `tools/list` |
139
+
140
+ **A tool's `scope` (`ToolDef.scope`):** required scope, `null` for "any authenticated
141
+ principal, deliberately" or omitted for the same at runtime. On a server
142
+ advertising more than one `scopes` entry, `createMcpApp` **refuses to
143
+ construct** if any tool omits `scope` — an omission there means "any
144
+ authenticated principal, including one holding none of the advertised
145
+ scopes," which is rarely the intent once there's more than one scope to
146
+ have gotten wrong. Pass `scope: null` to state the omission on purpose.
147
+ A single-scope server has no such ambiguity and is unaffected.
133
148
 
134
149
  ## Exports
135
150
 
@@ -137,14 +152,15 @@ validated against their own bound `redirectUris`.
137
152
  <summary><code>mcp-trellis</code></summary>
138
153
 
139
154
  - `createMcpApp`, `createMcpHandler`, `createToolRegistry`
140
- - `consoleAudit` — convenience `audit` sink that logs to the console (pass any function for metrics / DB / APM)
141
- - `defineTool`, `apiTool` — typed, validated tool authoring on top of `ToolDef`
155
+ - `consoleAudit` — convenience unified `audit` sink that logs to the console (pass any function for metrics / DB / APM)
156
+ - `UnifiedAuditEntry` — `{ source: "mcp" } & AuditEntry` \| `{ source: "oauth" } & OAuthAuditEntry`
157
+ - `defineTool`, `apiTool` — typed, validated tool authoring on top of `ToolDef`. `apiTool`'s `timeoutMs` (default 30000, or `false` to disable) and `maxResponseBytes` (default 1 MiB) bound the upstream call; either surfaces as `isError: true`, not a thrown exception
142
158
  - `CLIENT_PROFILES`, `DEFAULT_CLIENTS`, `authMethodsFor`, `redirectUrisFor`, `preRegisteredClients`, `hasDynamicClient`
143
- - `parseBearer`, `timingSafeEqual`, `matchesAny`, `wwwAuthenticateHeader`, `rejectQueryToken`
144
- - `validateAgainstSchema`, `JSON_SCHEMA_TYPES`, `SUPPORTED_SCHEMA_KEYWORDS`, `IGNORED_SCHEMA_KEYWORDS`, `unsupportedKeywords`, `missingObjectType`
159
+ - `parseBearer`, `wwwAuthenticateHeader`, `rejectQueryToken`
160
+ - `signedTokenAuth` — one-secret convenience `McpAppAuth` (HMAC access tokens via HKDF; no refresh/revoke). Type `SignedTokenAuthOptions`
145
161
  - `rpcResult`, `rpcError`, JSON-RPC error constants
146
- - `pickProtocolVersion`, `PROTOCOL_VERSIONS`, `DEFAULT_PROTOCOL_VERSION`, `ASSUMED_HEADER_PROTOCOL_VERSION`
147
- - `jsonResponse`, `emptyResponse`, `optionsResponse`, `corsHeaders`, `methodNotAllowed`
162
+ - `PROTOCOL_VERSIONS`, `DEFAULT_PROTOCOL_VERSION`
163
+ - `jsonResponse({ data, status?, headers?, cors? })` — type `JsonResponseInput` exported
148
164
  - Types: `McpApp`, `McpAppOptions`, `McpAppAuth`, `VerifiedToken`, `ClientName`, `ClientProfile`, `McpHandler`, `McpHandlerOptions`, `McpPorts`, `Principal`, `AuditEntry`, `ServerInfo`, `ToolDef`, `ToolHandler`, `ToolResult`, `ToolRegistry`, `JsonSchema`, `StandardSchemaV1`, `DefineToolOptions`, `ApiToolOptions`, `ApiRequest`, `ClientStore`, `RegisteredClient`, `CodeStore`, `MintAccessTokenInput`, `RefreshAccessTokenInput`, `RevokeTokenInput`, `MintedToken`, `OAuthUser`, …
149
165
 
150
166
  </details>
@@ -154,25 +170,42 @@ validated against their own bound `redirectUris`.
154
170
 
155
171
  - `createOAuthRouter`
156
172
  - `authorizationServerMetadata`, `protectedResourceMetadata`, `mcpWwwAuthenticate`
157
- - `canonicalResource`, `resourcesEqual`, `firstResourceError`, `resourceErrorInfo`, `DEFAULT_RESOURCE_PATH`
173
+ - `canonicalResource`, `resourcesEqual`, `DEFAULT_RESOURCE_PATH`
158
174
  - `isAllowedRedirectUri`, `CLAUDE_CALLBACK`
159
- - `issueAuthCode`, `consumeAuthCode`, `newClientId`
160
- - `verifyPkceS256`, `sha256Base64Url`, `randomBase64Url`
161
- - `readClientAuth`, `firstClientAuthError`, `unregisteredClientsAllowed`
162
- - `parseScope`, `formatScope`, `requestedScopes`, `firstScopeError`, `scopeErrorInfo`
163
- - `defaultScopes`, `registeredClientsRequired` — resolve the effective option value, same pattern as `unregisteredClientsAllowed`
175
+ - `isCimdClientId`, `resolveCimdClient`
164
176
  - `hashClientSecret`, `verifyClientSecret` — for `ClientStore.secretHash` (hash once at registration time; the library verifies)
165
- - `buildErrorRedirectUrl` — the RFC 6749 §4.1.2.1 helper `/authorize` uses internally, exported for hosts composing their own authorize flow via [compose the primitives](guide.md#advanced-compose-the-primitives)
166
- - `normalizeConfiguredPath` — strips a trailing slash from a configured path (`resourcePath`, `oauthPath`); `createOAuthRouter` applies this to `resourcePath` internally, exported for hosts composing their own routing
167
177
  - `GRANT_TYPES`, `OAUTH_ERRORS`, `DEFAULT_SCOPE`, `TOKEN_ENDPOINT_AUTH_METHODS`
168
178
  - Types: `OAuthUser`, `MintedToken`, `OAuthPorts`, `OAuthAuditEntry`, `OAuthRouterOptions`, `OAuthErrorInfo`, `AuthCodeRecord`, `CodeStore`, `ClientStore`, `RegisteredClient`, `ClientAssertion`, `ClientAuth`, `TokenEndpointAuthMethod`, `MintAccessTokenInput`, `RefreshAccessTokenInput`, `RevokeTokenInput`, `ConsentOptions`, `ConsentRequest`
169
179
 
170
180
  </details>
171
181
 
182
+ <details>
183
+ <summary><code>mcp-trellis/advanced</code></summary>
184
+
185
+ Building blocks for hosts assembling their own transport or OAuth flow. Less stable than the entries above.
186
+
187
+ - `timingSafeEqual`, `matchesAny`
188
+ - `validateAgainstSchema`, `JSON_SCHEMA_TYPES`, `SUPPORTED_SCHEMA_KEYWORDS`, `IGNORED_SCHEMA_KEYWORDS`, `unsupportedKeywords`, `missingObjectType`
189
+ - `pickProtocolVersion`, `ASSUMED_HEADER_PROTOCOL_VERSION`
190
+ - `emptyResponse`, `optionsResponse`, `corsHeaders`, `methodNotAllowed`
191
+ - `issueAuthCode`, `consumeAuthCode` (pass `codeStore`; omitting it is deprecated), `newClientId`
192
+ - `verifyPkceS256`, `sha256Base64Url`, `randomBase64Url`
193
+ - `readClientAuth`, `firstClientAuthError`
194
+ - `firstResourceError`, `resourceErrorInfo`, `normalizeConfiguredPath`
195
+ - `parseScope`, `formatScope`, `requestedScopes`, `firstScopeError`, `scopeErrorInfo`
196
+ - `advertisedScopes`, `defaultScopes`, `registeredClientsRequired`, `unregisteredClientsAllowed` — resolve the effective option value
197
+ - `buildErrorRedirectUrl` — the RFC 6749 §4.1.2.1 error-redirect builder `/authorize` uses
198
+ - Types: `AuthCodeRecord`, `CodeStore`, `ClientAuth`
199
+
200
+ **Deprecated in 2.1:** every symbol above is still importable from `mcp-trellis` or `mcp-trellis/oauth`, where it used to live, as a `@deprecated` alias. Those aliases are removed in 3.0.
201
+
202
+ </details>
203
+
172
204
  <details>
173
205
  <summary><code>mcp-trellis/node</code></summary>
174
206
 
175
207
  - `asNodeHandler`, `resolveOrigin`, `isAllowedOrigin`, `toWebRequest`, `sendWebResponse`, `readNodeBody`
176
208
  - Types: `NodeRequestLike`, `NodeResponseLike`, `ToWebRequestOptions`, `ResolveOriginOptions`, `AsNodeHandlerOptions`, `OriginAllowlistOptions`
209
+ - `InvalidOriginError` — thrown by `resolveOrigin` for a Host/`X-Forwarded-*` header that can't safely become an origin (missing, carrying a path/query/fragment/credentials, malformed, or a non-`http(s)` scheme). `asNodeHandler` answers it with **400**, not the generic 500 it gives an actual unexpected failure
177
210
 
178
211
  </details>
package/docs/security.md CHANGED
@@ -11,7 +11,7 @@ Ports and audience wiring: [guide.md](guide.md). Roadmap: [ROADMAP.md](ROADMAP.m
11
11
  - `jsonrpc` must be exactly `"2.0"` on every `/mcp` request → `-32600` otherwise, echoing the request id when one was present
12
12
  - `MCP-Protocol-Version` header validated on every request past `initialize` (which is what negotiates it) — unsupported → 400; missing → assumed `2025-03-26` per the spec's own backwards-compatibility rule
13
13
  - Query-string tokens rejected (`token` and RFC 6750 `access_token`)
14
- - `tools/call` checks `principal.scopes` (`*` = all), and the OAuth grant that produced them is validated at `authorize`
14
+ - `tools/call` checks `principal.scopes` (`*` = all — a wildcard that passes **every** tool scope check; reserve it for trusted internal principals and never mint it for an external connector), and the OAuth grant that produced them is validated at `authorize`
15
15
  - With `createMcpApp`, tokens whose audience is not this server's canonical resource are rejected by the library
16
16
  - **`fetch` never rejects.** If a host port you supply (`authenticate`, `context`, `resolveUser`, `mintAccessToken`, `clientStore`, …) throws, `createMcpHandler`, `createOAuthRouter`, and `createMcpApp` all catch it and return a real `500` (`-32603` for MCP, `server_error` for OAuth) instead of an unhandled rejection — whose shape and whether it even reaches the client varies by runtime. `asNodeHandler` catches independently too, since on raw `http.createServer` a rejection there leaves the socket open with no response ever sent.
17
17
 
@@ -21,30 +21,79 @@ This is a **library** threat model, not a third-party audit badge.
21
21
 
22
22
  | Risk | Mitigation |
23
23
  |------|------------|
24
- | Attacker-driven cross-site authorization (a logged-in user visits an attacker page and is silently walked through `/authorize`, catching the code on an attacker-controlled `redirect_uri`) | **`/authorize` never issues a code directly.** A resolved session renders a consent interstitial (built in, hardened — CSP, `X-Frame-Options: DENY`, `Referrer-Policy: no-referrer`, escaped reflection — or your own via `consent.render`) and only issues a code after an explicit `POST ${oauthPath}/consent` approval. The approval ticket is sealed (AES-GCM, HKDF-derived per-type key, type bound in as AEAD data — see `sealed.ts`), single-use, 5-minute TTL, and re-checked against the resolved user at redemption; it can never be redeemed as an auth code or vice versa. PKCE alone does **not** defend against this — it proves possession of the verifier, not identity of a legitimate client — so this is a distinct control, not a restatement of PKCE. `preApprovedClientIds` skips the screen only for ids `clientStore` actually resolves; a DCR or self-invented id is never trusted to skip it. |
24
+ | Attacker-driven cross-site authorization (a logged-in user visits an attacker page and is silently walked through `/authorize`, catching the code on an attacker-controlled `redirect_uri`) | **`/authorize` never issues a code directly.** A resolved session renders a consent interstitial (built in, hardened — CSP, `X-Frame-Options: DENY`, `Referrer-Policy: no-referrer`, escaped reflection — or your own via `consent.render`) and only issues a code after an explicit `POST ${oauthPath}/consent` approval. The approval ticket is sealed (AES-GCM, HKDF-derived per-type key, type bound in as AEAD data — see `sealed.ts`), single-use, 5-minute TTL, and re-checked against the resolved user at redemption; it can never be redeemed as an auth code or vice versa. PKCE alone does **not** defend against this — it proves possession of the verifier, not identity of a legitimate client — so this is a distinct control, not a restatement of PKCE. `preApprovedClientIds` skips the screen only for ids `clientStore` actually resolves; a DCR or self-invented id is never trusted to skip it. The built-in interstitial's CSP scopes `form-action` to `'self'` plus the validated `redirect_uri`'s own origin — Chromium enforces `form-action` across the redirect the approval POST produces, not just its immediate target, so a custom `consent.render` that sets its own CSP must do the same or the callback to the client's own origin gets silently blocked. |
25
25
  | Confused deputy (token usable at the wrong MCP) | RFC 8707: `resource` required on authorize + token; bound into the auth code; must equal this AS's canonical resource (`canonicalResource(origin, resourcePath)`). Passed into `mintAccessToken` / `refreshAccessToken` so you can embed `aud`. |
26
26
  | Refresh token replayed at another resource on the same AS | Library cannot inspect opaque refresh tokens — **`refreshAccessToken` MUST reject tokens not originally issued for the given `resource`** |
27
27
  | Audience not enforced at the RS | **`createMcpApp` enforces this** — `verifyToken` returns the token's `audience` and the library rejects any mismatch against `canonicalResource(origin, resourcePath)`. With bare `createMcpHandler`, the check is yours. |
28
28
  | Open redirect after login | Your login page must validate `next` is same-origin before redirecting |
29
29
  | Auth-code replay across instances | Pass a shared `codeStore`; in-memory `jti` (the auth code's single-use id) map is process-local only |
30
30
  | Origin spoofing on Node | Pass explicit `origin`, or `trustProxy: true` only behind a proxy that strips client `X-Forwarded-*`. Host-derived origin **requires** non-empty `allowedOrigins` on `asNodeHandler` (`["*"]` to opt out loudly) |
31
+ | A Host/`X-Forwarded-*` header carrying a path, query, fragment, or embedded credentials (`evil.test/../trusted.test`, `user:pass@evil.test`) building an origin string that merely looks well-formed | `resolveOrigin` always normalizes and validates through `URL`, rejecting any of those (plus a non-`http(s)` `X-Forwarded-Proto`) with `InvalidOriginError` — `asNodeHandler` answers it **400**, not the generic 500 an actual unexpected failure gets |
32
+ | Browser `Origin` on MCP (DNS rebinding / CSRF) | `allowedRequestOrigins` on `createMcpHandler` / `createMcpApp`. No `Origin` header → allowed (native clients). Omitted allowlist → **reject** any present `Origin`. `"*"` opts out. Distinct from Node Host `allowedOrigins` |
31
33
  | Scope escalation at authorize | Requested `scope` is validated against the advertised `scopes` and rejected with `invalid_scope`; the grant is bound into the auth code and handed to `mintAccessToken` |
34
+ | An unscoped tool silently callable by any authenticated principal, including one holding none of the advertised scopes — the gap `defaultScopes: []` alone doesn't close, since that only governs the token grant, not per-tool enforcement | `createMcpApp` refuses to construct once `scopes` has more than one entry if any tool omits `ToolDef.scope` — pass `scope: null` to state the omission on purpose. A single-scope server has no such ambiguity and is unaffected. `tools/call` itself always enforces whatever `scope` a tool does declare, independent of this guard (403 `insufficient_scope`) |
35
+ | Scope reduction lost on refresh | Optional `scope` on the refresh token request is validated against advertised scopes and forwarded on `RefreshAccessTokenInput.scope`. **`refreshAccessToken` MUST enforce ⊆ originally granted scopes** (opaque RT — same pattern as `resource`). The token response omits `scope` when neither the port nor the grant supplies one (RFC 6749 §5.1) — clients must not assume the field is always present |
32
36
  | Stolen access or refresh token | Host `revokeToken` (denylist or equivalent) that `verifyToken` and `refreshAccessToken` consult. Unknown / wrong-client tokens MUST no-op, not throw. Well-formed authenticated revoke is 200 even if the token is unknown |
33
37
  | Stolen confidential-client secret | `clientStore.verifySecret` owns comparison — store hashes, not plaintext. The library never sees or persists credentials. **Prefer `clientStore.secretHash`** instead: return a stored hash from `hashClientSecret` and the library does the comparison with the same constant-time primitive it uses everywhere else, rather than a hand-rolled `stored === presented` in your own `verifySecret` being a timing oracle with no warning |
34
- | An unauthenticated caller enumerating which `client_id`s a locked-down server knows, via distinguishable `/token`/`/revoke` error text or response timing | Every client-auth failure at `/token` and `/revoke` collapses to one `invalid_client` / "client authentication failed" — previously up to five distinguishable messages named exactly why (wrong method, missing secret, misconfigured store, unknown id). An unknown `client_id` on a locked-down server (`allowUnregisteredClients: false`) also runs a fixed-cost dummy comparison, so the crypto-verification cost itself can't distinguish "unknown" from "known, wrong secret." `/authorize`'s `unauthorized_client` is unchanged and stays descriptive — a human debugging a browser flow, not a bare API response, and a locked-down server already announces the same fact by omitting `registration_endpoint` |
38
+ | An unauthenticated caller enumerating which `client_id`s a locked-down server knows, via distinguishable `/token`/`/revoke` error text or response timing | Every client-auth failure at `/token` and `/revoke` collapses to one `invalid_client` / "client authentication failed" — previously up to five distinguishable messages named exactly why (wrong method, missing secret, misconfigured store, unknown id). Every rejection path in `firstClientAuthError` for a client_id that *is* known — wrong auth method, no secret presented, wrong secret, no `secretHash`/`verifySecret` on file — runs the same fixed-cost dummy or real comparison before returning, so response timing alone can't tell "known client, wrong method" apart from "known client, wrong secret." An unknown `client_id` on a locked-down server (`allowUnregisteredClients: false`) pays the identical fixed cost, so the crypto-verification cost itself can't distinguish "unknown" from "known, wrong secret" either. `/authorize`'s `unauthorized_client` is unchanged and stays descriptive — a human debugging a browser flow, not a bare API response, and a locked-down server already announces the same fact by omitting `registration_endpoint` |
35
39
  | Auth codes readable by anyone who sees one — browser history, `Referer`, proxy logs | Auth codes are sealed (AES-GCM, `sealed.ts`), not signed-only. Legacy v1 (HMAC-signed, not encrypted) codes are **rejected** since 1.0 — complete any rolling deploy before upgrading past 0.4.x |
36
40
  | An upstream API's error body leaking into the model or the audit log via `apiTool` | Default is status-only — the upstream response body is never forwarded on a non-2xx result, since it's a return value `onToolError` redaction never sees. Use `onError` for (a redacted version of) the body |
37
41
  | `/authorize` errors delivered as a bare JSON body a connector can't act on | RFC 6749 §4.1.2.1: once `redirect_uri` is validated, every remaining error (`unsupported_response_type`, PKCE, `invalid_scope`, `invalid_target`, …) redirects to the client's own callback with `?error=…&state=…` instead. `redirect_uri` validity itself, and an oversized `state`, stay direct JSON — the URI is what's in question in the first case, and reflecting an oversized `state` into its own error redirect would recreate the DoS the length cap exists to prevent |
38
- | DCR-issued `client_id` reused with a substituted `redirect_uri` | `/register` no longer returns a bare random id — the `client_id` **is** a sealed, self-verifying assertion of the exact `redirect_uris` it registered (`sealed.ts`, type `"client"`), with zero server-side storage. `/authorize` unseals it and binds the request to that list, the same protection a stored registration would give. This does not require the *identity* of the client to be verified — an attacker can still call `/register` and mint their own sealed id — so it stops URI substitution on a legitimate connector's id, not impersonation; the consent step above is what stops impersonation. `requireRegisteredClients` defaults to **true** since 1.0 (reject any `client_id` that isn't `clientStore`-resolved or self-sealed); set `false` only to accept invented public ids. The IETF's Client ID Metadata Document (CIMD) draft is the next step past this |
42
+ | DCR-issued `client_id` reused with a substituted `redirect_uri` | `/register` no longer returns a bare random id — the `client_id` **is** a sealed, self-verifying assertion of the exact `redirect_uris` it registered (`sealed.ts`, type `"client"`), with zero server-side storage. `/authorize` unseals it and binds the request to that list, the same protection a stored registration would give. This does not require the *identity* of the client to be verified — an attacker can still call `/register` and mint their own sealed id — so it stops URI substitution on a legitimate connector's id, not impersonation; the consent step above is what stops impersonation. `requireRegisteredClients` defaults to **true** since 1.0 (reject any `client_id` that isn't `clientStore`-resolved or self-sealed); set `false` only to accept invented public ids. Enforced at `/authorize`, and — since 1.2.0 — at `/token`'s `refresh_token` grant and at `/revoke` too, closing a gap where an invented `client_id` that never went through `/authorize` could still authenticate on either (the `authorization_code` grant was never actually exposed by this: a redeemable code's `client_id` is bound into its sealed payload at `/authorize` time, where the check already ran). Opt into CIMD with `cimd: true` for URL-shaped client_ids (off by default) |
43
+ | CIMD fetch as SSRF proxy (attacker supplies `https://…` `client_id`) | **Off by default** (`cimd: true` to enable). When on: HTTPS-only fetch, private/loopback/link-local/NAT64/`::ffff:` blocked (incl. bracketed IPv6 **and full-form mapped encodings DNS may return**, e.g. `0:0:0:0:0:ffff:7f00:1`), DNS lookup required before fetch and re-checked after the body, unparseable IPv6 lookup results fail closed, 5 KB stream-capped body / 10 s per hop covering the body read / 3 same-origin redirects (cross-origin redirects rejected), public clients only, `http:` redirect_uris loopback-only (`127.0.0.1` / `[::1]`, never `localhost`). Also blocked: `192.0.0/24`, `198.18/15`, multicast/reserved `224/4`–`240/4`, IPv6 multicast `ff00::/8`, 6to4 `2002::/16`, IPv4-compatible `::a.b.c.d` (and full-form private embeds). `/token` and `/revoke` **re-resolve** CIMD — never accept URL-shaped ids by shape alone; pass `cimdCache` (strongly recommended) or every refresh/revoke re-fetches the document, and a failed re-resolve is audited as `cimd_resolve_failed`. **Ceiling:** TCP connect is not pinned to the looked-up addresses (zero-dep Web `fetch`); classic DNS rebinding between lookup and connect remains possible until a custom dispatcher pins the IP — do not treat CIMD as production-complete SSRF defense until then |
39
44
  | Naming only confidential clients doesn't actually block public ones | `clients: ["gemini"]` (or `allowUnregisteredClients: false`) is **enforced**: DCR unmounted, dropped from AS metadata, unresolved `client_id` rejected with `unauthorized_client` at `authorize` and `invalid_client` at `token` and `revoke` |
40
45
  | DCR / token-endpoint abuse | Edge or reverse-proxy rate limiting on `/register` and `/token`, **plus** a library-enforced request body cap (see "Also designed in") — the library does not rate-limit by request count, but an unbounded body was previously a memory-exhaustion vector on its own regardless of rate |
41
- | A weak or copy-pasted `codeSecret` (it can forge an auth code — or, since 0.3.0, a consent ticket or DCR client assertion — for any userId/scope/resource) | Validated at the point of use: a string form throws at `createOAuthRouter` construction, a function form on every call. Minimum 32 characters, and the exact literals this package's own docs and examples publish are denylisted by name — copy-paste is the realistic failure mode. This only catches the obvious cases; a leaked or brute-forceable 32+ character secret is still a full bypass, same as before |
46
+ | A weak or copy-pasted `codeSecret` (it can forge an auth code — or, since 0.3.0, a consent ticket or DCR client assertion — for any userId/scope/resource) | Validated at the point of use: a string or array form throws at `createOAuthRouter` construction (every array entry checked, and the array itself must be non-empty), a function form on every call. Minimum 32 characters per entry, and the exact literals this package's own docs and examples have published are denylisted by name, as is any secret containing the examples' `do-not-reuse` marker — copy-paste is the realistic failure mode. The examples themselves read `MCP_SECRET` (or `OAUTH_CODE_SECRET` for the advanced multi-secret recipes) from the environment and ship no usable default, and a test fails if any `codeSecret` literal in `examples/`, `README.md` or `docs/` is one the library would accept. This only catches the obvious cases; a leaked or brute-forceable 32+ character secret is still a full bypass, same as before |
42
47
  | An operator unable to diagnose *why* a client was rejected, once `/token`/`/revoke` collapsed every failure to one generic `invalid_client` (see the enumeration-oracle row above) | `OAuthPorts.audit` (and `McpAppAuth.audit` on `createMcpApp`) — opt-in, sees the real reason (`"client requires client_secret_basic, got none"`, a rejected `codeSecret`'s actual message, …) that never reaches the caller. Same "never fails the request" guarantee as the MCP-side `audit` port |
43
- | A hanging `ports.audit` sink stalling every audited response indefinitely | `McpHandlerOptions.auditTimeoutMs` (default 1000ms) races the audit call against a timeout; "can't fail a request" already held, this adds "can't delay one" past a bound. A slow sink keeps running in the background and its own internal `catch` means it can never surface as an unhandled rejection later |
48
+ | A hanging `ports.audit` sink stalling every audited response indefinitely | `auditTimeoutMs` (default 1000ms) on both `McpHandlerOptions` and `OAuthRouterOptions` races the audit call against a timeout; "can't fail a request" already held, this adds "can't delay one" past a bound. A slow sink keeps running in the background and its own internal `catch` means it can never surface as an unhandled rejection later |
44
49
 
45
- **Also designed in:** PKCE S256 (timing-safe, length-capped); auth codes, consent tickets, and DCR client assertions are all AES-GCM sealed with domain-separated keys (`sealed.ts`) so none of the three token types can be presented as another, and each binds `userId`/`clientId`/`redirectUri`/challenge/`resource` as applicable; redirect allowlist + DCR filter; a `redirect_uri` carrying a fragment or embedded credentials is rejected outright; no query-string tokens (`token` and `access_token`); honest `grant_types_supported`; `/token` and `/revoke` omit CORS `*` and both accept JSON or form bodies; request bodies are capped (1 MiB on `/mcp`, 64 KiB on `/token`/`/revoke`/`/register`) and a chunked body with no declared length is aborted mid-stream past the cap, not buffered first; audit fires on auth/scope denial as well as tool results; 401 responses echo the real JSON-RPC request id instead of hardcoding `null`; `createOAuthRouter` refuses to construct if `oauthPath` and `resourcePath` would collide, or if `resourcePath` starts with `/.well-known`; `resourcePath` is normalized once at construction (`normalizeConfiguredPath`) so a trailing slash can't make `/authorize`, `/token`, and route matching disagree with each other about what the canonical resource is.
50
+ **Also designed in:** PKCE S256 (timing-safe, length-capped; `code_challenge` must be a 43-character base64url digest at `/authorize`); auth codes, consent tickets, and DCR client assertions are all AES-GCM sealed with domain-separated keys (`sealed.ts`) so none of the three token types can be presented as another, and each binds `userId`/`clientId`/`redirectUri`/challenge/`resource` as applicable; redirect allowlist + DCR filter; a `redirect_uri` carrying a fragment or embedded credentials is rejected outright; no query-string tokens (`token` and `access_token`); honest `grant_types_supported`; `/token` and `/revoke` omit CORS `*` and both accept JSON or form bodies; request bodies are capped (1 MiB on `/mcp`, 64 KiB on `/token`/`/revoke`/`/register`) and a chunked body with no declared length is aborted mid-stream past the cap, not buffered first; audit fires on auth/scope denial as well as tool results; a scope-missing `tools/call` from an authenticated principal is **403** `insufficient_scope` (RFC 6750 §3.1), not a 401 that would just send the client back through the same authorize flow forever; 401/403 responses echo the real JSON-RPC request id instead of hardcoding `null`; `createOAuthRouter` refuses to construct if `oauthPath` and `resourcePath` would collide, or if `resourcePath` starts with `/.well-known`; `resourcePath` is normalized once at construction (`normalizeConfiguredPath`) so a trailing slash can't make `/authorize`, `/token`, and route matching disagree with each other about what the canonical resource is.
46
51
 
47
- **Known limits:** in-memory jti map is process-local (multi-instance needs `codeStore`); the loopback redirect allowlist permits any path or port on `127.0.0.1`/`[::1]` over `http:` only — `localhost` and `https:` loopback are deliberately not accepted (RFC 8252 §8.3: `localhost` is DNS-resolvable, not a loopback guarantee, and native-app loopback redirects are `http:` only); DCR binds a client to its own `redirect_uris` via a sealed assertion but does not verify the *registrant's identity* until CIMD, so an attacker can still self-register (see the DCR row above).
52
+ **Known limits:** in-memory jti map is process-local (multi-instance needs `codeStore`); the loopback redirect allowlist permits any path or port on `127.0.0.1`/`[::1]` over `http:` only — `localhost` and `https:` loopback are deliberately not accepted (RFC 8252 §8.3: `localhost` is DNS-resolvable, not a loopback guarantee, and native-app loopback redirects are `http:` only); DCR binds a client to its own `redirect_uris` via a sealed assertion but does not verify the *registrant's identity* (opt into CIMD with `cimd: true`); CIMD DNS checks do not pin the TCP connect (see CIMD SSRF row).
53
+
54
+ ## Rotating codeSecret
55
+
56
+ `codeSecret` is the one secret behind auth codes, consent tickets, DCR
57
+ client assertions, and (when `clientStore.secretHash` is used)
58
+ confidential-client secret hashes. Before 1.2.0 it was a single string —
59
+ rotating it invalidated every DCR-issued `client_id` still in a connector's
60
+ hands and every stored `secretHash`, with no way to phase the change in.
61
+
62
+ Since 1.2.0, `codeSecret` also accepts an array:
63
+
64
+ ```ts
65
+ codeSecret: [newSecret, oldSecret],
66
+ ```
67
+
68
+ - The **first** entry is the one used to seal new auth codes, consent
69
+ tickets, and DCR client assertions, and to hash a client secret passed to
70
+ `hashClientSecret`.
71
+ - **Every** entry is tried, in order, when unsealing or verifying existing
72
+ material — so a DCR `client_id` sealed under `oldSecret`, or a
73
+ `secretHash` computed with it, keeps working.
74
+
75
+ To rotate:
76
+
77
+ 1. Deploy with `codeSecret: [newSecret, oldSecret]`.
78
+ 2. Watch `ports.audit` for `{ event: "legacy_code_secret_used", reason }` —
79
+ it fires whenever anything but the first entry is the one that actually
80
+ verified (a `client` assertion, `code`, `consent` ticket, or
81
+ `client_secret_hash`, named in `reason`). Once it stops firing for a
82
+ given `reason`, nothing still depends on `oldSecret` for that purpose.
83
+ 3. Drop `oldSecret`: `codeSecret: newSecret` (or `[newSecret]`).
84
+
85
+ A DCR `client_id` is only as durable as the key it was sealed with — once
86
+ you drop a key, every `client_id` sealed under it (and only it) stops
87
+ authorizing, and the connector must re-register. `client_secret_hash`
88
+ entries you control the storage for: re-hash them under the new primary key
89
+ (`hashClientSecret(secret, newSecret)`) before dropping the old one, rather
90
+ than waiting for the audit event, if you want zero downtime for confidential
91
+ clients. Auth codes and consent tickets are short-lived (10 and 5 minutes
92
+ respectively) and need no such care — anything issued before rotation has
93
+ long expired by the time you'd reasonably drop a key.
94
+
95
+ `hashClientSecret` itself is unchanged: call it once, at registration time,
96
+ with whichever `codeSecret` value is currently primary.
48
97
 
49
98
  ## Not in scope
50
99
 
@@ -60,4 +109,4 @@ Explicitly **out** of this package (do not expect parity with full MCP hosts or
60
109
  - **No rate limiting** — put quotas on `/register` and `/token` at the edge or reverse proxy
61
110
  - **No paid external security audit** claimed here — see Threat model above
62
111
 
63
- See [ROADMAP.md](ROADMAP.md) for what is next — CIMD and the `2026-07-28` stateless core are the two open items.
112
+ See [ROADMAP.md](ROADMAP.md) for what is next — the `2026-07-28` stateless core is the open protocol item; CIMD is shipped.
@@ -0,0 +1,19 @@
1
+ # Connector troubleshooting
2
+
3
+ | Symptom | Check | Next step |
4
+ |---|---|---|
5
+ | Client cannot discover auth | Is `/.well-known/oauth-protected-resource/mcp` reachable on the public origin? | Route discovery paths and `/mcp/oauth/*` through the same proxy as `/mcp`. |
6
+ | Discovery advertises localhost | Node adapter origin differs from public URL | Set the exact public HTTPS origin; restart the app. |
7
+ | `invalid_client` | Missing registration, wrong configured profile, or missing `clientStore` | Use DCR for public clients or resolve the pre-registered client; keep registration enforcement on. |
8
+ | Callback rejected | Callback is not an exact allowlisted URI | Add the callback shown by the client to `extraRedirectUris`; do not allow arbitrary hosts. |
9
+ | Browser shows an approval page | A login session exists but consent is still required | Approve in the browser. Do not expect `/authorize` to redirect immediately. |
10
+ | Login loops | Cookie domain/path/Secure flags do not match public host | Inspect your app's session handling and use a consistent origin. |
11
+ | `invalid_grant` | Expired/replayed code, wrong verifier or resource | Start a fresh authorization flow. Keep verifier and resource consistent. |
12
+ | Tool request returns 401 | Token expired, verification failed, or audience mismatch | Verify signature/expiry in app auth and return the requested MCP audience. |
13
+ | Tool request returns 403 | Tool scope missing | Grant and return the required scopes from token verification. |
14
+ | Wrong user's data | Data query ignores authenticated identity | Filter by verified principal and tenant; never trust a tool argument for ownership. |
15
+ | Switching login does not switch connector user | Connector retains its previously issued token | Disconnect and reauthorize with the intended account. |
16
+ | Initialization works but tools fail | Initialize is public | Test authenticated `tools/call`; initialize alone proves no auth flow. |
17
+ | Client requests an unsupported protocol | Package supports the revisions in `src/mcp/protocol.ts` | Record negotiation; do not claim a live pass from a successful HTTP status alone. |
18
+
19
+ See [security responsibilities](security.md) before replacing production token verification with example code. Share sanitized errors in an issue, never access tokens or session cookies.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcp-trellis",
3
- "version": "1.0.0",
4
- "description": "Zero-dependency MCP server + OAuth 2.1 authorization server in one package, for Claude, Gemini, and Codex connectors",
3
+ "version": "2.2.0",
4
+ "description": "Add authenticated remote MCP connectors to existing TypeScript SaaS apps using your existing users and auth.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "sideEffects": false,
@@ -25,7 +25,13 @@
25
25
  "model-context-protocol",
26
26
  "cloudflare-workers",
27
27
  "authorization-server",
28
- "connector"
28
+ "connector",
29
+ "typescript",
30
+ "mcp-server",
31
+ "saas",
32
+ "remote-mcp",
33
+ "byo-auth",
34
+ "ai-connector"
29
35
  ],
30
36
  "types": "./dist/server.d.ts",
31
37
  "exports": {
@@ -40,32 +46,43 @@
40
46
  "./node": {
41
47
  "types": "./dist/adapters/node.d.ts",
42
48
  "import": "./dist/adapters/node.js"
49
+ },
50
+ "./advanced": {
51
+ "types": "./dist/advanced.d.ts",
52
+ "import": "./dist/advanced.js"
43
53
  }
44
54
  },
45
55
  "files": [
46
56
  "dist",
47
- "docs",
57
+ "docs/*.md",
58
+ "docs/diagrams/**",
48
59
  "README.md",
49
60
  "LICENSE"
50
61
  ],
51
62
  "publishConfig": {
52
- "access": "public"
63
+ "access": "public",
64
+ "provenance": true
53
65
  },
54
66
  "scripts": {
55
67
  "build": "tsc -p tsconfig.build.json",
56
- "test": "tsx --test test/oauth/*.test.ts test/mcp/*.test.ts test/adapters/*.test.ts test/auth/*.test.ts",
68
+ "test": "tsx --test test/oauth/**/*.test.ts test/mcp/*.test.ts test/adapters/*.test.ts test/auth/*.test.ts test/util/*.test.ts test/*.test.ts",
57
69
  "test:e2e": "tsx --test test/e2e/*.test.ts",
58
- "test:coverage": "node --import tsx --test --experimental-test-coverage --test-coverage-lines=85 --test-coverage-branches=75 --enable-source-maps test/oauth/*.test.ts test/mcp/*.test.ts test/adapters/*.test.ts test/auth/*.test.ts",
70
+ "test:coverage": "node scripts/test-coverage.mjs",
59
71
  "typecheck": "tsc --noEmit",
72
+ "check:quickstart": "node scripts/check-quickstart.mjs",
60
73
  "lint": "biome check .",
61
- "prepublishOnly": "npm run build && npm test"
74
+ "prepublishOnly": "npm run build && npm test",
75
+ "demo": "npm run build && tsx examples/saas-demo/server.ts",
76
+ "smoke:pack": "node scripts/clean-npm-smoke.mjs",
77
+ "pretypecheck": "npm run build",
78
+ "pretest:e2e": "npm run build"
62
79
  },
63
80
  "engines": {
64
81
  "node": ">=20"
65
82
  },
66
83
  "devDependencies": {
67
- "@biomejs/biome": "2.5.9",
68
- "@types/node": "^22.13.10",
84
+ "@biomejs/biome": "2.5.11",
85
+ "@types/node": "^26.4.1",
69
86
  "tsx": "^4.19.3",
70
87
  "typescript": "^5.8.2"
71
88
  }
@@ -1,19 +0,0 @@
1
- /**
2
- * Origin allowlist for Host-derived Node origins (multi-tenant SaaS).
3
- * Same predicate shape as redirect allowlisting — not secret comparison.
4
- */
5
- export type OriginAllowlistOptions = {
6
- /**
7
- * Exact origins (`https://acme.example.com`) and/or `*.example.com`
8
- * wildcards. Wildcards match https subdomains only (not the apex, not http).
9
- * `"*"` admits any origin (loud opt-out).
10
- *
11
- * Empty/omitted list → admit all (only safe with a fixed `asNodeHandler`
12
- * `origin`). Host-derived `asNodeHandler` requires a non-empty list at
13
- * construction. Resolved origin outside the list → 400; a fixed `origin`
14
- * absent from a non-empty list throws at construction.
15
- */
16
- allowedOrigins?: string[];
17
- };
18
- export declare const isAllowedOrigin: (origin: string, options?: OriginAllowlistOptions) => boolean;
19
- //# sourceMappingURL=origins.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"origins.d.ts","sourceRoot":"","sources":["../../src/adapters/origins.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,sBAAsB,GAAG;IACnC;;;;;;;;;OASG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B,CAAC;AAyCF,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,EAAE,UAAS,sBAA2B,KAAG,OAQtF,CAAC"}
@@ -1,52 +0,0 @@
1
- /**
2
- * Origin allowlist for Host-derived Node origins (multi-tenant SaaS).
3
- * Same predicate shape as redirect allowlisting — not secret comparison.
4
- */
5
- const originOf = (value) => {
6
- try {
7
- return new URL(value).origin;
8
- }
9
- catch {
10
- return null;
11
- }
12
- };
13
- const isExactOrigin = (origin, options) => {
14
- const normalized = originOf(origin);
15
- if (!normalized)
16
- return false;
17
- return Boolean(options.allowedOrigins?.some((entry) => {
18
- if (entry.startsWith("*."))
19
- return false;
20
- return originOf(entry) === normalized;
21
- }));
22
- };
23
- /** `*.example.com` matches `https://acme.example.com`, not the apex, not http. */
24
- const isWildcardSubdomain = (origin, options) => {
25
- const patterns = options.allowedOrigins ?? [];
26
- try {
27
- const { protocol, hostname } = new URL(origin);
28
- if (protocol !== "https:")
29
- return false;
30
- return patterns.some((entry) => {
31
- if (!entry.startsWith("*."))
32
- return false;
33
- const suffix = entry.slice(1); // ".example.com"
34
- return hostname.endsWith(suffix) && hostname.length > suffix.length;
35
- });
36
- }
37
- catch {
38
- return false;
39
- }
40
- };
41
- const ORIGIN_PREDICATES = [isExactOrigin, isWildcardSubdomain];
42
- export const isAllowedOrigin = (origin, options = {}) => {
43
- // Empty/omitted: admit all — callers that derive origin from Host must
44
- // require a non-empty list before reaching here (see asNodeHandler).
45
- if (!options.allowedOrigins || options.allowedOrigins.length === 0) {
46
- return true;
47
- }
48
- if (options.allowedOrigins.includes("*"))
49
- return true;
50
- return ORIGIN_PREDICATES.some((predicate) => predicate(origin, options));
51
- };
52
- //# sourceMappingURL=origins.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"origins.js","sourceRoot":"","sources":["../../src/adapters/origins.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAkBH,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAiB,EAAE;IAChD,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,aAAa,GAAoB,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE;IACzD,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IAC9B,OAAO,OAAO,CACZ,OAAO,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;QACrC,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QACzC,OAAO,QAAQ,CAAC,KAAK,CAAC,KAAK,UAAU,CAAC;IACxC,CAAC,CAAC,CACH,CAAC;AACJ,CAAC,CAAC;AAEF,kFAAkF;AAClF,MAAM,mBAAmB,GAAoB,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE;IAC/D,MAAM,QAAQ,GAAG,OAAO,CAAC,cAAc,IAAI,EAAE,CAAC;IAC9C,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC/C,IAAI,QAAQ,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QACxC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;YAC7B,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,OAAO,KAAK,CAAC;YAC1C,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB;YAChD,OAAO,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QACtE,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAsB,CAAC,aAAa,EAAE,mBAAmB,CAAC,CAAC;AAElF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAc,EAAE,UAAkC,EAAE,EAAW,EAAE;IAC/F,uEAAuE;IACvE,qEAAqE;IACrE,IAAI,CAAC,OAAO,CAAC,cAAc,IAAI,OAAO,CAAC,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAC3E,CAAC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../src/body.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,qBAAa,iBAAkB,SAAQ,KAAK;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM;gBAAhB,QAAQ,EAAE,MAAM;CAItC;AAED,mGAAmG;AACnG,eAAO,MAAM,sBAAsB,UAAY,CAAC;AAEhD,uGAAuG;AACvG,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAuB/C;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAU,SAAS,OAAO,EAAE,UAAU,MAAM,KAAG,OAAO,CAAC,MAAM,CAgCxF,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,+EAA+E;IAC/E,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IACxE,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC7C,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAAC;IACzD,KAAK,IAAI,IAAI,CAAC;IACd,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;CAC7E,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,gBAG6B,CAAC;AA+DvF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,gBAAgB,GAAG,aAAa,CAAC,UAAU,GAAG,MAAM,CAAC,EAC7D,UAAU,MAAM,EAChB,UAAS,sBAA2B,KACnC,OAAO,CAAC,UAAU,CAQpB,CAAC"}
package/dist/body.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"body.js","sourceRoot":"","sources":["../src/body.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAkBA,OAAO,EAIL,KAAK,iBAAiB,EAIvB,MAAM,cAAc,CAAC;AAGtB,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,QAAQ,EACR,SAAS,EACT,UAAU,GACX,MAAM,cAAc,CAAC;AAEtB,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CAChD,CAAC;AAiIF,eAAO,MAAM,gBAAgB,GAAI,IAAI,EAAE,SAAS,iBAAiB,CAAC,IAAI,CAAC,KAAG,UAyBzE,CAAC"}