mcp-trellis 1.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/README.md +63 -93
  2. package/dist/adapters/node-request.d.ts +53 -0
  3. package/dist/adapters/node-request.d.ts.map +1 -0
  4. package/dist/adapters/node-request.js +165 -0
  5. package/dist/adapters/node-request.js.map +1 -0
  6. package/dist/adapters/node.d.ts +13 -30
  7. package/dist/adapters/node.d.ts.map +1 -1
  8. package/dist/adapters/node.js +55 -101
  9. package/dist/adapters/node.js.map +1 -1
  10. package/dist/advanced.d.ts +18 -0
  11. package/dist/advanced.d.ts.map +1 -0
  12. package/dist/advanced.js +18 -0
  13. package/dist/advanced.js.map +1 -0
  14. package/dist/app-options.d.ts +132 -0
  15. package/dist/app-options.d.ts.map +1 -0
  16. package/dist/app-options.js +2 -0
  17. package/dist/app-options.js.map +1 -0
  18. package/dist/app.d.ts +2 -89
  19. package/dist/app.d.ts.map +1 -1
  20. package/dist/app.js +143 -63
  21. package/dist/app.js.map +1 -1
  22. package/dist/audit.d.ts +5 -5
  23. package/dist/audit.d.ts.map +1 -1
  24. package/dist/audit.js +19 -8
  25. package/dist/audit.js.map +1 -1
  26. package/dist/auth/bearer.d.ts +4 -1
  27. package/dist/auth/bearer.d.ts.map +1 -1
  28. package/dist/auth/bearer.js +19 -4
  29. package/dist/auth/bearer.js.map +1 -1
  30. package/dist/clients.js +1 -1
  31. package/dist/clients.js.map +1 -1
  32. package/dist/deprecated.d.ts +42 -0
  33. package/dist/deprecated.d.ts.map +1 -0
  34. package/dist/deprecated.js +35 -0
  35. package/dist/deprecated.js.map +1 -0
  36. package/dist/{body.d.ts → http/body.d.ts} +27 -6
  37. package/dist/http/body.d.ts.map +1 -0
  38. package/dist/{body.js → http/body.js} +33 -13
  39. package/dist/http/body.js.map +1 -0
  40. package/dist/http/http.d.ts +42 -0
  41. package/dist/http/http.d.ts.map +1 -0
  42. package/dist/http/http.js +100 -0
  43. package/dist/http/http.js.map +1 -0
  44. package/dist/http/origins.d.ts +37 -0
  45. package/dist/http/origins.d.ts.map +1 -0
  46. package/dist/http/origins.js +87 -0
  47. package/dist/http/origins.js.map +1 -0
  48. package/dist/{dispatch.d.ts → mcp/dispatch.d.ts} +1 -1
  49. package/dist/mcp/dispatch.d.ts.map +1 -0
  50. package/dist/mcp/dispatch.js +99 -0
  51. package/dist/mcp/dispatch.js.map +1 -0
  52. package/dist/{jsonrpc.d.ts → mcp/jsonrpc.d.ts} +9 -0
  53. package/dist/mcp/jsonrpc.d.ts.map +1 -0
  54. package/dist/mcp/jsonrpc.js +88 -0
  55. package/dist/mcp/jsonrpc.js.map +1 -0
  56. package/dist/{methods.d.ts → mcp/methods.d.ts} +60 -11
  57. package/dist/mcp/methods.d.ts.map +1 -0
  58. package/dist/mcp/methods.js +84 -0
  59. package/dist/mcp/methods.js.map +1 -0
  60. package/dist/{protocol.d.ts → mcp/protocol.d.ts} +1 -3
  61. package/dist/mcp/protocol.d.ts.map +1 -0
  62. package/dist/{protocol.js → mcp/protocol.js} +1 -3
  63. package/dist/mcp/protocol.js.map +1 -0
  64. package/dist/mcp/registry.d.ts +58 -0
  65. package/dist/mcp/registry.d.ts.map +1 -0
  66. package/dist/mcp/registry.js +81 -0
  67. package/dist/mcp/registry.js.map +1 -0
  68. package/dist/mcp/rpc-handlers.d.ts +11 -0
  69. package/dist/mcp/rpc-handlers.d.ts.map +1 -0
  70. package/dist/mcp/rpc-handlers.js +133 -0
  71. package/dist/mcp/rpc-handlers.js.map +1 -0
  72. package/dist/{tools.d.ts → mcp/tools.d.ts} +20 -1
  73. package/dist/mcp/tools.d.ts.map +1 -0
  74. package/dist/mcp/tools.js +166 -0
  75. package/dist/mcp/tools.js.map +1 -0
  76. package/dist/{validate.d.ts → mcp/validate.d.ts} +18 -21
  77. package/dist/mcp/validate.d.ts.map +1 -0
  78. package/dist/{validate.js → mcp/validate.js} +59 -41
  79. package/dist/mcp/validate.js.map +1 -0
  80. package/dist/oauth/audit.d.ts +20 -0
  81. package/dist/oauth/audit.d.ts.map +1 -0
  82. package/dist/oauth/audit.js +29 -0
  83. package/dist/oauth/audit.js.map +1 -0
  84. package/dist/oauth/cimd/cimd-parse.d.ts +4 -0
  85. package/dist/oauth/cimd/cimd-parse.d.ts.map +1 -0
  86. package/dist/oauth/cimd/cimd-parse.js +70 -0
  87. package/dist/oauth/cimd/cimd-parse.js.map +1 -0
  88. package/dist/oauth/cimd/cimd-ssrf.d.ts +8 -0
  89. package/dist/oauth/cimd/cimd-ssrf.d.ts.map +1 -0
  90. package/dist/oauth/cimd/cimd-ssrf.js +193 -0
  91. package/dist/oauth/cimd/cimd-ssrf.js.map +1 -0
  92. package/dist/oauth/cimd/cimd.d.ts +36 -0
  93. package/dist/oauth/cimd/cimd.d.ts.map +1 -0
  94. package/dist/oauth/cimd/cimd.js +132 -0
  95. package/dist/oauth/cimd/cimd.js.map +1 -0
  96. package/dist/oauth/cimd/types.d.ts +12 -0
  97. package/dist/oauth/cimd/types.d.ts.map +1 -0
  98. package/dist/oauth/cimd/types.js +3 -0
  99. package/dist/oauth/cimd/types.js.map +1 -0
  100. package/dist/oauth/config.d.ts +51 -0
  101. package/dist/oauth/config.d.ts.map +1 -0
  102. package/dist/oauth/config.js +108 -0
  103. package/dist/oauth/config.js.map +1 -0
  104. package/dist/oauth/constants.d.ts +11 -0
  105. package/dist/oauth/constants.d.ts.map +1 -1
  106. package/dist/oauth/constants.js +11 -0
  107. package/dist/oauth/constants.js.map +1 -1
  108. package/dist/oauth/{base64url.d.ts → crypto/base64url.d.ts} +0 -1
  109. package/dist/oauth/crypto/base64url.d.ts.map +1 -0
  110. package/dist/oauth/{base64url.js → crypto/base64url.js} +0 -1
  111. package/dist/oauth/crypto/base64url.js.map +1 -0
  112. package/dist/oauth/crypto/clientauth.d.ts +13 -0
  113. package/dist/oauth/crypto/clientauth.d.ts.map +1 -0
  114. package/dist/oauth/crypto/clientauth.js +195 -0
  115. package/dist/oauth/crypto/clientauth.js.map +1 -0
  116. package/dist/oauth/crypto/codes.d.ts +65 -0
  117. package/dist/oauth/crypto/codes.d.ts.map +1 -0
  118. package/dist/oauth/crypto/codes.js +121 -0
  119. package/dist/oauth/crypto/codes.js.map +1 -0
  120. package/dist/oauth/crypto/hkdf.d.ts +13 -0
  121. package/dist/oauth/crypto/hkdf.d.ts.map +1 -0
  122. package/dist/oauth/crypto/hkdf.js +30 -0
  123. package/dist/oauth/crypto/hkdf.js.map +1 -0
  124. package/dist/oauth/crypto/keycache.d.ts +19 -0
  125. package/dist/oauth/crypto/keycache.d.ts.map +1 -0
  126. package/dist/oauth/crypto/keycache.js +44 -0
  127. package/dist/oauth/crypto/keycache.js.map +1 -0
  128. package/dist/oauth/crypto/pkce.d.ts.map +1 -0
  129. package/dist/oauth/{pkce.js → crypto/pkce.js} +1 -1
  130. package/dist/oauth/crypto/pkce.js.map +1 -0
  131. package/dist/oauth/{sealed.d.ts → crypto/sealed.d.ts} +12 -3
  132. package/dist/oauth/crypto/sealed.d.ts.map +1 -0
  133. package/dist/oauth/{sealed.js → crypto/sealed.js} +22 -14
  134. package/dist/oauth/crypto/sealed.js.map +1 -0
  135. package/dist/oauth/{secrethash.d.ts → crypto/secrethash.d.ts} +14 -0
  136. package/dist/oauth/crypto/secrethash.d.ts.map +1 -0
  137. package/dist/oauth/{secrethash.js → crypto/secrethash.js} +27 -11
  138. package/dist/oauth/crypto/secrethash.js.map +1 -0
  139. package/dist/oauth/deprecated.d.ts +52 -0
  140. package/dist/oauth/deprecated.d.ts.map +1 -0
  141. package/dist/oauth/deprecated.js +49 -0
  142. package/dist/oauth/deprecated.js.map +1 -0
  143. package/dist/oauth/endpoints/authorize.d.ts +3 -0
  144. package/dist/oauth/endpoints/authorize.d.ts.map +1 -0
  145. package/dist/oauth/endpoints/authorize.js +209 -0
  146. package/dist/oauth/endpoints/authorize.js.map +1 -0
  147. package/dist/oauth/endpoints/callback.d.ts +20 -0
  148. package/dist/oauth/endpoints/callback.d.ts.map +1 -0
  149. package/dist/oauth/endpoints/callback.js +31 -0
  150. package/dist/oauth/endpoints/callback.js.map +1 -0
  151. package/dist/oauth/endpoints/consent-page.d.ts +5 -0
  152. package/dist/oauth/endpoints/consent-page.d.ts.map +1 -0
  153. package/dist/oauth/endpoints/consent-page.js +63 -0
  154. package/dist/oauth/endpoints/consent-page.js.map +1 -0
  155. package/dist/oauth/endpoints/consent.d.ts +29 -0
  156. package/dist/oauth/endpoints/consent.d.ts.map +1 -0
  157. package/dist/oauth/endpoints/consent.js +98 -0
  158. package/dist/oauth/endpoints/consent.js.map +1 -0
  159. package/dist/oauth/{metadata.d.ts → endpoints/metadata.d.ts} +8 -2
  160. package/dist/oauth/endpoints/metadata.d.ts.map +1 -0
  161. package/dist/oauth/{metadata.js → endpoints/metadata.js} +10 -4
  162. package/dist/oauth/endpoints/metadata.js.map +1 -0
  163. package/dist/oauth/endpoints/register.d.ts +10 -0
  164. package/dist/oauth/endpoints/register.d.ts.map +1 -0
  165. package/dist/oauth/endpoints/register.js +96 -0
  166. package/dist/oauth/endpoints/register.js.map +1 -0
  167. package/dist/oauth/{revoke.d.ts → endpoints/revoke.d.ts} +1 -1
  168. package/dist/oauth/endpoints/revoke.d.ts.map +1 -0
  169. package/dist/oauth/{revoke.js → endpoints/revoke.js} +8 -18
  170. package/dist/oauth/endpoints/revoke.js.map +1 -0
  171. package/dist/oauth/endpoints/token.d.ts +3 -0
  172. package/dist/oauth/endpoints/token.d.ts.map +1 -0
  173. package/dist/oauth/{token.js → endpoints/token.js} +32 -25
  174. package/dist/oauth/endpoints/token.js.map +1 -0
  175. package/dist/oauth/{wellknown.d.ts → endpoints/wellknown.d.ts} +1 -1
  176. package/dist/oauth/endpoints/wellknown.d.ts.map +1 -0
  177. package/dist/oauth/{wellknown.js → endpoints/wellknown.js} +7 -9
  178. package/dist/oauth/endpoints/wellknown.js.map +1 -0
  179. package/dist/oauth/policy/redirect.d.ts.map +1 -0
  180. package/dist/oauth/policy/redirect.js.map +1 -0
  181. package/dist/oauth/policy/reqbody.d.ts +18 -0
  182. package/dist/oauth/policy/reqbody.d.ts.map +1 -0
  183. package/dist/oauth/policy/reqbody.js +68 -0
  184. package/dist/oauth/policy/reqbody.js.map +1 -0
  185. package/dist/oauth/{resource.d.ts → policy/resource.d.ts} +1 -1
  186. package/dist/oauth/policy/resource.d.ts.map +1 -0
  187. package/dist/oauth/{resource.js → policy/resource.js} +2 -2
  188. package/dist/oauth/policy/resource.js.map +1 -0
  189. package/dist/oauth/{scope.d.ts → policy/scope.d.ts} +2 -2
  190. package/dist/oauth/policy/scope.d.ts.map +1 -0
  191. package/dist/oauth/{scope.js → policy/scope.js} +3 -3
  192. package/dist/oauth/policy/scope.js.map +1 -0
  193. package/dist/oauth/router.d.ts +10 -13
  194. package/dist/oauth/router.d.ts.map +1 -1
  195. package/dist/oauth/router.js +93 -115
  196. package/dist/oauth/router.js.map +1 -1
  197. package/dist/oauth/types.d.ts +99 -45
  198. package/dist/oauth/types.d.ts.map +1 -1
  199. package/dist/oauth/types.js +1 -90
  200. package/dist/oauth/types.js.map +1 -1
  201. package/dist/server.d.ts +12 -12
  202. package/dist/server.d.ts.map +1 -1
  203. package/dist/server.js +9 -8
  204. package/dist/server.js.map +1 -1
  205. package/dist/util/defined.d.ts +11 -0
  206. package/dist/util/defined.d.ts.map +1 -0
  207. package/dist/util/defined.js +15 -0
  208. package/dist/util/defined.js.map +1 -0
  209. package/dist/util/json.d.ts +3 -0
  210. package/dist/util/json.d.ts.map +1 -0
  211. package/dist/util/json.js +3 -0
  212. package/dist/util/json.js.map +1 -0
  213. package/docs/ROADMAP.md +35 -47
  214. package/docs/compatibility.md +84 -0
  215. package/docs/diagrams/mcp-trellis-architecture.html +305 -0
  216. package/docs/guide.md +37 -13
  217. package/docs/reference.md +54 -22
  218. package/docs/security.md +58 -9
  219. package/docs/troubleshooting.md +19 -0
  220. package/package.json +23 -10
  221. package/dist/adapters/origins.d.ts +0 -19
  222. package/dist/adapters/origins.d.ts.map +0 -1
  223. package/dist/adapters/origins.js +0 -52
  224. package/dist/adapters/origins.js.map +0 -1
  225. package/dist/body.d.ts.map +0 -1
  226. package/dist/body.js.map +0 -1
  227. package/dist/dispatch.d.ts.map +0 -1
  228. package/dist/dispatch.js +0 -119
  229. package/dist/dispatch.js.map +0 -1
  230. package/dist/http.d.ts +0 -17
  231. package/dist/http.d.ts.map +0 -1
  232. package/dist/http.js +0 -38
  233. package/dist/http.js.map +0 -1
  234. package/dist/jsonrpc.d.ts.map +0 -1
  235. package/dist/jsonrpc.js +0 -18
  236. package/dist/jsonrpc.js.map +0 -1
  237. package/dist/methods.d.ts.map +0 -1
  238. package/dist/methods.js +0 -130
  239. package/dist/methods.js.map +0 -1
  240. package/dist/oauth/authorize.d.ts +0 -3
  241. package/dist/oauth/authorize.d.ts.map +0 -1
  242. package/dist/oauth/authorize.js +0 -141
  243. package/dist/oauth/authorize.js.map +0 -1
  244. package/dist/oauth/base64url.d.ts.map +0 -1
  245. package/dist/oauth/base64url.js.map +0 -1
  246. package/dist/oauth/clientauth.d.ts +0 -24
  247. package/dist/oauth/clientauth.d.ts.map +0 -1
  248. package/dist/oauth/clientauth.js +0 -119
  249. package/dist/oauth/clientauth.js.map +0 -1
  250. package/dist/oauth/codes.d.ts +0 -30
  251. package/dist/oauth/codes.d.ts.map +0 -1
  252. package/dist/oauth/codes.js +0 -85
  253. package/dist/oauth/codes.js.map +0 -1
  254. package/dist/oauth/consent.d.ts +0 -65
  255. package/dist/oauth/consent.d.ts.map +0 -1
  256. package/dist/oauth/consent.js +0 -173
  257. package/dist/oauth/consent.js.map +0 -1
  258. package/dist/oauth/metadata.d.ts.map +0 -1
  259. package/dist/oauth/metadata.js.map +0 -1
  260. package/dist/oauth/pkce.d.ts.map +0 -1
  261. package/dist/oauth/pkce.js.map +0 -1
  262. package/dist/oauth/redirect.d.ts.map +0 -1
  263. package/dist/oauth/redirect.js.map +0 -1
  264. package/dist/oauth/reqbody.d.ts +0 -10
  265. package/dist/oauth/reqbody.d.ts.map +0 -1
  266. package/dist/oauth/reqbody.js +0 -29
  267. package/dist/oauth/reqbody.js.map +0 -1
  268. package/dist/oauth/resource.d.ts.map +0 -1
  269. package/dist/oauth/resource.js.map +0 -1
  270. package/dist/oauth/revoke.d.ts.map +0 -1
  271. package/dist/oauth/revoke.js.map +0 -1
  272. package/dist/oauth/scope.d.ts.map +0 -1
  273. package/dist/oauth/scope.js.map +0 -1
  274. package/dist/oauth/sealed.d.ts.map +0 -1
  275. package/dist/oauth/sealed.js.map +0 -1
  276. package/dist/oauth/secrethash.d.ts.map +0 -1
  277. package/dist/oauth/secrethash.js.map +0 -1
  278. package/dist/oauth/token.d.ts +0 -3
  279. package/dist/oauth/token.d.ts.map +0 -1
  280. package/dist/oauth/token.js.map +0 -1
  281. package/dist/oauth/wellknown.d.ts.map +0 -1
  282. package/dist/oauth/wellknown.js.map +0 -1
  283. package/dist/protocol.d.ts.map +0 -1
  284. package/dist/protocol.js.map +0 -1
  285. package/dist/registry.d.ts +0 -37
  286. package/dist/registry.d.ts.map +0 -1
  287. package/dist/registry.js +0 -77
  288. package/dist/registry.js.map +0 -1
  289. package/dist/tools.d.ts.map +0 -1
  290. package/dist/tools.js +0 -98
  291. package/dist/tools.js.map +0 -1
  292. package/dist/validate.d.ts.map +0 -1
  293. package/dist/validate.js.map +0 -1
  294. /package/dist/oauth/{pkce.d.ts → crypto/pkce.d.ts} +0 -0
  295. /package/dist/oauth/{redirect.d.ts → policy/redirect.d.ts} +0 -0
  296. /package/dist/oauth/{redirect.js → policy/redirect.js} +0 -0
