@aotter/mantle 0.1.5-alpha.1 → 0.2.0-alpha.10

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 (495) hide show
  1. package/README.md +166 -298
  2. package/dist/auth/adapter.d.ts +32 -0
  3. package/dist/auth/adapter.js +288 -0
  4. package/dist/auth/admin.d.ts +3 -0
  5. package/dist/auth/admin.js +21 -0
  6. package/dist/auth/caller.d.ts +86 -0
  7. package/dist/auth/caller.js +155 -0
  8. package/dist/auth/hardening.d.ts +24 -0
  9. package/dist/auth/hardening.js +40 -0
  10. package/dist/auth/identity.d.ts +121 -0
  11. package/dist/auth/identity.js +203 -0
  12. package/dist/auth/oauth.d.ts +63 -0
  13. package/dist/auth/oauth.js +217 -0
  14. package/dist/auth/resource-seeds.js +10 -0
  15. package/dist/auth/routes.d.ts +32 -0
  16. package/dist/auth/routes.js +226 -0
  17. package/dist/build/compiler.d.ts +721 -0
  18. package/dist/build/compiler.js +7029 -0
  19. package/dist/build/spec/domain/model/ManifestGrammar.d.ts +439 -0
  20. package/dist/build/spec/domain/model/RuntimePlan.d.ts +168 -0
  21. package/dist/build/spec/domain/model/SqlIr.d.ts +129 -0
  22. package/dist/build/spec/domain/model/UserFields.d.ts +21 -0
  23. package/dist/build/spec/domain/service/PlanFingerprint.d.ts +7 -0
  24. package/dist/build/spec/kernel/diagnostic.d.ts +108 -0
  25. package/dist/bun/assets.d.ts +2 -0
  26. package/dist/bun/assets.js +24 -0
  27. package/dist/domain/adminOrder.js +24 -0
  28. package/dist/domain/columns.js +57 -0
  29. package/dist/domain/describe/chunk-ZVXBGP7Y.js +424 -0
  30. package/dist/domain/describe/index.d.ts +58 -0
  31. package/dist/domain/describe/index.js +115 -0
  32. package/dist/domain/describe/target-hash.d.ts +8 -0
  33. package/dist/domain/describe/target-hash.js +12 -0
  34. package/dist/domain/describe/targets.d.ts +40 -0
  35. package/dist/domain/entry.js +33 -0
  36. package/dist/domain/generated-tools.js +19 -0
  37. package/dist/domain/generation.d.ts +16 -0
  38. package/dist/domain/generation.js +1 -0
  39. package/dist/domain/input.js +17 -0
  40. package/dist/domain/model.js +1 -0
  41. package/dist/domain/paging.js +230 -0
  42. package/dist/domain/recipes.js +24 -0
  43. package/dist/domain/userReferences.js +5 -0
  44. package/dist/domain/wireScalars.js +47 -0
  45. package/dist/infrastructure/adminEntries.js +153 -0
  46. package/dist/infrastructure/adminPaging.js +72 -0
  47. package/dist/infrastructure/artifacts.js +71 -0
  48. package/dist/infrastructure/authSchema.js +83 -0
  49. package/dist/infrastructure/bundle.js +188 -0
  50. package/dist/infrastructure/cli.js +118 -0
  51. package/dist/infrastructure/generate.d.ts +12 -0
  52. package/dist/infrastructure/generate.js +189 -0
  53. package/dist/infrastructure/generateAuth.d.ts +8 -0
  54. package/dist/infrastructure/generateAuth.js +74 -0
  55. package/dist/infrastructure/help.js +60 -0
  56. package/dist/infrastructure/init.js +54 -0
  57. package/dist/infrastructure/inputs.js +41 -0
  58. package/dist/infrastructure/mounts.js +25 -0
  59. package/dist/infrastructure/wrangler.js +109 -0
  60. package/dist/kernel/authorization.d.ts +4 -0
  61. package/dist/kernel/authorization.js +16 -0
  62. package/dist/kernel/binds.d.ts +60 -0
  63. package/dist/kernel/binds.js +49 -0
  64. package/dist/kernel/caller-identity.d.ts +24 -0
  65. package/dist/kernel/caller-identity.js +22 -0
  66. package/dist/kernel/contract-error.d.ts +12 -0
  67. package/dist/kernel/contract-error.js +14 -0
  68. package/dist/kernel/contracts.d.ts +143 -0
  69. package/dist/kernel/contracts.js +97 -0
  70. package/dist/kernel/cron.d.ts +1 -0
  71. package/dist/kernel/cron.js +58 -0
  72. package/dist/kernel/dialect/common.d.ts +38 -0
  73. package/dist/kernel/dialect/common.js +6 -0
  74. package/dist/kernel/dialect/postgres.d.ts +16 -0
  75. package/dist/kernel/dialect/postgres.js +18 -0
  76. package/dist/kernel/dialect/sqlite.d.ts +20 -0
  77. package/dist/kernel/dialect/sqlite.js +34 -0
  78. package/dist/kernel/drain.d.ts +5 -0
  79. package/dist/kernel/drain.js +35 -0
  80. package/dist/kernel/driver/bun-sqlite.d.ts +19 -0
  81. package/dist/kernel/driver/bun-sqlite.js +77 -0
  82. package/dist/kernel/driver/d1.d.ts +24 -0
  83. package/dist/kernel/driver/d1.js +22 -0
  84. package/dist/kernel/driver/postgres.d.ts +25 -0
  85. package/dist/kernel/driver/postgres.js +99 -0
  86. package/dist/kernel/driver/transaction-context.d.ts +10 -0
  87. package/dist/kernel/driver/transaction-context.js +29 -0
  88. package/dist/kernel/email.d.ts +28 -0
  89. package/dist/kernel/email.js +38 -0
  90. package/dist/kernel/host-access.d.ts +10 -0
  91. package/dist/kernel/host-access.js +65 -0
  92. package/dist/kernel/host-caller.d.ts +39 -0
  93. package/dist/kernel/host-caller.js +56 -0
  94. package/dist/kernel/json-body.d.ts +4 -0
  95. package/dist/kernel/json-body.js +44 -0
  96. package/dist/kernel/mcp-apps.d.ts +69 -0
  97. package/dist/kernel/mcp-apps.js +40 -0
  98. package/dist/kernel/mcp-call.d.ts +5 -0
  99. package/dist/kernel/mcp-call.js +25 -0
  100. package/dist/kernel/mcp-handler.js +371 -0
  101. package/dist/kernel/mcp.d.ts +110 -0
  102. package/dist/kernel/mcp.js +50 -0
  103. package/dist/kernel/media.d.ts +27 -0
  104. package/dist/kernel/media.js +216 -0
  105. package/dist/kernel/paging.d.ts +55 -0
  106. package/dist/kernel/paging.js +95 -0
  107. package/dist/kernel/publishing.d.ts +4 -0
  108. package/dist/kernel/publishing.js +16 -0
  109. package/dist/kernel/r2-media.d.ts +42 -0
  110. package/dist/kernel/r2-media.js +78 -0
  111. package/dist/kernel/rest-dispatch.js +38 -0
  112. package/dist/kernel/rest-router.js +64 -0
  113. package/dist/kernel/rest.d.ts +58 -0
  114. package/dist/kernel/rest.js +41 -0
  115. package/dist/kernel/scalars.d.ts +6 -0
  116. package/dist/kernel/scalars.js +44 -0
  117. package/dist/kernel/search.js +5 -0
  118. package/dist/kernel/site-types.d.ts +146 -0
  119. package/dist/kernel/site-types.js +1 -0
  120. package/dist/kernel/site.d.ts +39 -0
  121. package/dist/kernel/site.js +32 -0
  122. package/dist/kernel/store.d.ts +175 -0
  123. package/dist/kernel/store.js +269 -0
  124. package/dist/kernel/surface.d.ts +10 -0
  125. package/dist/kernel/surface.js +19 -0
  126. package/dist/kernel/sweep.d.ts +17 -0
  127. package/dist/kernel/sweep.js +27 -0
  128. package/dist/kernel/translations.d.ts +1 -0
  129. package/dist/kernel/translations.js +13 -0
  130. package/dist/kernel/user-caller.d.ts +31 -0
  131. package/dist/kernel/user-caller.js +67 -0
  132. package/dist/kernel/user-field-name.js +3 -0
  133. package/dist/kernel/user-fields.d.ts +35 -0
  134. package/dist/kernel/user-fields.js +81 -0
  135. package/dist/kernel/validation.d.ts +15 -0
  136. package/dist/kernel/validation.js +54 -0
  137. package/dist/kernel/web-gateway.js +68 -0
  138. package/dist/kernel/web-head.js +15 -0
  139. package/dist/kernel/web-index.js +40 -0
  140. package/dist/kernel/web-response.js +8 -0
  141. package/dist/kernel/web.d.ts +77 -0
  142. package/dist/kernel/web.js +124 -0
  143. package/dist/kernel/where.js +27 -0
  144. package/dist/testing/index.d.ts +1 -0
  145. package/dist/testing/index.js +1 -0
  146. package/dist/usecase/adminBindings.js +143 -0
  147. package/dist/usecase/adminClient.js +87 -0
  148. package/dist/usecase/adminDescriptors.js +107 -0
  149. package/dist/usecase/generate.js +166 -0
  150. package/dist/usecase/handlers.js +21 -0
  151. package/dist/usecase/mcp-media.js +17 -0
  152. package/dist/usecase/mcp.js +100 -0
  153. package/dist/usecase/paging.js +71 -0
  154. package/dist/usecase/publishing.js +63 -0
  155. package/dist/usecase/refusals.js +27 -0
  156. package/dist/usecase/rest.js +54 -0
  157. package/dist/usecase/search.js +7 -0
  158. package/dist/usecase/site.js +39 -0
  159. package/dist/usecase/stores.js +64 -0
  160. package/dist/usecase/tool-version.js +9 -0
  161. package/dist/usecase/translations.js +78 -0
  162. package/dist/usecase/ttl.js +20 -0
  163. package/dist/usecase/validation.js +241 -0
  164. package/dist/usecase/web.js +23 -0
  165. package/docs/examples/README.md +39 -18
  166. package/docs/examples/app-shell/.dev.vars.example +4 -0
  167. package/docs/examples/app-shell/README.md +121 -0
  168. package/docs/examples/app-shell/accept.mjs +91 -0
  169. package/docs/examples/app-shell/gitignore +7 -0
  170. package/docs/examples/app-shell/package.json +43 -0
  171. package/docs/examples/app-shell/pnpm-workspace.yaml +8 -0
  172. package/docs/examples/app-shell/smoke-kit.mjs +168 -0
  173. package/docs/examples/app-shell/src/auth.ts +58 -0
  174. package/docs/examples/app-shell/src/handlers.ts +5 -0
  175. package/docs/examples/app-shell/src/index.ts +4 -0
  176. package/docs/examples/app-shell/src/routes.ts +14 -0
  177. package/docs/examples/app-shell/src/schedule.ts +8 -0
  178. package/docs/examples/app-shell/src/service.ts +70 -0
  179. package/docs/examples/app-shell/src/sign-in.ts +41 -0
  180. package/docs/examples/app-shell/src/signup.ts +8 -0
  181. package/docs/examples/app-shell/tsconfig.json +26 -0
  182. package/docs/examples/app-shell/worker-configuration.d.ts +22 -0
  183. package/docs/examples/app-shell/wrangler.jsonc +62 -0
  184. package/docs/examples/club-events/README.md +65 -0
  185. package/docs/examples/club-events/manifests/club-events.yaml +247 -0
  186. package/docs/examples/club-events/smoke.mjs +127 -0
  187. package/docs/examples/club-events/src/handlers.ts +25 -0
  188. package/docs/examples/club-events/src/signup.ts +9 -0
  189. package/docs/examples/generated-web/README.md +78 -0
  190. package/docs/examples/generated-web/posts.yaml +38 -0
  191. package/docs/examples/generated-web/seed.sql +6 -0
  192. package/docs/examples/generated-web/src/worker.ts +43 -0
  193. package/docs/examples/generated-web/tsconfig.json +8 -0
  194. package/docs/examples/generated-web/wrangler.jsonc +8 -0
  195. package/docs/examples/purchasing/.dev.vars.example +6 -0
  196. package/docs/examples/purchasing/README.md +99 -0
  197. package/docs/examples/purchasing/manifests/purchasing.yaml +374 -0
  198. package/docs/examples/purchasing/seed.sql +4 -0
  199. package/docs/examples/purchasing/smoke.mjs +191 -0
  200. package/docs/examples/purchasing/src/admin.tsx +13 -0
  201. package/docs/examples/purchasing/src/handlers.ts +33 -0
  202. package/docs/examples/purchasing/src/notify.ts +29 -0
  203. package/docs/examples/purchasing/src/requests-page.ts +57 -0
  204. package/docs/examples/purchasing/src/routes.ts +53 -0
  205. package/docs/examples/purchasing/src/schedule.ts +31 -0
  206. package/docs/examples/purchasing/src/signup.ts +5 -0
  207. package/docs/examples/purchasing/src/supplier.ts +32 -0
  208. package/docs/examples/purchasing/wrangler.jsonc +69 -0
  209. package/docs/examples/recipes/README.md +60 -0
  210. package/docs/examples/recipes/manifests/recipes.yaml +75 -0
  211. package/docs/examples/recipes/smoke.mjs +102 -0
  212. package/docs/examples/recipes/src/media.ts +13 -0
  213. package/docs/examples/recipes/src/pages.ts +99 -0
  214. package/docs/examples/recipes/src/routes.ts +71 -0
  215. package/docs/examples/recipes/src/site-config.ts +12 -0
  216. package/docs/examples/recipes/wrangler.jsonc +74 -0
  217. package/docs/handbook/cloudflare/authentication.md +180 -322
  218. package/docs/handbook/cloudflare/chatgpt-sites.md +5 -23
  219. package/docs/handbook/cloudflare/deploy-and-operate.md +55 -77
  220. package/docs/handbook/cloudflare/media-r2.md +61 -205
  221. package/docs/handbook/cloudflare/service-entry.md +239 -0
  222. package/docs/handbook/concepts/authorization.md +10 -107
  223. package/docs/handbook/concepts/four-atoms.md +9 -168
  224. package/docs/handbook/concepts/lifecycle-and-locales.md +9 -132
  225. package/docs/handbook/concepts/mcp-and-agents.md +24 -121
  226. package/docs/handbook/concepts/procedures-and-triggers.md +5 -217
  227. package/docs/handbook/concepts/runtime-and-adapters.md +15 -169
  228. package/docs/handbook/concepts/views.md +10 -185
  229. package/docs/handbook/examples/hub.md +3 -8
  230. package/docs/handbook/guides/adapter-guide.md +11 -0
  231. package/docs/handbook/guides/admin-ui.md +115 -133
  232. package/docs/handbook/guides/agent-setup.md +64 -74
  233. package/docs/handbook/guides/agent-workflow-acceptance.md +232 -0
  234. package/docs/handbook/guides/email.md +86 -0
  235. package/docs/handbook/guides/local-testing.md +40 -0
  236. package/docs/handbook/guides/mcp-apps.md +560 -0
  237. package/docs/handbook/guides/native-admin.md +566 -0
  238. package/docs/handbook/guides/native-execution.md +133 -0
  239. package/docs/handbook/guides/oauth.md +219 -0
  240. package/docs/handbook/guides/postgres-adapters.md +56 -0
  241. package/docs/handbook/guides/public-pages.md +87 -0
  242. package/docs/handbook/guides/size-and-startup.md +9 -0
  243. package/docs/handbook/guides/sql-patterns.md +266 -0
  244. package/docs/handbook/guides/typed-queries.md +211 -107
  245. package/docs/handbook/navigation.json +176 -55
  246. package/docs/handbook/reference/admin-descriptors.md +53 -0
  247. package/docs/handbook/reference/authorization.md +39 -266
  248. package/docs/handbook/reference/describe.md +56 -0
  249. package/docs/handbook/reference/diagnostics.md +19 -186
  250. package/docs/handbook/reference/features.md +45 -55
  251. package/docs/handbook/reference/manifest.md +6 -129
  252. package/docs/handbook/reference/procedure.md +11 -303
  253. package/docs/handbook/reference/schema.md +64 -240
  254. package/docs/handbook/reference/site-config.md +5 -218
  255. package/docs/handbook/reference/sql.md +64 -0
  256. package/docs/handbook/reference/surface.md +392 -201
  257. package/docs/handbook/reference/trigger.md +56 -192
  258. package/docs/handbook/reference/view.md +24 -243
  259. package/docs/handbook/releases/index.md +244 -77
  260. package/docs/handbook/start/agents.md +45 -0
  261. package/docs/handbook/start/overview.md +9 -48
  262. package/docs/handbook/start/project-and-cli.md +51 -104
  263. package/docs/handbook/start/quickstart-worker.md +43 -178
  264. package/docs/skills/develop/SKILL.md +79 -249
  265. package/docs/skills/integrate/SKILL.md +39 -50
  266. package/docs/skills/media-gc/SKILL.md +34 -10
  267. package/docs/skills/provision/SKILL.md +36 -186
  268. package/docs/skills/update/SKILL.md +23 -38
  269. package/docs/source/scripts/postgres-adapter.ts +101 -0
  270. package/docs/upgrade-0.1-to-0.2.md +227 -0
  271. package/package.json +120 -131
  272. package/skills/README.md +48 -108
  273. package/skills/mantle/SKILL.md +76 -0
  274. package/dist/admin-ui.d.ts +0 -2
  275. package/dist/admin-ui.d.ts.map +0 -1
  276. package/dist/admin-ui.js +0 -2
  277. package/dist/admin-ui.js.map +0 -1
  278. package/dist/admin.d.ts +0 -2
  279. package/dist/admin.d.ts.map +0 -1
  280. package/dist/admin.js +0 -2
  281. package/dist/admin.js.map +0 -1
  282. package/dist/auth.d.ts +0 -2
  283. package/dist/auth.d.ts.map +0 -1
  284. package/dist/auth.js +0 -2
  285. package/dist/auth.js.map +0 -1
  286. package/dist/bun.d.ts +0 -2
  287. package/dist/bun.d.ts.map +0 -1
  288. package/dist/bun.js +0 -2
  289. package/dist/bun.js.map +0 -1
  290. package/dist/cli/generate-cloudflare.d.ts +0 -7
  291. package/dist/cli/generate-cloudflare.d.ts.map +0 -1
  292. package/dist/cli/generate-cloudflare.js +0 -123
  293. package/dist/cli/generate-cloudflare.js.map +0 -1
  294. package/dist/cli/generate-project.d.ts +0 -25
  295. package/dist/cli/generate-project.d.ts.map +0 -1
  296. package/dist/cli/generate-project.js +0 -282
  297. package/dist/cli/generate-project.js.map +0 -1
  298. package/dist/cli/generate-sites.d.ts +0 -8
  299. package/dist/cli/generate-sites.d.ts.map +0 -1
  300. package/dist/cli/generate-sites.js +0 -273
  301. package/dist/cli/generate-sites.js.map +0 -1
  302. package/dist/cli/generate.d.ts +0 -20
  303. package/dist/cli/generate.d.ts.map +0 -1
  304. package/dist/cli/generate.js +0 -299
  305. package/dist/cli/generate.js.map +0 -1
  306. package/dist/cli/harness.d.ts +0 -3
  307. package/dist/cli/harness.d.ts.map +0 -1
  308. package/dist/cli/harness.js +0 -214
  309. package/dist/cli/harness.js.map +0 -1
  310. package/dist/cli/main.d.ts +0 -3
  311. package/dist/cli/main.d.ts.map +0 -1
  312. package/dist/cli/main.js +0 -77
  313. package/dist/cli/main.js.map +0 -1
  314. package/dist/cli/skills.d.ts +0 -3
  315. package/dist/cli/skills.d.ts.map +0 -1
  316. package/dist/cli/skills.js +0 -128
  317. package/dist/cli/skills.js.map +0 -1
  318. package/dist/cloudflare.d.ts +0 -2
  319. package/dist/cloudflare.d.ts.map +0 -1
  320. package/dist/cloudflare.js +0 -2
  321. package/dist/cloudflare.js.map +0 -1
  322. package/dist/codegen/emitMantleModule.d.ts +0 -22
  323. package/dist/codegen/emitMantleModule.d.ts.map +0 -1
  324. package/dist/codegen/emitMantleModule.js +0 -291
  325. package/dist/codegen/emitMantleModule.js.map +0 -1
  326. package/dist/codegen.d.ts +0 -2
  327. package/dist/codegen.d.ts.map +0 -1
  328. package/dist/codegen.js +0 -2
  329. package/dist/codegen.js.map +0 -1
  330. package/dist/runtime-testing.d.ts +0 -2
  331. package/dist/runtime-testing.d.ts.map +0 -1
  332. package/dist/runtime-testing.js +0 -2
  333. package/dist/runtime-testing.js.map +0 -1
  334. package/dist/runtime.d.ts +0 -2
  335. package/dist/runtime.d.ts.map +0 -1
  336. package/dist/runtime.js +0 -2
  337. package/dist/runtime.js.map +0 -1
  338. package/dist/spec.d.ts +0 -2
  339. package/dist/spec.d.ts.map +0 -1
  340. package/dist/spec.js +0 -2
  341. package/dist/spec.js.map +0 -1
  342. package/dist/vercel-libsql.d.ts +0 -2
  343. package/dist/vercel-libsql.d.ts.map +0 -1
  344. package/dist/vercel-libsql.js +0 -2
  345. package/dist/vercel-libsql.js.map +0 -1
  346. package/dist/vercel.d.ts +0 -2
  347. package/dist/vercel.d.ts.map +0 -1
  348. package/dist/vercel.js +0 -2
  349. package/dist/vercel.js.map +0 -1
  350. package/dist/web.d.ts +0 -2
  351. package/dist/web.d.ts.map +0 -1
  352. package/dist/web.js +0 -2
  353. package/dist/web.js.map +0 -1
  354. package/docs/adapter-guide.md +0 -222
  355. package/docs/adr/0001-four-atom-manifest-model.md +0 -440
  356. package/docs/adr/0002-closed-enums-for-bindings.md +0 -253
  357. package/docs/adr/0007-ai-as-primary-author.md +0 -473
  358. package/docs/adr/0008-structured-diagnostic-shape.md +0 -338
  359. package/docs/adr/0009-consumer-supplied-manifests.md +0 -149
  360. package/docs/adr/0010-locale-and-translates.md +0 -453
  361. package/docs/adr/0011-adapter-port-spec.md +0 -281
  362. package/docs/adr/0012-views-as-public-rest.md +0 -161
  363. package/docs/adr/0013-agent-provisioned-consumer-projects.md +0 -154
  364. package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +0 -900
  365. package/docs/adr/0016-site-semantic-layer.md +0 -70
  366. package/docs/adr/0017-media-multi-variant-agent-side-optimization.md +0 -181
  367. package/docs/adr/0018-core-starters-repository-boundary.md +0 -179
  368. package/docs/adr/0019-sealed-manifest-runtime-pipeline.md +0 -240
  369. package/docs/adr/0020-builtin-handler-contracts-and-matched-upsert.md +0 -199
  370. package/docs/adr/0021-retire-starter-scaffolding.md +0 -30
  371. package/docs/adr/0022-caller-observed-version-occ.md +0 -212
  372. package/docs/adr/0023-port-failure-contract.md +0 -69
  373. package/docs/adr/0024-manifest-native-schema-tables.md +0 -264
  374. package/docs/adr/0025-reserved-native-columns-and-published-only-public-views.md +0 -105
  375. package/docs/adr/0026-progressive-generate.md +0 -36
  376. package/docs/adr/0027-scheduled-procedure-triggers.md +0 -31
  377. package/docs/adr/0028-schema-ttl-expiry.md +0 -33
  378. package/docs/adr/README.md +0 -81
  379. package/docs/adr/adr-lite-803-request-diagnostics.md +0 -38
  380. package/docs/adr/adr-lite-808-route-readiness.md +0 -47
  381. package/docs/adr/adr-lite-809-bounded-public-content.md +0 -71
  382. package/docs/adr/adr-lite-812-native-parity.md +0 -150
  383. package/docs/adr/adr-lite-823-home-statistics.md +0 -64
  384. package/docs/adr/adr-lite-842-mcp-authoring.md +0 -30
  385. package/docs/adr/adr-lite-845-frontend-client.md +0 -38
  386. package/docs/adr/adr-lite-861-admin-webmcp.md +0 -42
  387. package/docs/adr/adr-lite-909-admin-ui-kit.md +0 -37
  388. package/docs/agent-prompts.md +0 -64
  389. package/docs/api-mcp-authorization.md +0 -5
  390. package/docs/assets/mantle-admin-developer.png +0 -0
  391. package/docs/assets/mantle-admin-operations.png +0 -0
  392. package/docs/assets/mantle-hero.jpg +0 -0
  393. package/docs/auth-hosting-model.md +0 -166
  394. package/docs/cloudflare-low-level-composition.md +0 -5
  395. package/docs/consumer-onboarding-audit.md +0 -65
  396. package/docs/deferred-lifecycle-queues.md +0 -5
  397. package/docs/design-atoms.md +0 -16
  398. package/docs/design-references/editorial-blog-2026-05-05.md +0 -218
  399. package/docs/direct-authoring.md +0 -5
  400. package/docs/examples/builtin-commerce.md +0 -269
  401. package/docs/examples/builtin-intake.md +0 -143
  402. package/docs/examples/builtin-legal-documents.md +0 -189
  403. package/docs/examples/builtin-procurement.md +0 -241
  404. package/docs/examples/builtin-publication.md +0 -241
  405. package/docs/examples/builtin-reservation.md +0 -149
  406. package/docs/examples/cf-primitives-commerce-inventory.md +0 -809
  407. package/docs/examples/cf-primitives-guarded-api.md +0 -429
  408. package/docs/examples/cf-primitives-intake-hooks.md +0 -319
  409. package/docs/examples/host-chatgpt-sites/.openai/hosting.json +0 -1
  410. package/docs/examples/host-chatgpt-sites/README.md +0 -70
  411. package/docs/examples/host-chatgpt-sites/drizzle/0000_sites_users.sql +0 -8
  412. package/docs/examples/host-chatgpt-sites/drizzle/0001_mantle.sql +0 -294
  413. package/docs/examples/host-chatgpt-sites/drizzle/0002_article_cover.sql +0 -5
  414. package/docs/examples/host-chatgpt-sites/drizzle/0003_store_instance_id.sql +0 -1
  415. package/docs/examples/host-chatgpt-sites/drizzle/0004_mantle.sql +0 -10
  416. package/docs/examples/host-chatgpt-sites/drizzle/0005_mantle.sql +0 -24
  417. package/docs/examples/host-chatgpt-sites/drizzle/meta/_journal.json +0 -1
  418. package/docs/examples/host-chatgpt-sites/drizzle/meta/mantle-state.json +0 -60
  419. package/docs/examples/host-chatgpt-sites/manifests/site.yaml +0 -27
  420. package/docs/examples/host-chatgpt-sites/package.json +0 -1
  421. package/docs/examples/host-chatgpt-sites/public/site.css +0 -1
  422. package/docs/examples/host-chatgpt-sites/scripts/build.mjs +0 -13
  423. package/docs/examples/host-chatgpt-sites/scripts/check.mjs +0 -101
  424. package/docs/examples/host-chatgpt-sites/scripts/migration.mjs +0 -43
  425. package/docs/examples/host-chatgpt-sites/src/chatgpt-auth.ts +0 -66
  426. package/docs/examples/host-chatgpt-sites/src/index.ts +0 -46
  427. package/docs/examples/host-chatgpt-sites/src/mcp.ts +0 -49
  428. package/docs/examples/host-chatgpt-sites/src/media.ts +0 -109
  429. package/docs/examples/host-chatgpt-sites/src/r2-lab.ts +0 -38
  430. package/docs/examples/host-chatgpt-sites/src/storage-fingerprint.json +0 -1
  431. package/docs/examples/host-chatgpt-sites/src/web.ts +0 -35
  432. package/docs/examples/host-chatgpt-sites/tsconfig.json +0 -1
  433. package/docs/examples/host-chatgpt-sites/wrangler.jsonc +0 -10
  434. package/docs/examples/host-local-admin-otp/.dev.vars.example +0 -3
  435. package/docs/examples/host-local-admin-otp/README.md +0 -80
  436. package/docs/examples/host-local-admin-otp/ensure-dev-vars.mjs +0 -5
  437. package/docs/examples/host-local-admin-otp/manifests/site.yaml +0 -25
  438. package/docs/examples/host-local-admin-otp/package.json +0 -29
  439. package/docs/examples/host-local-admin-otp/public/.gitkeep +0 -1
  440. package/docs/examples/host-local-admin-otp/smoke.mjs +0 -141
  441. package/docs/examples/host-local-admin-otp/src/index.ts +0 -54
  442. package/docs/examples/host-local-admin-otp/tsconfig.json +0 -17
  443. package/docs/examples/host-local-admin-otp/wrangler.jsonc +0 -23
  444. package/docs/examples/host-minimal-worker/README.md +0 -41
  445. package/docs/examples/host-minimal-worker/manifests/site.yaml +0 -25
  446. package/docs/examples/host-minimal-worker/package.json +0 -26
  447. package/docs/examples/host-minimal-worker/smoke.mjs +0 -23
  448. package/docs/examples/host-minimal-worker/src/index.ts +0 -4
  449. package/docs/examples/host-minimal-worker/tsconfig.json +0 -17
  450. package/docs/examples/host-minimal-worker/wrangler.jsonc +0 -22
  451. package/docs/handbook/chatgpt-sites/equipment-checkout.md +0 -231
  452. package/docs/handbook/chatgpt-sites/generated-app.md +0 -84
  453. package/docs/handbook/chatgpt-sites/host-reference.md +0 -113
  454. package/docs/handbook/chatgpt-sites/index.md +0 -133
  455. package/docs/handbook/cloudflare/bindings.md +0 -291
  456. package/docs/handbook/cloudflare/conventional-worker.md +0 -166
  457. package/docs/handbook/cloudflare/deferred-hooks-queues.md +0 -170
  458. package/docs/handbook/cloudflare/low-level-composition.md +0 -169
  459. package/docs/handbook/cloudflare/public-web.md +0 -144
  460. package/docs/handbook/cloudflare/site-chrome.md +0 -75
  461. package/docs/handbook/examples/commerce-transaction.md +0 -11
  462. package/docs/handbook/examples/commerce.md +0 -11
  463. package/docs/handbook/examples/guarded-api.md +0 -10
  464. package/docs/handbook/examples/intake-form.md +0 -12
  465. package/docs/handbook/examples/intake-hooks.md +0 -11
  466. package/docs/handbook/examples/legal-documents.md +0 -10
  467. package/docs/handbook/examples/procurement-approvals.md +0 -10
  468. package/docs/handbook/examples/publication.md +0 -10
  469. package/docs/handbook/examples/reservation.md +0 -10
  470. package/docs/handbook/start/quickstart-admin.md +0 -52
  471. package/docs/labels.md +0 -71
  472. package/docs/media-uploads.md +0 -5
  473. package/docs/migration-0.1.2.md +0 -13
  474. package/docs/performance-harness.md +0 -228
  475. package/docs/release-process.md +0 -244
  476. package/docs/schema-indexes.md +0 -5
  477. package/docs/sealed-pipeline-ownership.md +0 -101
  478. package/docs/skills/plugin/SKILL.md +0 -132
  479. package/docs/skills/theme/SKILL.md +0 -77
  480. package/docs/spec-only-host-adoption.md +0 -157
  481. package/docs/transaction-patterns.md +0 -5
  482. package/skills/develop/SKILL.md +0 -258
  483. package/skills/install/SKILL.md +0 -112
  484. package/skills/integrate/SKILL.md +0 -60
  485. package/skills/media-gc/SKILL.md +0 -85
  486. package/skills/plugin/SKILL.md +0 -132
  487. package/skills/provision/SKILL.md +0 -194
  488. package/skills/theme/SKILL.md +0 -77
  489. package/skills/update/SKILL.md +0 -51
  490. package/templates/chatgpt-sites/auth.ts +0 -66
  491. package/templates/chatgpt-sites/build.mjs +0 -14
  492. package/templates/chatgpt-sites/env.ts +0 -1
  493. package/templates/chatgpt-sites/mcp.ts +0 -48
  494. package/templates/chatgpt-sites/public-mcp.ts +0 -29
  495. package/templates/chatgpt-sites/smoke-local.mjs +0 -20