@@ -0,0 +1,84 @@
1
+ # Compatibility evidence
2
+
3
+ Updated 2026-09-14. Repository implementation and automated coverage are in good
4
+ shape after the pre-prod security pass. **Vendor-client UI sessions are still
5
+ Pending** — do not convert product rows to ✅ until a live Claude/ChatGPT/Codex/Gemini
6
+ check below is filled in.
7
+
8
+ | Product | Server profile | Repository evidence | Live evidence |
9
+ |---|---|---|---|
10
+ | Claude custom connector | `claude`: DCR / CIMD, public client, S256 PKCE; built-in callback | `test/oauth/policy/clients.test.ts`, `test/oauth/endpoints/dcr.test.ts`, `test/oauth/cimd/cimd.test.ts`, `test/oauth/crypto/pkce.test.ts`; saas-demo e2e + `scripts/prep-claude-oauth.mjs` | Pending (UI) |
11
+ | ChatGPT | `codex`: public-client profile; exact hosted callback must be configured | Profile tests only; no ChatGPT UI test | Pending |
12
+ | Codex | `codex`: public client, PKCE, loopback callbacks allowed | Profile/redirect tests; no actual Codex login run | Pending |
13
+ | Gemini Enterprise | `gemini`: `clientStore`, pre-registration, `client_secret_basic` / `client_secret_post` | Client authentication tests; no Enterprise environment | Pending |
14
+
15
+ The Gemini profile does not imply compatibility with Gemini CLI or every Gemini product. ChatGPT and Codex need separate live checks even though the package groups them under one profile.
16
+
17
+ `src/mcp/protocol.ts` implements `2024-11-05`, `2025-03-26`, `2025-06-18`. Initialization defaults to `2025-06-18`; absent request headers use `2025-03-26`. This is an implementation inventory, not a claim to implement the latest revision. **CIMD is shipped opt-in** (`cimd: true` on `createMcpApp` / `createOAuthRouter`; off by default — see [security.md](security.md) SSRF ceiling). Live vendor UI evidence stays **Pending** until an operator connects a real client.
18
+
19
+ ## Demo deploy (Worker)
20
+
21
+ Compile-checked Worker sketch: [`examples/cloudflare-worker.ts`](../examples/cloudflare-worker.ts).
22
+
23
+ One-liner after you add a minimal `wrangler.toml` (name, `compatibility_date`, and secrets
24
+ `OAUTH_CODE_SECRET` / `ACCESS_TOKEN_SECRET`):
25
+
26
+ ```bash
27
+ npx wrangler secret put OAUTH_CODE_SECRET
28
+ npx wrangler secret put ACCESS_TOKEN_SECRET
29
+ npx wrangler deploy examples/cloudflare-worker.ts
30
+ ```
31
+
32
+ Point a connector at `https://YOUR-WORKER/mcp` only after secrets and an origin
33
+ allowlist match your route. This path proves the Worker mounts; it does **not**
34
+ replace the live client rows below.
35
+
36
+ ## Host contract covered in-repo
37
+
38
+ `examples/saas-demo` now mints refresh tokens and enforces RFC 6749 §6: requested
39
+ refresh `scope` must be ⊆ the scopes originally bound to that refresh token
40
+ (`isScopeSubset` pattern also in `examples/stores.ts`). Escalation →
41
+ `invalid_grant`. Covered by `test/e2e/saas-demo.test.ts`.
42
+
43
+ ## Automated package smoke (not a vendor live pass)
44
+
45
+ ```bash
46
+ npm run smoke:pack
47
+ ```
48
+
49
+ Installs the local `npm pack` tarball in a temp directory and imports
50
+ `createMcpApp` / `asNodeHandler`. Record date/commit when you run it before a
51
+ release; it does **not** replace the live client row.
52
+
53
+ | Date | Commit / package | Command | Result |
54
+ |---|---|---|---|
55
+ | 2026-09-12 | unreleased working tree (`1.1.0` pack) | `npm run smoke:pack` | Pass — `createMcpApp` + `asNodeHandler` import from tarball |
56
+
57
+ ## Evidence required for a live pass
58
+
59
+ Record client product, version/build (or web UI and test date), account tier, package version/commit, hosting environment and configured callback. Capture discovery → registration → app login → consent → token exchange → initialize → tools/list → `list_my_projects`. Verify wrong audience/invalid token rejection and user isolation separately. Save sanitized output or recording; exclude cookies, passwords, codes and tokens.
60
+
61
+ Against a public HTTPS demo (`PUBLIC_ORIGIN` + `DEMO_PASSWORD`):
62
+
63
+ ```bash
64
+ npm run demo # reverse-proxy to HTTPS; set PUBLIC_ORIGIN
65
+ node scripts/prep-claude-oauth.mjs # scripted OAuth + refresh scope + tool
66
+ # Then open the vendor UI with MCP URL https://YOUR-HOST/mcp and record below.
67
+ ```
68
+
69
+ | Date | Client/build | Package/commit | Protocol negotiated | Tool + expected result | Evidence | Result |
70
+ |---|---|---|---|---|---|---|
71
+ | — | — | — | — | — | — | Pending |
72
+
73
+ ## Under-ten-minute onboarding test
74
+
75
+ Use a clean folder and the published npm tarball, start a timer at `npm install`, and stop at a real client's first correct user-scoped tool response. Record setup prerequisites, elapsed time, every edit and any error. A local source import or simulated OAuth flow is insufficient. Target: under ten minutes with hosting and a supported client account available; no time claim is published until measured.
76
+
77
+ | Field | Value |
78
+ |---|---|
79
+ | Start (`npm install`) | — |
80
+ | Stop (first correct tool in real client) | — |
81
+ | Elapsed | — |
82
+ | Prerequisites (hosting, account, callback) | — |
83
+ | Edits / errors | — |
84
+ | Result | Pending |
@@ -0,0 +1,305 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>mcp-trellis · Architecture</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Hebrew:wght@400;500;600;700;800&display=swap" rel="stylesheet">
8
+ <style>
9
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
10
+ :root {
11
+ --color-paper: #f5f5fb;
12
+ --color-ink: #1e1f29;
13
+ --color-muted: #55566b;
14
+ --color-accent: #5b5bf0;
15
+ --font-sans: 'Noto Sans Hebrew', system-ui, sans-serif;
16
+ --font-mono: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
17
+ }
18
+
19
+ body {
20
+ font-family: var(--font-sans);
21
+ background: var(--color-paper);
22
+ color: var(--color-ink);
23
+ min-height: 100vh;
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ padding: 3rem 2rem;
28
+ }
29
+
30
+ .frame { max-width: 1280px; width: 100%; }
31
+
32
+ .eyebrow {
33
+ font-family: var(--font-mono);
34
+ font-size: 0.66rem;
35
+ font-weight: 500;
36
+ letter-spacing: 0.18em;
37
+ text-transform: uppercase;
38
+ color: var(--color-muted);
39
+ margin-bottom: 0.5rem;
40
+ }
41
+
42
+ h1 {
43
+ font-family: var(--font-sans);
44
+ font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem);
45
+ font-weight: 700;
46
+ letter-spacing: -0.02em;
47
+ line-height: 1.15;
48
+ color: var(--color-ink);
49
+ margin-bottom: 1.5rem;
50
+ }
51
+
52
+ svg { width: 100%; min-width: 960px; display: block; }
53
+ </style>
54
+ </head>
55
+ <body>
56
+ <div class="frame">
57
+ <p class="eyebrow">Architecture · mcp-trellis</p>
58
+ <h1>Host-agnostic MCP + OAuth 2.1</h1>
59
+
60
+ <svg viewBox="0 0 1280 760" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="mcp-trellis-architecture-title mcp-trellis-architecture-desc">
61
+ <title id="mcp-trellis-architecture-title">mcp-trellis architecture</title>
62
+ <desc id="mcp-trellis-architecture-desc">Connectors reach an edge host, then createMcpApp routes OAuth and MCP traffic to library handlers and your login, token, store, and tool ports.</desc>
63
+ <defs>
64
+ <marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#55566b"/></marker>
65
+ <marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#5b5bf0"/></marker>
66
+ <marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#4545c8"/></marker>
67
+ </defs>
68
+
69
+ <rect width="100%" height="100%" fill="#f5f5fb"/>
70
+
71
+ <!-- Zones (before arrows) -->
72
+ <rect x="40" y="96" width="168" height="400" rx="8"
73
+ fill="rgba(30,31,41,0.02)" stroke="rgba(30,31,41,0.10)" stroke-width="0.8"/>
74
+ <rect x="84" y="100" width="80" height="12" rx="2" fill="#f5f5fb"/>
75
+ <text x="124" y="109" fill="rgba(30,31,41,0.40)" font-size="7" font-family="ui-monospace, monospace"
76
+ text-anchor="middle" letter-spacing="0.14em">CONNECTORS</text>
77
+
78
+ <rect x="440" y="56" width="480" height="520" rx="8"
79
+ fill="rgba(30,31,41,0.02)" stroke="rgba(30,31,41,0.10)" stroke-width="0.8"/>
80
+ <rect x="624" y="60" width="112" height="12" rx="2" fill="#f5f5fb"/>
81
+ <text x="680" y="69" fill="rgba(30,31,41,0.40)" font-size="7" font-family="ui-monospace, monospace"
82
+ text-anchor="middle" letter-spacing="0.14em">MCP-TRELLIS</text>
83
+
84
+ <rect x="980" y="96" width="260" height="440" rx="8"
85
+ fill="rgba(30,31,41,0.02)" stroke="rgba(30,31,41,0.10)" stroke-width="0.8"/>
86
+ <rect x="1052" y="100" width="116" height="12" rx="2" fill="#f5f5fb"/>
87
+ <text x="1110" y="109" fill="rgba(30,31,41,0.40)" font-size="7" font-family="ui-monospace, monospace"
88
+ text-anchor="middle" letter-spacing="0.14em">YOUR PORTS</text>
89
+
90
+ <!-- Arrows -->
91
+ <!-- Clients → Edge (link) -->
92
+ <line x1="192" y1="172" x2="248" y2="172" stroke="#4545c8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
93
+ <line x1="192" y1="292" x2="248" y2="292" stroke="#4545c8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
94
+ <line x1="192" y1="412" x2="248" y2="412" stroke="#4545c8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
95
+
96
+ <!-- Edge → createMcpApp (accent primary) -->
97
+ <path d="M 392,292 H 480 Q 488,292 488,284 V 120 Q 488,112 496,112 H 592"
98
+ fill="none" stroke="#5b5bf0" stroke-width="1.4" marker-end="url(#arrow-accent)"/>
99
+
100
+ <!-- createMcpApp → OAuth / MCP -->
101
+ <path d="M 640,144 V 168 H 558 Q 550,168 550,176 V 200"
102
+ fill="none" stroke="#55566b" stroke-width="1.2" marker-end="url(#arrow)"/>
103
+ <path d="M 720,144 V 168 H 802 Q 810,168 810,176 V 200"
104
+ fill="none" stroke="#55566b" stroke-width="1.2" marker-end="url(#arrow)"/>
105
+
106
+ <!-- OAuth → Consent / Discovery -->
107
+ <line x1="550" y1="280" x2="550" y2="312" stroke="#5b5bf0" stroke-width="1.2" marker-end="url(#arrow-accent)"/>
108
+ <line x1="550" y1="376" x2="550" y2="408" stroke="#55566b" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow)"/>
109
+
110
+ <!-- MCP → Audience → Registry -->
111
+ <line x1="810" y1="280" x2="810" y2="312" stroke="#55566b" stroke-width="1.2" marker-end="url(#arrow)"/>
112
+ <line x1="810" y1="376" x2="810" y2="408" stroke="#55566b" stroke-width="1.2" marker-end="url(#arrow)"/>
113
+
114
+ <!-- Library → Ports (distinct Auth attach points: MINT@160, VERIFY@184) -->
115
+ <path d="M 640,228 H 680 Q 688,228 688,220 V 168 Q 688,160 696,160 H 1000"
116
+ fill="none" stroke="#4545c8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
117
+ <path d="M 640,256 H 680 Q 688,256 688,264 V 288 Q 688,296 696,296 H 1000"
118
+ fill="none" stroke="#55566b" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow)"/>
119
+ <path d="M 900,344 H 940 Q 948,344 948,336 V 192 Q 948,184 956,184 H 1000"
120
+ fill="none" stroke="#55566b" stroke-width="1.2" marker-end="url(#arrow)"/>
121
+ <line x1="900" y1="440" x2="1000" y2="440" stroke="#55566b" stroke-width="1.2" marker-end="url(#arrow)"/>
122
+
123
+ <!-- Arrow labels (6–10px gap above stroke) -->
124
+ <rect x="204" y="152" width="36" height="12" rx="2" fill="#f5f5fb"/>
125
+ <text x="222" y="161" fill="#4545c8" font-size="8" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.06em">HTTPS</text>
126
+
127
+ <rect x="500" y="92" width="48" height="12" rx="2" fill="#f5f5fb"/>
128
+ <text x="524" y="101" fill="#5b5bf0" font-size="8" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.06em">FETCH</text>
129
+
130
+ <rect x="568" y="148" width="40" height="12" rx="2" fill="#f5f5fb"/>
131
+ <text x="588" y="157" fill="#55566b" font-size="8" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.06em">OAUTH</text>
132
+
133
+ <rect x="744" y="148" width="32" height="12" rx="2" fill="#f5f5fb"/>
134
+ <text x="760" y="157" fill="#55566b" font-size="8" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.06em">MCP</text>
135
+
136
+ <rect x="560" y="288" width="52" height="12" rx="2" fill="#f5f5fb"/>
137
+ <text x="586" y="297" fill="#5b5bf0" font-size="8" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.06em">APPROVE</text>
138
+
139
+ <rect x="560" y="384" width="40" height="12" rx="2" fill="#f5f5fb"/>
140
+ <text x="580" y="393" fill="#8d8ea3" font-size="8" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.06em">META</text>
141
+
142
+ <rect x="820" y="288" width="36" height="12" rx="2" fill="#f5f5fb"/>
143
+ <text x="838" y="297" fill="#55566b" font-size="8" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.06em">AUD</text>
144
+
145
+ <rect x="820" y="384" width="48" height="12" rx="2" fill="#f5f5fb"/>
146
+ <text x="844" y="393" fill="#55566b" font-size="8" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.06em">SCOPE</text>
147
+
148
+ <rect x="820" y="140" width="40" height="12" rx="2" fill="#f5f5fb"/>
149
+ <text x="840" y="149" fill="#4545c8" font-size="8" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.06em">MINT</text>
150
+
151
+ <rect x="924" y="276" width="44" height="12" rx="2" fill="#f5f5fb"/>
152
+ <text x="946" y="285" fill="#8d8ea3" font-size="8" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.06em">STORE</text>
153
+
154
+ <rect x="880" y="164" width="48" height="12" rx="2" fill="#f5f5fb"/>
155
+ <text x="904" y="173" fill="#55566b" font-size="8" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.06em">VERIFY</text>
156
+
157
+ <rect x="928" y="420" width="40" height="12" rx="2" fill="#f5f5fb"/>
158
+ <text x="948" y="429" fill="#55566b" font-size="8" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.06em">CALL</text>
159
+
160
+ <!-- Nodes: Connectors -->
161
+ <rect x="56" y="140" width="136" height="64" rx="6" fill="#f5f5fb"/>
162
+ <rect x="56" y="140" width="136" height="64" rx="6" fill="rgba(85,86,107,0.10)" stroke="#8d8ea3" stroke-width="1"/>
163
+ <rect x="64" y="148" width="40" height="12" rx="2" fill="transparent" stroke="rgba(141,142,163,0.40)" stroke-width="0.8"/>
164
+ <text x="84" y="157" fill="#8d8ea3" font-size="7" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.08em">CLIENT</text>
165
+ <text x="124" y="176" fill="#1e1f29" font-size="12" font-weight="600" font-family="'Noto Sans Hebrew', sans-serif" text-anchor="middle">Claude</text>
166
+ <text x="124" y="192" fill="#55566b" font-size="9" font-family="ui-monospace, monospace" text-anchor="middle">DCR + PKCE</text>
167
+
168
+ <rect x="56" y="260" width="136" height="64" rx="6" fill="#f5f5fb"/>
169
+ <rect x="56" y="260" width="136" height="64" rx="6" fill="rgba(85,86,107,0.10)" stroke="#8d8ea3" stroke-width="1"/>
170
+ <rect x="64" y="268" width="40" height="12" rx="2" fill="transparent" stroke="rgba(141,142,163,0.40)" stroke-width="0.8"/>
171
+ <text x="84" y="277" fill="#8d8ea3" font-size="7" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.08em">CLIENT</text>
172
+ <text x="124" y="296" fill="#1e1f29" font-size="12" font-weight="600" font-family="'Noto Sans Hebrew', sans-serif" text-anchor="middle">Gemini</text>
173
+ <text x="124" y="312" fill="#55566b" font-size="9" font-family="ui-monospace, monospace" text-anchor="middle">pre-reg secret</text>
174
+
175
+ <rect x="56" y="380" width="136" height="64" rx="6" fill="#f5f5fb"/>
176
+ <rect x="56" y="380" width="136" height="64" rx="6" fill="rgba(85,86,107,0.10)" stroke="#8d8ea3" stroke-width="1"/>
177
+ <rect x="64" y="388" width="40" height="12" rx="2" fill="transparent" stroke="rgba(141,142,163,0.40)" stroke-width="0.8"/>
178
+ <text x="84" y="397" fill="#8d8ea3" font-size="7" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.08em">CLIENT</text>
179
+ <text x="124" y="416" fill="#1e1f29" font-size="12" font-weight="600" font-family="'Noto Sans Hebrew', sans-serif" text-anchor="middle">Codex</text>
180
+ <text x="124" y="432" fill="#55566b" font-size="9" font-family="ui-monospace, monospace" text-anchor="middle">OAuth 2.1 PKCE</text>
181
+
182
+ <!-- Edge host -->
183
+ <rect x="248" y="148" width="144" height="320" rx="6" fill="#f5f5fb"/>
184
+ <rect x="248" y="148" width="144" height="320" rx="6" fill="rgba(30,31,41,0.03)" stroke="rgba(30,31,41,0.30)" stroke-width="1"/>
185
+ <rect x="256" y="156" width="36" height="12" rx="2" fill="transparent" stroke="rgba(30,31,41,0.22)" stroke-width="0.8"/>
186
+ <text x="274" y="165" fill="#8d8ea3" font-size="7" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.08em">EDGE</text>
187
+ <text x="320" y="292" fill="#1e1f29" font-size="12" font-weight="600" font-family="'Noto Sans Hebrew', sans-serif" text-anchor="middle">Your host</text>
188
+ <text x="320" y="312" fill="#55566b" font-size="9" font-family="ui-monospace, monospace" text-anchor="middle">Workers · Next</text>
189
+ <text x="320" y="328" fill="#55566b" font-size="9" font-family="ui-monospace, monospace" text-anchor="middle">Node · Deno · Bun</text>
190
+ <text x="320" y="352" fill="#8d8ea3" font-size="8" font-family="ui-monospace, monospace" text-anchor="middle">Request → Response</text>
191
+
192
+ <!-- createMcpApp (focal) -->
193
+ <rect x="592" y="80" width="176" height="64" rx="6" fill="#f5f5fb"/>
194
+ <rect x="592" y="80" width="176" height="64" rx="6" fill="#efeeff" stroke="#5b5bf0" stroke-width="1"/>
195
+ <rect x="600" y="88" width="36" height="12" rx="2" fill="transparent" stroke="rgba(91,91,240,0.50)" stroke-width="0.8"/>
196
+ <text x="618" y="97" fill="#5b5bf0" font-size="7" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.08em">APP</text>
197
+ <text x="680" y="116" fill="#1e1f29" font-size="12" font-weight="600" font-family="'Noto Sans Hebrew', sans-serif" text-anchor="middle">createMcpApp</text>
198
+ <text x="680" y="132" fill="#55566b" font-size="9" font-family="ui-monospace, monospace" text-anchor="middle">oauth.tryHandle → /mcp</text>
199
+
200
+ <!-- OAuth AS -->
201
+ <rect x="460" y="200" width="180" height="80" rx="6" fill="#f5f5fb"/>
202
+ <rect x="460" y="200" width="180" height="80" rx="6" fill="#ffffff" stroke="#1e1f29" stroke-width="1"/>
203
+ <rect x="468" y="208" width="28" height="12" rx="2" fill="transparent" stroke="rgba(30,31,41,0.40)" stroke-width="0.8"/>
204
+ <text x="482" y="217" fill="#1e1f29" font-size="7" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.08em">AS</text>
205
+ <text x="550" y="240" fill="#1e1f29" font-size="12" font-weight="600" font-family="'Noto Sans Hebrew', sans-serif" text-anchor="middle">OAuth router</text>
206
+ <text x="550" y="256" fill="#55566b" font-size="9" font-family="ui-monospace, monospace" text-anchor="middle">/authorize · /token</text>
207
+ <text x="550" y="268" fill="#55566b" font-size="9" font-family="ui-monospace, monospace" text-anchor="middle">/register · /revoke</text>
208
+
209
+ <!-- Consent (security, 2nd accent) -->
210
+ <rect x="460" y="312" width="180" height="64" rx="6" fill="#f5f5fb"/>
211
+ <rect x="460" y="312" width="180" height="64" rx="6" fill="rgba(91,91,240,0.05)" stroke="rgba(91,91,240,0.50)" stroke-width="1" stroke-dasharray="4,4"/>
212
+ <rect x="468" y="320" width="48" height="12" rx="2" fill="transparent" stroke="rgba(91,91,240,0.40)" stroke-width="0.8"/>
213
+ <text x="492" y="329" fill="#5b5bf0" font-size="7" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.08em">SECURE</text>
214
+ <text x="550" y="348" fill="#1e1f29" font-size="12" font-weight="600" font-family="'Noto Sans Hebrew', sans-serif" text-anchor="middle">Consent</text>
215
+ <text x="550" y="364" fill="#55566b" font-size="9" font-family="ui-monospace, monospace" text-anchor="middle">POST …/consent</text>
216
+
217
+ <!-- Discovery -->
218
+ <rect x="460" y="408" width="180" height="64" rx="6" fill="#f5f5fb"/>
219
+ <rect x="460" y="408" width="180" height="64" rx="6" fill="rgba(30,31,41,0.02)" stroke="rgba(30,31,41,0.20)" stroke-width="1" stroke-dasharray="4,3"/>
220
+ <rect x="468" y="416" width="36" height="12" rx="2" fill="transparent" stroke="rgba(30,31,41,0.25)" stroke-width="0.8"/>
221
+ <text x="486" y="425" fill="#8d8ea3" font-size="7" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.08em">META</text>
222
+ <text x="550" y="444" fill="#1e1f29" font-size="12" font-weight="600" font-family="'Noto Sans Hebrew', sans-serif" text-anchor="middle">Discovery</text>
223
+ <text x="550" y="460" fill="#55566b" font-size="9" font-family="ui-monospace, monospace" text-anchor="middle">.well-known/*</text>
224
+
225
+ <!-- MCP Handler -->
226
+ <rect x="720" y="200" width="180" height="80" rx="6" fill="#f5f5fb"/>
227
+ <rect x="720" y="200" width="180" height="80" rx="6" fill="#ffffff" stroke="#1e1f29" stroke-width="1"/>
228
+ <rect x="728" y="208" width="36" height="12" rx="2" fill="transparent" stroke="rgba(30,31,41,0.40)" stroke-width="0.8"/>
229
+ <text x="746" y="217" fill="#1e1f29" font-size="7" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.08em">MCP</text>
230
+ <text x="810" y="240" fill="#1e1f29" font-size="12" font-weight="600" font-family="'Noto Sans Hebrew', sans-serif" text-anchor="middle">MCP handler</text>
231
+ <text x="810" y="256" fill="#55566b" font-size="9" font-family="ui-monospace, monospace" text-anchor="middle">JSON-RPC /mcp</text>
232
+ <text x="810" y="268" fill="#55566b" font-size="9" font-family="ui-monospace, monospace" text-anchor="middle">initialize · tools/*</text>
233
+
234
+ <!-- Audience gate (ink dashed — accent reserved for createMcpApp + Consent) -->
235
+ <rect x="720" y="312" width="180" height="64" rx="6" fill="#f5f5fb"/>
236
+ <rect x="720" y="312" width="180" height="64" rx="6" fill="rgba(30,31,41,0.02)" stroke="rgba(30,31,41,0.30)" stroke-width="1" stroke-dasharray="4,4"/>
237
+ <rect x="728" y="320" width="36" height="12" rx="2" fill="transparent" stroke="rgba(30,31,41,0.30)" stroke-width="0.8"/>
238
+ <text x="746" y="329" fill="#55566b" font-size="7" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.08em">GATE</text>
239
+ <text x="810" y="348" fill="#1e1f29" font-size="12" font-weight="600" font-family="'Noto Sans Hebrew', sans-serif" text-anchor="middle">Audience check</text>
240
+ <text x="810" y="364" fill="#55566b" font-size="9" font-family="ui-monospace, monospace" text-anchor="middle">RFC 8707 aud</text>
241
+
242
+ <!-- Tool registry -->
243
+ <rect x="720" y="408" width="180" height="64" rx="6" fill="#f5f5fb"/>
244
+ <rect x="720" y="408" width="180" height="64" rx="6" fill="rgba(30,31,41,0.05)" stroke="#55566b" stroke-width="1"/>
245
+ <rect x="728" y="416" width="40" height="12" rx="2" fill="transparent" stroke="rgba(85,86,107,0.50)" stroke-width="0.8"/>
246
+ <text x="748" y="425" fill="#55566b" font-size="7" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.08em">REG</text>
247
+ <text x="810" y="444" fill="#1e1f29" font-size="12" font-weight="600" font-family="'Noto Sans Hebrew', sans-serif" text-anchor="middle">Tool registry</text>
248
+ <text x="810" y="460" fill="#55566b" font-size="9" font-family="ui-monospace, monospace" text-anchor="middle">scope-gated call</text>
249
+
250
+ <!-- Your ports -->
251
+ <rect x="1000" y="140" width="220" height="72" rx="6" fill="#f5f5fb"/>
252
+ <rect x="1000" y="140" width="220" height="72" rx="6" fill="rgba(85,86,107,0.10)" stroke="#8d8ea3" stroke-width="1"/>
253
+ <rect x="1008" y="148" width="40" height="12" rx="2" fill="transparent" stroke="rgba(141,142,163,0.40)" stroke-width="0.8"/>
254
+ <text x="1028" y="157" fill="#8d8ea3" font-size="7" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.08em">AUTH</text>
255
+ <text x="1110" y="176" fill="#1e1f29" font-size="12" font-weight="600" font-family="'Noto Sans Hebrew', sans-serif" text-anchor="middle">Auth ports</text>
256
+ <text x="1110" y="192" fill="#55566b" font-size="9" font-family="ui-monospace, monospace" text-anchor="middle">resolve · mint · verify</text>
257
+
258
+ <rect x="1000" y="260" width="220" height="72" rx="6" fill="#f5f5fb"/>
259
+ <rect x="1000" y="260" width="220" height="72" rx="6" fill="rgba(30,31,41,0.05)" stroke="#55566b" stroke-width="1"/>
260
+ <rect x="1008" y="268" width="44" height="12" rx="2" fill="transparent" stroke="rgba(85,86,107,0.50)" stroke-width="0.8"/>
261
+ <text x="1030" y="277" fill="#55566b" font-size="7" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.08em">STORE</text>
262
+ <text x="1110" y="296" fill="#1e1f29" font-size="12" font-weight="600" font-family="'Noto Sans Hebrew', sans-serif" text-anchor="middle">Stores</text>
263
+ <text x="1110" y="312" fill="#55566b" font-size="9" font-family="ui-monospace, monospace" text-anchor="middle">clientStore · codeStore</text>
264
+
265
+ <rect x="1000" y="380" width="220" height="72" rx="6" fill="#f5f5fb"/>
266
+ <rect x="1000" y="380" width="220" height="72" rx="6" fill="#ffffff" stroke="#1e1f29" stroke-width="1"/>
267
+ <rect x="1008" y="388" width="40" height="12" rx="2" fill="transparent" stroke="rgba(30,31,41,0.40)" stroke-width="0.8"/>
268
+ <text x="1028" y="397" fill="#1e1f29" font-size="7" font-family="ui-monospace, monospace" text-anchor="middle" letter-spacing="0.08em">TOOLS</text>
269
+ <text x="1110" y="416" fill="#1e1f29" font-size="12" font-weight="600" font-family="'Noto Sans Hebrew', sans-serif" text-anchor="middle">Tool handlers</text>
270
+ <text x="1110" y="432" fill="#55566b" font-size="9" font-family="ui-monospace, monospace" text-anchor="middle">your business logic</text>
271
+
272
+ <!-- Legend -->
273
+ <line x1="40" y1="640" x2="1240" y2="640" stroke="rgba(30,31,41,0.10)" stroke-width="0.8"/>
274
+ <text x="40" y="660" fill="#55566b" font-size="8" font-family="ui-monospace, monospace" letter-spacing="0.14em">LEGEND</text>
275
+
276
+ <rect x="40" y="676" width="14" height="10" rx="2" fill="#efeeff" stroke="#5b5bf0" stroke-width="1"/>
277
+ <text x="60" y="684" fill="#55566b" font-size="8" font-family="'Noto Sans Hebrew', sans-serif">Focal router</text>
278
+
279
+ <rect x="168" y="676" width="14" height="10" rx="2" fill="#ffffff" stroke="#1e1f29" stroke-width="1"/>
280
+ <text x="188" y="684" fill="#55566b" font-size="8" font-family="'Noto Sans Hebrew', sans-serif">Library handler</text>
281
+
282
+ <rect x="316" y="676" width="14" height="10" rx="2" fill="rgba(91,91,240,0.05)" stroke="rgba(91,91,240,0.50)" stroke-width="1" stroke-dasharray="3,2"/>
283
+ <text x="336" y="684" fill="#55566b" font-size="8" font-family="'Noto Sans Hebrew', sans-serif">Security control</text>
284
+
285
+ <rect x="468" y="676" width="14" height="10" rx="2" fill="rgba(30,31,41,0.05)" stroke="#55566b" stroke-width="1"/>
286
+ <text x="488" y="684" fill="#55566b" font-size="8" font-family="'Noto Sans Hebrew', sans-serif">Store / registry</text>
287
+
288
+ <rect x="620" y="676" width="14" height="10" rx="2" fill="rgba(30,31,41,0.03)" stroke="rgba(30,31,41,0.30)" stroke-width="1"/>
289
+ <text x="640" y="684" fill="#55566b" font-size="8" font-family="'Noto Sans Hebrew', sans-serif">Edge host</text>
290
+
291
+ <rect x="732" y="676" width="14" height="10" rx="2" fill="rgba(85,86,107,0.10)" stroke="#8d8ea3" stroke-width="1"/>
292
+ <text x="752" y="684" fill="#55566b" font-size="8" font-family="'Noto Sans Hebrew', sans-serif">External / port</text>
293
+
294
+ <line x1="880" y1="680" x2="908" y2="680" stroke="#5b5bf0" stroke-width="1.4" marker-end="url(#arrow-accent)"/>
295
+ <text x="916" y="684" fill="#55566b" font-size="8" font-family="'Noto Sans Hebrew', sans-serif">Primary</text>
296
+
297
+ <line x1="1000" y1="680" x2="1028" y2="680" stroke="#4545c8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
298
+ <text x="1036" y="684" fill="#55566b" font-size="8" font-family="'Noto Sans Hebrew', sans-serif">HTTP / mint</text>
299
+
300
+ <line x1="1140" y1="680" x2="1168" y2="680" stroke="#55566b" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow)"/>
301
+ <text x="1176" y="684" fill="#55566b" font-size="8" font-family="'Noto Sans Hebrew', sans-serif">Optional</text>
302
+ </svg>
303
+ </div>
304
+ </body>
305
+ </html>
package/docs/guide.md CHANGED
@@ -181,7 +181,8 @@ Also mount `.well-known/*` and `/mcp/oauth/*` the same way (or route everything
181
181
 
182
182
  Express, Cloud Functions, Cloud Run, `http.createServer` — bridge with
183
183
  `asNodeHandler`. Runnable recipe: [`examples/http-server.ts`](../examples/http-server.ts)
184
- (`npx tsx examples/http-server.ts`).
184
+ (set `OAUTH_CODE_SECRET` and `ACCESS_TOKEN_SECRET`, e.g. from `openssl rand -base64 32`,
185
+ then `npx tsx examples/http-server.ts`).
185
186
 
186
187
  ## Ports — what you implement
187
188
 
@@ -196,13 +197,13 @@ The library stays protocol-shaped. Your app plugs in the seams:
196
197
  | `verifyToken(token, req)` | Decode a bearer and return `{ userId, scopes, audience, claims? }`, or `null`. **The library compares `audience` against this server's canonical resource** — you cannot forget the check. Optional `claims` are passed to `context` on `principal` |
197
198
  | `resolveUser`, `loginUrl`, `mintAccessToken`, `codeSecret` | Same as the OAuth ports below |
198
199
  | `clientStore?` | Pre-registered clients; required for confidential clients like Gemini |
199
- | `refreshAccessToken?` | If set, metadata advertises `refresh_token` |
200
+ | `refreshAccessToken?` | If set, metadata advertises `refresh_token`. Receives `resource` and optional reduced `scope` — **MUST reject tokens not originally issued for that resource, and any `scope` outside the originally granted set** (see [security.md](security.md)) |
200
201
  | `revokeToken?` | RFC 7009; presence mounts `/revoke` and advertises `revocation_endpoint` |
201
202
 
202
203
  `consent?` (approval-page policy — see [Consent](#consent), below) and
203
204
  `defaultScopes?` sit as top-level `McpAppOptions`, not under `auth` — they
204
205
  are policy, not credentials, the same reasoning as `allowLoopback`.
205
- | `context?`, `audit?` | Same as the MCP ports below; `context` defaults to an empty object. Receives `principal.claims` when `verifyToken` set them |
206
+ | `context?`, `audit?` | `context` defaults to `{}`. Top-level `audit` is a **unified** sink for MCP and OAuth (`entry.source` is `"mcp"` or `"oauth"`). `auth.audit` overrides OAuth delivery when both are set |
206
207
 
207
208
  ### MCP (`createMcpHandler`)
208
209
 
@@ -213,7 +214,7 @@ The lower-level handler. Here the audience check is **yours** — prefer
213
214
  |------|------|
214
215
  | `authenticate(req, method, tool?)` | Return `{ id, scopes, claims? }`, or `null` → 401 + `WWW-Authenticate`. **Must reject tokens whose audience is not this server's canonical resource** |
215
216
  | `context(req, principal)` | Build per-request ctx for tools (DB, env, …). Use `principal?.claims` for tenant / plan / role without re-decoding the bearer |
216
- | `audit?(entry)` | Opt-in **metrics hook**: pass any function to receive tool results **and** auth denials (bad token, missing scope, query-string token), plus the 500 path when a host port throws. `entry.method` is `""` for transport-level denials made before parsing. Protocol errors (malformed JSON, bad `jsonrpc`, unsupported protocol version, oversized body, batch) are **not** audited. Throwing from this port never fails the request, and neither does hanging — see `auditTimeoutMs` below. Omit it and the library stays silent |
217
+ | `audit?(entry)` | Opt-in **metrics hook**: pass any function to receive tool results **and** auth denials (bad token, missing scope, query-string token, rejected browser Origin), plus the 500 path when a host port throws. `entry.method` is `""` for transport-level denials made before parsing (`query_string_token`, `unauthorized`, `origin_not_allowed`). Protocol errors (malformed JSON, bad `jsonrpc`, unsupported protocol version, oversized body, batch) are **not** audited. Throwing from this port never fails the request, and neither does hanging — see `auditTimeoutMs` below. Omit it and the library stays silent |
217
218
 
218
219
  `auditTimeoutMs` (default 1000ms) races `audit` against a timeout, so a
219
220
  slow sink can't stall a response past that bound either.
@@ -233,14 +234,14 @@ createMcpApp({ /* … */, audit: consoleAudit });
233
234
  createMcpApp({
234
235
  /* … */,
235
236
  audit: (entry) => {
236
- // entry: { method, tool?, principalId?, ok, error?, durationMs }
237
+ // entry.source === "mcp" | "oauth"
237
238
  void metrics.record(entry);
238
239
  },
239
240
  });
240
241
  ```
241
242
 
242
- Failures (denials and tool exceptions) go to `console.error` when using
243
- `consoleAudit`; everything else to `console.log`. Entries never carry raw
243
+ Failures (MCP denials and tool exceptions) go to `console.error` when using
244
+ `consoleAudit`; MCP successes and OAuth events go to `console.log`. Entries never carry raw
244
245
  exceptions or stack traces, so console output is safe. For anything beyond
245
246
  stdout, write your own `audit` function — that is the intended production path.
246
247
 
@@ -248,15 +249,21 @@ stdout, write your own `audit` function — that is the intended production path
248
249
 
249
250
  By default `initialize`, `ping`, and notifications are public. Override `publicMethods` if every method must require Bearer.
250
251
 
252
+ **Browser `Origin`.** MCP Streamable HTTP expects servers to validate the
253
+ request `Origin` header. Pass `allowedRequestOrigins` on `createMcpHandler` /
254
+ `createMcpApp`. Native connectors omit the header and are admitted. An omitted
255
+ allowlist **rejects** any present `Origin` (use `["*"]` only if you mean it).
256
+ This is separate from Node Host `allowedOrigins` on `asNodeHandler`.
257
+
251
258
  ### OAuth (`createOAuthRouter`)
252
259
 
253
260
  | Port | Role |
254
261
  |------|------|
255
- | `codeSecret` | HMAC secret for signed auth codes (string or `(req) => string`) |
262
+ | `codeSecret` | Secret(s) sealing auth codes, consent tickets, and DCR client assertions, and keying `secretHash` (string, `string[]`, or `(req) => string \| string[]`). A single string works as before; pass an array to rotate keys — see [security.md](security.md#rotating-codesecret) |
256
263
  | `resolveUser` | Consenting user, or `null` → redirect to `loginUrl` |
257
264
  | `loginUrl` | Where unauthenticated authorize requests go |
258
265
  | `mintAccessToken` | Issue a **user-bound**, **audience-bound** access token (`resource` is the RFC 8707 URI) — never a shared god token |
259
- | `refreshAccessToken?` | If set, metadata advertises `refresh_token`; also receives `resource` |
266
+ | `refreshAccessToken?` | If set, metadata advertises `refresh_token`; receives `resource` and optional client `scope` (advertised-scope check is library-side). **MUST reject refresh tokens not originally issued for that `resource`, and any `scope` outside the originally granted set** — opaque RT, same pattern as audience |
260
267
  | `revokeToken?` | If set, mounts `/revoke` and advertises `revocation_endpoint`. Well-formed authenticated revoke → 200 even if the token is unknown; `invalid_request` 400 and `invalid_client` 401 still apply |
261
268
  | `codeStore?` | Shared single-use jti store for multi-instance (`consume(jti, expMs)`); pruning in-memory default otherwise. Also backs consent-ticket single-use — see below |
262
269
  | `clientStore?` | Pre-registered clients: `get(clientId)` returns registered redirect URIs and auth method. Two ways to authenticate a confidential client's secret — **prefer `secretHash(clientId)`**: return a stored hash from `hashClientSecret`, and the library compares it with the same constant-time primitive it uses everywhere else; `verifySecret(clientId, presented)` is the fallback when you'd rather compare yourself. Credentials never enter the library either way |
@@ -315,17 +322,25 @@ createToolRegistry(tools, { validateArgs: false }) // default: off, easy adoptio
315
322
  ```