@@ -0,0 +1,69 @@
1
+ /** MCP Apps (ADR-0029 D7): UI resources served beside the tools that render in them. */
2
+ export declare const RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
3
+ /** What `resources/read` passes an HTML loader. */
4
+ export interface McpAppRender {
5
+ /** The tools this caller's `tools/list` shows on the endpoint, so the HTML names none it cannot see. */
6
+ readonly tools: ReadonlySet<string>;
7
+ /** The request's environment as the handler received it: Worker bindings such as `ASSETS`, or whatever a Bun host passes. */
8
+ readonly env: unknown;
9
+ /** The `resources/read` request, for its origin. */
10
+ readonly request: Request;
11
+ }
12
+ /** The CSP a resource asks its host for, as `_meta.ui.csp`. */
13
+ export type McpAppCsp = {
14
+ readonly connectDomains?: readonly string[];
15
+ readonly resourceDomains?: readonly string[];
16
+ readonly frameDomains?: readonly string[];
17
+ readonly baseUriDomains?: readonly string[];
18
+ };
19
+ /** What a loader may return beside a plain string: the HTML and a CSP known only when it is read. It is sent on `resources/read` and replaces the resource's static `csp` (lists are not merged). */
20
+ export interface McpAppPage {
21
+ readonly html: string;
22
+ readonly csp?: McpAppCsp;
23
+ }
24
+ /** One `ui://` resource. The HTML is a reusable asset and never carries caller data: that travels in tool results. */
25
+ export interface McpAppResource {
26
+ readonly uri: string;
27
+ readonly name: string;
28
+ readonly title?: string;
29
+ readonly description?: string;
30
+ readonly html: string | ((render: McpAppRender) => string | McpAppPage | Promise<string | McpAppPage>);
31
+ readonly csp?: McpAppCsp;
32
+ readonly permissions?: {
33
+ readonly camera?: Record<string, never>;
34
+ readonly microphone?: Record<string, never>;
35
+ readonly geolocation?: Record<string, never>;
36
+ readonly clipboardWrite?: Record<string, never>;
37
+ };
38
+ readonly prefersBorder?: boolean;
39
+ /** `_meta.ui.domain`: the dedicated sandbox origin a host should serve this App from, in the form that host documents. */
40
+ readonly domain?: string;
41
+ /** Tool names whose results this resource renders. */
42
+ readonly renders?: readonly string[];
43
+ /** Views only this App may call (`visibility: ["app"]`); a client without MCP Apps does not see them. A Procedure cannot be hidden from the model. */
44
+ readonly appOnly?: readonly string[];
45
+ }
46
+ export interface McpApps {
47
+ readonly resources: readonly McpAppResource[];
48
+ }
49
+ /** An application-owned contributor resolved after the endpoint's caller gate. */
50
+ export type McpAppsSource = McpApps | ((context: Pick<McpAppRender, "env" | "request">) => McpApps | Promise<McpApps>);
51
+ /** Flatten contributors; `mcp(...)` reads static resources getters once at construction. */
52
+ export declare const resourcesOf: (apps: McpApps | readonly McpApps[]) => McpApps;
53
+ /** Static contributors are checked at construction; resolved factories are checked per request. A request-bound tool table defers tool-link checks until its first request. */
54
+ export declare function linkApps(apps: McpApps | readonly McpApps[], tools?: ReadonlySet<string>, views?: ReadonlySet<string>): {
55
+ resources: McpAppResource[];
56
+ renders: Map<string, string>;
57
+ appOnly: Set<string>;
58
+ };
59
+ /** One endpoint's Apps bound to its tool table; `previous` is reused while the table's names are unchanged. */
60
+ export declare function linkSurface(apps: McpApps | readonly McpApps[], selected: readonly {
61
+ name: string;
62
+ page?: unknown;
63
+ }[], previous?: {
64
+ key: string;
65
+ links: ReturnType<typeof linkApps>;
66
+ }): {
67
+ key: string;
68
+ links: ReturnType<typeof linkApps>;
69
+ };
@@ -0,0 +1,40 @@
1
+ import { ContractError } from "./contracts.js";
2
+ /** MCP Apps (ADR-0029 D7): UI resources served beside the tools that render in them. */
3
+ export const RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
4
+ /** Flatten contributors; `mcp(...)` reads static resources getters once at construction. */
5
+ export const resourcesOf = (apps) => ({ resources: (Array.isArray(apps) ? apps : [apps]).flatMap(app => app.resources) });
6
+ /** Static contributors are checked at construction; resolved factories are checked per request. A request-bound tool table defers tool-link checks until its first request. */
7
+ export function linkApps(apps, tools, views) {
8
+ const resources = resourcesOf(apps).resources;
9
+ const uris = new Set(), names = new Set(), renders = new Map(), appOnly = new Set();
10
+ const refuse = (message) => { throw new ContractError("MCP_APP_CONFLICT", message, "mcp:apps", { status: 500 }); };
11
+ for (const resource of resources) {
12
+ if (!resource.uri.startsWith("ui://"))
13
+ refuse(`MCP App resource '${resource.name}' must use a ui:// URI.`);
14
+ if (uris.has(resource.uri))
15
+ refuse(`MCP App resource URI '${resource.uri}' is registered twice.`);
16
+ if (names.has(resource.name))
17
+ refuse(`MCP App resource name '${resource.name}' is registered twice.`);
18
+ uris.add(resource.uri);
19
+ names.add(resource.name);
20
+ for (const tool of [...resource.renders ?? [], ...resource.appOnly ?? []]) {
21
+ if (tools && !tools.has(tool))
22
+ refuse(`MCP App resource '${resource.name}' names tool '${tool}', which this endpoint does not serve.`);
23
+ if (renders.has(tool) && renders.get(tool) !== resource.uri)
24
+ refuse(`Tool '${tool}' is claimed by two MCP App resources.`);
25
+ renders.set(tool, resource.uri);
26
+ }
27
+ // an App acts for the user without the model seeing the call, so only a read may be hidden from the model
28
+ for (const tool of resource.appOnly ?? []) {
29
+ if (views && !views.has(tool))
30
+ refuse(`App-only tool '${tool}' must be a View this endpoint serves.`);
31
+ appOnly.add(tool);
32
+ }
33
+ }
34
+ return { resources, renders, appOnly };
35
+ }
36
+ /** One endpoint's Apps bound to its tool table; `previous` is reused while the table's names are unchanged. */
37
+ export function linkSurface(apps, selected, previous) {
38
+ const key = JSON.stringify(selected.map(tool => [tool.name, !!tool.page]).sort(([a], [b]) => a.localeCompare(b)));
39
+ return previous?.key === key ? previous : { key, links: linkApps(apps, new Set(selected.map(tool => tool.name)), new Set(selected.filter(tool => tool.page).map(tool => tool.name))) };
40
+ }
@@ -0,0 +1,5 @@
1
+ import type { Caller } from "./contracts.js";
2
+ import type { Tool } from "./mcp.js";
3
+ export declare function callTool<Database, Handlers>(tool: Tool<Database, Handlers>, raw: Record<string, unknown>, db: Database, caller: Caller, handlers: Handlers): Promise<unknown>;
4
+ /** Transport input is checked before acquiring an application-owned native handle. */
5
+ export declare function callToolLazy<Database, Handlers>(tool: Tool<Database, Handlers>, raw: Record<string, unknown>, getDb: () => Database, caller: Caller, handlers: Handlers, dryRun?: () => unknown): Promise<unknown>;
@@ -0,0 +1,25 @@
1
+ import { convert, invalid } from "./surface.js";
2
+ import { checkPageLimit } from "./paging.js";
3
+ export async function callTool(tool, raw, db, caller, handlers) {
4
+ return callToolLazy(tool, raw, () => db, caller, handlers);
5
+ }
6
+ /** Transport input is checked before acquiring an application-owned native handle. */
7
+ export async function callToolLazy(tool, raw, getDb, caller, handlers, dryRun) {
8
+ if (tool.page) {
9
+ const { limit, cursor, ...input } = raw;
10
+ if (limit !== undefined)
11
+ checkPageLimit(limit, tool.path);
12
+ if (cursor !== undefined && typeof cursor !== "string")
13
+ invalid("Invalid paging options.", tool.path);
14
+ await tool.validate(input);
15
+ convert(input, tool.convert);
16
+ if (dryRun)
17
+ return dryRun();
18
+ return tool.page(getDb(), caller, input, { limit: limit, cursor: cursor });
19
+ }
20
+ const input = await tool.validate(raw);
21
+ convert(input, tool.convert);
22
+ if (dryRun)
23
+ return dryRun();
24
+ return tool.run(getDb(), caller, input, handlers);
25
+ }
@@ -0,0 +1,371 @@
1
+ /** Request-only MCP implementation, excluded from Worker startup. */
2
+ import { authorized, authorizeTarget } from "./authorization.js";
3
+ import { forwarded, isAdmin, ContractError, checkOrigin, surfaceCaller, surfaceError, wire } from "./contracts.js";
4
+ import { draining } from "./drain.js";
5
+ import { callToolLazy as callTool } from "./mcp-call.js";
6
+ import { RESOURCE_MIME_TYPE, linkSurface, resourcesOf } from "./mcp-apps.js";
7
+ const text = (value, locale) => {
8
+ if (typeof value === "string")
9
+ return value;
10
+ if (!value)
11
+ return;
12
+ return Object.hasOwn(value, locale) ? value[locale] : Object.hasOwn(value, "en") ? value.en : Object.values(value)[0];
13
+ };
14
+ function observe(listener, event) {
15
+ if (listener)
16
+ try {
17
+ void Promise.resolve(listener(event)).catch(() => { });
18
+ }
19
+ catch { }
20
+ }
21
+ const paging = (schema) => ({ ...schema, properties: { ...schema.properties, limit: { type: "integer", minimum: 1, maximum: 500 }, cursor: { type: "string" } } });
22
+ /**
23
+ * Characters of `JSON.stringify(page)` an MCP View page may hold. A result carries its rows twice (text block and
24
+ * `structuredContent`) and hosts are reported to truncate near 150k characters, so one copy stays at 60k.
25
+ * ponytail: one fixed number; raise it or make it an option if hosts publish their limits.
26
+ */
27
+ const MCP_PAGE_BUDGET = 60_000;
28
+ /** The scopes a challenge asks for: the credential's, plus what the request needs. */
29
+ const union = (scopes, extra) => [...new Set([...scopes, ...extra])].join(" ");
30
+ /** The handler for `/mcp` (public tools) and `/mcp/admin` (tools for `admin` roles); other paths are undefined. */
31
+ export function handler(tools, options, apps = {}, reads = []) {
32
+ const allowed = options.allowedOrigins ?? [];
33
+ return draining(async (request, env, ctx) => {
34
+ const access = options.gateway ? (await import("./host-access.js")).accessRequest(ctx, options) : undefined;
35
+ let resolved;
36
+ try {
37
+ const requiredScopes = options.requiredScopes ?? ["mcp"], locale = options.locale ?? "en";
38
+ const url = new URL(request.url), path = url.pathname.replace(/\/+$/, ""), surface = path === "/mcp" ? "public" : path === "/mcp/admin" ? "admin" : undefined;
39
+ if (!surface)
40
+ return;
41
+ // MCP transport MUST: a present Origin is a browser, answered only for loopback or `allowedOrigins`. The request's own
42
+ // host is not implied, since a DNS-rebinding page sends Origin == its rebound Host; a missing Origin is a server client.
43
+ if (request.headers.has("origin")) {
44
+ const sdk = await import("@modelcontextprotocol/server");
45
+ const foreign = sdk.originValidationResponse(request, [...sdk.localhostAllowedOrigins(), ...allowed]);
46
+ if (foreign) {
47
+ access?.finish(new ContractError("AUTH_DENIED", "Origin is not allowed.", "request:origin"));
48
+ observe(options.onObservation, { kind: "request-refused", at: Date.now(), surface, status: 403 });
49
+ observe(options.onRefusal, { at: Date.now(), status: 403, reason: "origin" });
50
+ return foreign;
51
+ }
52
+ }
53
+ const select = async () => (typeof tools === "function" ? await tools(request, env) : tools).filter(tool => tool.surface === surface);
54
+ // Dynamic admin tables stay behind the admin gate; cache classification uses generated metadata only.
55
+ let selected = surface === "public" ? await select() : [];
56
+ let parsedBody, tooLarge = false;
57
+ const cookieOnly = request.headers.has("cookie") && !request.headers.has("authorization");
58
+ const parse = async () => {
59
+ if (options.gateway) {
60
+ try {
61
+ const raw = await (await import("./json-body.js")).bytes(request, 1024 * 1024, () => { tooLarge = true; throw new Error("Body limit"); });
62
+ parsedBody = JSON.parse(new TextDecoder().decode(raw));
63
+ }
64
+ catch { }
65
+ return;
66
+ }
67
+ const { readRequestBody } = await import("@modelcontextprotocol/server");
68
+ try {
69
+ const body = await readRequestBody(request.clone(), 1024 * 1024);
70
+ tooLarge = body.tooLarge;
71
+ if (!body.tooLarge)
72
+ parsedBody = JSON.parse(body.text);
73
+ }
74
+ catch { }
75
+ };
76
+ if (request.method === "POST" && (cookieOnly || options.gateway))
77
+ await parse();
78
+ const messages = Array.isArray(parsedBody) ? parsedBody : [parsedBody];
79
+ const readOnly = request.method === "POST" && messages.length > 0 && messages.every(message => {
80
+ if (!isRecord(message) || message.jsonrpc !== "2.0" || typeof message.method !== "string")
81
+ return false;
82
+ if (["initialize", "tools/list", "resources/list", "resources/read"].includes(message.method))
83
+ return true;
84
+ if (message.method !== "tools/call")
85
+ return false;
86
+ const name = isRecord(message.params) ? message.params.name : undefined;
87
+ return reads.some(tool => tool.surface === surface && tool.name === name);
88
+ });
89
+ const challenge = async (error, scope, bare = false) => {
90
+ access?.finish(error);
91
+ const result = wire(error);
92
+ if (result.status === 401 || result.status === 403) {
93
+ observe(options.onObservation, { kind: "request-refused", at: Date.now(), surface, status: result.status });
94
+ observe(options.onRefusal, { at: Date.now(), status: result.status, reason: "credential" });
95
+ }
96
+ if (result.status === 401)
97
+ scope = union(scope === undefined ? requiredScopes : scope.split(" "), options.authorizationScopes ?? []);
98
+ return (await import("./host-caller.js")).hostFailure(error, resolved, `mcp ${path}`, bare ? undefined : 'Bearer ' + (scope ? (result.status === 403 ? 'error="insufficient_scope", ' : '') + 'scope="' + scope + '", ' : '') + 'resource_metadata="' + (options.resourceMetadata ?? url.origin + '/.well-known/oauth-protected-resource/mcp') + '"');
99
+ };
100
+ let caller;
101
+ try {
102
+ const requested = isRecord(parsedBody) && isRecord(parsedBody.params) ? parsedBody.params.name : undefined;
103
+ const requestedTool = selected.find(t => t.name === requested) ?? (typeof tools === "function" ? undefined : tools.find(t => t.surface === surface && t.name === requested));
104
+ await access?.select(requestedTool?.target ? `${requestedTool.target.kind}:${requestedTool.target.name}` : typeof requested === "string" ? `mcp:${requested}` : "mcp");
105
+ caller = resolved = forwarded(ctx, options.gateway)
106
+ ? (await import("./host-caller.js")).gatewayCaller(ctx, options.gateway, options.fields)
107
+ : await surfaceCaller(request, env, options.caller ?? (() => ({ kind: "anonymous" })), readOnly);
108
+ await access?.select(requestedTool?.target ? `${requestedTool.target.kind}:${requestedTool.target.name}` : "target" in caller && caller.target ? caller.target : "mcp", caller);
109
+ if (caller.kind === "anonymous" && (surface === "admin" || selected.length > 0 && selected.every(tool => tool.closed)))
110
+ throw new ContractError("UNAUTHENTICATED", "Authentication is required.", "mcp");
111
+ if (caller.kind === "system")
112
+ return challenge(new ContractError("AUTH_DENIED", "System callers cannot use MCP.", "mcp"), undefined, true);
113
+ if (surface === "admin" && !isAdmin(caller))
114
+ return challenge(new ContractError("AUTH_DENIED", "Admin role is required.", "mcp/admin"), undefined, true);
115
+ if (!options.gateway && caller.kind === "user" && caller.credential !== "session" && requiredScopes.some(scope => !caller.scopes.includes(scope)))
116
+ return challenge(new ContractError("AUTH_DENIED", "The credential needs the required MCP scopes.", "mcp"), union(caller.scopes, requiredScopes));
117
+ }
118
+ catch (error) {
119
+ return challenge(error);
120
+ }
121
+ if (surface === "admin")
122
+ selected = await select();
123
+ // Before per-caller filtering, so every caller of a deploy sees one catalogue version.
124
+ const version = catalogueVersion(selected);
125
+ const { McpServer, ProtocolError, ProtocolErrorCode, createMcpHandler, fromJsonSchema } = await import("@modelcontextprotocol/server");
126
+ if (request.method === "POST" && !cookieOnly && !options.gateway)
127
+ await parse();
128
+ if (tooLarge && access)
129
+ return challenge(new ContractError("INPUT_VALIDATION_FAILED", "MCP body exceeds its limit.", "mcp"), undefined, true);
130
+ if (tooLarge)
131
+ return Response.json({ jsonrpc: "2.0", id: null, error: { code: -32000, message: "Request body exceeds 1048576 bytes." } }, { status: 413 });
132
+ const host = options.gateway && (await import("./host-caller.js")).isHostCaller(caller);
133
+ if (host) {
134
+ try {
135
+ if (Array.isArray(parsedBody))
136
+ throw new ContractError("INPUT_VALIDATION_FAILED", "Host calls cannot batch MCP messages.", "mcp");
137
+ if (isRecord(parsedBody) && typeof parsedBody.method === "string" && !["initialize", "tools/call"].includes(parsedBody.method))
138
+ throw new ContractError("AUTH_DENIED", "Host callers may only initialize or call a tool.", "mcp", { status: 403 });
139
+ if (isRecord(parsedBody) && parsedBody.method === "initialize") {
140
+ const pinned = "target" in caller ? caller.target : undefined;
141
+ if (pinned && !selected.some(tool => (tool.target ? `${tool.target.kind}:${tool.target.name}` : `admin:tool:${tool.name}`) === pinned))
142
+ throw new ContractError("TARGET_MISMATCH", "Initialize must pin a mounted tool.", pinned, { status: 412 });
143
+ await access?.select(pinned ?? "mcp", caller);
144
+ await access?.check(true);
145
+ }
146
+ else {
147
+ const params = isRecord(parsedBody) && isRecord(parsedBody.params) ? parsedBody.params : undefined;
148
+ const tool = selected.find(t => t.name === params?.name);
149
+ if (tool) {
150
+ await access?.select(tool.target ? `${tool.target.kind}:${tool.target.name}` : `admin:tool:${tool.name}`, caller);
151
+ await access?.check(!!tool.page || tool.readOnly === true);
152
+ }
153
+ }
154
+ }
155
+ catch (error) {
156
+ return challenge(error, undefined, true);
157
+ }
158
+ }
159
+ if (request.method === "POST") {
160
+ for (const message of Array.isArray(parsedBody) ? parsedBody : [parsedBody]) {
161
+ if (!message || message.method !== "tools/call")
162
+ continue;
163
+ const tool = selected.find(tool => tool.name === message.params?.name);
164
+ if (!tool)
165
+ continue;
166
+ const scopes = tool.scopes ?? [];
167
+ if (caller.kind === "anonymous" && tool.closed)
168
+ return challenge(new ContractError("UNAUTHENTICATED", "Authentication is required.", "mcp"), union(requiredScopes, scopes));
169
+ if (!options.gateway && caller.kind === "user" && caller.credential === "oauth" && scopes.some(scope => !caller.scopes.includes(scope)))
170
+ return challenge(new ContractError("AUTH_DENIED", "The credential needs additional scopes.", "mcp"), union(caller.scopes, scopes));
171
+ }
172
+ }
173
+ const app = options.apps?.[surface];
174
+ let links;
175
+ if (app) {
176
+ const sources = (Array.isArray(app) ? app : [app]);
177
+ const dynamic = sources.some(source => typeof source === "function");
178
+ const resolved = resourcesOf(await Promise.all(sources.map(source => typeof source === "function" ? source({ env, request }) : source)));
179
+ // Equal tool names say nothing about request-bound resource identity or ownership.
180
+ const current = linkSurface(resolved, selected, dynamic ? undefined : apps[surface]);
181
+ if (!dynamic)
182
+ apps[surface] = current;
183
+ links = current.links;
184
+ }
185
+ if (options.authorize) {
186
+ try {
187
+ for (const message of Array.isArray(parsedBody) ? parsedBody : [parsedBody]) {
188
+ if (message?.method !== "tools/call")
189
+ continue;
190
+ const tool = selected.find(tool => tool.name === message.params?.name);
191
+ if (!tool)
192
+ continue;
193
+ tool.check?.(caller);
194
+ await authorizeTarget(options.authorize, tool.target ?? { kind: tool.page ? "view" : "procedure", name: tool.name }, caller);
195
+ }
196
+ // an App's HTML is rendered for the same tools the caller's tools/list shows
197
+ if ((Array.isArray(parsedBody) ? parsedBody : [parsedBody]).some(message => message?.method === "tools/list" || message?.method === "resources/read")) {
198
+ const visible = [];
199
+ for (const tool of selected) {
200
+ if (caller.kind === "anonymous" && tool.closed || !options.gateway && caller.kind === "user" && caller.credential === "oauth" && tool.scopes?.some(scope => !caller.scopes.includes(scope)))
201
+ continue;
202
+ try {
203
+ tool.check?.(caller);
204
+ }
205
+ catch (error) {
206
+ if (error instanceof ContractError && ["AUTH_DENIED", "UNAUTHENTICATED"].includes(error.code))
207
+ continue;
208
+ throw error;
209
+ }
210
+ if (await authorized(options.authorize, tool.target ?? { kind: tool.page ? "view" : "procedure", name: tool.name }, caller))
211
+ visible.push(tool);
212
+ }
213
+ selected = visible;
214
+ }
215
+ }
216
+ catch (error) {
217
+ return challenge(error);
218
+ }
219
+ }
220
+ const serverInfo = { name: "aotter.mantle", ...options.serverInfo, version: options.serverInfo?.version ?? version };
221
+ // Host calls execute in the SDK callback, after transport validation.
222
+ const handler = createMcpHandler(() => {
223
+ const schema = (value) => fromJsonSchema(value, { getValidator() { return (input) => ({ valid: true, data: input, errorMessage: undefined }); } });
224
+ // Tools capability after construction: given to the constructor, the SDK installs its tools/call handler before the wrap below.
225
+ const server = new McpServer(serverInfo, options.instructions ? { instructions: options.instructions } : {});
226
+ server.server.registerCapabilities({ tools: { listChanged: false } });
227
+ if (host)
228
+ server.server.onclose = () => access?.finish(isRecord(parsedBody) && parsedBody.method === "initialize" ? undefined : new ContractError("INPUT_VALIDATION_FAILED", "MCP request refused.", "mcp", { status: 400 }));
229
+ const appMeta = links ? registerApps(server, links, parsedBody, new Set(selected.map(tool => tool.name)), { env, request }) : undefined;
230
+ // A host's stale copy names a tool this catalogue lacks: say so, with the catalogue version, instead of the SDK's bare "not found".
231
+ // Wrapped where the SDK stores it, so transport and request validation still answer first.
232
+ const registered = new Set(), set = server.server.setRequestHandler.bind(server.server);
233
+ server.server.setRequestHandler = ((method, handler) => set(method, method === "tools/call" && typeof handler === "function" ? (request, context) => {
234
+ if (!registered.has(request.params.name))
235
+ throw new ProtocolError(ProtocolErrorCode.InvalidParams, `No tool named ${JSON.stringify(request.params.name)} exists in the current catalogue (catalogue version ${serverInfo.version}).`);
236
+ return handler(request, context);
237
+ } : handler));
238
+ for (const tool of selected) {
239
+ const meta = appMeta?.(tool.name);
240
+ if (meta === false)
241
+ continue;
242
+ registered.add(tool.name);
243
+ // Not part of structured output: the version, and on failure the current input contract.
244
+ const about = (contract) => { const text = [tool.version && `tool version ${tool.version}`, contract && `input schema ${JSON.stringify(tool.page ? paging(tool.inputSchema) : tool.inputSchema)}`].filter(Boolean).join("; "); return text ? [{ type: "text", text }] : []; };
245
+ server.registerTool(tool.name, { ...(meta ? { _meta: meta } : {}), ...(tool.title ? { title: text(tool.title, locale) } : {}), description: text(tool.description, locale) ?? text(tool.title, locale) ?? tool.name, inputSchema: schema(tool.page ? paging(tool.inputSchema) : tool.inputSchema), ...(tool.outputSchema ? { outputSchema: schema(tool.outputSchema) } : {}), ...(tool.annotations ? { annotations: tool.annotations } : {}) }, async (raw) => {
246
+ const metadata = options.onObservation ? { kind: "invocation", surface, invocationId: crypto.randomUUID(), tool: tool.name, caller: Object.freeze(caller.kind === "user" ? { ...caller, scopes: Object.freeze([...caller.scopes]) } : { ...caller }) } : undefined;
247
+ const started = metadata ? performance.now() : 0;
248
+ if (metadata)
249
+ observe(options.onObservation, { ...metadata, phase: "attempt", at: Date.now() });
250
+ let outcome = "succeeded", code;
251
+ try {
252
+ if (host)
253
+ tool.check?.(caller);
254
+ if (tool.run && (!host || !tool.readOnly))
255
+ checkOrigin(request, caller);
256
+ let db;
257
+ const run = (input) => callTool(tool, input, () => db ??= options.db(env), caller, options.handlers, access?.dryRun ? () => ({ dryRun: true, event: access.finish() }) : undefined);
258
+ let data = await run(raw);
259
+ // A page is cut before the first row that overflows the budget: the same page re-runs with the limit that fits, so the cursor is the View's own.
260
+ // Data can change between reads, so the re-run is measured again. A View without an order is one page and refuses a smaller limit: it stays whole.
261
+ for (let tries = 0; tool.page && isRecord(data) && Array.isArray(data.rows); tries++) {
262
+ const rows = data.rows, total = JSON.stringify(data).length;
263
+ if (total <= MCP_PAGE_BUDGET)
264
+ break;
265
+ if (rows.length === 1 || tries === 4)
266
+ break;
267
+ const sizes = rows.map(row => JSON.stringify(row).length);
268
+ let size = total - sizes.reduce((sum, n) => sum + n, 0) - (rows.length - 1), fit = 0;
269
+ for (const n of sizes) {
270
+ size += n + (fit ? 1 : 0);
271
+ if (size > MCP_PAGE_BUDGET)
272
+ break;
273
+ fit++;
274
+ }
275
+ const cut = await run({ ...raw, limit: Math.max(1, fit) }).catch(() => undefined);
276
+ if (cut === undefined)
277
+ break;
278
+ data = cut;
279
+ }
280
+ if (host)
281
+ access?.finish();
282
+ const text = JSON.stringify(data), notice = tool.page && text.length > MCP_PAGE_BUDGET;
283
+ return { ...(meta ? { _meta: { "net.aotter.mantle/tool": tool.name } } : {}), content: [{ type: "text", text }, ...about(false), ...(notice ? [{ type: "text", text: `This page is larger than the ${MCP_PAGE_BUDGET}-character page size budget (a single row over it, or a View without an order, is returned whole), so a host may truncate it. Continue with nextCursor, if any.` }] : [])], ...(data && typeof data === "object" && !Array.isArray(data) ? { structuredContent: data } : {}) };
284
+ }
285
+ catch (error) {
286
+ if (host)
287
+ access?.finish(error);
288
+ const diagnostic = host ? (await (await import("./host-caller.js")).hostFailure(error, caller, `mcp ${tool.name}`).json()).error : surfaceError(error, `mcp ${tool.name}`).error;
289
+ code = diagnostic.code;
290
+ outcome = code === "AUTH_DENIED" || code === "UNAUTHENTICATED" ? "denied" : "failed";
291
+ if (diagnostic.path === "request:origin")
292
+ observe(options.onRefusal, { at: Date.now(), status: 403, reason: "origin" });
293
+ const payload = { diagnostics: [diagnostic] };
294
+ return { ...(meta ? { _meta: { "net.aotter.mantle/tool": tool.name } } : {}), isError: true, content: [{ type: "text", text: JSON.stringify(payload) }, ...about(outcome === "failed")], ...(tool.outputSchema ? {} : { structuredContent: payload }) };
295
+ }
296
+ finally {
297
+ if (metadata)
298
+ observe(options.onObservation, { ...metadata, phase: "completion", at: Date.now(), outcome, durationMs: Math.max(0, performance.now() - started), ...(code ? { code } : {}) });
299
+ }
300
+ });
301
+ }
302
+ // An empty catalogue still answers tools/list and refuses calls the same way.
303
+ if (registered.size === 0) {
304
+ server.server.setRequestHandler("tools/list", () => ({ tools: [] }));
305
+ server.server.setRequestHandler("tools/call", () => ({ content: [] }));
306
+ }
307
+ return server;
308
+ }, { legacy: "stateless", maxRequestBodySize: 1024 * 1024, onerror: error => console.error("[mantle mcp] request failed", error) });
309
+ const response = await handler.fetch(request, { parsedBody });
310
+ if (host) {
311
+ if (response.status >= 400)
312
+ access?.finish(new ContractError("INPUT_VALIDATION_FAILED", "MCP request refused.", "mcp", { status: response.status }));
313
+ }
314
+ return response;
315
+ }
316
+ catch (error) {
317
+ access?.finish(error);
318
+ return (await import("./host-caller.js")).hostFailure(error, resolved, "mcp");
319
+ }
320
+ });
321
+ }
322
+ /** A hash of the tools an endpoint serves (`surface:name:version`, sorted), as 32-bit FNV-1a hex: it changes with any tool's definition. */
323
+ function catalogueVersion(tools) {
324
+ const text = JSON.stringify(tools.map(tool => `${tool.surface}:${tool.name}:${tool.version ?? ""}`).sort());
325
+ return [...text].reduce((h, c) => Math.imul(h ^ c.codePointAt(0), 16777619) >>> 0, 2166136261).toString(16).padStart(8, "0");
326
+ }
327
+ const CLIENT_CAPABILITIES_META_KEY = "io.modelcontextprotocol/clientCapabilities";
328
+ const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
329
+ /** `unknown`: a stateless request after `initialize` carries no capabilities, so only an explicit refusal hides Apps. */
330
+ function clientUiSupport(message) {
331
+ for (const m of Array.isArray(message) ? message : [message]) {
332
+ if (!isRecord(m) || !isRecord(m.params))
333
+ continue;
334
+ const meta = m.params._meta;
335
+ const declared = isRecord(meta) && isRecord(meta[CLIENT_CAPABILITIES_META_KEY]) ? meta[CLIENT_CAPABILITIES_META_KEY] : m.method === "initialize" && isRecord(m.params.capabilities) ? m.params.capabilities : undefined;
336
+ if (!declared)
337
+ continue;
338
+ const extensions = declared.extensions, ui = isRecord(extensions) ? extensions["io.modelcontextprotocol/ui"] : undefined;
339
+ return isRecord(ui) && Array.isArray(ui.mimeTypes) && ui.mimeTypes.includes(RESOURCE_MIME_TYPE) ? "supported" : "unsupported";
340
+ }
341
+ return "unknown";
342
+ }
343
+ function appMeta(r, csp = r.csp) {
344
+ const ui = { ...(csp ? { csp } : {}), ...(r.permissions ? { permissions: r.permissions } : {}), ...(r.domain !== undefined ? { domain: r.domain } : {}), ...(r.prefersBorder !== undefined ? { prefersBorder: r.prefersBorder } : {}) };
345
+ return Object.keys(ui).length > 0 ? { ui } : undefined;
346
+ }
347
+ /** Registration uses the official SDK; HTML loaders run only on resources/read. */
348
+ function registerApps(server, links, message, tools, request) {
349
+ const enabled = clientUiSupport(message) !== "unsupported";
350
+ if (enabled)
351
+ for (const resource of links.resources) {
352
+ const meta = appMeta(resource);
353
+ server.registerResource(resource.name, resource.uri, { mimeType: RESOURCE_MIME_TYPE, title: resource.title, description: resource.description, ...(meta ? { _meta: meta } : {}) }, async () => {
354
+ try {
355
+ const loaded = typeof resource.html === "string" ? resource.html : await resource.html({ tools, ...request });
356
+ const page = typeof loaded === "string" ? { html: loaded } : loaded, read = page.csp ? appMeta(resource, page.csp) : meta;
357
+ return { contents: [{ uri: resource.uri, mimeType: RESOURCE_MIME_TYPE, text: page.html, ...(read ? { _meta: read } : {}) }] };
358
+ }
359
+ catch (error) {
360
+ console.error("[mantle mcp] resource rendering failed:", error instanceof Error ? error.message : String(error));
361
+ throw new Error("Internal error.");
362
+ }
363
+ });
364
+ }
365
+ return (tool) => {
366
+ if (!enabled)
367
+ return links.appOnly.has(tool) ? false : undefined;
368
+ const uri = links.renders.get(tool);
369
+ return uri ? { ui: { resourceUri: uri, ...(links.appOnly.has(tool) ? { visibility: ["app"] } : {}) }, "ui/resourceUri": uri, ...(links.appOnly.has(tool) ? {} : { "openai/outputTemplate": uri }) } : undefined;
370
+ };
371
+ }
@@ -0,0 +1,110 @@
1
+ /** MCP request handling for a generated tool table; the official SDK loads on the first MCP request. */
2
+ import type { Caller, Target, Authorize, Gateway, RequestContext, GatewayOptions } from "./contracts.js";
3
+ import type { Conversions } from "./surface.js";
4
+ import { type McpAppsSource } from "./mcp-apps.js";
5
+ type Call = typeof import("./mcp-call.js");
6
+ export declare const callTool: Call["callTool"];
7
+ export declare const callToolLazy: Call["callToolLazy"];
8
+ export type { McpApps, McpAppsSource, McpAppResource, McpAppRender, McpAppPage, McpAppCsp } from "./mcp-apps.js";
9
+ /** Options of a generated `mcp(...)`; it adds required `handlers` when the app declares ref Procedures. */
10
+ export interface Options<Env, Database> extends GatewayOptions {
11
+ apps?: Readonly<Partial<Record<"public" | "admin", McpAppsSource | readonly McpAppsSource[]>>>;
12
+ authorize?: Authorize;
13
+ gateway?: Gateway;
14
+ fields?: import("./user-fields.js").UserFields;
15
+ db: (env: Env) => Database;
16
+ caller?: (request: Request, env: Env, authority: {
17
+ readonly readOnly: boolean;
18
+ }) => Caller | Promise<Caller>;
19
+ locale?: string;
20
+ instructions?: string;
21
+ serverInfo?: {
22
+ name?: string;
23
+ title?: string;
24
+ version?: string;
25
+ icons?: {
26
+ src: string;
27
+ mimeType?: string;
28
+ sizes?: string[];
29
+ theme?: "light" | "dark";
30
+ }[];
31
+ };
32
+ requiredScopes?: readonly string[];
33
+ /** Additional scopes requested by an unauthenticated OAuth challenge (for example offline_access), not an API scope floor. */
34
+ authorizationScopes?: readonly string[];
35
+ resourceMetadata?: string;
36
+ /** Browser origins MCP answers besides loopback, compared by hostname: `https://console.example` or the official SDK's bare `console.example`. The site's own origin is not implied. */
37
+ allowedOrigins?: readonly string[];
38
+ onObservation?: (event: McpObservation) => void | Promise<void>;
39
+ onRefusal?: (event: CallerRefusal) => void | Promise<void>;
40
+ }
41
+ export interface CallerRefusal {
42
+ readonly at: number;
43
+ readonly status: 401 | 403;
44
+ readonly reason: "origin" | "credential";
45
+ }
46
+ export type McpObservation = {
47
+ readonly kind: "request-refused";
48
+ readonly at: number;
49
+ readonly surface: "public" | "admin";
50
+ readonly status: 401 | 403;
51
+ } | {
52
+ readonly kind: "invocation";
53
+ readonly at: number;
54
+ readonly surface: "public" | "admin";
55
+ readonly invocationId: string;
56
+ readonly tool: string;
57
+ readonly caller: Caller;
58
+ } & ({
59
+ readonly phase: "attempt";
60
+ } | {
61
+ readonly phase: "completion";
62
+ readonly outcome: "succeeded" | "failed" | "denied";
63
+ readonly durationMs: number;
64
+ readonly code?: string;
65
+ });
66
+ type LocalizedText = string | Readonly<Record<string, string>>;
67
+ type Paging = {
68
+ limit?: number;
69
+ cursor?: string;
70
+ };
71
+ /** One tool: a paged View (`page`) or a Procedure (`run`, a write), on the public or admin surface. */
72
+ export interface Tool<Database, Handlers = never> {
73
+ readonly readOnly?: boolean;
74
+ readonly name: string;
75
+ readonly target?: Target;
76
+ /** Generated requires guard, used before application authorization. */
77
+ readonly check?: (caller: Caller) => void;
78
+ readonly surface: string;
79
+ readonly title?: LocalizedText;
80
+ readonly description: LocalizedText;
81
+ readonly outputSchema?: unknown;
82
+ readonly annotations?: {
83
+ readonly readOnlyHint?: boolean;
84
+ readonly destructiveHint?: boolean;
85
+ readonly openWorldHint?: boolean;
86
+ readonly idempotentHint?: boolean;
87
+ };
88
+ readonly inputSchema: unknown;
89
+ /** The listed definition's version; results carry it so a model holding an older definition can tell. */
90
+ readonly version?: string;
91
+ /** Declares an auth requirement: anonymous callers are challenged. */
92
+ readonly closed?: boolean;
93
+ /** OAuth scopes the requirement names. */
94
+ readonly scopes?: readonly string[];
95
+ readonly validate: (raw: unknown) => Record<string, unknown> | Promise<Record<string, unknown>>;
96
+ readonly convert?: Conversions;
97
+ /** A View tool: its operation path and paged boundary. */
98
+ readonly path?: string;
99
+ readonly page?: (db: Database, caller: Caller, input: never, paging: Paging) => Promise<unknown>;
100
+ readonly run?: (db: Database, caller: Caller, input: never, handlers: Handlers) => Promise<unknown>;
101
+ }
102
+ /** Request-bound tool tables keep native environment bindings outside the cached App catalogue. */
103
+ export type Tools<Env, Database, Handlers = never> = readonly Tool<Database, Handlers>[] | ((request: Request, env: Env) => readonly Tool<Database, Handlers>[] | Promise<readonly Tool<Database, Handlers>[]>);
104
+ /** The selected MCP routes; all request work loads only when an MCP request arrives. */
105
+ export declare function mcp<Env, Database, Handlers = never>(tools: Tools<Env, Database, Handlers>, options: Options<Env, Database> & {
106
+ handlers?: Handlers;
107
+ }, reads?: readonly {
108
+ readonly name: string;
109
+ readonly surface: string;
110
+ }[]): (request: Request, env: Env, ctx?: RequestContext) => Promise<Response | undefined>;