316
323
 
317
324
  - Handler may return a `string` or `{ content, isError? }`
318
- - Unknown tools and thrown errors become `isError: true` (not transport errors)
325
+ - A thrown handler exception becomes `isError: true` (not a transport error)
326
+ - `registry.call(name, ...)` itself returns `isError: true` for an unknown
327
+ `name` too — but the JSON-RPC `tools/call` dispatch (`createMcpHandler` /
328
+ `createMcpApp`) checks the name against the registry *before* calling it,
329
+ and answers an unrecognized one with a protocol-level `-32602` error
330
+ instead: MCP distinguishes "this request doesn't refer to a real tool"
331
+ from a tool's own execution outcome. The registry's `isError: true`
332
+ fallback only surfaces to a caller that invokes `registry.call` directly,
333
+ outside that dispatch.
319
334
  - Thrown exceptions are **redacted** by default (`"Tool execution failed"`); pass `onToolError` to map them. An `onToolError` that itself throws or returns a non-string falls back to the redacted default
320
335
  - Duplicate tool names throw at registry construction
321
336
  - Turn `validateArgs: true` once clients send schema-valid args. The evaluated
322
337
  subset is `type`, `enum`, `required`, `properties`, `items`, `minimum`,
323
- `maximum`. Pure metadata (`description`, `title`, `$schema`, `$id`,
338
+ `maximum`, and boolean `additionalProperties`. Pure metadata (`description`, `title`, `$schema`, `$id`,
324
339
  `$comment`, `default`, `examples`, `deprecated`, `readOnly`, `writeOnly`,
325
340
  `format`) is allowed and never evaluated. Any other keyword is not
326
341
  evaluated — and with `validateArgs: true` `createToolRegistry` throws at
327
- construction so a schema like `pattern` or `additionalProperties` cannot
328
- silently look enforced
342
+ construction so a schema like `pattern` or a schema-valued
343
+ `additionalProperties` cannot silently look enforced
329
344
 
330
345
  ### Wrapping an API as a tool
331
346
 
@@ -370,6 +385,15 @@ const getWeather = apiTool({
370
385
  echoes back). Pass `onError(res)` to shape it yourself, e.g. include a
371
386
  redacted version of the body. `fetch` is overridable — testing, request
372
387
  signing, a custom agent.
388
+ - **`timeoutMs`** (default `30000`) aborts the upstream call past that many
389
+ ms — an upstream that never responds otherwise hangs the tool call (and
390
+ the MCP request behind it) indefinitely. Pass `false` to disable; a
391
+ timeout firing surfaces as `isError: true` with a clear message, not a
392
+ thrown exception `onToolError` would redact.
393
+ - **`maxResponseBytes`** (default 1 MiB) caps the upstream response body
394
+ before `respond`/`onError` ever see it — the same memory-exhaustion shape
395
+ an unbounded *incoming* request body is. Exceeding it also surfaces as
396
+ `isError: true`, not a thrown exception.
373
397
  - `defineTool` alone (without `request`/`respond`) is just the typed-args
374
398
  layer over a regular `ToolDef` — use it for anything, not only REST calls.
375
399