dineway 0.1.9 → 0.1.12

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 (720) hide show
  1. package/README.md +63 -17
  2. package/dist/activity-events-B4wp7CrU.mjs +540 -0
  3. package/dist/allowed-origins-C1AKK9AT.mjs +68 -0
  4. package/dist/api/route-utils.d.mts +42 -0
  5. package/dist/api/route-utils.mjs +26 -0
  6. package/dist/api/schemas/index.d.mts +3 -0
  7. package/dist/api/schemas/index.mjs +6 -0
  8. package/dist/api/schemas/setup.d.mts +42 -0
  9. package/dist/api/schemas/setup.mjs +39 -0
  10. package/dist/api-BR7Y0GBo.mjs +2704 -0
  11. package/dist/api-tokens-CPjC3zf8.mjs +3 -0
  12. package/dist/api-tokens-D7UjLbdt.mjs +153 -0
  13. package/dist/{apply-iVSqz2qs.mjs → apply-Bm5QgdrE.mjs} +15 -689
  14. package/dist/astro/index.d.mts +11 -6
  15. package/dist/astro/index.mjs +86 -11
  16. package/dist/astro/middleware/auth.d.mts +11 -7
  17. package/dist/astro/middleware/auth.mjs +19 -104
  18. package/dist/astro/middleware/redirect.mjs +24 -14
  19. package/dist/astro/middleware/request-context.mjs +9 -6
  20. package/dist/astro/middleware/setup.mjs +1 -1
  21. package/dist/astro/middleware.mjs +88 -145
  22. package/dist/astro/routes/PluginRegistry.d.mts +14 -0
  23. package/dist/astro/routes/PluginRegistry.mjs +24 -0
  24. package/dist/astro/routes/api/admin/allowed-domains/_domain_.d.mts +14 -0
  25. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +65 -0
  26. package/dist/astro/routes/api/admin/allowed-domains/index.d.mts +14 -0
  27. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +65 -0
  28. package/dist/astro/routes/api/admin/api-tokens/_id_.d.mts +10 -0
  29. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +33 -0
  30. package/dist/astro/routes/api/admin/api-tokens/index.d.mts +16 -0
  31. package/dist/astro/routes/api/admin/api-tokens/index.mjs +59 -0
  32. package/dist/astro/routes/api/admin/briefing.d.mts +7 -0
  33. package/dist/astro/routes/api/admin/briefing.mjs +71 -0
  34. package/dist/astro/routes/api/admin/bylines/_id_/index.d.mts +9 -0
  35. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +74 -0
  36. package/dist/astro/routes/api/admin/bylines/index.d.mts +8 -0
  37. package/dist/astro/routes/api/admin/bylines/index.mjs +61 -0
  38. package/dist/astro/routes/api/admin/comments/_id_/status.d.mts +7 -0
  39. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +80 -0
  40. package/dist/astro/routes/api/admin/comments/_id_.d.mts +14 -0
  41. package/dist/astro/routes/api/admin/comments/_id_.mjs +46 -0
  42. package/dist/astro/routes/api/admin/comments/bulk.d.mts +7 -0
  43. package/dist/astro/routes/api/admin/comments/bulk.mjs +36 -0
  44. package/dist/astro/routes/api/admin/comments/counts.d.mts +7 -0
  45. package/dist/astro/routes/api/admin/comments/counts.mjs +24 -0
  46. package/dist/astro/routes/api/admin/comments/index.d.mts +10 -0
  47. package/dist/astro/routes/api/admin/comments/index.mjs +40 -0
  48. package/dist/astro/routes/api/admin/context/_id_/history.d.mts +7 -0
  49. package/dist/astro/routes/api/admin/context/_id_/history.mjs +46 -0
  50. package/dist/astro/routes/api/admin/context/_id_/index.d.mts +7 -0
  51. package/dist/astro/routes/api/admin/context/_id_/index.mjs +46 -0
  52. package/dist/astro/routes/api/admin/context/_id_/review.d.mts +7 -0
  53. package/dist/astro/routes/api/admin/context/_id_/review.mjs +61 -0
  54. package/dist/astro/routes/api/admin/context/_id_/supersede.d.mts +7 -0
  55. package/dist/astro/routes/api/admin/context/_id_/supersede.mjs +64 -0
  56. package/dist/astro/routes/api/admin/context/diff.d.mts +7 -0
  57. package/dist/astro/routes/api/admin/context/diff.mjs +50 -0
  58. package/dist/astro/routes/api/admin/context/index.d.mts +8 -0
  59. package/dist/astro/routes/api/admin/context/index.mjs +72 -0
  60. package/dist/astro/routes/api/admin/context/stale.d.mts +7 -0
  61. package/dist/astro/routes/api/admin/context/stale.mjs +50 -0
  62. package/dist/astro/routes/api/admin/hitl-requests/_id_/index.d.mts +7 -0
  63. package/dist/astro/routes/api/admin/hitl-requests/_id_/index.mjs +52 -0
  64. package/dist/astro/routes/api/admin/hitl-requests/_id_/resolve.d.mts +7 -0
  65. package/dist/astro/routes/api/admin/hitl-requests/_id_/resolve.mjs +68 -0
  66. package/dist/astro/routes/api/admin/hitl-requests/index.d.mts +7 -0
  67. package/dist/astro/routes/api/admin/hitl-requests/index.mjs +56 -0
  68. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.d.mts +7 -0
  69. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +99 -0
  70. package/dist/astro/routes/api/admin/hooks/exclusive/index.d.mts +7 -0
  71. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +33 -0
  72. package/dist/astro/routes/api/admin/oauth-clients/_id_.d.mts +18 -0
  73. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +79 -0
  74. package/dist/astro/routes/api/admin/oauth-clients/index.d.mts +14 -0
  75. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +58 -0
  76. package/dist/astro/routes/api/admin/plugins/_id_/disable.d.mts +7 -0
  77. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +90 -0
  78. package/dist/astro/routes/api/admin/plugins/_id_/enable.d.mts +7 -0
  79. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +90 -0
  80. package/dist/astro/routes/api/admin/plugins/_id_/index.d.mts +7 -0
  81. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +55 -0
  82. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.d.mts +7 -0
  83. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +99 -0
  84. package/dist/astro/routes/api/admin/plugins/_id_/update.d.mts +7 -0
  85. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +132 -0
  86. package/dist/astro/routes/api/admin/plugins/index.d.mts +7 -0
  87. package/dist/astro/routes/api/admin/plugins/index.mjs +53 -0
  88. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.d.mts +7 -0
  89. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +36 -0
  90. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.d.mts +7 -0
  91. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +55 -0
  92. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.d.mts +7 -0
  93. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +129 -0
  94. package/dist/astro/routes/api/admin/plugins/marketplace/index.d.mts +7 -0
  95. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +62 -0
  96. package/dist/astro/routes/api/admin/plugins/updates.d.mts +7 -0
  97. package/dist/astro/routes/api/admin/plugins/updates.mjs +53 -0
  98. package/dist/astro/routes/api/admin/review-requests/_id_/index.d.mts +7 -0
  99. package/dist/astro/routes/api/admin/review-requests/_id_/index.mjs +26 -0
  100. package/dist/astro/routes/api/admin/review-requests/_id_/resolve.d.mts +7 -0
  101. package/dist/astro/routes/api/admin/review-requests/_id_/resolve.mjs +98 -0
  102. package/dist/astro/routes/api/admin/review-requests/index.d.mts +7 -0
  103. package/dist/astro/routes/api/admin/review-requests/index.mjs +31 -0
  104. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.d.mts +7 -0
  105. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +55 -0
  106. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.d.mts +7 -0
  107. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +36 -0
  108. package/dist/astro/routes/api/admin/themes/marketplace/index.d.mts +7 -0
  109. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +71 -0
  110. package/dist/astro/routes/api/admin/users/_id_/disable.d.mts +7 -0
  111. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +38 -0
  112. package/dist/astro/routes/api/admin/users/_id_/enable.d.mts +7 -0
  113. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +29 -0
  114. package/dist/astro/routes/api/admin/users/_id_/index.d.mts +8 -0
  115. package/dist/astro/routes/api/admin/users/_id_/index.mjs +104 -0
  116. package/dist/astro/routes/api/admin/users/_id_/send-recovery.d.mts +7 -0
  117. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +43 -0
  118. package/dist/astro/routes/api/admin/users/index.d.mts +7 -0
  119. package/dist/astro/routes/api/admin/users/index.mjs +54 -0
  120. package/dist/astro/routes/api/auth/dev-bypass.d.mts +8 -0
  121. package/dist/astro/routes/api/auth/dev-bypass.mjs +81 -0
  122. package/dist/astro/routes/api/auth/invite/accept.d.mts +7 -0
  123. package/dist/astro/routes/api/auth/invite/accept.mjs +31 -0
  124. package/dist/astro/routes/api/auth/invite/complete.d.mts +7 -0
  125. package/dist/astro/routes/api/auth/invite/complete.mjs +54 -0
  126. package/dist/astro/routes/api/auth/invite/index.d.mts +7 -0
  127. package/dist/astro/routes/api/auth/invite/index.mjs +51 -0
  128. package/dist/astro/routes/api/auth/invite/register-options.d.mts +7 -0
  129. package/dist/astro/routes/api/auth/invite/register-options.mjs +44 -0
  130. package/dist/astro/routes/api/auth/logout.d.mts +7 -0
  131. package/dist/astro/routes/api/auth/logout.mjs +24 -0
  132. package/dist/astro/routes/api/auth/magic-link/send.d.mts +7 -0
  133. package/dist/astro/routes/api/auth/magic-link/send.mjs +48 -0
  134. package/dist/astro/routes/api/auth/magic-link/verify.d.mts +7 -0
  135. package/dist/astro/routes/api/auth/magic-link/verify.mjs +32 -0
  136. package/dist/astro/routes/api/auth/me.d.mts +13 -0
  137. package/dist/astro/routes/api/auth/me.mjs +41 -0
  138. package/dist/astro/routes/api/auth/mode.d.mts +7 -0
  139. package/dist/astro/routes/api/auth/mode.mjs +28 -0
  140. package/dist/astro/routes/api/auth/oauth/_provider_/callback.d.mts +7 -0
  141. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +114 -0
  142. package/dist/astro/routes/api/auth/oauth/_provider_.d.mts +7 -0
  143. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +58 -0
  144. package/dist/astro/routes/api/auth/passkey/_id_.d.mts +14 -0
  145. package/dist/astro/routes/api/auth/passkey/_id_.mjs +62 -0
  146. package/dist/astro/routes/api/auth/passkey/index.d.mts +7 -0
  147. package/dist/astro/routes/api/auth/passkey/index.mjs +25 -0
  148. package/dist/astro/routes/api/auth/passkey/options.d.mts +7 -0
  149. package/dist/astro/routes/api/auth/passkey/options.mjs +46 -0
  150. package/dist/astro/routes/api/auth/passkey/register/options.d.mts +7 -0
  151. package/dist/astro/routes/api/auth/passkey/register/options.mjs +44 -0
  152. package/dist/astro/routes/api/auth/passkey/register/verify.d.mts +7 -0
  153. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +59 -0
  154. package/dist/astro/routes/api/auth/passkey/verify.d.mts +7 -0
  155. package/dist/astro/routes/api/auth/passkey/verify.mjs +47 -0
  156. package/dist/astro/routes/api/auth/signup/complete.d.mts +7 -0
  157. package/dist/astro/routes/api/auth/signup/complete.mjs +55 -0
  158. package/dist/astro/routes/api/auth/signup/request.d.mts +7 -0
  159. package/dist/astro/routes/api/auth/signup/request.mjs +44 -0
  160. package/dist/astro/routes/api/auth/signup/verify.d.mts +7 -0
  161. package/dist/astro/routes/api/auth/signup/verify.mjs +32 -0
  162. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.d.mts +14 -0
  163. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +193 -0
  164. package/dist/astro/routes/api/content/_collection_/_id_/compare.d.mts +7 -0
  165. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +17 -0
  166. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.d.mts +7 -0
  167. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +36 -0
  168. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.d.mts +7 -0
  169. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +39 -0
  170. package/dist/astro/routes/api/content/_collection_/_id_/permanent.d.mts +7 -0
  171. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +31 -0
  172. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.d.mts +7 -0
  173. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +78 -0
  174. package/dist/astro/routes/api/content/_collection_/_id_/publish.d.mts +7 -0
  175. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +93 -0
  176. package/dist/astro/routes/api/content/_collection_/_id_/restore.d.mts +7 -0
  177. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +36 -0
  178. package/dist/astro/routes/api/content/_collection_/_id_/revisions.d.mts +7 -0
  179. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +19 -0
  180. package/dist/astro/routes/api/content/_collection_/_id_/schedule.d.mts +8 -0
  181. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +75 -0
  182. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.d.mts +14 -0
  183. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +85 -0
  184. package/dist/astro/routes/api/content/_collection_/_id_/translations.d.mts +7 -0
  185. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +40 -0
  186. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.d.mts +7 -0
  187. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +36 -0
  188. package/dist/astro/routes/api/content/_collection_/_id_.d.mts +9 -0
  189. package/dist/astro/routes/api/content/_collection_/_id_.mjs +114 -0
  190. package/dist/astro/routes/api/content/_collection_/index.d.mts +8 -0
  191. package/dist/astro/routes/api/content/_collection_/index.mjs +74 -0
  192. package/dist/astro/routes/api/content/_collection_/trash.d.mts +7 -0
  193. package/dist/astro/routes/api/content/_collection_/trash.mjs +23 -0
  194. package/dist/astro/routes/api/dashboard.d.mts +7 -0
  195. package/dist/astro/routes/api/dashboard.mjs +26 -0
  196. package/dist/astro/routes/api/dev/emails.d.mts +8 -0
  197. package/dist/astro/routes/api/dev/emails.mjs +17 -0
  198. package/dist/astro/routes/api/health.d.mts +7 -0
  199. package/dist/astro/routes/api/health.mjs +34 -0
  200. package/dist/astro/routes/api/import/probe.d.mts +17 -0
  201. package/dist/astro/routes/api/import/probe.mjs +33 -0
  202. package/dist/astro/routes/api/import/wordpress/analyze.d.mts +87 -0
  203. package/dist/astro/routes/api/import/wordpress/analyze.mjs +305 -0
  204. package/dist/astro/routes/api/import/wordpress/execute.d.mts +37 -0
  205. package/dist/astro/routes/api/import/wordpress/execute.mjs +198 -0
  206. package/dist/astro/routes/api/import/wordpress/media.d.mts +35 -0
  207. package/dist/astro/routes/api/import/wordpress/media.mjs +222 -0
  208. package/dist/astro/routes/api/import/wordpress/prepare.d.mts +19 -0
  209. package/dist/astro/routes/api/import/wordpress/prepare.mjs +156 -0
  210. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +21 -0
  211. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +290 -0
  212. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +15 -0
  213. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +69 -0
  214. package/dist/astro/routes/api/import/wordpress-plugin/callback.d.mts +7 -0
  215. package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +28 -0
  216. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +19 -0
  217. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +269 -0
  218. package/dist/astro/routes/api/manifest.d.mts +7 -0
  219. package/dist/astro/routes/api/manifest.mjs +50 -0
  220. package/dist/astro/routes/api/mcp.d.mts +15 -0
  221. package/dist/astro/routes/api/mcp.mjs +2701 -0
  222. package/dist/astro/routes/api/media/_id_/confirm.d.mts +10 -0
  223. package/dist/astro/routes/api/media/_id_/confirm.mjs +59 -0
  224. package/dist/astro/routes/api/media/_id_.d.mts +22 -0
  225. package/dist/astro/routes/api/media/_id_.mjs +81 -0
  226. package/dist/astro/routes/api/media/file/_...key_.d.mts +7 -0
  227. package/dist/astro/routes/api/media/file/_...key_.mjs +49 -0
  228. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.d.mts +14 -0
  229. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +49 -0
  230. package/dist/astro/routes/api/media/providers/_providerId_/index.d.mts +14 -0
  231. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +72 -0
  232. package/dist/astro/routes/api/media/providers/index.d.mts +10 -0
  233. package/dist/astro/routes/api/media/providers/index.mjs +18 -0
  234. package/dist/astro/routes/api/media/upload-url.d.mts +10 -0
  235. package/dist/astro/routes/api/media/upload-url.mjs +82 -0
  236. package/dist/astro/routes/api/media.d.mts +16 -0
  237. package/dist/astro/routes/api/media.mjs +137 -0
  238. package/dist/astro/routes/api/menus/_name_/items.d.mts +9 -0
  239. package/dist/astro/routes/api/menus/_name_/items.mjs +165 -0
  240. package/dist/astro/routes/api/menus/_name_/reorder.d.mts +7 -0
  241. package/dist/astro/routes/api/menus/_name_/reorder.mjs +78 -0
  242. package/dist/astro/routes/api/menus/_name_.d.mts +9 -0
  243. package/dist/astro/routes/api/menus/_name_.mjs +124 -0
  244. package/dist/astro/routes/api/menus/index.d.mts +8 -0
  245. package/dist/astro/routes/api/menus/index.mjs +85 -0
  246. package/dist/astro/routes/api/oauth/authorize.d.mts +8 -0
  247. package/dist/astro/routes/api/oauth/authorize.mjs +265 -0
  248. package/dist/astro/routes/api/oauth/device/authorize.d.mts +7 -0
  249. package/dist/astro/routes/api/oauth/device/authorize.mjs +30 -0
  250. package/dist/astro/routes/api/oauth/device/code.d.mts +7 -0
  251. package/dist/astro/routes/api/oauth/device/code.mjs +34 -0
  252. package/dist/astro/routes/api/oauth/device/token.d.mts +7 -0
  253. package/dist/astro/routes/api/oauth/device/token.mjs +45 -0
  254. package/dist/astro/routes/api/oauth/register.d.mts +8 -0
  255. package/dist/astro/routes/api/oauth/register.mjs +115 -0
  256. package/dist/astro/routes/api/oauth/token/refresh.d.mts +7 -0
  257. package/dist/astro/routes/api/oauth/token/refresh.mjs +28 -0
  258. package/dist/astro/routes/api/oauth/token/revoke.d.mts +7 -0
  259. package/dist/astro/routes/api/oauth/token/revoke.mjs +25 -0
  260. package/dist/astro/routes/api/oauth/token.d.mts +8 -0
  261. package/dist/astro/routes/api/oauth/token.mjs +138 -0
  262. package/dist/astro/routes/api/openapi.json.d.mts +7 -0
  263. package/dist/astro/routes/api/openapi.json.mjs +2638 -0
  264. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.d.mts +11 -0
  265. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +77 -0
  266. package/dist/astro/routes/api/redirects/404s/index.d.mts +9 -0
  267. package/dist/astro/routes/api/redirects/404s/index.mjs +62 -0
  268. package/dist/astro/routes/api/redirects/404s/summary.d.mts +7 -0
  269. package/dist/astro/routes/api/redirects/404s/summary.mjs +34 -0
  270. package/dist/astro/routes/api/redirects/_id_.d.mts +9 -0
  271. package/dist/astro/routes/api/redirects/_id_.mjs +153 -0
  272. package/dist/astro/routes/api/redirects/index.d.mts +8 -0
  273. package/dist/astro/routes/api/redirects/index.mjs +98 -0
  274. package/dist/astro/routes/api/revisions/_revisionId_/index.d.mts +7 -0
  275. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +16 -0
  276. package/dist/astro/routes/api/revisions/_revisionId_/restore.d.mts +7 -0
  277. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +23 -0
  278. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.d.mts +9 -0
  279. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +99 -0
  280. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.d.mts +8 -0
  281. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +81 -0
  282. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.d.mts +7 -0
  283. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +68 -0
  284. package/dist/astro/routes/api/schema/collections/_slug_/index.d.mts +9 -0
  285. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +98 -0
  286. package/dist/astro/routes/api/schema/collections/index.d.mts +8 -0
  287. package/dist/astro/routes/api/schema/collections/index.mjs +78 -0
  288. package/dist/astro/routes/api/schema/index.d.mts +7 -0
  289. package/dist/astro/routes/api/schema/index.mjs +79 -0
  290. package/dist/astro/routes/api/schema/orphans/_slug_.d.mts +7 -0
  291. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +59 -0
  292. package/dist/astro/routes/api/schema/orphans/index.d.mts +7 -0
  293. package/dist/astro/routes/api/schema/orphans/index.mjs +54 -0
  294. package/dist/astro/routes/api/search/enable.d.mts +15 -0
  295. package/dist/astro/routes/api/search/enable.mjs +55 -0
  296. package/dist/astro/routes/api/search/index.d.mts +16 -0
  297. package/dist/astro/routes/api/search/index.mjs +52 -0
  298. package/dist/astro/routes/api/search/rebuild.d.mts +13 -0
  299. package/dist/astro/routes/api/search/rebuild.mjs +48 -0
  300. package/dist/astro/routes/api/search/stats.d.mts +10 -0
  301. package/dist/astro/routes/api/search/stats.mjs +28 -0
  302. package/dist/astro/routes/api/search/suggest.d.mts +15 -0
  303. package/dist/astro/routes/api/search/suggest.mjs +43 -0
  304. package/dist/astro/routes/api/sections/_slug_.d.mts +9 -0
  305. package/dist/astro/routes/api/sections/_slug_.mjs +157 -0
  306. package/dist/astro/routes/api/sections/index.d.mts +8 -0
  307. package/dist/astro/routes/api/sections/index.mjs +100 -0
  308. package/dist/astro/routes/api/settings/email.d.mts +17 -0
  309. package/dist/astro/routes/api/settings/email.mjs +102 -0
  310. package/dist/astro/routes/api/settings.d.mts +20 -0
  311. package/dist/astro/routes/api/settings.mjs +102 -0
  312. package/dist/astro/routes/api/setup/admin-verify.d.mts +7 -0
  313. package/dist/astro/routes/api/setup/admin-verify.mjs +67 -0
  314. package/dist/astro/routes/api/setup/admin.d.mts +7 -0
  315. package/dist/astro/routes/api/setup/admin.mjs +68 -0
  316. package/dist/astro/routes/api/setup/dev-bypass.d.mts +8 -0
  317. package/dist/astro/routes/api/setup/dev-bypass.mjs +137 -0
  318. package/dist/astro/routes/api/setup/dev-reset.d.mts +7 -0
  319. package/dist/astro/routes/api/setup/dev-reset.mjs +22 -0
  320. package/dist/astro/routes/api/setup/index.d.mts +7 -0
  321. package/dist/astro/routes/api/setup/index.mjs +93 -0
  322. package/dist/astro/routes/api/setup/status.d.mts +7 -0
  323. package/dist/astro/routes/api/setup/status.mjs +57 -0
  324. package/dist/astro/routes/api/snapshot.d.mts +7 -0
  325. package/dist/astro/routes/api/snapshot.mjs +227 -0
  326. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.d.mts +18 -0
  327. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +190 -0
  328. package/dist/astro/routes/api/taxonomies/_name_/terms/index.d.mts +14 -0
  329. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +114 -0
  330. package/dist/astro/routes/api/taxonomies/index.d.mts +14 -0
  331. package/dist/astro/routes/api/taxonomies/index.mjs +104 -0
  332. package/dist/astro/routes/api/themes/preview.d.mts +7 -0
  333. package/dist/astro/routes/api/themes/preview.mjs +47 -0
  334. package/dist/astro/routes/api/typegen.d.mts +17 -0
  335. package/dist/astro/routes/api/typegen.mjs +75 -0
  336. package/dist/astro/routes/api/well-known/auth.d.mts +7 -0
  337. package/dist/astro/routes/api/well-known/auth.mjs +42 -0
  338. package/dist/astro/routes/api/well-known/oauth-authorization-server.d.mts +7 -0
  339. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +33 -0
  340. package/dist/astro/routes/api/well-known/oauth-protected-resource.d.mts +7 -0
  341. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +21 -0
  342. package/dist/astro/routes/api/widget-areas/_name_/reorder.d.mts +7 -0
  343. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +89 -0
  344. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.d.mts +8 -0
  345. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +159 -0
  346. package/dist/astro/routes/api/widget-areas/_name_/widgets.d.mts +7 -0
  347. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +105 -0
  348. package/dist/astro/routes/api/widget-areas/_name_.d.mts +8 -0
  349. package/dist/astro/routes/api/widget-areas/_name_.mjs +100 -0
  350. package/dist/astro/routes/api/widget-areas/index.d.mts +8 -0
  351. package/dist/astro/routes/api/widget-areas/index.mjs +109 -0
  352. package/dist/astro/routes/api/widget-components.d.mts +7 -0
  353. package/dist/astro/routes/api/widget-components.mjs +15 -0
  354. package/dist/astro/routes/robots.txt.d.mts +7 -0
  355. package/dist/astro/routes/robots.txt.mjs +60 -0
  356. package/dist/astro/routes/sitemap-_collection_.xml.d.mts +7 -0
  357. package/dist/astro/routes/sitemap-_collection_.xml.mjs +70 -0
  358. package/dist/astro/routes/sitemap.xml.d.mts +7 -0
  359. package/dist/astro/routes/sitemap.xml.mjs +63 -0
  360. package/dist/astro/types.d.mts +42 -9
  361. package/dist/auth/providers/github-admin.d.mts +9 -0
  362. package/dist/auth/providers/github-admin.mjs +27 -0
  363. package/dist/auth/providers/github.d.mts +12 -0
  364. package/dist/auth/providers/github.mjs +17 -0
  365. package/dist/auth/providers/google-admin.d.mts +9 -0
  366. package/dist/auth/providers/google-admin.mjs +43 -0
  367. package/dist/auth/providers/google.d.mts +12 -0
  368. package/dist/auth/providers/google.mjs +17 -0
  369. package/dist/auth-control-guard-DOZ3UCsP.mjs +13 -0
  370. package/dist/authorize-BAdbMCwC.mjs +36 -0
  371. package/dist/briefing-MVYe_Uyf.mjs +1294 -0
  372. package/dist/briefing-rty4O-wa.mjs +29 -0
  373. package/dist/{byline-OhH2dlRu.mjs → byline-naZxOPSa.mjs} +3 -3
  374. package/dist/{bylines-BGpD9_hy.mjs → bylines-C4LIBOOO.mjs} +20 -53
  375. package/dist/bylines-eVVCuOe4.d.mts +2023 -0
  376. package/dist/{cache-BdSY-gQN.mjs → cache-DEbQ13c9.mjs} +21 -11
  377. package/dist/challenge-store-DDTbisbf.mjs +48 -0
  378. package/dist/cli/index.mjs +142 -22
  379. package/dist/client/external-auth-headers.d.mts +1 -1
  380. package/dist/client/index.d.mts +1 -1
  381. package/dist/client/index.mjs +3 -3
  382. package/dist/comment-DFO-gWDH.mjs +246 -0
  383. package/dist/comments--BsZ9pqA.mjs +186 -0
  384. package/dist/components-BPknylYg.mjs +107 -0
  385. package/dist/{content-DWi4d0rT.mjs → content-CyLkb-qH.mjs} +33 -44
  386. package/dist/context-CNIkMzot.mjs +849 -0
  387. package/dist/context-DNfcm853.mjs +184 -0
  388. package/dist/context-route-helpers-MurhoxWF.mjs +45 -0
  389. package/dist/context-types-C-LwdAxx.mjs +23 -0
  390. package/dist/cron-CKxvBrRT.mjs +263 -0
  391. package/dist/dashboard-DqnYU8EU.mjs +120 -0
  392. package/dist/db/index.d.mts +3 -3
  393. package/dist/db/libsql.d.mts +1 -1
  394. package/dist/db/libsql.mjs +3 -3
  395. package/dist/db/postgres.d.mts +1 -1
  396. package/dist/db/sqlite.d.mts +1 -1
  397. package/dist/db/sqlite.mjs +1 -2
  398. package/dist/device-flow-BGEH5jfn.mjs +487 -0
  399. package/dist/email-console-CuefUXfX.mjs +36 -0
  400. package/dist/entity-aliases-C0v-yNET.mjs +51 -0
  401. package/dist/error-BMUPwxgx.mjs +435 -0
  402. package/dist/escape-BRVaw1Ai.mjs +8 -0
  403. package/dist/experimental-workflows-C9X7yblQ.mjs +38 -0
  404. package/dist/fts-manager-B1pTNEG_.mjs +297 -0
  405. package/dist/hash-CDX7M0ze.mjs +32 -0
  406. package/dist/hitl-requests-ChT32Ilo.mjs +118 -0
  407. package/dist/hitl-route-helpers-CSit54Ru.mjs +96 -0
  408. package/dist/import-BHRLhXAn.mjs +1323 -0
  409. package/dist/import-CNcKWTbp.mjs +243 -0
  410. package/dist/index-CYfhYgXd.d.mts +227 -0
  411. package/dist/index-EUAWaIxW.d.mts +835 -0
  412. package/dist/index.d.mts +17 -11
  413. package/dist/index.mjs +63 -22
  414. package/dist/jsonld-D2gUY4kA.d.mts +141 -0
  415. package/dist/{loader-sMG4TZ-u.mjs → loader-PZnPxFLc.mjs} +42 -5
  416. package/dist/{manifest-schema-D1MSVnoI.mjs → manifest-schema-CgFJAp0H.mjs} +22 -10
  417. package/dist/media/index.d.mts +2 -1
  418. package/dist/media/index.mjs +2 -1
  419. package/dist/media/local-runtime.d.mts +12 -7
  420. package/dist/media/local-runtime.mjs +3 -3
  421. package/dist/{media-DMTr80Gv.mjs → media-_7Fxdu45.mjs} +1 -1
  422. package/dist/menus-DS3_5nWY.mjs +312 -0
  423. package/dist/menus-DYW_UHjv.mjs +256 -0
  424. package/dist/normalize-C49G_o1k.mjs +126 -0
  425. package/dist/oauth-authorization-DxGjiWKL.mjs +283 -0
  426. package/dist/oauth-clients-DxO_NO7k.mjs +298 -0
  427. package/dist/oauth-state-store-C5UFhzwD.mjs +48 -0
  428. package/dist/oauth-user-lookup-Bi0ek9eM.mjs +25 -0
  429. package/dist/options-z8VVg1Ll.mjs +114 -0
  430. package/dist/page/index.d.mts +2 -139
  431. package/dist/page/index.mjs +1 -427
  432. package/dist/parse-C9106ehs.mjs +88 -0
  433. package/dist/passkey-config-BRSZx4pW.mjs +42 -0
  434. package/dist/{patterns-CrCYkMBb.mjs → patterns-K0DLqWir.mjs} +53 -1
  435. package/dist/placeholder-Bh1dfUOd.d.mts +40 -0
  436. package/dist/{placeholder-Cp8g5Emj.mjs → placeholder-C2P5fKa4.mjs} +1 -126
  437. package/dist/plugins/adapt-sandbox-entry.d.mts +10 -5
  438. package/dist/plugins/adapt-sandbox-entry.mjs +4 -4
  439. package/dist/plugins-D7-ILNib.mjs +3249 -0
  440. package/dist/preview-DvYRU-Oy.mjs +788 -0
  441. package/dist/provider-loader-BiQ6lNmf.d.mts +20 -0
  442. package/dist/provider-loader-C21b9OpH.mjs +36 -0
  443. package/dist/public-url-Cun8N3NU.mjs +71 -0
  444. package/dist/{query-kDmwCsHh.mjs → query-B9BO5goQ.mjs} +93 -19
  445. package/dist/query-CsbOywSY.mjs +35 -0
  446. package/dist/rate-limit-DmVTHI5v.mjs +112 -0
  447. package/dist/{redirect-DnEWAkVg.mjs → redirect-CGl64yOX.mjs} +9 -5
  448. package/dist/redirect-COZy-3iY.mjs +16 -0
  449. package/dist/redirects-Cgi_cZfN.mjs +499 -0
  450. package/dist/redirects-lrlmYXVE.mjs +1023 -0
  451. package/dist/{registry-C0zjeB9P.mjs → registry-C-_hxLqa.mjs} +26 -294
  452. package/dist/request-meta-DixlNKKa.mjs +130 -0
  453. package/dist/review-requests-C2DIHwlJ.mjs +148 -0
  454. package/dist/review-requests-DIyjw-K_.mjs +79 -0
  455. package/dist/{runner-CFI6B6J2.d.mts → runner-BU6Lo1ZS.d.mts} +1 -1
  456. package/dist/{index-yvc6E_17.d.mts → runtime-Db4LbNVZ.d.mts} +1188 -2484
  457. package/dist/runtime.d.mts +11 -24
  458. package/dist/runtime.mjs +4 -38
  459. package/dist/schema-BECjUhP8.mjs +8 -0
  460. package/dist/search-DqTHQqtV.mjs +337 -0
  461. package/dist/secrets-CkoJ9zN0.mjs +160 -0
  462. package/dist/sections-B61OxnfB.mjs +338 -0
  463. package/dist/seed/index.d.mts +2 -2
  464. package/dist/seed/index.mjs +18 -13
  465. package/dist/seo/index.d.mts +1 -1
  466. package/dist/seo-C007Luwn.mjs +85 -0
  467. package/dist/seo-CUQctrog.mjs +129 -0
  468. package/dist/seo-contributions-B1fWCnqY.mjs +429 -0
  469. package/dist/service-CyOsm0R6.mjs +194 -0
  470. package/dist/settings-DGtLLSaz.mjs +237 -0
  471. package/dist/settings-Dzgswvg4.mjs +50 -0
  472. package/dist/setup-complete-ChilE-da.mjs +21 -0
  473. package/dist/setup-nonce-BpmLXAuu.mjs +17 -0
  474. package/dist/sidecar-client-B1C6Cf80.mjs +66 -0
  475. package/dist/site-activity-B8FjLIVh.mjs +104 -0
  476. package/dist/site-context-WBxoD99D.mjs +4122 -0
  477. package/dist/site-url-BP7k7OCe.mjs +12 -0
  478. package/dist/slugify-PDTDtMXp.mjs +30 -0
  479. package/dist/ssrf-DDwRxF6B.mjs +248 -0
  480. package/dist/storage/local.d.mts +1 -1
  481. package/dist/storage/local.mjs +1 -1
  482. package/dist/storage/s3.d.mts +1 -1
  483. package/dist/storage/s3.mjs +2 -2
  484. package/dist/{taxonomies-1s5PaS_8.mjs → taxonomies-D2aZGuns.mjs} +11 -7
  485. package/dist/taxonomies-z6Lz91BC.mjs +355 -0
  486. package/dist/taxonomy-D5cbhc8u.mjs +165 -0
  487. package/dist/{tokens-CJz9ubV6.mjs → tokens-BOJw-D9F.mjs} +1 -1
  488. package/dist/{transport-DB5eDN4x.mjs → transport-D3i4yWRE.mjs} +5 -4
  489. package/dist/trusted-proxy-BbaZfkT9.mjs +30 -0
  490. package/dist/types-0Vr68fc2.d.mts +344 -0
  491. package/dist/types-BFmjniC2.d.mts +165 -0
  492. package/dist/{types-BawVha09.mjs → types-Bs6lTBBW.mjs} +1 -1
  493. package/dist/{types-BuMDPy5C.d.mts → types-C0mmVRJN.d.mts} +6 -0
  494. package/dist/{placeholder--wOi4TbO.d.mts → types-OPs5Q_sX.d.mts} +1 -38
  495. package/dist/{types-Cj0KMIZV.d.mts → types-Q616b2Hn.d.mts} +54 -16
  496. package/dist/ui/client-runtime.d.mts +12 -0
  497. package/dist/ui/client-runtime.mjs +32 -0
  498. package/dist/ui/server-runtime.d.mts +33 -0
  499. package/dist/ui/server-runtime.mjs +80 -0
  500. package/dist/url-DNjT2abR.mjs +49 -0
  501. package/dist/user-CcXq-zoL.mjs +154 -0
  502. package/dist/utils-C0ONdBul.mjs +285 -0
  503. package/dist/{validate-IPf8n4Fj.d.mts → validate-BwmQEbu8.d.mts} +3 -3
  504. package/dist/{validate-BZ5wnLLp.mjs → validate-C7TzfamJ.mjs} +1 -1
  505. package/dist/version-D3vDb22n.mjs +6 -0
  506. package/dist/widgets-B7DRpZvy.mjs +104 -0
  507. package/dist/wordpress-slugs-CnporCYH.mjs +14 -0
  508. package/dist/zod-generator-DBVP8D0P.mjs +132 -0
  509. package/locals.d.ts +1 -6
  510. package/package.json +81 -11
  511. package/src/components/Button.astro +1 -1
  512. package/src/components/CommentForm.astro +1 -1
  513. package/src/components/Comments.astro +1 -3
  514. package/src/components/DinewayBodyEnd.astro +5 -3
  515. package/src/components/DinewayBodyStart.astro +5 -3
  516. package/src/components/DinewayHead.astro +15 -9
  517. package/src/components/DinewayImage.astro +12 -8
  518. package/src/components/DinewayMedia.astro +15 -6
  519. package/src/components/Embed.astro +1 -2
  520. package/src/components/File.astro +1 -1
  521. package/src/components/Gallery.astro +5 -3
  522. package/src/components/HtmlBlock.astro +1 -1
  523. package/src/components/Image.astro +9 -3
  524. package/src/components/InlinePortableTextEditor.tsx +69 -20
  525. package/src/components/PortableText.astro +1 -1
  526. package/src/components/WidgetArea.astro +1 -1
  527. package/src/components/WidgetRenderer.astro +1 -3
  528. package/src/components/marks/Link.astro +1 -1
  529. package/src/components/widgets/Archives.astro +1 -1
  530. package/src/components/widgets/Categories.astro +1 -1
  531. package/src/components/widgets/RecentPosts.astro +1 -1
  532. package/src/components/widgets/Tags.astro +1 -1
  533. package/dist/error-BmL6QipT.mjs +0 -30
  534. package/dist/search-DxopAWxs.mjs +0 -11200
  535. package/dist/version-BPz1imu2.mjs +0 -6
  536. package/src/astro/routes/PluginRegistry.tsx +0 -21
  537. package/src/astro/routes/api/admin/allowed-domains/[domain].ts +0 -112
  538. package/src/astro/routes/api/admin/allowed-domains/index.ts +0 -108
  539. package/src/astro/routes/api/admin/api-tokens/[id].ts +0 -44
  540. package/src/astro/routes/api/admin/api-tokens/index.ts +0 -90
  541. package/src/astro/routes/api/admin/briefing.ts +0 -76
  542. package/src/astro/routes/api/admin/bylines/[id]/index.ts +0 -90
  543. package/src/astro/routes/api/admin/bylines/index.ts +0 -74
  544. package/src/astro/routes/api/admin/comments/[id]/status.ts +0 -120
  545. package/src/astro/routes/api/admin/comments/[id].ts +0 -64
  546. package/src/astro/routes/api/admin/comments/bulk.ts +0 -42
  547. package/src/astro/routes/api/admin/comments/counts.ts +0 -30
  548. package/src/astro/routes/api/admin/comments/index.ts +0 -46
  549. package/src/astro/routes/api/admin/context/[id]/history.ts +0 -35
  550. package/src/astro/routes/api/admin/context/[id]/index.ts +0 -35
  551. package/src/astro/routes/api/admin/context/[id]/review.ts +0 -57
  552. package/src/astro/routes/api/admin/context/[id]/supersede.ts +0 -58
  553. package/src/astro/routes/api/admin/context/diff.ts +0 -35
  554. package/src/astro/routes/api/admin/context/index.ts +0 -69
  555. package/src/astro/routes/api/admin/context/stale.ts +0 -35
  556. package/src/astro/routes/api/admin/hitl-requests/[id]/index.ts +0 -38
  557. package/src/astro/routes/api/admin/hitl-requests/[id]/resolve.ts +0 -54
  558. package/src/astro/routes/api/admin/hitl-requests/index.ts +0 -38
  559. package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +0 -132
  560. package/src/astro/routes/api/admin/hooks/exclusive/index.ts +0 -51
  561. package/src/astro/routes/api/admin/oauth-clients/[id].ts +0 -137
  562. package/src/astro/routes/api/admin/oauth-clients/index.ts +0 -95
  563. package/src/astro/routes/api/admin/plugins/[id]/disable.ts +0 -91
  564. package/src/astro/routes/api/admin/plugins/[id]/enable.ts +0 -91
  565. package/src/astro/routes/api/admin/plugins/[id]/index.ts +0 -38
  566. package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +0 -98
  567. package/src/astro/routes/api/admin/plugins/[id]/update.ts +0 -154
  568. package/src/astro/routes/api/admin/plugins/index.ts +0 -32
  569. package/src/astro/routes/api/admin/plugins/marketplace/[id]/icon.ts +0 -62
  570. package/src/astro/routes/api/admin/plugins/marketplace/[id]/index.ts +0 -33
  571. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +0 -135
  572. package/src/astro/routes/api/admin/plugins/marketplace/index.ts +0 -38
  573. package/src/astro/routes/api/admin/plugins/updates.ts +0 -28
  574. package/src/astro/routes/api/admin/review-requests/[id]/index.ts +0 -35
  575. package/src/astro/routes/api/admin/review-requests/[id]/resolve.ts +0 -52
  576. package/src/astro/routes/api/admin/review-requests/index.ts +0 -35
  577. package/src/astro/routes/api/admin/themes/marketplace/[id]/index.ts +0 -33
  578. package/src/astro/routes/api/admin/themes/marketplace/[id]/thumbnail.ts +0 -62
  579. package/src/astro/routes/api/admin/themes/marketplace/index.ts +0 -45
  580. package/src/astro/routes/api/admin/users/[id]/disable.ts +0 -72
  581. package/src/astro/routes/api/admin/users/[id]/enable.ts +0 -48
  582. package/src/astro/routes/api/admin/users/[id]/index.ts +0 -166
  583. package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +0 -72
  584. package/src/astro/routes/api/admin/users/index.ts +0 -66
  585. package/src/astro/routes/api/auth/dev-bypass.ts +0 -139
  586. package/src/astro/routes/api/auth/invite/accept.ts +0 -52
  587. package/src/astro/routes/api/auth/invite/complete.ts +0 -86
  588. package/src/astro/routes/api/auth/invite/index.ts +0 -99
  589. package/src/astro/routes/api/auth/invite/register-options.ts +0 -73
  590. package/src/astro/routes/api/auth/logout.ts +0 -40
  591. package/src/astro/routes/api/auth/magic-link/send.ts +0 -90
  592. package/src/astro/routes/api/auth/magic-link/verify.ts +0 -71
  593. package/src/astro/routes/api/auth/me.ts +0 -60
  594. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +0 -221
  595. package/src/astro/routes/api/auth/oauth/[provider].ts +0 -120
  596. package/src/astro/routes/api/auth/passkey/[id].ts +0 -124
  597. package/src/astro/routes/api/auth/passkey/index.ts +0 -54
  598. package/src/astro/routes/api/auth/passkey/options.ts +0 -85
  599. package/src/astro/routes/api/auth/passkey/register/options.ts +0 -88
  600. package/src/astro/routes/api/auth/passkey/register/verify.ts +0 -119
  601. package/src/astro/routes/api/auth/passkey/verify.ts +0 -72
  602. package/src/astro/routes/api/auth/signup/complete.ts +0 -87
  603. package/src/astro/routes/api/auth/signup/request.ts +0 -89
  604. package/src/astro/routes/api/auth/signup/verify.ts +0 -53
  605. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +0 -310
  606. package/src/astro/routes/api/content/[collection]/[id]/compare.ts +0 -28
  607. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +0 -68
  608. package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +0 -77
  609. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +0 -42
  610. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +0 -107
  611. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +0 -100
  612. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +0 -64
  613. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +0 -31
  614. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +0 -129
  615. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +0 -143
  616. package/src/astro/routes/api/content/[collection]/[id]/translations.ts +0 -50
  617. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +0 -69
  618. package/src/astro/routes/api/content/[collection]/[id].ts +0 -173
  619. package/src/astro/routes/api/content/[collection]/index.ts +0 -103
  620. package/src/astro/routes/api/content/[collection]/trash.ts +0 -33
  621. package/src/astro/routes/api/dashboard.ts +0 -32
  622. package/src/astro/routes/api/dev/emails.ts +0 -36
  623. package/src/astro/routes/api/health.ts +0 -54
  624. package/src/astro/routes/api/import/probe.ts +0 -47
  625. package/src/astro/routes/api/import/wordpress/analyze.ts +0 -523
  626. package/src/astro/routes/api/import/wordpress/execute.ts +0 -330
  627. package/src/astro/routes/api/import/wordpress/media.ts +0 -338
  628. package/src/astro/routes/api/import/wordpress/prepare.ts +0 -212
  629. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +0 -425
  630. package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +0 -111
  631. package/src/astro/routes/api/import/wordpress-plugin/callback.ts +0 -58
  632. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +0 -399
  633. package/src/astro/routes/api/manifest.ts +0 -75
  634. package/src/astro/routes/api/mcp.ts +0 -125
  635. package/src/astro/routes/api/media/[id]/confirm.ts +0 -93
  636. package/src/astro/routes/api/media/[id].ts +0 -145
  637. package/src/astro/routes/api/media/file/[...key].ts +0 -79
  638. package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +0 -91
  639. package/src/astro/routes/api/media/providers/[providerId]/index.ts +0 -111
  640. package/src/astro/routes/api/media/providers/index.ts +0 -30
  641. package/src/astro/routes/api/media/upload-url.ts +0 -146
  642. package/src/astro/routes/api/media.ts +0 -204
  643. package/src/astro/routes/api/menus/[name]/items.ts +0 -206
  644. package/src/astro/routes/api/menus/[name]/reorder.ts +0 -79
  645. package/src/astro/routes/api/menus/[name].ts +0 -145
  646. package/src/astro/routes/api/menus/index.ts +0 -91
  647. package/src/astro/routes/api/oauth/authorize.ts +0 -430
  648. package/src/astro/routes/api/oauth/device/authorize.ts +0 -45
  649. package/src/astro/routes/api/oauth/device/code.ts +0 -56
  650. package/src/astro/routes/api/oauth/device/token.ts +0 -70
  651. package/src/astro/routes/api/oauth/register.ts +0 -182
  652. package/src/astro/routes/api/oauth/token/refresh.ts +0 -38
  653. package/src/astro/routes/api/oauth/token/revoke.ts +0 -38
  654. package/src/astro/routes/api/oauth/token.ts +0 -195
  655. package/src/astro/routes/api/openapi.json.ts +0 -33
  656. package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +0 -109
  657. package/src/astro/routes/api/redirects/404s/index.ts +0 -72
  658. package/src/astro/routes/api/redirects/404s/summary.ts +0 -33
  659. package/src/astro/routes/api/redirects/[id].ts +0 -183
  660. package/src/astro/routes/api/redirects/index.ts +0 -100
  661. package/src/astro/routes/api/revisions/[revisionId]/index.ts +0 -29
  662. package/src/astro/routes/api/revisions/[revisionId]/restore.ts +0 -62
  663. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +0 -104
  664. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +0 -67
  665. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +0 -45
  666. package/src/astro/routes/api/schema/collections/[slug]/index.ts +0 -107
  667. package/src/astro/routes/api/schema/collections/index.ts +0 -61
  668. package/src/astro/routes/api/schema/index.ts +0 -109
  669. package/src/astro/routes/api/schema/orphans/[slug].ts +0 -36
  670. package/src/astro/routes/api/schema/orphans/index.ts +0 -26
  671. package/src/astro/routes/api/search/enable.ts +0 -64
  672. package/src/astro/routes/api/search/index.ts +0 -52
  673. package/src/astro/routes/api/search/rebuild.ts +0 -72
  674. package/src/astro/routes/api/search/stats.ts +0 -35
  675. package/src/astro/routes/api/search/suggest.ts +0 -50
  676. package/src/astro/routes/api/sections/[slug].ts +0 -203
  677. package/src/astro/routes/api/sections/index.ts +0 -107
  678. package/src/astro/routes/api/settings/email.ts +0 -150
  679. package/src/astro/routes/api/settings.ts +0 -116
  680. package/src/astro/routes/api/setup/admin-verify.ts +0 -122
  681. package/src/astro/routes/api/setup/admin.ts +0 -104
  682. package/src/astro/routes/api/setup/dev-bypass.ts +0 -200
  683. package/src/astro/routes/api/setup/dev-reset.ts +0 -40
  684. package/src/astro/routes/api/setup/index.ts +0 -128
  685. package/src/astro/routes/api/setup/status.ts +0 -122
  686. package/src/astro/routes/api/snapshot.ts +0 -76
  687. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +0 -232
  688. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +0 -131
  689. package/src/astro/routes/api/taxonomies/index.ts +0 -114
  690. package/src/astro/routes/api/themes/preview.ts +0 -78
  691. package/src/astro/routes/api/typegen.ts +0 -114
  692. package/src/astro/routes/api/well-known/auth.ts +0 -71
  693. package/src/astro/routes/api/well-known/oauth-authorization-server.ts +0 -48
  694. package/src/astro/routes/api/well-known/oauth-protected-resource.ts +0 -39
  695. package/src/astro/routes/api/widget-areas/[name]/reorder.ts +0 -114
  696. package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +0 -213
  697. package/src/astro/routes/api/widget-areas/[name]/widgets.ts +0 -126
  698. package/src/astro/routes/api/widget-areas/[name].ts +0 -135
  699. package/src/astro/routes/api/widget-areas/index.ts +0 -149
  700. package/src/astro/routes/api/widget-components.ts +0 -22
  701. package/src/astro/routes/robots.txt.ts +0 -81
  702. package/src/astro/routes/sitemap-[collection].xml.ts +0 -104
  703. package/src/astro/routes/sitemap.xml.ts +0 -92
  704. /package/dist/{adapters-C2ypTrZZ.d.mts → adapters-DuLQZhRY.d.mts} +0 -0
  705. /package/{src → dist}/astro/routes/admin.astro +0 -0
  706. /package/dist/{base64-F8-DUraK.mjs → base64-Cz-aU0X1.mjs} +0 -0
  707. /package/dist/{chunks--4F8ddV4.mjs → chunks-D_jVet6z.mjs} +0 -0
  708. /package/dist/{config-BXwuX8Bx.mjs → config-CAMFxGaV.mjs} +0 -0
  709. /package/dist/{db-errors-CEqD7qH9.mjs → db-errors-DyZkswzF.mjs} +0 -0
  710. /package/dist/{default-VjJyuuG9.mjs → default-D4ngTpW8.mjs} +0 -0
  711. /package/dist/{load-Coc9HpHH.mjs → load-B2XtDw__.mjs} +0 -0
  712. /package/dist/{mode-47goXBBK.mjs → mode-DUhxwUhv.mjs} +0 -0
  713. /package/dist/{request-cache-Dk5qPSOx.mjs → request-cache-DHMRr2Lf.mjs} +0 -0
  714. /package/dist/{transaction-Cn2rjY78.mjs → transaction-x2tJQ-A1.mjs} +0 -0
  715. /package/dist/{transport-Wge_IzKl.d.mts → transport-BXe1AM79.d.mts} +0 -0
  716. /package/dist/{types-CWbdtiux.d.mts → types-B7kpsMJ3.d.mts} +0 -0
  717. /package/dist/{types-BzcUjoqg.d.mts → types-DJlpx5Ay.d.mts} +0 -0
  718. /package/dist/{types-COeOq9nK.mjs → types-DL7Y8D_t.mjs} +0 -0
  719. /package/dist/{types-DOrVigru.d.mts → types-DZPw8Rru.d.mts} +0 -0
  720. /package/dist/{types-griIBQOQ.mjs → types-fAInWQDO.mjs} +0 -0
@@ -0,0 +1,2701 @@
1
+ import "../../../dialect-helpers-DhTzaUxP.mjs";
2
+ import "../../../content-CyLkb-qH.mjs";
3
+ import "../../../base64-Cz-aU0X1.mjs";
4
+ import "../../../types-Bs6lTBBW.mjs";
5
+ import "../../../media-_7Fxdu45.mjs";
6
+ import "../../../options-z8VVg1Ll.mjs";
7
+ import "../../../site-activity-B8FjLIVh.mjs";
8
+ import { t as ReviewRequestRepository } from "../../../review-requests-C2DIHwlJ.mjs";
9
+ import "../../../entity-aliases-C0v-yNET.mjs";
10
+ import { a as ContextRepository, i as parseSiteBriefingScope, r as formatSiteBriefingText, t as SiteBriefingAssembler } from "../../../briefing-MVYe_Uyf.mjs";
11
+ import { t as SITE_CONTEXT_TYPES } from "../../../context-types-C-LwdAxx.mjs";
12
+ import "../../../fts-manager-B1pTNEG_.mjs";
13
+ import "../../../registry-C-_hxLqa.mjs";
14
+ import "../../../loader-PZnPxFLc.mjs";
15
+ import "../../../settings-DGtLLSaz.mjs";
16
+ import "../../../request-cache-DHMRr2Lf.mjs";
17
+ import { n as VERSION } from "../../../version-D3vDb22n.mjs";
18
+ import "../../../query-CsbOywSY.mjs";
19
+ import "../../../zod-generator-DBVP8D0P.mjs";
20
+ import "../../../schema-BECjUhP8.mjs";
21
+ import { t as apiError } from "../../../error-BMUPwxgx.mjs";
22
+ import { P as settingsUpdateBody, rn as contentSeoInput, vn as contentBylineInputSchema } from "../../../redirects-lrlmYXVE.mjs";
23
+ import "../../../import-CNcKWTbp.mjs";
24
+ import "../../../api/schemas/index.mjs";
25
+ import "../../../utils-C0ONdBul.mjs";
26
+ import "../../../search-DqTHQqtV.mjs";
27
+ import { i as hasScope } from "../../../api-tokens-CPjC3zf8.mjs";
28
+ import { C as HITL_REQUEST_STATUSES, E as ASSIGNMENT_STATUSES, S as HITL_REQUEST_PRIORITIES, T as ASSIGNMENT_PRIORITIES, _ as RiskPolicyEvaluator, a as ActorExpertiseService, b as toPublicContextEntry, f as SettingsHitlPayloadBuilder, g as SchemaHitlPayloadBuilder, i as HandoffSnapshotStore, n as HitlRequestService, o as EntityResolver, r as AssignmentService, t as WorkflowHitlCoordinator, v as ReviewPayloadBuilder, w as ASSIGNMENT_ACTOR_TYPES, x as toPublicContextEntryPage, y as toPublicContextDiff } from "../../../site-context-WBxoD99D.mjs";
29
+ import { T as resolveActorIdentity, b as schemaMcpToolSource, d as logSchemaActivity, o as logContentActivity, p as logSiteActivitySafely, r as contentMcpToolSource, t as activityChangedKeys } from "../../../activity-events-B4wp7CrU.mjs";
30
+ import { z } from "zod";
31
+ import { Role, canActOnOwn, hasPermission } from "@dineway-ai/auth";
32
+ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
33
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
34
+ import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
35
+
36
+ //#region src/mcp/server.ts
37
+ const COLLECTION_SLUG_PATTERN = /^[a-z][a-z0-9_]*$/;
38
+ const ENTITY_RESOLVE_TYPES = [
39
+ "content",
40
+ "collection",
41
+ "plugin",
42
+ "taxonomy",
43
+ "menu"
44
+ ];
45
+ const ACTOR_EXPERTISE_ACTOR_TYPES = [
46
+ "user",
47
+ "api_token",
48
+ "system"
49
+ ];
50
+ const HANDOFF_KINDS = [
51
+ "pause",
52
+ "review_request",
53
+ "assignment"
54
+ ];
55
+ const HANDOFF_REFERENCE_TYPES = ["review_request", "assignment"];
56
+ const settingsMcpUpdateBody = settingsUpdateBody.extend({ hitl_request_id: z.string().optional().describe("Approved HITL request id required for API-token settings changes.") });
57
+ /**
58
+ * Unwrap an ApiResult<T> into MCP tool result format.
59
+ * On success, returns the data as pretty-printed JSON text content.
60
+ * On failure, returns the error message with isError flag.
61
+ */
62
+ function unwrap(result) {
63
+ if (result.success && result.data !== void 0) return { content: [{
64
+ type: "text",
65
+ text: JSON.stringify(result.data, null, 2)
66
+ }] };
67
+ return {
68
+ content: [{
69
+ type: "text",
70
+ text: result.error && typeof result.error === "object" && "message" in result.error ? String(result.error.message) : "Unknown error"
71
+ }],
72
+ isError: true
73
+ };
74
+ }
75
+ /**
76
+ * Return a JSON text block.
77
+ */
78
+ function jsonResult(data) {
79
+ return { content: [{
80
+ type: "text",
81
+ text: JSON.stringify(data, null, 2)
82
+ }] };
83
+ }
84
+ /**
85
+ * Return an error text block.
86
+ */
87
+ function errorResult(error) {
88
+ return {
89
+ content: [{
90
+ type: "text",
91
+ text: error instanceof Error ? error.message : String(error)
92
+ }],
93
+ isError: true
94
+ };
95
+ }
96
+ function getExtra(extra) {
97
+ const payload = extra.authInfo?.extra;
98
+ if (!payload?.dineway) throw new Error("Dineway not available — server misconfigured");
99
+ return payload;
100
+ }
101
+ function getDineway(extra) {
102
+ return getExtra(extra).dineway;
103
+ }
104
+ function canReadDrafts(extra) {
105
+ return hasPermission({ role: getExtra(extra).userRole }, "content:read_drafts");
106
+ }
107
+ function requireDraftAccess(extra) {
108
+ if (!canReadDrafts(extra)) throw new McpError(ErrorCode.InvalidRequest, "Insufficient permissions for this operation");
109
+ }
110
+ function isPublishedRecord(value) {
111
+ return typeof value === "object" && value !== null && "status" in value && value.status === "published";
112
+ }
113
+ /**
114
+ * Enforce a scope requirement on the current request.
115
+ *
116
+ * When tokenScopes is undefined (session auth), all operations are allowed
117
+ * since session users have full access based on their role. When scopes are
118
+ * present (token auth), the required scope must be included.
119
+ */
120
+ function requireScope(extra, scope) {
121
+ const payload = getExtra(extra);
122
+ if (payload.tokenScopes && !hasScope(payload.tokenScopes, scope)) throw new McpError(ErrorCode.InvalidRequest, `Insufficient scope: requires ${scope}`);
123
+ }
124
+ function requireAnyScope(extra, scopes) {
125
+ const tokenScopes = getExtra(extra).tokenScopes;
126
+ if (!tokenScopes) return;
127
+ if (scopes.some((scope) => hasScope(tokenScopes, scope))) return;
128
+ throw new McpError(ErrorCode.InvalidRequest, `Insufficient scope: requires one of ${scopes.join(", ")}`);
129
+ }
130
+ function tokenAllows(payload, scope) {
131
+ return !payload.tokenScopes || hasScope(payload.tokenScopes, scope);
132
+ }
133
+ function briefingPermissions(payload) {
134
+ const user = { role: payload.userRole };
135
+ return {
136
+ canReadContent: hasPermission(user, "content:read") && tokenAllows(payload, "content:read"),
137
+ canReadSchema: hasPermission(user, "schema:read") && tokenAllows(payload, "schema:read"),
138
+ canReadSettings: hasPermission(user, "settings:read") && tokenAllows(payload, "admin"),
139
+ canReadPlugins: hasPermission(user, "plugins:read") && tokenAllows(payload, "admin")
140
+ };
141
+ }
142
+ function contentScopeAllowedForHandoff(payload) {
143
+ return hasPermission({ role: payload.userRole }, "content:read") && tokenAllows(payload, "content:read") || payload.userRole >= Role.AUTHOR && tokenAllows(payload, "content:write");
144
+ }
145
+ function contentDiffReadableForHandoff(payload) {
146
+ return hasPermission({ role: payload.userRole }, "content:read") && tokenAllows(payload, "content:read");
147
+ }
148
+ function schemaReadableForHandoff(payload) {
149
+ return hasPermission({ role: payload.userRole }, "schema:read") && tokenAllows(payload, "schema:read");
150
+ }
151
+ function pluginsReadableForHandoff(payload) {
152
+ return hasPermission({ role: payload.userRole }, "plugins:read") && tokenAllows(payload, "admin");
153
+ }
154
+ function requireHandoffScope(extra, scope) {
155
+ requireRole(extra, Role.AUTHOR);
156
+ const payload = getExtra(extra);
157
+ switch (parseSiteBriefingScope(scope).type) {
158
+ case "content":
159
+ case "taxonomy":
160
+ case "menu":
161
+ if (!contentScopeAllowedForHandoff(payload)) throw new McpError(ErrorCode.InvalidRequest, "Insufficient permission to capture or resume a content-adjacent handoff for this scope.");
162
+ return;
163
+ case "collection":
164
+ case "site":
165
+ if (contentDiffReadableForHandoff(payload) || schemaReadableForHandoff(payload) || pluginsReadableForHandoff(payload)) return;
166
+ throw new McpError(ErrorCode.InvalidRequest, "Insufficient permission to capture or resume a site-context handoff for this scope.");
167
+ case "plugin":
168
+ if (pluginsReadableForHandoff(payload)) return;
169
+ throw new McpError(ErrorCode.InvalidRequest, "Insufficient permission to capture or resume a plugin handoff for this scope.");
170
+ }
171
+ }
172
+ function requireContextRead(extra) {
173
+ requireAnyScope(extra, ["content:read", "admin"]);
174
+ requireRole(extra, Role.SUBSCRIBER);
175
+ }
176
+ function requireContextWrite(extra) {
177
+ requireScope(extra, "admin");
178
+ requireRole(extra, Role.EDITOR);
179
+ }
180
+ function actorLocals(payload) {
181
+ return {
182
+ user: { id: payload.userId },
183
+ tokenScopes: payload.tokenScopes,
184
+ authToken: payload.authToken
185
+ };
186
+ }
187
+ function scopesFromContextToolArgs(scope, includeInherited) {
188
+ if (!scope) return void 0;
189
+ const parsed = parseSiteBriefingScope(scope);
190
+ return includeInherited ? parsed.ancestors : [parsed.scope];
191
+ }
192
+ function requireReviewSubmit(extra) {
193
+ requireAnyScope(extra, ["content:write", "admin"]);
194
+ requireRole(extra, Role.AUTHOR);
195
+ }
196
+ function requireReviewResolve(extra) {
197
+ requireAnyScope(extra, ["content:write", "admin"]);
198
+ requireRole(extra, Role.EDITOR);
199
+ }
200
+ function requireReviewRead(extra) {
201
+ requireAnyScope(extra, ["content:read", "admin"]);
202
+ requireRole(extra, Role.SUBSCRIBER);
203
+ }
204
+ function requireWorkflowRead(extra) {
205
+ requireAnyScope(extra, [
206
+ "workflow:read",
207
+ "workflow:write",
208
+ "admin"
209
+ ]);
210
+ requireRole(extra, Role.AUTHOR);
211
+ }
212
+ function requireWorkflowWrite(extra) {
213
+ requireAnyScope(extra, ["workflow:write", "admin"]);
214
+ requireRole(extra, Role.AUTHOR);
215
+ }
216
+ function requireHitlRead(extra) {
217
+ requireWorkflowRead(extra);
218
+ }
219
+ function requireHitlSubmit(extra) {
220
+ requireWorkflowWrite(extra);
221
+ }
222
+ function requireHitlResolve(extra) {
223
+ requireAnyScope(extra, ["workflow:write", "admin"]);
224
+ requireRole(extra, Role.EDITOR);
225
+ }
226
+ function workflowActorContext(payload) {
227
+ const actor = resolveActorIdentity(actorLocals(payload));
228
+ return {
229
+ actorType: actor.actorType,
230
+ actorId: actor.actorId,
231
+ authMetadata: actor.authMetadata,
232
+ userRole: payload.userRole
233
+ };
234
+ }
235
+ function scopeReadableForExpertise(payload, scope) {
236
+ try {
237
+ switch (parseSiteBriefingScope(scope).type) {
238
+ case "content":
239
+ case "taxonomy":
240
+ case "menu": return contentDiffReadableForHandoff(payload);
241
+ case "collection":
242
+ case "site": return contentDiffReadableForHandoff(payload) || schemaReadableForHandoff(payload) || pluginsReadableForHandoff(payload);
243
+ case "plugin": return pluginsReadableForHandoff(payload);
244
+ }
245
+ } catch {
246
+ return false;
247
+ }
248
+ }
249
+ function requireExpertiseRead(extra) {
250
+ requireAnyScope(extra, [
251
+ "content:read",
252
+ "schema:read",
253
+ "admin"
254
+ ]);
255
+ requireRole(extra, Role.SUBSCRIBER);
256
+ }
257
+ function requireExpertiseScope(extra, scope) {
258
+ requireExpertiseRead(extra);
259
+ if (scopeReadableForExpertise(getExtra(extra), scope)) return;
260
+ throw new McpError(ErrorCode.InvalidRequest, "Insufficient permission to inspect actor expertise for this scope.");
261
+ }
262
+ function allowedEntityResolveTypes(payload, requestedTypes) {
263
+ const user = { role: payload.userRole };
264
+ const allowed = ENTITY_RESOLVE_TYPES.filter((entityType) => {
265
+ switch (entityType) {
266
+ case "content":
267
+ case "taxonomy":
268
+ case "menu": return hasPermission(user, "content:read") && tokenAllows(payload, "content:read");
269
+ case "collection": return hasPermission(user, "schema:read") && tokenAllows(payload, "schema:read");
270
+ case "plugin": return hasPermission(user, "plugins:read") && tokenAllows(payload, "admin");
271
+ }
272
+ });
273
+ if (!requestedTypes || requestedTypes.length === 0) return allowed;
274
+ const disallowed = requestedTypes.filter((entityType) => !allowed.includes(entityType));
275
+ if (disallowed.length > 0) throw new McpError(ErrorCode.InvalidRequest, `Insufficient scope or role for entity types: ${disallowed.join(", ")}`);
276
+ return requestedTypes;
277
+ }
278
+ function contentPublishReviewRequired(extra) {
279
+ const payload = getExtra(extra);
280
+ const actor = resolveActorIdentity(actorLocals(payload));
281
+ return new RiskPolicyEvaluator({
282
+ db: payload.dineway.db,
283
+ handlers: payload.dineway
284
+ }).requiresContentPublishReview(actor);
285
+ }
286
+ async function requireApprovedContentPublishReview(extra, collection, id, reviewRequestId) {
287
+ const payload = getExtra(extra);
288
+ const actor = resolveActorIdentity(actorLocals(payload));
289
+ const decision = await new RiskPolicyEvaluator({
290
+ db: payload.dineway.db,
291
+ handlers: payload.dineway
292
+ }).evaluateContentPublishReview({
293
+ actor,
294
+ collection,
295
+ id,
296
+ reviewRequestId
297
+ });
298
+ if (!decision.allowed) throw new McpError(ErrorCode.InvalidRequest, decision.message);
299
+ }
300
+ async function ensureWorkflowHitlRequest(extra, action) {
301
+ const payload = getExtra(extra);
302
+ const ensured = await new WorkflowHitlCoordinator(payload.dineway.db).ensureRequest({
303
+ actor: workflowActorContext(payload),
304
+ action
305
+ });
306
+ if (!ensured.created) return ensured;
307
+ await logSiteActivitySafely(payload.dineway.db, actorLocals(payload), {
308
+ actionType: "hitl.submitted",
309
+ subjectType: "hitl_request",
310
+ subjectId: ensured.request.id,
311
+ scope: ensured.request.scope,
312
+ summary: `Submitted HITL request for ${ensured.request.actionType}`,
313
+ detail: {
314
+ actionType: ensured.request.actionType,
315
+ targetRefType: ensured.request.targetRefType,
316
+ targetRefId: ensured.request.targetRefId,
317
+ priority: ensured.request.priority,
318
+ riskReason: ensured.request.riskReason
319
+ }
320
+ });
321
+ return ensured;
322
+ }
323
+ async function resolveReviewRequestSubmitTarget(payload, args) {
324
+ if (args.id && args.entity_query) throw new McpError(ErrorCode.InvalidRequest, "Provide either id or entity_query when submitting a review request, not both.");
325
+ if (!args.id && !args.entity_query) throw new McpError(ErrorCode.InvalidRequest, "review_request_submit requires either an exact id or a fuzzy entity_query.");
326
+ if (args.id) {
327
+ if (!args.collection) throw new McpError(ErrorCode.InvalidRequest, "collection is required when review_request_submit uses an exact id or slug.");
328
+ return {
329
+ status: "ready",
330
+ collection: args.collection,
331
+ id: args.id
332
+ };
333
+ }
334
+ const query = args.entity_query;
335
+ const result = await new EntityResolver({
336
+ db: payload.dineway.db,
337
+ handlers: payload.dineway
338
+ }).resolve({
339
+ query,
340
+ entityTypes: ["content"],
341
+ collection: args.collection,
342
+ limit: args.entity_limit
343
+ });
344
+ if (result.status === "resolved") {
345
+ const resolved = result.resolved;
346
+ if (!resolved?.collection) throw new McpError(ErrorCode.InternalError, "Resolved content target is missing its collection slug.");
347
+ return {
348
+ status: "ready",
349
+ collection: resolved.collection,
350
+ id: resolved.entityId
351
+ };
352
+ }
353
+ const actionType = args.action_type ?? "content.publish";
354
+ const suggestion = result.suggestion ?? "Publish review requests must bind to one exact content target. Narrow the query or ask a human to choose the correct item before retrying.";
355
+ if (result.status === "ambiguous") return {
356
+ status: "ambiguous",
357
+ actionType,
358
+ query,
359
+ candidates: result.candidates ?? [],
360
+ suggestion
361
+ };
362
+ return {
363
+ status: "not_found",
364
+ actionType,
365
+ query,
366
+ suggestion
367
+ };
368
+ }
369
+ /**
370
+ * Defense-in-depth: enforce a minimum RBAC role on the current request.
371
+ *
372
+ * This is checked in addition to scope requirements. Even if a token has
373
+ * the right scopes (e.g. due to a bug in scope clamping), the user's
374
+ * actual role must still meet the minimum.
375
+ */
376
+ function requireRole(extra, minRole) {
377
+ if (getExtra(extra).userRole < minRole) throw new McpError(ErrorCode.InvalidRequest, "Insufficient permissions for this operation");
378
+ }
379
+ /**
380
+ * Enforce ownership-based permission checks, mirroring the REST API's
381
+ * requireOwnerPerm() pattern.
382
+ *
383
+ * If the user is the owner, checks ownPermission. Otherwise checks
384
+ * anyPermission (which requires EDITOR+ role).
385
+ */
386
+ function requireOwnership(extra, ownerId, ownPermission, anyPermission) {
387
+ const payload = getExtra(extra);
388
+ if (!canActOnOwn({
389
+ id: payload.userId,
390
+ role: payload.userRole
391
+ }, ownerId, ownPermission, anyPermission)) throw new McpError(ErrorCode.InvalidRequest, "Insufficient permissions for this operation");
392
+ }
393
+ /**
394
+ * Extract the author ID from a content handler response.
395
+ *
396
+ * Content handlers return `{ item: { id, authorId, ... }, _rev? }`.
397
+ * This helper navigates that shape safely.
398
+ */
399
+ function extractContentAuthorId(data) {
400
+ if (!data || typeof data !== "object") throw new McpError(ErrorCode.InternalError, "Cannot determine content ownership: no data returned");
401
+ const obj = data;
402
+ const item = obj.item && typeof obj.item === "object" ? obj.item : obj;
403
+ const authorId = typeof item?.authorId === "string" ? item.authorId : "";
404
+ if (!authorId) throw new McpError(ErrorCode.InternalError, "Cannot determine content ownership: content has no authorId");
405
+ return authorId;
406
+ }
407
+ /**
408
+ * Extract the resolved ID from a content handler response.
409
+ * Handles slug -> ID resolution performed by the handler.
410
+ */
411
+ function extractContentId(data) {
412
+ if (!data || typeof data !== "object") return void 0;
413
+ const obj = data;
414
+ const item = obj.item && typeof obj.item === "object" ? obj.item : obj;
415
+ return typeof item?.id === "string" ? item.id : void 0;
416
+ }
417
+ async function logMcpContentActivity(dineway, locals, collection, entryId, action, toolName, detail) {
418
+ await logContentActivity(dineway.db, locals, {
419
+ action,
420
+ collection,
421
+ entryId,
422
+ ...contentMcpToolSource(toolName),
423
+ detail
424
+ });
425
+ }
426
+ function experimentalWorkflowDescription(description) {
427
+ return "Experimental: this deferred Site Context workflow surface exists on the current branch but is not part of the approved baseline contract. " + description;
428
+ }
429
+ function createMcpServer() {
430
+ const server = new McpServer({
431
+ name: "dineway",
432
+ version: VERSION
433
+ }, { capabilities: { logging: {} } });
434
+ server.registerTool("site_briefing_get", {
435
+ title: "Get Site Briefing",
436
+ description: "Build a read-only Site Context Engine briefing for a site, collection, content item, plugin, taxonomy, or menu scope. The briefing includes inherited scopes, authorized schema, recent activity, active plugin capabilities, curated settings/SEO, and forward-compatible context/review/staleness sections.",
437
+ inputSchema: z.object({
438
+ scope: z.string().optional().describe("Scope string: site, collection:{slug}, content:{collection}:{id}, plugin:{id}, taxonomy:{id}, or menu:{id}. Defaults to site."),
439
+ since: z.string().optional().describe("ISO timestamp or relative age such as 30m, 12h, 7d, or 2w."),
440
+ context_types: z.array(z.string()).optional().describe("Optional context entry type filter for inherited site-context entries."),
441
+ include_stale: z.boolean().optional().describe("Whether to include stale context warnings. Defaults to true."),
442
+ format: z.enum(["json", "text"]).optional().describe("Response format. Defaults to json."),
443
+ token_budget: z.number().int().min(1).max(1e5).optional().describe("Approximate token budget for packable sections. Schema is never truncated."),
444
+ activity_limit: z.number().int().min(1).max(100).optional().describe("Maximum activity rows to consider before budget packing. Defaults to 10.")
445
+ }),
446
+ annotations: { readOnlyHint: true }
447
+ }, async (args, extra) => {
448
+ requireAnyScope(extra, [
449
+ "content:read",
450
+ "schema:read",
451
+ "admin"
452
+ ]);
453
+ const payload = getExtra(extra);
454
+ const ec = payload.dineway;
455
+ try {
456
+ const briefing = await new SiteBriefingAssembler({
457
+ db: ec.db,
458
+ storage: ec.storage,
459
+ configuredPlugins: ec.configuredPlugins,
460
+ marketplaceUrl: ec.config.marketplace,
461
+ permissions: briefingPermissions(payload)
462
+ }).assemble({
463
+ scope: args.scope,
464
+ since: args.since,
465
+ contextTypes: args.context_types,
466
+ includeStale: args.include_stale,
467
+ format: args.format,
468
+ tokenBudget: args.token_budget,
469
+ activityLimit: args.activity_limit
470
+ });
471
+ if (args.format === "text") return { content: [{
472
+ type: "text",
473
+ text: formatSiteBriefingText(briefing)
474
+ }] };
475
+ return jsonResult(briefing);
476
+ } catch (error) {
477
+ return errorResult(error);
478
+ }
479
+ });
480
+ server.registerTool("context_add", {
481
+ title: "Add Context Entry",
482
+ description: "Add a typed Site Context Engine entry attached to a Dineway scope. Use this for durable operational knowledge, not content revisions.",
483
+ inputSchema: z.object({
484
+ scope: z.string().describe("Scope string: site, collection:{slug}, content:{collection}:{id}, plugin:{id}, taxonomy:{id}, or menu:{id}."),
485
+ context_type: z.enum(SITE_CONTEXT_TYPES).describe("Typed context category."),
486
+ title: z.string().min(1).describe("Short human-readable title."),
487
+ body: z.string().min(1).describe("Operational guidance, decision, risk, or note body."),
488
+ tags: z.array(z.string()).optional().describe("Search/filter tags."),
489
+ policy_key: z.string().optional().describe("Stable key for a replaceable policy/rule."),
490
+ source_activity_id: z.string().optional().describe("Optional source activity log id."),
491
+ valid_until: z.string().optional().describe("Optional ISO timestamp when this entry should be reviewed.")
492
+ })
493
+ }, async (args, extra) => {
494
+ requireContextWrite(extra);
495
+ const payload = getExtra(extra);
496
+ const locals = actorLocals(payload);
497
+ const actor = resolveActorIdentity(locals);
498
+ const repository = new ContextRepository(payload.dineway.db);
499
+ try {
500
+ parseSiteBriefingScope(args.scope);
501
+ const entry = await repository.create({
502
+ scope: args.scope,
503
+ contextType: args.context_type,
504
+ title: args.title,
505
+ body: args.body,
506
+ tags: args.tags,
507
+ policyKey: args.policy_key,
508
+ sourceActivityId: args.source_activity_id,
509
+ validUntil: args.valid_until,
510
+ createdByActorType: actor.actorType,
511
+ createdByActorId: actor.actorId,
512
+ createdAuthMetadata: actor.authMetadata
513
+ });
514
+ await logSiteActivitySafely(payload.dineway.db, locals, {
515
+ actionType: "context.added",
516
+ subjectType: "context_entry",
517
+ subjectId: entry.id,
518
+ scope: entry.scope,
519
+ sourceType: "mcp_tool",
520
+ sourceName: "context_add",
521
+ summary: `Added ${entry.contextType} context: ${entry.title}`,
522
+ detail: {
523
+ contextType: entry.contextType,
524
+ policyKey: entry.policyKey,
525
+ tags: entry.tags
526
+ }
527
+ });
528
+ return jsonResult({ item: toPublicContextEntry(entry) });
529
+ } catch (error) {
530
+ return errorResult(error);
531
+ }
532
+ });
533
+ server.registerTool("context_get", {
534
+ title: "Get Context Entry",
535
+ description: "Get a Site Context Engine entry by id.",
536
+ inputSchema: z.object({ id: z.string().describe("Context entry id.") }),
537
+ annotations: { readOnlyHint: true }
538
+ }, async (args, extra) => {
539
+ requireContextRead(extra);
540
+ const entry = await new ContextRepository(getDineway(extra).db).findById(args.id);
541
+ return entry ? jsonResult({ item: toPublicContextEntry(entry) }) : errorResult(`Context entry not found: ${args.id}`);
542
+ });
543
+ server.registerTool("context_list", {
544
+ title: "List Context Entries",
545
+ description: "List current context entries, optionally scoped and inherited.",
546
+ inputSchema: z.object({
547
+ scope: z.string().optional().describe("Optional scope filter."),
548
+ include_inherited: z.boolean().optional().describe("Include ancestor scopes for the provided scope."),
549
+ context_types: z.array(z.enum(SITE_CONTEXT_TYPES)).optional().describe("Context type filters."),
550
+ tags: z.array(z.string()).optional().describe("Tag filters; matches any provided tag."),
551
+ current_only: z.boolean().optional().describe("Defaults to true. Set false to include superseded entries."),
552
+ limit: z.number().int().min(1).max(100).optional(),
553
+ cursor: z.string().optional()
554
+ }),
555
+ annotations: { readOnlyHint: true }
556
+ }, async (args, extra) => {
557
+ requireContextRead(extra);
558
+ try {
559
+ return jsonResult(toPublicContextEntryPage(await new ContextRepository(getDineway(extra).db).list({
560
+ scopes: scopesFromContextToolArgs(args.scope, args.include_inherited),
561
+ contextTypes: args.context_types,
562
+ tags: args.tags,
563
+ currentOnly: args.current_only,
564
+ limit: args.limit,
565
+ cursor: args.cursor
566
+ })));
567
+ } catch (error) {
568
+ return errorResult(error);
569
+ }
570
+ });
571
+ server.registerTool("context_search", {
572
+ title: "Search Context Entries",
573
+ description: "Portable keyword search over context title, body, scope, and policy key.",
574
+ inputSchema: z.object({
575
+ query: z.string().min(1).describe("Keyword query."),
576
+ scope: z.string().optional().describe("Optional scope filter."),
577
+ include_inherited: z.boolean().optional().describe("Include ancestor scopes for the provided scope."),
578
+ context_types: z.array(z.enum(SITE_CONTEXT_TYPES)).optional(),
579
+ tags: z.array(z.string()).optional(),
580
+ current_only: z.boolean().optional(),
581
+ limit: z.number().int().min(1).max(100).optional(),
582
+ cursor: z.string().optional()
583
+ }),
584
+ annotations: { readOnlyHint: true }
585
+ }, async (args, extra) => {
586
+ requireContextRead(extra);
587
+ try {
588
+ return jsonResult(toPublicContextEntryPage(await new ContextRepository(getDineway(extra).db).search({
589
+ query: args.query,
590
+ scopes: scopesFromContextToolArgs(args.scope, args.include_inherited),
591
+ contextTypes: args.context_types,
592
+ tags: args.tags,
593
+ currentOnly: args.current_only,
594
+ limit: args.limit,
595
+ cursor: args.cursor
596
+ })));
597
+ } catch (error) {
598
+ return errorResult(error);
599
+ }
600
+ });
601
+ server.registerTool("context_supersede", {
602
+ title: "Supersede Context Entry",
603
+ description: "Replace a context entry while preserving its audit history.",
604
+ inputSchema: z.object({
605
+ id: z.string().describe("Context entry id to supersede."),
606
+ scope: z.string().optional().describe("Replacement scope. Defaults to the old entry scope."),
607
+ context_type: z.enum(SITE_CONTEXT_TYPES).optional().describe("Replacement type. Defaults to old type."),
608
+ title: z.string().min(1),
609
+ body: z.string().min(1),
610
+ tags: z.array(z.string()).optional(),
611
+ policy_key: z.string().optional(),
612
+ source_activity_id: z.string().optional(),
613
+ valid_until: z.string().optional()
614
+ })
615
+ }, async (args, extra) => {
616
+ requireContextWrite(extra);
617
+ const payload = getExtra(extra);
618
+ const locals = actorLocals(payload);
619
+ const actor = resolveActorIdentity(locals);
620
+ try {
621
+ if (args.scope) parseSiteBriefingScope(args.scope);
622
+ const entry = await new ContextRepository(payload.dineway.db).supersede(args.id, {
623
+ scope: args.scope,
624
+ contextType: args.context_type,
625
+ title: args.title,
626
+ body: args.body,
627
+ tags: args.tags,
628
+ policyKey: args.policy_key,
629
+ sourceActivityId: args.source_activity_id,
630
+ validUntil: args.valid_until,
631
+ createdByActorType: actor.actorType,
632
+ createdByActorId: actor.actorId,
633
+ createdAuthMetadata: actor.authMetadata
634
+ });
635
+ if (!entry) return errorResult(`Context entry not found: ${args.id}`);
636
+ await logSiteActivitySafely(payload.dineway.db, locals, {
637
+ actionType: "context.superseded",
638
+ subjectType: "context_entry",
639
+ subjectId: entry.id,
640
+ scope: entry.scope,
641
+ sourceType: "mcp_tool",
642
+ sourceName: "context_supersede",
643
+ summary: `Superseded context entry ${args.id}`,
644
+ detail: {
645
+ supersedesId: args.id,
646
+ replacementId: entry.id,
647
+ contextType: entry.contextType,
648
+ policyKey: entry.policyKey
649
+ }
650
+ });
651
+ return jsonResult({ item: toPublicContextEntry(entry) });
652
+ } catch (error) {
653
+ return errorResult(error);
654
+ }
655
+ });
656
+ server.registerTool("context_review", {
657
+ title: "Review Context Entry",
658
+ description: "Mark a current context entry reviewed and optionally extend its validity.",
659
+ inputSchema: z.object({
660
+ id: z.string(),
661
+ review_note: z.string().optional(),
662
+ valid_until: z.string().optional()
663
+ })
664
+ }, async (args, extra) => {
665
+ requireContextWrite(extra);
666
+ const payload = getExtra(extra);
667
+ const locals = actorLocals(payload);
668
+ const actor = resolveActorIdentity(locals);
669
+ const entry = await new ContextRepository(payload.dineway.db).review(args.id, {
670
+ reviewedByActorType: actor.actorType,
671
+ reviewedByActorId: actor.actorId,
672
+ reviewNote: args.review_note,
673
+ validUntil: args.valid_until
674
+ });
675
+ if (!entry) return errorResult(`Context entry not found: ${args.id}`);
676
+ await logSiteActivitySafely(payload.dineway.db, locals, {
677
+ actionType: "context.reviewed",
678
+ subjectType: "context_entry",
679
+ subjectId: entry.id,
680
+ scope: entry.scope,
681
+ sourceType: "mcp_tool",
682
+ sourceName: "context_review",
683
+ summary: `Reviewed context entry ${entry.title}`,
684
+ detail: {
685
+ contextType: entry.contextType,
686
+ validUntil: entry.validUntil
687
+ }
688
+ });
689
+ return jsonResult({ item: toPublicContextEntry(entry) });
690
+ });
691
+ server.registerTool("context_stale", {
692
+ title: "List Stale Context Entries",
693
+ description: "List current context entries whose valid_until timestamp has passed.",
694
+ inputSchema: z.object({
695
+ scope: z.string().optional(),
696
+ include_inherited: z.boolean().optional(),
697
+ context_types: z.array(z.enum(SITE_CONTEXT_TYPES)).optional(),
698
+ tags: z.array(z.string()).optional(),
699
+ now: z.string().optional(),
700
+ limit: z.number().int().min(1).max(100).optional(),
701
+ cursor: z.string().optional()
702
+ }),
703
+ annotations: { readOnlyHint: true }
704
+ }, async (args, extra) => {
705
+ requireContextRead(extra);
706
+ try {
707
+ return jsonResult(toPublicContextEntryPage(await new ContextRepository(getDineway(extra).db).listStale({
708
+ scopes: scopesFromContextToolArgs(args.scope, args.include_inherited),
709
+ contextTypes: args.context_types,
710
+ tags: args.tags,
711
+ now: args.now,
712
+ limit: args.limit,
713
+ cursor: args.cursor
714
+ })));
715
+ } catch (error) {
716
+ return errorResult(error);
717
+ }
718
+ });
719
+ server.registerTool("context_diff", {
720
+ title: "Diff Context Entries",
721
+ description: "Return context entries that are new, superseded, stale, or reviewed since a timestamp.",
722
+ inputSchema: z.object({
723
+ since: z.string().describe("ISO timestamp lower bound."),
724
+ scope: z.string().optional(),
725
+ include_inherited: z.boolean().optional(),
726
+ now: z.string().optional()
727
+ }),
728
+ annotations: { readOnlyHint: true }
729
+ }, async (args, extra) => {
730
+ requireContextRead(extra);
731
+ try {
732
+ return jsonResult(toPublicContextDiff(await new ContextRepository(getDineway(extra).db).diff({
733
+ scopes: scopesFromContextToolArgs(args.scope, args.include_inherited),
734
+ since: args.since,
735
+ now: args.now
736
+ })));
737
+ } catch (error) {
738
+ return errorResult(error);
739
+ }
740
+ });
741
+ server.registerTool("review_request_submit", {
742
+ title: "Submit Review Request",
743
+ description: "Submit a lightweight content review request before executing a gated publish action. Provide either an exact content id/slug plus collection, or a fuzzy content entity_query. Resolved targets create a bound review request; ambiguous or missing matches return a structured result without creating a review request.",
744
+ inputSchema: z.object({
745
+ collection: z.string().optional().describe("Collection slug, or an optional content collection hint when using entity_query."),
746
+ id: z.string().optional().describe("Exact content item id or slug. Required unless entity_query is provided."),
747
+ entity_query: z.string().optional().describe("Fuzzy content reference to resolve before submitting the publish review request."),
748
+ entity_limit: z.number().int().min(1).max(10).optional().describe("Max ambiguity candidates to return when entity_query does not resolve cleanly."),
749
+ action_type: z.enum(["content.publish"]).optional().describe("Action requiring review. Defaults to content.publish."),
750
+ risk_reason: z.string().optional().describe("Reason this action needs review."),
751
+ summary: z.string().optional().describe("Human-readable review summary.")
752
+ })
753
+ }, async (args, extra) => {
754
+ requireReviewSubmit(extra);
755
+ const payload = getExtra(extra);
756
+ const locals = actorLocals(payload);
757
+ const actor = resolveActorIdentity(locals);
758
+ try {
759
+ const target = await resolveReviewRequestSubmitTarget(payload, args);
760
+ if (target.status !== "ready") return jsonResult(target);
761
+ const existing = await payload.dineway.handleContentGet(target.collection, target.id);
762
+ if (!existing.success) return unwrap(existing);
763
+ requireOwnership(extra, extractContentAuthorId(existing.data), "content:publish_own", "content:publish_any");
764
+ const resolvedId = extractContentId(existing.data) ?? target.id;
765
+ const built = await new ReviewPayloadBuilder(payload.dineway).buildContentReviewPayload({
766
+ collection: target.collection,
767
+ id: resolvedId,
768
+ actionType: args.action_type,
769
+ summary: args.summary
770
+ });
771
+ const request = await new ReviewRequestRepository(payload.dineway.db).create({
772
+ collection: built.collection,
773
+ entryId: built.entryId,
774
+ scope: built.scope,
775
+ liveRevisionId: built.liveRevisionId,
776
+ draftRevisionId: built.draftRevisionId,
777
+ reviewedRev: built.reviewedRev,
778
+ actionType: built.actionType,
779
+ actionHash: built.actionHash,
780
+ riskReason: args.risk_reason,
781
+ reviewPayload: built.reviewPayload,
782
+ requestedByActorType: actor.actorType,
783
+ requestedByActorId: actor.actorId,
784
+ requestedAuthMetadata: actor.authMetadata
785
+ });
786
+ await logSiteActivitySafely(payload.dineway.db, locals, {
787
+ actionType: "review.submitted",
788
+ subjectType: "review_request",
789
+ subjectId: request.id,
790
+ scope: request.scope,
791
+ sourceType: "mcp_tool",
792
+ sourceName: "review_request_submit",
793
+ resultStatus: "pending",
794
+ summary: `Submitted review request for ${request.actionType}`,
795
+ detail: {
796
+ collection: request.collection,
797
+ entryId: request.entryId,
798
+ actionType: request.actionType,
799
+ actionHash: request.actionHash,
800
+ riskReason: request.riskReason
801
+ }
802
+ });
803
+ return jsonResult({
804
+ status: "submitted",
805
+ request
806
+ });
807
+ } catch (error) {
808
+ return errorResult(error);
809
+ }
810
+ });
811
+ server.registerTool("review_request_check", {
812
+ title: "Check Review Request",
813
+ description: "Check a lightweight review request by id.",
814
+ inputSchema: z.object({ id: z.string().describe("Review request id.") }),
815
+ annotations: { readOnlyHint: true }
816
+ }, async (args, extra) => {
817
+ requireReviewRead(extra);
818
+ const request = await new ReviewRequestRepository(getDineway(extra).db).findById(args.id);
819
+ return request ? jsonResult({ request }) : errorResult(`Review request not found: ${args.id}`);
820
+ });
821
+ server.registerTool("review_request_resolve", {
822
+ title: "Resolve Review Request",
823
+ description: "Approve or reject a pending lightweight review request.",
824
+ inputSchema: z.object({
825
+ id: z.string().describe("Review request id."),
826
+ decision: z.enum(["approved", "rejected"]).describe("Human review decision."),
827
+ review_note: z.string().optional().describe("Optional review note.")
828
+ })
829
+ }, async (args, extra) => {
830
+ requireReviewResolve(extra);
831
+ const payload = getExtra(extra);
832
+ const locals = actorLocals(payload);
833
+ const actor = resolveActorIdentity(locals);
834
+ const request = await new ReviewRequestRepository(payload.dineway.db).resolve(args.id, {
835
+ decision: args.decision,
836
+ resolvedByActorType: actor.actorType,
837
+ resolvedByActorId: actor.actorId,
838
+ resolvedAuthMetadata: actor.authMetadata,
839
+ reviewNote: args.review_note
840
+ });
841
+ if (!request) return errorResult(`Review request not found or already resolved: ${args.id}`);
842
+ await logSiteActivitySafely(payload.dineway.db, locals, {
843
+ actionType: args.decision === "approved" ? "review.approved" : "review.rejected",
844
+ subjectType: "review_request",
845
+ subjectId: request.id,
846
+ scope: request.scope,
847
+ sourceType: "mcp_tool",
848
+ sourceName: "review_request_resolve",
849
+ summary: `${args.decision === "approved" ? "Approved" : "Rejected"} review request ${request.id}`,
850
+ detail: {
851
+ collection: request.collection,
852
+ entryId: request.entryId,
853
+ actionType: request.actionType,
854
+ actionHash: request.actionHash,
855
+ reviewNote: request.reviewNote
856
+ }
857
+ });
858
+ return jsonResult({ request });
859
+ });
860
+ server.registerTool("assignment_create", {
861
+ title: "Create Assignment",
862
+ description: experimentalWorkflowDescription("Create a workflow assignment attached to a Dineway scope. Assignments are durable human-agent handoffs and must use explicit lifecycle transitions instead of ad hoc status patches."),
863
+ inputSchema: z.object({
864
+ scope: z.string().describe("Assignment scope string."),
865
+ assignment_type: z.string().describe("Workflow-specific assignment type label."),
866
+ title: z.string().describe("Short assignment title."),
867
+ summary: z.string().optional().describe("Optional short summary."),
868
+ details: z.string().optional().describe("Optional detailed handoff or execution brief."),
869
+ priority: z.enum(ASSIGNMENT_PRIORITIES).optional().describe("Assignment priority."),
870
+ assigned_to_actor_type: z.enum(ASSIGNMENT_ACTOR_TYPES).describe("Actor type for the assignee."),
871
+ assigned_to_actor_id: z.string().describe("Actor id for the assignee."),
872
+ due_at: z.string().optional().describe("Optional ISO due timestamp."),
873
+ related_handoff_snapshot_id: z.string().optional().describe("Optional handoff snapshot id linked to this assignment."),
874
+ metadata: z.record(z.string(), z.unknown()).optional().describe("Optional non-secret workflow metadata.")
875
+ })
876
+ }, async (args, extra) => {
877
+ requireWorkflowWrite(extra);
878
+ const payload = getExtra(extra);
879
+ const locals = actorLocals(payload);
880
+ try {
881
+ const assignment = await new AssignmentService(payload.dineway.db).create(workflowActorContext(payload), {
882
+ scope: args.scope,
883
+ assignmentType: args.assignment_type,
884
+ title: args.title,
885
+ summary: args.summary,
886
+ details: args.details,
887
+ priority: args.priority,
888
+ assignedToActorType: args.assigned_to_actor_type,
889
+ assignedToActorId: args.assigned_to_actor_id,
890
+ dueAt: args.due_at,
891
+ relatedHandoffSnapshotId: args.related_handoff_snapshot_id,
892
+ metadata: args.metadata
893
+ });
894
+ await logSiteActivitySafely(payload.dineway.db, locals, {
895
+ actionType: "assignment.created",
896
+ subjectType: "assignment",
897
+ subjectId: assignment.id,
898
+ scope: assignment.scope,
899
+ summary: `Created assignment ${assignment.title}`,
900
+ detail: {
901
+ assignmentType: assignment.assignmentType,
902
+ priority: assignment.priority,
903
+ assignedToActorType: assignment.assignedToActorType,
904
+ assignedToActorId: assignment.assignedToActorId
905
+ }
906
+ });
907
+ return jsonResult({ assignment });
908
+ } catch (error) {
909
+ return errorResult(error);
910
+ }
911
+ });
912
+ server.registerTool("assignment_get", {
913
+ title: "Get Assignment",
914
+ description: experimentalWorkflowDescription("Fetch one workflow assignment by id."),
915
+ inputSchema: z.object({ id: z.string().describe("Assignment id.") }),
916
+ annotations: { readOnlyHint: true }
917
+ }, async (args, extra) => {
918
+ requireWorkflowRead(extra);
919
+ const payload = getExtra(extra);
920
+ try {
921
+ const assignment = await new AssignmentService(payload.dineway.db).get(args.id, workflowActorContext(payload));
922
+ return assignment ? jsonResult({ assignment }) : errorResult(`Assignment not found: ${args.id}`);
923
+ } catch (error) {
924
+ return errorResult(error);
925
+ }
926
+ });
927
+ server.registerTool("assignment_list", {
928
+ title: "List Assignments",
929
+ description: experimentalWorkflowDescription("List workflow assignments visible to the current actor. Non-editor actors are automatically limited to assignments they assigned or were assigned."),
930
+ inputSchema: z.object({
931
+ scope: z.string().optional().describe("Optional exact scope filter."),
932
+ statuses: z.array(z.enum(ASSIGNMENT_STATUSES)).optional().describe("Optional assignment status filters."),
933
+ priority: z.enum(ASSIGNMENT_PRIORITIES).optional().describe("Optional priority filter."),
934
+ assignment_type: z.string().optional().describe("Optional assignment type filter."),
935
+ assigned_to_me: z.boolean().optional().describe("Restrict to assignments currently assigned to the current actor."),
936
+ assigned_by_me: z.boolean().optional().describe("Restrict to assignments created by the current actor."),
937
+ since: z.string().optional().describe("Optional ISO updated_at lower bound."),
938
+ limit: z.number().int().min(1).max(100).optional().describe("Page size. Defaults to 50."),
939
+ cursor: z.string().optional().describe("Pagination cursor.")
940
+ }),
941
+ annotations: { readOnlyHint: true }
942
+ }, async (args, extra) => {
943
+ requireWorkflowRead(extra);
944
+ const payload = getExtra(extra);
945
+ try {
946
+ return jsonResult(await new AssignmentService(payload.dineway.db).list(workflowActorContext(payload), {
947
+ scope: args.scope,
948
+ statuses: args.statuses,
949
+ priority: args.priority,
950
+ assignmentType: args.assignment_type,
951
+ assignedToMe: args.assigned_to_me,
952
+ assignedByMe: args.assigned_by_me,
953
+ since: args.since,
954
+ limit: args.limit,
955
+ cursor: args.cursor
956
+ }));
957
+ } catch (error) {
958
+ return errorResult(error);
959
+ }
960
+ });
961
+ server.registerTool("assignment_accept", {
962
+ title: "Accept Assignment",
963
+ description: experimentalWorkflowDescription("Accept a pending workflow assignment."),
964
+ inputSchema: z.object({ id: z.string().describe("Assignment id.") })
965
+ }, async (args, extra) => {
966
+ requireWorkflowWrite(extra);
967
+ const payload = getExtra(extra);
968
+ const locals = actorLocals(payload);
969
+ try {
970
+ const service = new AssignmentService(payload.dineway.db);
971
+ const before = await service.get(args.id, workflowActorContext(payload));
972
+ if (!before) return errorResult(`Assignment not found: ${args.id}`);
973
+ const assignment = await service.accept({
974
+ id: args.id,
975
+ actor: workflowActorContext(payload)
976
+ });
977
+ await logSiteActivitySafely(payload.dineway.db, locals, {
978
+ actionType: "assignment.accepted",
979
+ subjectType: "assignment",
980
+ subjectId: assignment.id,
981
+ scope: assignment.scope,
982
+ summary: `Accepted assignment ${assignment.title}`,
983
+ detail: {
984
+ beforeStatus: before.status,
985
+ afterStatus: assignment.status
986
+ }
987
+ });
988
+ return jsonResult({ assignment });
989
+ } catch (error) {
990
+ return errorResult(error);
991
+ }
992
+ });
993
+ server.registerTool("assignment_start", {
994
+ title: "Start Assignment",
995
+ description: experimentalWorkflowDescription("Move an assignment into in_progress. When the assignment is currently blocked, this acts as the validated resume transition."),
996
+ inputSchema: z.object({ id: z.string().describe("Assignment id.") })
997
+ }, async (args, extra) => {
998
+ requireWorkflowWrite(extra);
999
+ const payload = getExtra(extra);
1000
+ const locals = actorLocals(payload);
1001
+ try {
1002
+ const service = new AssignmentService(payload.dineway.db);
1003
+ const before = await service.get(args.id, workflowActorContext(payload));
1004
+ if (!before) return errorResult(`Assignment not found: ${args.id}`);
1005
+ const assignment = await service.start({
1006
+ id: args.id,
1007
+ actor: workflowActorContext(payload)
1008
+ });
1009
+ await logSiteActivitySafely(payload.dineway.db, locals, {
1010
+ actionType: before.status === "blocked" ? "assignment.resumed" : "assignment.started",
1011
+ subjectType: "assignment",
1012
+ subjectId: assignment.id,
1013
+ scope: assignment.scope,
1014
+ summary: before.status === "blocked" ? `Resumed assignment ${assignment.title}` : `Started assignment ${assignment.title}`,
1015
+ detail: {
1016
+ beforeStatus: before.status,
1017
+ afterStatus: assignment.status
1018
+ }
1019
+ });
1020
+ return jsonResult({ assignment });
1021
+ } catch (error) {
1022
+ return errorResult(error);
1023
+ }
1024
+ });
1025
+ server.registerTool("assignment_complete", {
1026
+ title: "Complete Assignment",
1027
+ description: experimentalWorkflowDescription("Mark a workflow assignment complete."),
1028
+ inputSchema: z.object({
1029
+ id: z.string().describe("Assignment id."),
1030
+ completion_note: z.string().optional().describe("Optional completion note.")
1031
+ })
1032
+ }, async (args, extra) => {
1033
+ requireWorkflowWrite(extra);
1034
+ const payload = getExtra(extra);
1035
+ const locals = actorLocals(payload);
1036
+ try {
1037
+ const service = new AssignmentService(payload.dineway.db);
1038
+ const before = await service.get(args.id, workflowActorContext(payload));
1039
+ if (!before) return errorResult(`Assignment not found: ${args.id}`);
1040
+ const assignment = await service.complete({
1041
+ id: args.id,
1042
+ actor: workflowActorContext(payload),
1043
+ reason: args.completion_note
1044
+ });
1045
+ await logSiteActivitySafely(payload.dineway.db, locals, {
1046
+ actionType: "assignment.completed",
1047
+ subjectType: "assignment",
1048
+ subjectId: assignment.id,
1049
+ scope: assignment.scope,
1050
+ summary: `Completed assignment ${assignment.title}`,
1051
+ detail: {
1052
+ beforeStatus: before.status,
1053
+ afterStatus: assignment.status,
1054
+ completionNote: args.completion_note
1055
+ }
1056
+ });
1057
+ return jsonResult({ assignment });
1058
+ } catch (error) {
1059
+ return errorResult(error);
1060
+ }
1061
+ });
1062
+ server.registerTool("assignment_decline", {
1063
+ title: "Decline Assignment",
1064
+ description: experimentalWorkflowDescription("Decline a workflow assignment."),
1065
+ inputSchema: z.object({
1066
+ id: z.string().describe("Assignment id."),
1067
+ reason: z.string().optional().describe("Optional decline reason.")
1068
+ })
1069
+ }, async (args, extra) => {
1070
+ requireWorkflowWrite(extra);
1071
+ const payload = getExtra(extra);
1072
+ const locals = actorLocals(payload);
1073
+ try {
1074
+ const service = new AssignmentService(payload.dineway.db);
1075
+ const before = await service.get(args.id, workflowActorContext(payload));
1076
+ if (!before) return errorResult(`Assignment not found: ${args.id}`);
1077
+ const assignment = await service.decline({
1078
+ id: args.id,
1079
+ actor: workflowActorContext(payload),
1080
+ reason: args.reason
1081
+ });
1082
+ await logSiteActivitySafely(payload.dineway.db, locals, {
1083
+ actionType: "assignment.declined",
1084
+ subjectType: "assignment",
1085
+ subjectId: assignment.id,
1086
+ scope: assignment.scope,
1087
+ summary: `Declined assignment ${assignment.title}`,
1088
+ detail: {
1089
+ beforeStatus: before.status,
1090
+ afterStatus: assignment.status,
1091
+ reason: args.reason
1092
+ }
1093
+ });
1094
+ return jsonResult({ assignment });
1095
+ } catch (error) {
1096
+ return errorResult(error);
1097
+ }
1098
+ });
1099
+ server.registerTool("assignment_block", {
1100
+ title: "Block Assignment",
1101
+ description: experimentalWorkflowDescription("Mark a workflow assignment blocked and record why."),
1102
+ inputSchema: z.object({
1103
+ id: z.string().describe("Assignment id."),
1104
+ reason: z.string().optional().describe("Optional blocking reason.")
1105
+ })
1106
+ }, async (args, extra) => {
1107
+ requireWorkflowWrite(extra);
1108
+ const payload = getExtra(extra);
1109
+ const locals = actorLocals(payload);
1110
+ try {
1111
+ const service = new AssignmentService(payload.dineway.db);
1112
+ const before = await service.get(args.id, workflowActorContext(payload));
1113
+ if (!before) return errorResult(`Assignment not found: ${args.id}`);
1114
+ const assignment = await service.block({
1115
+ id: args.id,
1116
+ actor: workflowActorContext(payload),
1117
+ reason: args.reason
1118
+ });
1119
+ await logSiteActivitySafely(payload.dineway.db, locals, {
1120
+ actionType: "assignment.blocked",
1121
+ subjectType: "assignment",
1122
+ subjectId: assignment.id,
1123
+ scope: assignment.scope,
1124
+ summary: `Blocked assignment ${assignment.title}`,
1125
+ detail: {
1126
+ beforeStatus: before.status,
1127
+ afterStatus: assignment.status,
1128
+ reason: args.reason
1129
+ }
1130
+ });
1131
+ return jsonResult({ assignment });
1132
+ } catch (error) {
1133
+ return errorResult(error);
1134
+ }
1135
+ });
1136
+ server.registerTool("assignment_cancel", {
1137
+ title: "Cancel Assignment",
1138
+ description: experimentalWorkflowDescription("Cancel a workflow assignment that should no longer continue."),
1139
+ inputSchema: z.object({
1140
+ id: z.string().describe("Assignment id."),
1141
+ reason: z.string().optional().describe("Optional cancellation reason.")
1142
+ })
1143
+ }, async (args, extra) => {
1144
+ requireWorkflowWrite(extra);
1145
+ const payload = getExtra(extra);
1146
+ const locals = actorLocals(payload);
1147
+ try {
1148
+ const service = new AssignmentService(payload.dineway.db);
1149
+ const before = await service.get(args.id, workflowActorContext(payload));
1150
+ if (!before) return errorResult(`Assignment not found: ${args.id}`);
1151
+ const assignment = await service.cancel({
1152
+ id: args.id,
1153
+ actor: workflowActorContext(payload),
1154
+ reason: args.reason
1155
+ });
1156
+ await logSiteActivitySafely(payload.dineway.db, locals, {
1157
+ actionType: "assignment.cancelled",
1158
+ subjectType: "assignment",
1159
+ subjectId: assignment.id,
1160
+ scope: assignment.scope,
1161
+ summary: `Cancelled assignment ${assignment.title}`,
1162
+ detail: {
1163
+ beforeStatus: before.status,
1164
+ afterStatus: assignment.status,
1165
+ reason: args.reason
1166
+ }
1167
+ });
1168
+ return jsonResult({ assignment });
1169
+ } catch (error) {
1170
+ return errorResult(error);
1171
+ }
1172
+ });
1173
+ server.registerTool("hitl_request_submit", {
1174
+ title: "Submit HITL Request",
1175
+ description: experimentalWorkflowDescription("Submit a generic human-in-the-loop approval request for a high-risk non-content action. This stores the structured review payload, target ref, optional assignment/handoff links, and waits for a human decision."),
1176
+ inputSchema: z.object({
1177
+ scope: z.string().describe("Scope string: site, collection:{slug}, content:{collection}:{id}, or plugin:{id}."),
1178
+ action_type: z.string().describe("High-risk action label such as plugin.enable or schema.delete_field."),
1179
+ title: z.string().describe("Short reviewer-facing title."),
1180
+ summary: z.string().optional().describe("Optional reviewer-facing summary."),
1181
+ risk_reason: z.string().describe("Reason this action requires human approval."),
1182
+ review_payload: z.record(z.string(), z.unknown()).describe("Structured human-readable review payload."),
1183
+ target_ref_type: z.string().describe("Target ref type, such as plugin, collection, menu, or site."),
1184
+ target_ref_id: z.string().describe("Target ref id or stable key."),
1185
+ priority: z.enum(HITL_REQUEST_PRIORITIES).optional().describe("Optional priority. Defaults to normal."),
1186
+ metadata: z.record(z.string(), z.unknown()).optional().describe("Optional non-secret routing metadata."),
1187
+ related_assignment_id: z.string().optional().describe("Optional linked assignment id."),
1188
+ related_handoff_snapshot_id: z.string().optional().describe("Optional linked handoff snapshot id."),
1189
+ sla_due_at: z.string().optional().describe("Optional ISO timestamp for later SLA handling.")
1190
+ })
1191
+ }, async (args, extra) => {
1192
+ requireHitlSubmit(extra);
1193
+ const payload = getExtra(extra);
1194
+ const locals = actorLocals(payload);
1195
+ try {
1196
+ const request = await new HitlRequestService(payload.dineway.db).create(workflowActorContext(payload), {
1197
+ scope: args.scope,
1198
+ actionType: args.action_type,
1199
+ title: args.title,
1200
+ summary: args.summary,
1201
+ riskReason: args.risk_reason,
1202
+ reviewPayload: args.review_payload,
1203
+ targetRefType: args.target_ref_type,
1204
+ targetRefId: args.target_ref_id,
1205
+ priority: args.priority,
1206
+ metadata: args.metadata,
1207
+ relatedAssignmentId: args.related_assignment_id,
1208
+ relatedHandoffSnapshotId: args.related_handoff_snapshot_id,
1209
+ slaDueAt: args.sla_due_at
1210
+ });
1211
+ await logSiteActivitySafely(payload.dineway.db, locals, {
1212
+ actionType: "hitl.submitted",
1213
+ subjectType: "hitl_request",
1214
+ subjectId: request.id,
1215
+ scope: request.scope,
1216
+ summary: `Submitted HITL request for ${request.actionType}`,
1217
+ detail: {
1218
+ actionType: request.actionType,
1219
+ targetRefType: request.targetRefType,
1220
+ targetRefId: request.targetRefId,
1221
+ priority: request.priority,
1222
+ riskReason: request.riskReason,
1223
+ relatedAssignmentId: request.relatedAssignmentId
1224
+ }
1225
+ });
1226
+ return jsonResult({
1227
+ status: "submitted",
1228
+ request
1229
+ });
1230
+ } catch (error) {
1231
+ return errorResult(error);
1232
+ }
1233
+ });
1234
+ server.registerTool("hitl_request_get", {
1235
+ title: "Get HITL Request",
1236
+ description: experimentalWorkflowDescription("Get one generic HITL approval request by id."),
1237
+ inputSchema: z.object({ id: z.string().describe("HITL request id.") }),
1238
+ annotations: { readOnlyHint: true }
1239
+ }, async (args, extra) => {
1240
+ requireHitlRead(extra);
1241
+ const payload = getExtra(extra);
1242
+ try {
1243
+ const request = await new HitlRequestService(payload.dineway.db).get(args.id, workflowActorContext(payload));
1244
+ return request ? jsonResult({ request }) : errorResult(`HITL request not found: ${args.id}`);
1245
+ } catch (error) {
1246
+ return errorResult(error);
1247
+ }
1248
+ });
1249
+ server.registerTool("hitl_request_list", {
1250
+ title: "List HITL Requests",
1251
+ description: experimentalWorkflowDescription("List generic HITL approval requests. Non-editor actors only see requests they submitted; editors can inspect all matching requests."),
1252
+ inputSchema: z.object({
1253
+ scope: z.string().optional().describe("Optional scope string: site, collection:{slug}, content:{collection}:{id}, or plugin:{id}."),
1254
+ include_inherited: z.boolean().optional().describe("Include ancestor scopes when scope is provided. Defaults to false."),
1255
+ statuses: z.array(z.enum(HITL_REQUEST_STATUSES)).optional().describe("Optional status filter."),
1256
+ action_type: z.string().optional().describe("Optional action type filter."),
1257
+ priority: z.enum(HITL_REQUEST_PRIORITIES).optional().describe("Optional priority filter."),
1258
+ target_ref_type: z.string().optional().describe("Optional target ref type filter."),
1259
+ target_ref_id: z.string().optional().describe("Optional target ref id filter."),
1260
+ related_assignment_id: z.string().optional().describe("Optional related assignment id filter."),
1261
+ since: z.string().optional().describe("Optional updated_at lower bound ISO timestamp."),
1262
+ limit: z.number().int().min(1).max(100).optional().describe("Maximum rows to return."),
1263
+ cursor: z.string().optional().describe("Opaque pagination cursor.")
1264
+ }),
1265
+ annotations: { readOnlyHint: true }
1266
+ }, async (args, extra) => {
1267
+ requireHitlRead(extra);
1268
+ const payload = getExtra(extra);
1269
+ try {
1270
+ return jsonResult(await new HitlRequestService(payload.dineway.db).list(workflowActorContext(payload), {
1271
+ scope: args.include_inherited ? void 0 : args.scope,
1272
+ scopes: scopesFromContextToolArgs(args.scope, args.include_inherited),
1273
+ statuses: args.statuses,
1274
+ actionType: args.action_type,
1275
+ priority: args.priority,
1276
+ targetRefType: args.target_ref_type,
1277
+ targetRefId: args.target_ref_id,
1278
+ relatedAssignmentId: args.related_assignment_id,
1279
+ since: args.since,
1280
+ limit: args.limit,
1281
+ cursor: args.cursor
1282
+ }));
1283
+ } catch (error) {
1284
+ return errorResult(error);
1285
+ }
1286
+ });
1287
+ server.registerTool("hitl_request_resolve", {
1288
+ title: "Resolve HITL Request",
1289
+ description: experimentalWorkflowDescription("Approve or reject a pending generic HITL request as an editor or admin reviewer."),
1290
+ inputSchema: z.object({
1291
+ id: z.string().describe("HITL request id."),
1292
+ decision: z.enum(["approved", "rejected"]).describe("Review decision."),
1293
+ review_note: z.string().optional().describe("Optional reviewer note.")
1294
+ })
1295
+ }, async (args, extra) => {
1296
+ requireHitlResolve(extra);
1297
+ const payload = getExtra(extra);
1298
+ const locals = actorLocals(payload);
1299
+ try {
1300
+ const service = new HitlRequestService(payload.dineway.db);
1301
+ const before = await service.get(args.id, workflowActorContext(payload));
1302
+ if (!before) return errorResult(`HITL request not found: ${args.id}`);
1303
+ const request = await service.resolve({
1304
+ id: args.id,
1305
+ actor: workflowActorContext(payload),
1306
+ decision: args.decision,
1307
+ reviewNote: args.review_note
1308
+ });
1309
+ await logSiteActivitySafely(payload.dineway.db, locals, {
1310
+ actionType: args.decision === "approved" ? "hitl.approved" : "hitl.rejected",
1311
+ subjectType: "hitl_request",
1312
+ subjectId: request.id,
1313
+ scope: request.scope,
1314
+ summary: `${args.decision === "approved" ? "Approved" : "Rejected"} HITL request ${request.id}`,
1315
+ detail: {
1316
+ beforeStatus: before.status,
1317
+ afterStatus: request.status,
1318
+ actionType: request.actionType,
1319
+ targetRefType: request.targetRefType,
1320
+ targetRefId: request.targetRefId,
1321
+ reviewNote: request.reviewNote
1322
+ }
1323
+ });
1324
+ return jsonResult({ request });
1325
+ } catch (error) {
1326
+ return errorResult(error);
1327
+ }
1328
+ });
1329
+ server.registerTool("actor_expertise", {
1330
+ title: "Actor Expertise",
1331
+ description: experimentalWorkflowDescription("Inspect expertise derived from activity, current context authorship, and review-request history without requiring a separate actors table."),
1332
+ inputSchema: z.discriminatedUnion("mode", [z.object({
1333
+ mode: z.literal("by_scope"),
1334
+ scope: z.string().describe("Scope string: site, collection:{slug}, content:{collection}:{id}, or plugin:{id}."),
1335
+ include_inherited: z.boolean().optional().describe("Include ancestor scopes in the expertise ranking. Defaults to true."),
1336
+ limit: z.number().int().min(1).max(100).optional().describe("Maximum experts to return. Defaults to 10.")
1337
+ }), z.object({
1338
+ mode: z.literal("by_actor"),
1339
+ actor_type: z.enum(ACTOR_EXPERTISE_ACTOR_TYPES).describe("Actor type: user, api_token, or system."),
1340
+ actor_id: z.string().describe("Actor id to summarize."),
1341
+ limit: z.number().int().min(1).max(100).optional().describe("Maximum readable scopes to return. Defaults to 10.")
1342
+ })]),
1343
+ annotations: { readOnlyHint: true }
1344
+ }, async (args, extra) => {
1345
+ const payload = getExtra(extra);
1346
+ const service = new ActorExpertiseService({ db: payload.dineway.db });
1347
+ if (args.mode === "by_scope") {
1348
+ requireExpertiseScope(extra, args.scope);
1349
+ return jsonResult(await service.findByScope({
1350
+ scope: args.scope,
1351
+ includeInherited: args.include_inherited,
1352
+ limit: args.limit
1353
+ }));
1354
+ }
1355
+ requireExpertiseRead(extra);
1356
+ const result = await service.findByActor({
1357
+ actorType: args.actor_type,
1358
+ actorId: args.actor_id
1359
+ });
1360
+ const limit = Math.min(Math.max(1, args.limit ?? 10), 100);
1361
+ const scopes = result.scopes.filter((item) => scopeReadableForExpertise(payload, item.scope));
1362
+ const counts = scopes.reduce((accumulator, item) => ({
1363
+ contextEntries: accumulator.contextEntries + item.counts.contextEntries,
1364
+ reviewRequests: accumulator.reviewRequests + item.counts.reviewRequests,
1365
+ activities: accumulator.activities + item.counts.activities,
1366
+ total: accumulator.total + item.counts.total
1367
+ }), {
1368
+ contextEntries: 0,
1369
+ reviewRequests: 0,
1370
+ activities: 0,
1371
+ total: 0
1372
+ });
1373
+ return jsonResult({
1374
+ ...result,
1375
+ counts,
1376
+ scopes: scopes.slice(0, limit)
1377
+ });
1378
+ });
1379
+ server.registerTool("agent_handoff_capture", {
1380
+ title: "Capture Agent Handoff",
1381
+ description: experimentalWorkflowDescription("Capture the current agent objective, reasoning, findings, touched objects, and pending actions before pausing or handing work off."),
1382
+ inputSchema: z.object({
1383
+ scope: z.string().describe("Scope string: site, collection:{slug}, content:{collection}:{id}, or plugin:{id}."),
1384
+ objective: z.string().min(1).describe("Current objective or outcome the agent was pursuing."),
1385
+ reasoning: z.string().min(1).describe("Concise summary of what was found, decided, or blocked at the handoff point."),
1386
+ key_findings: z.array(z.object({
1387
+ summary: z.string().min(1),
1388
+ confidence: z.number().min(0).max(1).optional(),
1389
+ evidence_type: z.enum([
1390
+ "context_entry",
1391
+ "review_request",
1392
+ "activity"
1393
+ ]).optional(),
1394
+ evidence_id: z.string().optional()
1395
+ })).optional().describe("Important findings or conclusions with optional evidence references."),
1396
+ touched_objects: z.array(z.object({
1397
+ entity_type: z.string().min(1),
1398
+ id: z.string().min(1),
1399
+ label: z.string().optional(),
1400
+ collection: z.string().optional(),
1401
+ scope: z.string().optional()
1402
+ })).optional().describe("Objects already inspected or modified during this workflow."),
1403
+ pending_actions: z.array(z.object({
1404
+ action_type: z.string().min(1),
1405
+ summary: z.string().min(1),
1406
+ status: z.enum([
1407
+ "pending",
1408
+ "blocked",
1409
+ "waiting_on_review"
1410
+ ]).optional(),
1411
+ scope: z.string().optional()
1412
+ })).optional().describe("Concrete next actions or blockers left unresolved."),
1413
+ tool_evidence: z.array(z.object({
1414
+ tool_name: z.string().min(1),
1415
+ args_summary: z.string().optional(),
1416
+ result_summary: z.string().optional()
1417
+ })).optional().describe("Non-secret summaries of the key tools used to reach this state."),
1418
+ confidence: z.number().min(0).max(1).optional().describe("Optional self-assessed confidence in the current reasoning state."),
1419
+ handoff_kind: z.enum(HANDOFF_KINDS).optional().describe("Why the work is being paused or handed off. Defaults to pause."),
1420
+ reference_type: z.enum(HANDOFF_REFERENCE_TYPES).optional().describe("Optional linked review request or assignment type."),
1421
+ reference_id: z.string().optional().describe("Optional linked review request or assignment id.")
1422
+ })
1423
+ }, async (args, extra) => {
1424
+ requireHandoffScope(extra, args.scope);
1425
+ const payload = getExtra(extra);
1426
+ const locals = actorLocals(payload);
1427
+ const actor = resolveActorIdentity(locals);
1428
+ const store = new HandoffSnapshotStore({ db: payload.dineway.db });
1429
+ try {
1430
+ const snapshot = await store.capture({
1431
+ scope: args.scope,
1432
+ actorType: actor.actorType,
1433
+ actorId: actor.actorId,
1434
+ authMetadata: actor.authMetadata,
1435
+ objective: args.objective,
1436
+ reasoning: args.reasoning,
1437
+ keyFindings: args.key_findings?.map((item) => ({
1438
+ summary: item.summary,
1439
+ confidence: item.confidence,
1440
+ evidenceType: item.evidence_type,
1441
+ evidenceId: item.evidence_id
1442
+ })),
1443
+ touchedObjects: args.touched_objects?.map((item) => ({
1444
+ entityType: item.entity_type,
1445
+ id: item.id,
1446
+ label: item.label,
1447
+ collection: item.collection,
1448
+ scope: item.scope
1449
+ })),
1450
+ pendingActions: args.pending_actions?.map((item) => ({
1451
+ actionType: item.action_type,
1452
+ summary: item.summary,
1453
+ status: item.status,
1454
+ scope: item.scope
1455
+ })),
1456
+ toolEvidence: args.tool_evidence?.map((item) => ({
1457
+ toolName: item.tool_name,
1458
+ argsSummary: item.args_summary,
1459
+ resultSummary: item.result_summary
1460
+ })),
1461
+ confidence: args.confidence,
1462
+ handoffKind: args.handoff_kind,
1463
+ referenceType: args.reference_type,
1464
+ referenceId: args.reference_id
1465
+ });
1466
+ await logSiteActivitySafely(payload.dineway.db, locals, {
1467
+ actionType: "handoff.captured",
1468
+ subjectType: "handoff_snapshot",
1469
+ subjectId: snapshot.id,
1470
+ scope: snapshot.scope,
1471
+ summary: `Captured ${snapshot.handoffKind} handoff snapshot`,
1472
+ detail: {
1473
+ handoffKind: snapshot.handoffKind,
1474
+ referenceType: snapshot.referenceType,
1475
+ referenceId: snapshot.referenceId,
1476
+ objective: snapshot.objective
1477
+ }
1478
+ });
1479
+ return jsonResult({ snapshot });
1480
+ } catch (error) {
1481
+ return errorResult(error);
1482
+ }
1483
+ });
1484
+ server.registerTool("agent_handoff_resume", {
1485
+ title: "Resume Agent Handoff",
1486
+ description: experimentalWorkflowDescription("Resume from a stored handoff snapshot and return a deterministic diff of activity, context, and review changes since capture time."),
1487
+ inputSchema: z.object({
1488
+ snapshot_id: z.string().describe("Handoff snapshot id returned by agent_handoff_capture."),
1489
+ activity_limit: z.number().int().min(1).max(100).optional().describe("Maximum activity rows to include in the diff. Defaults to 25."),
1490
+ review_limit: z.number().int().min(1).max(100).optional().describe("Maximum review request rows to include in the diff. Defaults to 25.")
1491
+ })
1492
+ }, async (args, extra) => {
1493
+ requireRole(extra, Role.AUTHOR);
1494
+ const payload = getExtra(extra);
1495
+ const store = new HandoffSnapshotStore({ db: payload.dineway.db });
1496
+ try {
1497
+ const snapshot = await store.get(args.snapshot_id);
1498
+ if (!snapshot) return errorResult(`Handoff snapshot not found: ${args.snapshot_id}`);
1499
+ requireHandoffScope(extra, snapshot.scope);
1500
+ const existing = await store.resume({
1501
+ id: args.snapshot_id,
1502
+ permissions: { canReadContent: contentDiffReadableForHandoff(payload) },
1503
+ activityLimit: args.activity_limit,
1504
+ reviewLimit: args.review_limit
1505
+ });
1506
+ if (!existing) return errorResult(`Handoff snapshot not found: ${args.snapshot_id}`);
1507
+ await logSiteActivitySafely(payload.dineway.db, actorLocals(payload), {
1508
+ actionType: "handoff.resumed",
1509
+ subjectType: "handoff_snapshot",
1510
+ subjectId: existing.snapshot.id,
1511
+ scope: existing.snapshot.scope,
1512
+ summary: `Resumed ${existing.snapshot.handoffKind} handoff snapshot`,
1513
+ detail: {
1514
+ handoffKind: existing.snapshot.handoffKind,
1515
+ referenceType: existing.snapshot.referenceType,
1516
+ referenceId: existing.snapshot.referenceId,
1517
+ resumedAt: existing.resumedAt
1518
+ }
1519
+ });
1520
+ return jsonResult(existing);
1521
+ } catch (error) {
1522
+ return errorResult(error);
1523
+ }
1524
+ });
1525
+ server.registerTool("content_list", {
1526
+ title: "List Content",
1527
+ description: "List content items in a collection with optional filtering and pagination. Returns items sorted by the specified field. Use the nextCursor value from the response to fetch the next page. Status can be 'draft', 'published', or 'scheduled'. If no status is given, all non-trashed items are returned.",
1528
+ inputSchema: z.object({
1529
+ collection: z.string().describe("Collection slug (e.g. 'posts', 'pages')"),
1530
+ status: z.enum([
1531
+ "draft",
1532
+ "published",
1533
+ "scheduled"
1534
+ ]).optional().describe("Filter by content status"),
1535
+ limit: z.number().int().min(1).max(100).optional().describe("Max items to return (default 50, max 100)"),
1536
+ cursor: z.string().optional().describe("Pagination cursor from a previous response"),
1537
+ orderBy: z.string().optional().describe("Field to sort by (e.g. 'created_at', 'updated_at')"),
1538
+ order: z.enum(["asc", "desc"]).optional().describe("Sort direction (default 'desc')"),
1539
+ locale: z.string().optional().describe("Filter by locale (e.g. 'en', 'fr'). Only relevant when i18n is enabled.")
1540
+ }),
1541
+ annotations: { readOnlyHint: true }
1542
+ }, async (args, extra) => {
1543
+ requireScope(extra, "content:read");
1544
+ const ec = getDineway(extra);
1545
+ const status = canReadDrafts(extra) ? args.status : "published";
1546
+ return unwrap(await ec.handleContentList(args.collection, {
1547
+ status,
1548
+ limit: args.limit,
1549
+ cursor: args.cursor,
1550
+ orderBy: args.orderBy,
1551
+ order: args.order,
1552
+ locale: args.locale
1553
+ }));
1554
+ });
1555
+ server.registerTool("content_get", {
1556
+ title: "Get Content",
1557
+ description: "Get a single content item by its ID or slug. Returns the full content data including all field values, metadata, and a _rev token for optimistic concurrency (pass _rev back when updating to detect conflicts).",
1558
+ inputSchema: z.object({
1559
+ collection: z.string().describe("Collection slug (e.g. 'posts', 'pages')"),
1560
+ id: z.string().describe("Content item ID (ULID) or slug"),
1561
+ locale: z.string().optional().describe("Locale to scope slug lookup (e.g. 'fr'). Only affects slug resolution; IDs are globally unique.")
1562
+ }),
1563
+ annotations: { readOnlyHint: true }
1564
+ }, async (args, extra) => {
1565
+ requireScope(extra, "content:read");
1566
+ const result = await getDineway(extra).handleContentGet(args.collection, args.id, args.locale);
1567
+ if (result.success && !canReadDrafts(extra)) {
1568
+ const data = result.data && typeof result.data === "object" ? result.data : {};
1569
+ if (!isPublishedRecord("item" in data ? data.item : data)) return errorResult("Content not found");
1570
+ }
1571
+ return unwrap(result);
1572
+ });
1573
+ server.registerTool("content_create", {
1574
+ title: "Create Content",
1575
+ description: "Create a new content item in a collection. The 'data' object should contain field values matching the collection's schema (use schema_get_collection to check). Rich text fields accept Portable Text JSON arrays. A slug is auto-generated if not provided. Items are created as 'draft' by default — use content_publish to make them live.",
1576
+ inputSchema: z.object({
1577
+ collection: z.string().describe("Collection slug (e.g. 'posts', 'pages')"),
1578
+ data: z.record(z.string(), z.unknown()).describe("Field values as key-value pairs matching the collection schema"),
1579
+ slug: z.string().optional().describe("URL slug (auto-generated from title if omitted)"),
1580
+ status: z.enum(["draft", "published"]).optional().describe("Initial status (default 'draft'). Requires publish permission."),
1581
+ locale: z.string().optional().describe("Locale for this content (e.g. 'fr'). Defaults to default locale."),
1582
+ translationOf: z.string().optional().describe("ID of the content item this is a translation of. Links items in the same translation group.")
1583
+ }),
1584
+ annotations: { destructiveHint: false }
1585
+ }, async (args, extra) => {
1586
+ requireScope(extra, "content:write");
1587
+ requireRole(extra, Role.CONTRIBUTOR);
1588
+ const payload = getExtra(extra);
1589
+ const { dineway, userId } = payload;
1590
+ const locals = actorLocals(payload);
1591
+ if (args.translationOf) {
1592
+ const source = await dineway.handleContentGet(args.collection, args.translationOf);
1593
+ if (!source.success) return unwrap(source);
1594
+ requireOwnership(extra, extractContentAuthorId(source.data), "content:edit_own", "content:edit_any");
1595
+ }
1596
+ if (args.status === "published") {
1597
+ if (!hasPermission({
1598
+ id: userId,
1599
+ role: getExtra(extra).userRole
1600
+ }, "content:publish_own")) throw new McpError(ErrorCode.InvalidRequest, "Insufficient permissions: publishing requires content:publish_own");
1601
+ if (contentPublishReviewRequired(extra)) throw new McpError(ErrorCode.InvalidRequest, "Token-authenticated content creation with immediate publish is review-gated. Create a draft first, submit review_request_submit, resolve it as approved, then call content_publish with review_request_id.");
1602
+ const result = await dineway.handleContentCreate(args.collection, {
1603
+ data: args.data,
1604
+ slug: args.slug,
1605
+ authorId: userId,
1606
+ locale: args.locale,
1607
+ translationOf: args.translationOf
1608
+ });
1609
+ if (!result.success) return unwrap(result);
1610
+ const itemId = extractContentId(result.data);
1611
+ if (itemId) {
1612
+ await logMcpContentActivity(dineway, locals, args.collection, itemId, "created", "content_create", {
1613
+ changedFields: activityChangedKeys(args.data),
1614
+ slugProvided: args.slug !== void 0,
1615
+ locale: args.locale ?? null,
1616
+ translationOf: args.translationOf ?? null
1617
+ });
1618
+ const publishResult = await dineway.handleContentPublish(args.collection, itemId);
1619
+ if (publishResult.success) await logMcpContentActivity(dineway, locals, args.collection, itemId, "published", "content_create");
1620
+ return unwrap(publishResult);
1621
+ }
1622
+ return unwrap(result);
1623
+ }
1624
+ const result = await dineway.handleContentCreate(args.collection, {
1625
+ data: args.data,
1626
+ slug: args.slug,
1627
+ authorId: userId,
1628
+ locale: args.locale,
1629
+ translationOf: args.translationOf
1630
+ });
1631
+ if (result.success) {
1632
+ const itemId = extractContentId(result.data);
1633
+ if (itemId) await logMcpContentActivity(dineway, locals, args.collection, itemId, "created", "content_create", {
1634
+ changedFields: activityChangedKeys(args.data),
1635
+ slugProvided: args.slug !== void 0,
1636
+ locale: args.locale ?? null,
1637
+ translationOf: args.translationOf ?? null
1638
+ });
1639
+ }
1640
+ return unwrap(result);
1641
+ });
1642
+ server.registerTool("content_update", {
1643
+ title: "Update Content",
1644
+ description: "Update an existing content item. Only include fields you want to change in the 'data' object — unspecified fields are left unchanged. Pass the _rev token from content_get to enable optimistic concurrency checking (the update fails if the item was modified since you read it). `seo` and `bylines` are persisted alongside field updates. `publishedAt` requires content:publish_any permission and is useful for content imports.",
1645
+ inputSchema: z.object({
1646
+ collection: z.string().describe("Collection slug"),
1647
+ id: z.string().describe("Content item ID or slug"),
1648
+ data: z.record(z.string(), z.unknown()).optional().describe("Field values to update (only include changed fields)"),
1649
+ slug: z.string().optional().describe("New URL slug"),
1650
+ status: z.enum(["draft", "published"]).optional().describe("New status. Setting to 'published' requires publish permission. Setting to 'draft' unpublishes the item and also requires publish permission."),
1651
+ seo: contentSeoInput.optional().describe("Per-content SEO metadata. Only valid for collections with SEO enabled."),
1652
+ bylines: z.array(contentBylineInputSchema).optional().describe("Replace the byline list for this item. The first entry becomes the primary byline."),
1653
+ publishedAt: z.iso.datetime({
1654
+ offset: true,
1655
+ message: "must be an ISO 8601 datetime"
1656
+ }).nullish().describe("Override the publication timestamp. Requires content:publish_any permission. Pass null to clear."),
1657
+ _rev: z.string().optional().describe("Revision token from content_get for conflict detection"),
1658
+ review_request_id: z.string().optional().describe("Approved review request id required for token-authenticated content.publish actions.")
1659
+ })
1660
+ }, async (args, extra) => {
1661
+ requireScope(extra, "content:write");
1662
+ requireRole(extra, Role.AUTHOR);
1663
+ const payload = getExtra(extra);
1664
+ const { dineway, userId, userRole } = payload;
1665
+ const locals = actorLocals(payload);
1666
+ const existing = await dineway.handleContentGet(args.collection, args.id);
1667
+ if (!existing.success) return unwrap(existing);
1668
+ requireOwnership(extra, extractContentAuthorId(existing.data), "content:edit_own", "content:edit_any");
1669
+ const resolvedId = extractContentId(existing.data) ?? args.id;
1670
+ const ownerId = extractContentAuthorId(existing.data);
1671
+ if (args.publishedAt !== void 0) {
1672
+ if (!hasPermission({
1673
+ id: userId,
1674
+ role: userRole
1675
+ }, "content:publish_any")) return errorResult("[INSUFFICIENT_PERMISSIONS] Setting publishedAt requires content:publish_any permission");
1676
+ }
1677
+ const hasInlineUpdates = args.data || args.slug || args.seo !== void 0 || args.bylines !== void 0 || args.publishedAt !== void 0;
1678
+ if (args.status === "published") {
1679
+ requireOwnership(extra, ownerId, "content:publish_own", "content:publish_any");
1680
+ if (contentPublishReviewRequired(extra) && hasInlineUpdates) throw new McpError(ErrorCode.InvalidRequest, "Token-authenticated publish with inline content changes is review-gated. Save the draft first, submit review_request_submit, resolve it as approved, then publish with review_request_id.");
1681
+ if (hasInlineUpdates) {
1682
+ const updateResult = await dineway.handleContentUpdate(args.collection, resolvedId, {
1683
+ data: args.data,
1684
+ slug: args.slug,
1685
+ authorId: userId,
1686
+ seo: args.seo,
1687
+ bylines: args.bylines,
1688
+ publishedAt: args.publishedAt,
1689
+ _rev: args._rev
1690
+ });
1691
+ if (!updateResult.success) return unwrap(updateResult);
1692
+ await logMcpContentActivity(dineway, locals, args.collection, resolvedId, "updated", "content_update", {
1693
+ changedFields: activityChangedKeys(args.data),
1694
+ slugChanged: args.slug !== void 0,
1695
+ seoChanged: args.seo !== void 0,
1696
+ bylinesChanged: args.bylines !== void 0,
1697
+ publishedAtChanged: args.publishedAt !== void 0
1698
+ });
1699
+ }
1700
+ await requireApprovedContentPublishReview(extra, args.collection, resolvedId, args.review_request_id);
1701
+ const publishResult = await dineway.handleContentPublish(args.collection, resolvedId);
1702
+ if (publishResult.success) await logMcpContentActivity(dineway, locals, args.collection, resolvedId, "published", "content_update", { reviewRequestId: args.review_request_id ?? null });
1703
+ return unwrap(publishResult);
1704
+ }
1705
+ if (args.status === "draft") {
1706
+ requireOwnership(extra, ownerId, "content:publish_own", "content:publish_any");
1707
+ if (hasInlineUpdates) {
1708
+ const updateResult = await dineway.handleContentUpdate(args.collection, resolvedId, {
1709
+ data: args.data,
1710
+ slug: args.slug,
1711
+ authorId: userId,
1712
+ seo: args.seo,
1713
+ bylines: args.bylines,
1714
+ publishedAt: args.publishedAt,
1715
+ _rev: args._rev
1716
+ });
1717
+ if (!updateResult.success) return unwrap(updateResult);
1718
+ await logMcpContentActivity(dineway, locals, args.collection, resolvedId, "updated", "content_update", {
1719
+ changedFields: activityChangedKeys(args.data),
1720
+ slugChanged: args.slug !== void 0,
1721
+ seoChanged: args.seo !== void 0,
1722
+ bylinesChanged: args.bylines !== void 0,
1723
+ publishedAtChanged: args.publishedAt !== void 0
1724
+ });
1725
+ }
1726
+ const unpublishResult = await dineway.handleContentUnpublish(args.collection, resolvedId);
1727
+ if (unpublishResult.success) await logMcpContentActivity(dineway, locals, args.collection, resolvedId, "unpublished", "content_update");
1728
+ return unwrap(unpublishResult);
1729
+ }
1730
+ const result = await dineway.handleContentUpdate(args.collection, resolvedId, {
1731
+ data: args.data,
1732
+ slug: args.slug,
1733
+ authorId: userId,
1734
+ seo: args.seo,
1735
+ bylines: args.bylines,
1736
+ publishedAt: args.publishedAt,
1737
+ _rev: args._rev
1738
+ });
1739
+ if (result.success) await logMcpContentActivity(dineway, locals, args.collection, resolvedId, "updated", "content_update", {
1740
+ changedFields: activityChangedKeys(args.data),
1741
+ slugChanged: args.slug !== void 0,
1742
+ seoChanged: args.seo !== void 0,
1743
+ bylinesChanged: args.bylines !== void 0,
1744
+ publishedAtChanged: args.publishedAt !== void 0
1745
+ });
1746
+ return unwrap(result);
1747
+ });
1748
+ server.registerTool("content_delete", {
1749
+ title: "Delete Content (Trash)",
1750
+ description: "Soft-delete a content item by moving it to the trash. The item can be restored later with content_restore, or permanently deleted with content_permanent_delete.",
1751
+ inputSchema: z.object({
1752
+ collection: z.string().describe("Collection slug"),
1753
+ id: z.string().describe("Content item ID or slug")
1754
+ }),
1755
+ annotations: { destructiveHint: true }
1756
+ }, async (args, extra) => {
1757
+ requireScope(extra, "content:write");
1758
+ requireRole(extra, Role.AUTHOR);
1759
+ const payload = getExtra(extra);
1760
+ const ec = payload.dineway;
1761
+ const locals = actorLocals(payload);
1762
+ const existing = await ec.handleContentGet(args.collection, args.id);
1763
+ if (!existing.success) return unwrap(existing);
1764
+ requireOwnership(extra, extractContentAuthorId(existing.data), "content:delete_own", "content:delete_any");
1765
+ const resolvedId = extractContentId(existing.data) ?? args.id;
1766
+ const result = await ec.handleContentDelete(args.collection, resolvedId);
1767
+ if (result.success) await logMcpContentActivity(ec, locals, args.collection, resolvedId, "deleted", "content_delete");
1768
+ return unwrap(result);
1769
+ });
1770
+ server.registerTool("content_restore", {
1771
+ title: "Restore Content",
1772
+ description: "Restore a soft-deleted content item from the trash back to its previous state.",
1773
+ inputSchema: z.object({
1774
+ collection: z.string().describe("Collection slug"),
1775
+ id: z.string().describe("Content item ID or slug")
1776
+ })
1777
+ }, async (args, extra) => {
1778
+ requireScope(extra, "content:write");
1779
+ requireRole(extra, Role.AUTHOR);
1780
+ const payload = getExtra(extra);
1781
+ const ec = payload.dineway;
1782
+ const locals = actorLocals(payload);
1783
+ const existing = await ec.handleContentGetIncludingTrashed(args.collection, args.id);
1784
+ if (!existing.success) return unwrap(existing);
1785
+ requireOwnership(extra, extractContentAuthorId(existing.data), "content:edit_own", "content:edit_any");
1786
+ const resolvedId = extractContentId(existing.data) ?? args.id;
1787
+ const result = await ec.handleContentRestore(args.collection, resolvedId);
1788
+ if (result.success) await logMcpContentActivity(ec, locals, args.collection, resolvedId, "restored", "content_restore");
1789
+ return unwrap(result);
1790
+ });
1791
+ server.registerTool("content_permanent_delete", {
1792
+ title: "Permanently Delete Content",
1793
+ description: "Permanently and irreversibly delete a trashed content item. The item must be in the trash first (use content_delete). This cannot be undone.",
1794
+ inputSchema: z.object({
1795
+ collection: z.string().describe("Collection slug"),
1796
+ id: z.string().describe("Content item ID or slug")
1797
+ }),
1798
+ annotations: { destructiveHint: true }
1799
+ }, async (args, extra) => {
1800
+ requireScope(extra, "content:write");
1801
+ requireRole(extra, Role.ADMIN);
1802
+ const payload = getExtra(extra);
1803
+ const ec = payload.dineway;
1804
+ const locals = actorLocals(payload);
1805
+ const existing = await ec.handleContentGetIncludingTrashed(args.collection, args.id);
1806
+ const resolvedId = existing.success ? extractContentId(existing.data) ?? args.id : args.id;
1807
+ const result = await ec.handleContentPermanentDelete(args.collection, resolvedId);
1808
+ if (result.success) await logMcpContentActivity(ec, locals, args.collection, resolvedId, "permanently_deleted", "content_permanent_delete");
1809
+ return unwrap(result);
1810
+ });
1811
+ server.registerTool("content_publish", {
1812
+ title: "Publish Content",
1813
+ description: "Publish a content item, making it live on the site. Creates a published revision from the current draft. Further edits create a new draft without affecting the live version until re-published. Pass `publishedAt` to backdate imported or historical content; this requires content:publish_any.",
1814
+ inputSchema: z.object({
1815
+ collection: z.string().describe("Collection slug"),
1816
+ id: z.string().describe("Content item ID or slug"),
1817
+ publishedAt: z.iso.datetime({
1818
+ offset: true,
1819
+ message: "must be an ISO 8601 datetime"
1820
+ }).optional().describe("Override publication timestamp. Requires content:publish_any permission."),
1821
+ review_request_id: z.string().optional().describe("Approved review request id required for token-authenticated publish actions.")
1822
+ })
1823
+ }, async (args, extra) => {
1824
+ requireScope(extra, "content:write");
1825
+ requireRole(extra, Role.AUTHOR);
1826
+ const payload = getExtra(extra);
1827
+ const { dineway: ec, userId, userRole } = payload;
1828
+ const locals = actorLocals(payload);
1829
+ const existing = await ec.handleContentGet(args.collection, args.id);
1830
+ if (!existing.success) return unwrap(existing);
1831
+ requireOwnership(extra, extractContentAuthorId(existing.data), "content:publish_own", "content:publish_any");
1832
+ const resolvedId = extractContentId(existing.data) ?? args.id;
1833
+ if (args.publishedAt !== void 0) {
1834
+ if (!hasPermission({
1835
+ id: userId,
1836
+ role: userRole
1837
+ }, "content:publish_any")) return errorResult("[INSUFFICIENT_PERMISSIONS] Setting publishedAt requires content:publish_any permission");
1838
+ }
1839
+ await requireApprovedContentPublishReview(extra, args.collection, resolvedId, args.review_request_id);
1840
+ const result = await ec.handleContentPublish(args.collection, resolvedId, { publishedAt: args.publishedAt });
1841
+ if (result.success) await logMcpContentActivity(ec, locals, args.collection, resolvedId, "published", "content_publish", {
1842
+ reviewRequestId: args.review_request_id ?? null,
1843
+ publishedAt: args.publishedAt ?? null
1844
+ });
1845
+ return unwrap(result);
1846
+ });
1847
+ server.registerTool("content_unpublish", {
1848
+ title: "Unpublish Content",
1849
+ description: "Unpublish a content item, reverting it to draft status. It will no longer be visible on the live site but its content is preserved.",
1850
+ inputSchema: z.object({
1851
+ collection: z.string().describe("Collection slug"),
1852
+ id: z.string().describe("Content item ID or slug")
1853
+ })
1854
+ }, async (args, extra) => {
1855
+ requireScope(extra, "content:write");
1856
+ requireRole(extra, Role.AUTHOR);
1857
+ const payload = getExtra(extra);
1858
+ const ec = payload.dineway;
1859
+ const locals = actorLocals(payload);
1860
+ const existing = await ec.handleContentGet(args.collection, args.id);
1861
+ if (!existing.success) return unwrap(existing);
1862
+ requireOwnership(extra, extractContentAuthorId(existing.data), "content:publish_own", "content:publish_any");
1863
+ const resolvedId = extractContentId(existing.data) ?? args.id;
1864
+ const result = await ec.handleContentUnpublish(args.collection, resolvedId);
1865
+ if (result.success) await logMcpContentActivity(ec, locals, args.collection, resolvedId, "unpublished", "content_unpublish");
1866
+ return unwrap(result);
1867
+ });
1868
+ server.registerTool("content_schedule", {
1869
+ title: "Schedule Content",
1870
+ description: "Schedule a content item for future publication. It will be automatically published at the specified date/time. The scheduledAt value must be an ISO 8601 datetime string in the future (e.g. '2025-06-01T09:00:00Z').",
1871
+ inputSchema: z.object({
1872
+ collection: z.string().describe("Collection slug"),
1873
+ id: z.string().describe("Content item ID or slug"),
1874
+ scheduledAt: z.string().describe("ISO 8601 datetime for publication (e.g. '2025-06-01T09:00:00Z')")
1875
+ })
1876
+ }, async (args, extra) => {
1877
+ requireScope(extra, "content:write");
1878
+ requireRole(extra, Role.AUTHOR);
1879
+ const payload = getExtra(extra);
1880
+ const ec = payload.dineway;
1881
+ const locals = actorLocals(payload);
1882
+ const existing = await ec.handleContentGet(args.collection, args.id);
1883
+ if (!existing.success) return unwrap(existing);
1884
+ requireOwnership(extra, extractContentAuthorId(existing.data), "content:publish_own", "content:publish_any");
1885
+ const resolvedId = extractContentId(existing.data) ?? args.id;
1886
+ const result = await ec.handleContentSchedule(args.collection, resolvedId, args.scheduledAt);
1887
+ if (result.success) await logMcpContentActivity(ec, locals, args.collection, resolvedId, "scheduled", "content_schedule", { scheduledAt: args.scheduledAt });
1888
+ return unwrap(result);
1889
+ });
1890
+ server.registerTool("content_compare", {
1891
+ title: "Compare Live vs Draft",
1892
+ description: "Compare the published (live) version of a content item with its current draft. Returns both versions and a flag indicating whether there are changes. Useful for reviewing unpublished edits before publishing.",
1893
+ inputSchema: z.object({
1894
+ collection: z.string().describe("Collection slug"),
1895
+ id: z.string().describe("Content item ID or slug")
1896
+ }),
1897
+ annotations: { readOnlyHint: true }
1898
+ }, async (args, extra) => {
1899
+ requireScope(extra, "content:read");
1900
+ requireDraftAccess(extra);
1901
+ return unwrap(await getDineway(extra).handleContentCompare(args.collection, args.id));
1902
+ });
1903
+ server.registerTool("content_discard_draft", {
1904
+ title: "Discard Draft",
1905
+ description: "Discard the current draft changes and revert to the last published version. Only works on items that have been published at least once.",
1906
+ inputSchema: z.object({
1907
+ collection: z.string().describe("Collection slug"),
1908
+ id: z.string().describe("Content item ID or slug")
1909
+ }),
1910
+ annotations: { destructiveHint: true }
1911
+ }, async (args, extra) => {
1912
+ requireScope(extra, "content:write");
1913
+ requireRole(extra, Role.AUTHOR);
1914
+ const payload = getExtra(extra);
1915
+ const ec = payload.dineway;
1916
+ const locals = actorLocals(payload);
1917
+ const existing = await ec.handleContentGet(args.collection, args.id);
1918
+ if (!existing.success) return unwrap(existing);
1919
+ requireOwnership(extra, extractContentAuthorId(existing.data), "content:edit_own", "content:edit_any");
1920
+ const resolvedId = extractContentId(existing.data) ?? args.id;
1921
+ const result = await ec.handleContentDiscardDraft(args.collection, resolvedId);
1922
+ if (result.success) await logMcpContentActivity(ec, locals, args.collection, resolvedId, "draft_discarded", "content_discard_draft");
1923
+ return unwrap(result);
1924
+ });
1925
+ server.registerTool("content_list_trashed", {
1926
+ title: "List Trashed Content",
1927
+ description: "List soft-deleted content items in a collection's trash. These items can be restored with content_restore or permanently deleted with content_permanent_delete.",
1928
+ inputSchema: z.object({
1929
+ collection: z.string().describe("Collection slug"),
1930
+ limit: z.number().int().min(1).max(100).optional().describe("Max items (default 50)"),
1931
+ cursor: z.string().optional().describe("Pagination cursor")
1932
+ }),
1933
+ annotations: { readOnlyHint: true }
1934
+ }, async (args, extra) => {
1935
+ requireScope(extra, "content:read");
1936
+ requireDraftAccess(extra);
1937
+ return unwrap(await getDineway(extra).handleContentListTrashed(args.collection, {
1938
+ limit: args.limit,
1939
+ cursor: args.cursor
1940
+ }));
1941
+ });
1942
+ server.registerTool("content_duplicate", {
1943
+ title: "Duplicate Content",
1944
+ description: "Create a copy of an existing content item. The duplicate is created as a draft with '(Copy)' appended to the title and an auto-generated slug.",
1945
+ inputSchema: z.object({
1946
+ collection: z.string().describe("Collection slug"),
1947
+ id: z.string().describe("Content item ID or slug to duplicate")
1948
+ })
1949
+ }, async (args, extra) => {
1950
+ requireScope(extra, "content:write");
1951
+ requireRole(extra, Role.CONTRIBUTOR);
1952
+ const payload = getExtra(extra);
1953
+ const ec = payload.dineway;
1954
+ const locals = actorLocals(payload);
1955
+ const result = await ec.handleContentDuplicate(args.collection, args.id);
1956
+ if (result.success) {
1957
+ const duplicatedId = extractContentId(result.data);
1958
+ if (duplicatedId) await logMcpContentActivity(ec, locals, args.collection, duplicatedId, "duplicated", "content_duplicate", { sourceEntryId: args.id });
1959
+ }
1960
+ return unwrap(result);
1961
+ });
1962
+ server.registerTool("content_translations", {
1963
+ title: "Get Content Translations",
1964
+ description: "Get all locale variants of a content item. Returns the translation group and a summary of each locale version (id, locale, slug, status). Only relevant when i18n is enabled on the site.",
1965
+ inputSchema: z.object({
1966
+ collection: z.string().describe("Collection slug"),
1967
+ id: z.string().describe("Content item ID or slug")
1968
+ }),
1969
+ annotations: { readOnlyHint: true }
1970
+ }, async (args, extra) => {
1971
+ requireScope(extra, "content:read");
1972
+ const result = await getDineway(extra).handleContentTranslations(args.collection, args.id);
1973
+ if (result.success && !canReadDrafts(extra)) {
1974
+ const data = result.data && typeof result.data === "object" ? result.data : {};
1975
+ const translations = "translations" in data && Array.isArray(data.translations) ? data.translations : [];
1976
+ return unwrap({
1977
+ success: true,
1978
+ data: {
1979
+ ...data,
1980
+ translations: translations.filter(isPublishedRecord)
1981
+ }
1982
+ });
1983
+ }
1984
+ return unwrap(result);
1985
+ });
1986
+ server.registerTool("schema_list_collections", {
1987
+ title: "List Collections",
1988
+ description: "List all content collections defined in the CMS. Each collection represents a content type (e.g. posts, pages, products) with its own schema and database table. Returns slug, label, supported features, and timestamps.",
1989
+ inputSchema: z.object({}),
1990
+ annotations: { readOnlyHint: true }
1991
+ }, async (_args, extra) => {
1992
+ requireScope(extra, "schema:read");
1993
+ requireRole(extra, Role.EDITOR);
1994
+ const ec = getDineway(extra);
1995
+ try {
1996
+ const { SchemaRegistry } = await import("../../../schema-BECjUhP8.mjs").then((n) => n.t);
1997
+ return jsonResult({ items: await new SchemaRegistry(ec.db).listCollections() });
1998
+ } catch (error) {
1999
+ return errorResult(error);
2000
+ }
2001
+ });
2002
+ server.registerTool("schema_get_collection", {
2003
+ title: "Get Collection Schema",
2004
+ description: "Get detailed info about a collection including all field definitions. Fields describe the data model: name, type (string, text, number, boolean, datetime, portableText, image, reference, json, select, multiSelect, slug), constraints, and validation rules. Use this to understand what data content_create and content_update expect.",
2005
+ inputSchema: z.object({ slug: z.string().describe("Collection slug (e.g. 'posts'). Use schema_list_collections to see available slugs.") }),
2006
+ annotations: { readOnlyHint: true }
2007
+ }, async (args, extra) => {
2008
+ requireScope(extra, "schema:read");
2009
+ requireRole(extra, Role.EDITOR);
2010
+ const ec = getDineway(extra);
2011
+ try {
2012
+ const { SchemaRegistry } = await import("../../../schema-BECjUhP8.mjs").then((n) => n.t);
2013
+ const collection = await new SchemaRegistry(ec.db).getCollectionWithFields(args.slug);
2014
+ if (!collection) return errorResult(`Collection '${args.slug}' not found`);
2015
+ return jsonResult(collection);
2016
+ } catch (error) {
2017
+ return errorResult(error);
2018
+ }
2019
+ });
2020
+ server.registerTool("schema_create_collection", {
2021
+ title: "Create Collection",
2022
+ description: "Create a new content collection (content type). This creates a database table and schema definition. The slug must be lowercase alphanumeric with underscores, starting with a letter. Supports: 'drafts' (draft/publish workflow), 'revisions' (version history), 'preview' (live preview), 'scheduling' (timed publish), 'search' (full-text indexing).",
2023
+ inputSchema: z.object({
2024
+ slug: z.string().regex(COLLECTION_SLUG_PATTERN).describe("Unique identifier (lowercase letters, numbers, underscores)"),
2025
+ label: z.string().describe("Display name (plural, e.g. 'Blog Posts')"),
2026
+ labelSingular: z.string().optional().describe("Singular display name (e.g. 'Blog Post')"),
2027
+ description: z.string().optional().describe("Description of this collection"),
2028
+ icon: z.string().optional().describe("Icon name for the admin UI"),
2029
+ supports: z.array(z.enum([
2030
+ "drafts",
2031
+ "revisions",
2032
+ "preview",
2033
+ "scheduling",
2034
+ "search"
2035
+ ])).optional().describe("Features to enable (default: ['drafts', 'revisions'])")
2036
+ })
2037
+ }, async (args, extra) => {
2038
+ requireScope(extra, "schema:write");
2039
+ requireRole(extra, Role.ADMIN);
2040
+ const ec = getDineway(extra);
2041
+ try {
2042
+ const { SchemaRegistry } = await import("../../../schema-BECjUhP8.mjs").then((n) => n.t);
2043
+ const collection = await new SchemaRegistry(ec.db).createCollection({
2044
+ slug: args.slug,
2045
+ label: args.label,
2046
+ labelSingular: args.labelSingular,
2047
+ description: args.description,
2048
+ icon: args.icon,
2049
+ supports: args.supports
2050
+ });
2051
+ ec.invalidateManifest();
2052
+ await logSchemaActivity(ec.db, actorLocals(getExtra(extra)), {
2053
+ action: "collection_created",
2054
+ collection: collection.slug,
2055
+ ...schemaMcpToolSource("collection_created"),
2056
+ summary: `Created collection ${collection.slug}`,
2057
+ detail: {
2058
+ label: args.label,
2059
+ labelSingular: args.labelSingular ?? null,
2060
+ supports: args.supports ?? null
2061
+ }
2062
+ });
2063
+ return jsonResult(collection);
2064
+ } catch (error) {
2065
+ return errorResult(error);
2066
+ }
2067
+ });
2068
+ server.registerTool("schema_delete_collection", {
2069
+ title: "Delete Collection",
2070
+ description: "Delete a collection and its database table. This is irreversible and deletes all content in the collection. Use with extreme caution.",
2071
+ inputSchema: z.object({
2072
+ slug: z.string().describe("Collection slug to delete"),
2073
+ force: z.boolean().optional().describe("Force deletion even if the collection has content (default false)"),
2074
+ hitl_request_id: z.string().optional().describe("Approved HITL request id required for API-token destructive schema actions.")
2075
+ }),
2076
+ annotations: { destructiveHint: true }
2077
+ }, async (args, extra) => {
2078
+ requireScope(extra, "schema:write");
2079
+ requireRole(extra, Role.ADMIN);
2080
+ const payload = getExtra(extra);
2081
+ const ec = payload.dineway;
2082
+ const locals = actorLocals(payload);
2083
+ const actor = resolveActorIdentity(locals);
2084
+ try {
2085
+ const action = await new SchemaHitlPayloadBuilder(ec.db).buildDeleteCollectionRequest({
2086
+ slug: args.slug,
2087
+ force: args.force
2088
+ });
2089
+ const decision = await new RiskPolicyEvaluator({
2090
+ db: ec.db,
2091
+ handlers: ec
2092
+ }).evaluateWorkflowHitl({
2093
+ actor,
2094
+ hitlRequestId: args.hitl_request_id,
2095
+ action
2096
+ });
2097
+ if (!decision.allowed) {
2098
+ const { created, request } = await ensureWorkflowHitlRequest(extra, decision.action);
2099
+ return jsonResult({
2100
+ status: "hitl_required",
2101
+ created,
2102
+ message: decision.message,
2103
+ request
2104
+ });
2105
+ }
2106
+ const { SchemaRegistry } = await import("../../../schema-BECjUhP8.mjs").then((n) => n.t);
2107
+ await new SchemaRegistry(ec.db).deleteCollection(args.slug, { force: args.force });
2108
+ ec.invalidateManifest();
2109
+ await logSchemaActivity(ec.db, locals, {
2110
+ action: "collection_deleted",
2111
+ collection: args.slug,
2112
+ ...schemaMcpToolSource("collection_deleted"),
2113
+ summary: `Deleted collection ${args.slug}`,
2114
+ detail: {
2115
+ force: args.force === true,
2116
+ hitlRequestId: decision.required ? decision.hitlRequest.id : null
2117
+ }
2118
+ });
2119
+ return jsonResult({ deleted: args.slug });
2120
+ } catch (error) {
2121
+ return errorResult(error);
2122
+ }
2123
+ });
2124
+ server.registerTool("schema_create_field", {
2125
+ title: "Add Field to Collection",
2126
+ description: "Add a new field to a collection's schema. This adds a column to the database table. Field types: string (short text), text (long text), number (decimal), integer, boolean, datetime, select (single choice), multiSelect (multiple), portableText (rich text), image, file, reference (link to another collection), json, slug (URL-safe id). For select/multiSelect, provide choices in validation.options array.",
2127
+ inputSchema: z.object({
2128
+ collection: z.string().describe("Collection slug to add the field to"),
2129
+ slug: z.string().regex(COLLECTION_SLUG_PATTERN).describe("Field identifier (lowercase letters, numbers, underscores)"),
2130
+ label: z.string().describe("Display name for the field"),
2131
+ type: z.enum([
2132
+ "string",
2133
+ "text",
2134
+ "number",
2135
+ "integer",
2136
+ "boolean",
2137
+ "datetime",
2138
+ "select",
2139
+ "multiSelect",
2140
+ "portableText",
2141
+ "image",
2142
+ "file",
2143
+ "reference",
2144
+ "json",
2145
+ "slug"
2146
+ ]).describe("Data type for this field"),
2147
+ required: z.boolean().optional().describe("Whether the field is required (default false)"),
2148
+ unique: z.boolean().optional().describe("Whether values must be unique (default false)"),
2149
+ defaultValue: z.unknown().optional().describe("Default value for new items"),
2150
+ validation: z.object({
2151
+ min: z.number().optional(),
2152
+ max: z.number().optional(),
2153
+ minLength: z.number().optional(),
2154
+ maxLength: z.number().optional(),
2155
+ pattern: z.string().optional(),
2156
+ options: z.array(z.string()).optional().describe("Allowed values for select/multiSelect")
2157
+ }).optional().describe("Validation constraints"),
2158
+ options: z.object({
2159
+ collection: z.string().optional().describe("Target collection slug for reference fields"),
2160
+ rows: z.number().optional().describe("Number of rows for textarea")
2161
+ }).passthrough().optional().describe("Widget configuration"),
2162
+ searchable: z.boolean().optional().describe("Include in full-text search index (default false)"),
2163
+ translatable: z.boolean().optional().describe("Whether this field is translatable (default true). Non-translatable fields are synced across all locales in a translation group.")
2164
+ })
2165
+ }, async (args, extra) => {
2166
+ requireScope(extra, "schema:write");
2167
+ requireRole(extra, Role.ADMIN);
2168
+ const ec = getDineway(extra);
2169
+ try {
2170
+ const { SchemaRegistry } = await import("../../../schema-BECjUhP8.mjs").then((n) => n.t);
2171
+ const field = await new SchemaRegistry(ec.db).createField(args.collection, {
2172
+ slug: args.slug,
2173
+ label: args.label,
2174
+ type: args.type,
2175
+ required: args.required,
2176
+ unique: args.unique,
2177
+ defaultValue: args.defaultValue,
2178
+ validation: args.validation,
2179
+ options: args.options,
2180
+ searchable: args.searchable,
2181
+ translatable: args.translatable
2182
+ });
2183
+ ec.invalidateManifest();
2184
+ await logSchemaActivity(ec.db, actorLocals(getExtra(extra)), {
2185
+ action: "field_created",
2186
+ collection: args.collection,
2187
+ field: field.slug,
2188
+ ...schemaMcpToolSource("field_created"),
2189
+ summary: `Created field ${args.collection}.${field.slug}`,
2190
+ detail: {
2191
+ fieldType: args.type,
2192
+ required: args.required === true,
2193
+ searchable: args.searchable === true
2194
+ }
2195
+ });
2196
+ return jsonResult(field);
2197
+ } catch (error) {
2198
+ return errorResult(error);
2199
+ }
2200
+ });
2201
+ server.registerTool("schema_delete_field", {
2202
+ title: "Remove Field from Collection",
2203
+ description: "Remove a field from a collection. This drops the column from the database table and deletes all data in that field. Irreversible.",
2204
+ inputSchema: z.object({
2205
+ collection: z.string().describe("Collection slug"),
2206
+ fieldSlug: z.string().describe("Field slug to remove"),
2207
+ hitl_request_id: z.string().optional().describe("Approved HITL request id required for API-token destructive schema actions.")
2208
+ }),
2209
+ annotations: { destructiveHint: true }
2210
+ }, async (args, extra) => {
2211
+ requireScope(extra, "schema:write");
2212
+ requireRole(extra, Role.ADMIN);
2213
+ const payload = getExtra(extra);
2214
+ const ec = payload.dineway;
2215
+ const locals = actorLocals(payload);
2216
+ const actor = resolveActorIdentity(locals);
2217
+ try {
2218
+ const action = await new SchemaHitlPayloadBuilder(ec.db).buildDeleteFieldRequest({
2219
+ collection: args.collection,
2220
+ fieldSlug: args.fieldSlug
2221
+ });
2222
+ const decision = await new RiskPolicyEvaluator({
2223
+ db: ec.db,
2224
+ handlers: ec
2225
+ }).evaluateWorkflowHitl({
2226
+ actor,
2227
+ hitlRequestId: args.hitl_request_id,
2228
+ action
2229
+ });
2230
+ if (!decision.allowed) {
2231
+ const { created, request } = await ensureWorkflowHitlRequest(extra, decision.action);
2232
+ return jsonResult({
2233
+ status: "hitl_required",
2234
+ created,
2235
+ message: decision.message,
2236
+ request
2237
+ });
2238
+ }
2239
+ const { SchemaRegistry } = await import("../../../schema-BECjUhP8.mjs").then((n) => n.t);
2240
+ await new SchemaRegistry(ec.db).deleteField(args.collection, args.fieldSlug);
2241
+ ec.invalidateManifest();
2242
+ await logSchemaActivity(ec.db, locals, {
2243
+ action: "field_deleted",
2244
+ collection: args.collection,
2245
+ field: args.fieldSlug,
2246
+ ...schemaMcpToolSource("field_deleted"),
2247
+ summary: `Deleted field ${args.collection}.${args.fieldSlug}`,
2248
+ detail: { hitlRequestId: decision.required ? decision.hitlRequest.id : null }
2249
+ });
2250
+ return jsonResult({
2251
+ deleted: args.fieldSlug,
2252
+ collection: args.collection
2253
+ });
2254
+ } catch (error) {
2255
+ return errorResult(error);
2256
+ }
2257
+ });
2258
+ server.registerTool("media_list", {
2259
+ title: "List Media",
2260
+ description: "List uploaded media files (images, documents, etc.) with optional MIME type filtering and pagination. Returns file metadata including filename, URL, dimensions, and alt text.",
2261
+ inputSchema: z.object({
2262
+ mimeType: z.string().optional().describe("Filter by MIME type prefix (e.g. 'image/', 'application/pdf')"),
2263
+ limit: z.number().int().min(1).max(100).optional().describe("Max items (default 50)"),
2264
+ cursor: z.string().optional().describe("Pagination cursor")
2265
+ }),
2266
+ annotations: { readOnlyHint: true }
2267
+ }, async (args, extra) => {
2268
+ requireScope(extra, "media:read");
2269
+ return unwrap(await getDineway(extra).handleMediaList({
2270
+ mimeType: args.mimeType,
2271
+ limit: args.limit,
2272
+ cursor: args.cursor
2273
+ }));
2274
+ });
2275
+ server.registerTool("media_get", {
2276
+ title: "Get Media Item",
2277
+ description: "Get details of a single media file by its ID. Returns metadata including filename, MIME type, size, dimensions, alt text, and URL.",
2278
+ inputSchema: z.object({ id: z.string().describe("Media item ID") }),
2279
+ annotations: { readOnlyHint: true }
2280
+ }, async (args, extra) => {
2281
+ requireScope(extra, "media:read");
2282
+ return unwrap(await getDineway(extra).handleMediaGet(args.id));
2283
+ });
2284
+ server.registerTool("media_update", {
2285
+ title: "Update Media Metadata",
2286
+ description: "Update the metadata of an uploaded media file. You can change the alt text, caption, and dimensions. The file itself cannot be changed.",
2287
+ inputSchema: z.object({
2288
+ id: z.string().describe("Media item ID"),
2289
+ alt: z.string().optional().describe("Alt text for accessibility"),
2290
+ caption: z.string().optional().describe("Caption text"),
2291
+ width: z.number().int().optional().describe("Image width in pixels"),
2292
+ height: z.number().int().optional().describe("Image height in pixels")
2293
+ })
2294
+ }, async (args, extra) => {
2295
+ requireScope(extra, "media:write");
2296
+ requireRole(extra, Role.AUTHOR);
2297
+ const ec = getDineway(extra);
2298
+ const existing = await ec.handleMediaGet(args.id);
2299
+ if (!existing.success) return unwrap(existing);
2300
+ const media = existing.data?.item;
2301
+ requireOwnership(extra, typeof media?.authorId === "string" ? media.authorId : "", "media:edit_own", "media:edit_any");
2302
+ return unwrap(await ec.handleMediaUpdate(args.id, {
2303
+ alt: args.alt,
2304
+ caption: args.caption,
2305
+ width: args.width,
2306
+ height: args.height
2307
+ }));
2308
+ });
2309
+ server.registerTool("media_delete", {
2310
+ title: "Delete Media",
2311
+ description: "Permanently delete an uploaded media file. Removes the database record and the file from storage. Content referencing this media will have broken references. Cannot be undone.",
2312
+ inputSchema: z.object({ id: z.string().describe("Media item ID") }),
2313
+ annotations: { destructiveHint: true }
2314
+ }, async (args, extra) => {
2315
+ requireScope(extra, "media:write");
2316
+ requireRole(extra, Role.AUTHOR);
2317
+ const ec = getDineway(extra);
2318
+ const existing = await ec.handleMediaGet(args.id);
2319
+ if (!existing.success) return unwrap(existing);
2320
+ const media = existing.data?.item;
2321
+ requireOwnership(extra, typeof media?.authorId === "string" ? media.authorId : "", "media:delete_own", "media:delete_any");
2322
+ return unwrap(await ec.handleMediaDelete(args.id));
2323
+ });
2324
+ server.registerTool("entity_resolve", {
2325
+ title: "Resolve Entity",
2326
+ description: "Resolve a fuzzy Dineway object reference into an exact content, collection, plugin, taxonomy, or menu target. Returns resolved, ambiguous, or not_found with deterministic tier ranking.",
2327
+ inputSchema: z.object({
2328
+ query: z.string().describe("Natural-language entity reference to resolve"),
2329
+ entity_types: z.array(z.enum(ENTITY_RESOLVE_TYPES)).optional().describe("Restrict resolution to specific entity types"),
2330
+ collection: z.string().regex(COLLECTION_SLUG_PATTERN).optional().describe("Optional content collection hint for content resolution"),
2331
+ limit: z.number().int().min(1).max(20).optional().describe("Max candidates (default 5)")
2332
+ }),
2333
+ annotations: { readOnlyHint: true }
2334
+ }, async (args, extra) => {
2335
+ requireAnyScope(extra, [
2336
+ "content:read",
2337
+ "schema:read",
2338
+ "admin"
2339
+ ]);
2340
+ requireRole(extra, Role.SUBSCRIBER);
2341
+ try {
2342
+ const payload = getExtra(extra);
2343
+ const entityTypes = allowedEntityResolveTypes(payload, args.entity_types);
2344
+ if (entityTypes.length === 0) throw new McpError(ErrorCode.InvalidRequest, "No readable entity types are available for this token or role.");
2345
+ return jsonResult(await new EntityResolver({
2346
+ db: payload.dineway.db,
2347
+ handlers: payload.dineway
2348
+ }).resolve({
2349
+ query: args.query,
2350
+ entityTypes,
2351
+ collection: args.collection,
2352
+ limit: args.limit
2353
+ }));
2354
+ } catch (error) {
2355
+ return errorResult(error);
2356
+ }
2357
+ });
2358
+ server.registerTool("search", {
2359
+ title: "Search Content",
2360
+ description: "Full-text search across content collections. Searches indexed fields for matching content. Collections must have 'search' in their supports list and fields must be marked as searchable. Returns collection, item ID, title, excerpt, and relevance score.",
2361
+ inputSchema: z.object({
2362
+ query: z.string().describe("Search query text"),
2363
+ collections: z.array(z.string()).optional().describe("Limit search to specific collection slugs (all if omitted)"),
2364
+ locale: z.string().optional().describe("Filter results by locale (omit to search all locales)"),
2365
+ limit: z.number().int().min(1).max(50).optional().describe("Max results (default 20)")
2366
+ }),
2367
+ annotations: { readOnlyHint: true }
2368
+ }, async (args, extra) => {
2369
+ requireScope(extra, "content:read");
2370
+ const ec = getDineway(extra);
2371
+ try {
2372
+ const { searchWithDb } = await import("../../../search-DqTHQqtV.mjs").then((n) => n.t);
2373
+ return jsonResult(await searchWithDb(ec.db, args.query, {
2374
+ collections: args.collections,
2375
+ locale: args.locale,
2376
+ limit: args.limit
2377
+ }));
2378
+ } catch (error) {
2379
+ return errorResult(error);
2380
+ }
2381
+ });
2382
+ server.registerTool("taxonomy_list", {
2383
+ title: "List Taxonomies",
2384
+ description: "List all taxonomy definitions (e.g. categories, tags). Taxonomies are classification systems applied to content. Each has a name, label, and can be hierarchical (categories) or flat (tags).",
2385
+ inputSchema: z.object({}),
2386
+ annotations: { readOnlyHint: true }
2387
+ }, async (_args, extra) => {
2388
+ requireScope(extra, "content:read");
2389
+ const ec = getDineway(extra);
2390
+ try {
2391
+ return jsonResult((await ec.db.selectFrom("_dineway_taxonomy_defs").selectAll().execute()).map((row) => ({
2392
+ id: row.id,
2393
+ name: row.name,
2394
+ label: row.label,
2395
+ labelSingular: row.label_singular ?? void 0,
2396
+ hierarchical: row.hierarchical === 1,
2397
+ collections: row.collections ? JSON.parse(row.collections) : []
2398
+ })));
2399
+ } catch (error) {
2400
+ return errorResult(error);
2401
+ }
2402
+ });
2403
+ server.registerTool("taxonomy_list_terms", {
2404
+ title: "List Taxonomy Terms",
2405
+ description: "List terms in a taxonomy with pagination. Terms are individual entries (e.g. specific categories or tags). Hierarchical taxonomies can have parent-child relationships.",
2406
+ inputSchema: z.object({
2407
+ taxonomy: z.string().describe("Taxonomy name (e.g. 'categories', 'tags')"),
2408
+ limit: z.number().int().min(1).max(100).optional().describe("Max items (default 50)"),
2409
+ cursor: z.string().optional().describe("Pagination cursor")
2410
+ }),
2411
+ annotations: { readOnlyHint: true }
2412
+ }, async (args, extra) => {
2413
+ requireScope(extra, "content:read");
2414
+ const ec = getDineway(extra);
2415
+ try {
2416
+ const taxonomy = await ec.db.selectFrom("_dineway_taxonomy_defs").select("id").where("name", "=", args.taxonomy).executeTakeFirst();
2417
+ if (!taxonomy) return errorResult(`Taxonomy '${args.taxonomy}' not found`);
2418
+ const limit = Math.min(args.limit ?? 50, 100);
2419
+ let query = ec.db.selectFrom("_dineway_taxonomy_terms").selectAll().where("taxonomy_id", "=", taxonomy.id).orderBy("label", "asc").limit(limit + 1);
2420
+ if (args.cursor) query = query.where("id", ">", args.cursor);
2421
+ const rows = await query.execute();
2422
+ const hasMore = rows.length > limit;
2423
+ const items = hasMore ? rows.slice(0, limit) : rows;
2424
+ return jsonResult({
2425
+ items,
2426
+ nextCursor: hasMore ? items.at(-1)?.id : void 0
2427
+ });
2428
+ } catch (error) {
2429
+ return errorResult(error);
2430
+ }
2431
+ });
2432
+ server.registerTool("taxonomy_create_term", {
2433
+ title: "Create Taxonomy Term",
2434
+ description: "Create a new term in a taxonomy. For hierarchical taxonomies like categories, you can specify a parentId to create a child term.",
2435
+ inputSchema: z.object({
2436
+ taxonomy: z.string().describe("Taxonomy name (e.g. 'categories', 'tags')"),
2437
+ slug: z.string().describe("URL-safe identifier for the term"),
2438
+ label: z.string().describe("Display name"),
2439
+ parentId: z.string().optional().describe("Parent term ID for hierarchical taxonomies"),
2440
+ description: z.string().optional().describe("Description of the term")
2441
+ })
2442
+ }, async (args, extra) => {
2443
+ requireScope(extra, "content:write");
2444
+ requireRole(extra, Role.EDITOR);
2445
+ const ec = getDineway(extra);
2446
+ try {
2447
+ const { ulid } = await import("ulidx");
2448
+ const taxonomy = await ec.db.selectFrom("_dineway_taxonomy_defs").select("id").where("name", "=", args.taxonomy).executeTakeFirst();
2449
+ if (!taxonomy) return errorResult(`Taxonomy '${args.taxonomy}' not found`);
2450
+ const id = ulid();
2451
+ await ec.db.insertInto("_dineway_taxonomy_terms").values({
2452
+ id,
2453
+ taxonomy_id: taxonomy.id,
2454
+ slug: args.slug,
2455
+ label: args.label,
2456
+ parent_id: args.parentId ?? null,
2457
+ description: args.description ?? null
2458
+ }).execute();
2459
+ return jsonResult(await ec.db.selectFrom("_dineway_taxonomy_terms").selectAll().where("id", "=", id).executeTakeFirstOrThrow());
2460
+ } catch (error) {
2461
+ return errorResult(error);
2462
+ }
2463
+ });
2464
+ server.registerTool("menu_list", {
2465
+ title: "List Menus",
2466
+ description: "List all navigation menus defined in the CMS. Menus are named navigation structures (e.g. 'main', 'footer') containing ordered items with labels, URLs, and optional nesting.",
2467
+ inputSchema: z.object({}),
2468
+ annotations: { readOnlyHint: true }
2469
+ }, async (_args, extra) => {
2470
+ requireScope(extra, "content:read");
2471
+ const ec = getDineway(extra);
2472
+ try {
2473
+ return jsonResult(await ec.db.selectFrom("_dineway_menus").select([
2474
+ "id",
2475
+ "name",
2476
+ "label",
2477
+ "created_at",
2478
+ "updated_at"
2479
+ ]).orderBy("name", "asc").execute());
2480
+ } catch (error) {
2481
+ return errorResult(error);
2482
+ }
2483
+ });
2484
+ server.registerTool("menu_get", {
2485
+ title: "Get Menu with Items",
2486
+ description: "Get a menu by name including all its items in order. Items have a label, URL, type (custom/content/collection), and optional parent for nesting.",
2487
+ inputSchema: z.object({ name: z.string().describe("Menu name (e.g. 'main', 'footer')") }),
2488
+ annotations: { readOnlyHint: true }
2489
+ }, async (args, extra) => {
2490
+ requireScope(extra, "content:read");
2491
+ const ec = getDineway(extra);
2492
+ try {
2493
+ const menu = await ec.db.selectFrom("_dineway_menus").selectAll().where("name", "=", args.name).executeTakeFirst();
2494
+ if (!menu) return errorResult(`Menu '${args.name}' not found`);
2495
+ const items = await ec.db.selectFrom("_dineway_menu_items").selectAll().where("menu_id", "=", menu.id).orderBy("sort_order", "asc").execute();
2496
+ return jsonResult({
2497
+ ...menu,
2498
+ items
2499
+ });
2500
+ } catch (error) {
2501
+ return errorResult(error);
2502
+ }
2503
+ });
2504
+ server.registerTool("revision_list", {
2505
+ title: "List Revisions",
2506
+ description: "List revision history for a content item. Revisions are snapshots created on publish or update. Returns newest-first. Requires the collection to support 'revisions'.",
2507
+ inputSchema: z.object({
2508
+ collection: z.string().describe("Collection slug"),
2509
+ id: z.string().describe("Content item ID or slug"),
2510
+ limit: z.number().int().min(1).max(50).optional().describe("Max revisions (default 20)")
2511
+ }),
2512
+ annotations: { readOnlyHint: true }
2513
+ }, async (args, extra) => {
2514
+ requireScope(extra, "content:read");
2515
+ requireDraftAccess(extra);
2516
+ return unwrap(await getDineway(extra).handleRevisionList(args.collection, args.id, { limit: args.limit }));
2517
+ });
2518
+ server.registerTool("revision_restore", {
2519
+ title: "Restore Revision",
2520
+ description: "Restore a content item to a previous revision. Replaces the current draft with the specified revision's data. Not automatically published — use content_publish afterward if needed.",
2521
+ inputSchema: z.object({ revisionId: z.string().describe("Revision ID to restore") })
2522
+ }, async (args, extra) => {
2523
+ requireScope(extra, "content:write");
2524
+ requireRole(extra, Role.AUTHOR);
2525
+ const { dineway, userId } = getExtra(extra);
2526
+ const revision = await dineway.handleRevisionGet(args.revisionId);
2527
+ if (!revision.success) return unwrap(revision);
2528
+ const revItem = revision.data?.item;
2529
+ if (!revItem?.collection || !revItem?.entryId) return errorResult("Revision is missing collection or entry reference");
2530
+ const existing = await dineway.handleContentGet(revItem.collection, revItem.entryId);
2531
+ if (!existing.success) return unwrap(existing);
2532
+ requireOwnership(extra, extractContentAuthorId(existing.data), "content:edit_own", "content:edit_any");
2533
+ return unwrap(await dineway.handleRevisionRestore(args.revisionId, userId));
2534
+ });
2535
+ server.registerTool("settings_get", {
2536
+ title: "Get Site Settings",
2537
+ description: "Get all site-wide settings including title, tagline, media references, canonical URL, date formatting, social links, and SEO defaults. Media references include resolved URLs when storage can resolve them.",
2538
+ inputSchema: z.object({}),
2539
+ annotations: { readOnlyHint: true }
2540
+ }, async (_args, extra) => {
2541
+ requireScope(extra, "admin");
2542
+ requireRole(extra, Role.ADMIN);
2543
+ const ec = getDineway(extra);
2544
+ try {
2545
+ const { handleSettingsGet } = await import("../../../settings-Dzgswvg4.mjs").then((n) => n.r);
2546
+ return unwrap(await handleSettingsGet(ec.db, ec.storage));
2547
+ } catch (error) {
2548
+ return errorResult(error);
2549
+ }
2550
+ });
2551
+ server.registerTool("settings_update", {
2552
+ title: "Update Site Settings",
2553
+ description: "Update one or more site-wide settings. This is a partial update: omitted fields keep their existing values. Media references use { mediaId, alt? }.",
2554
+ inputSchema: settingsMcpUpdateBody
2555
+ }, async (args, extra) => {
2556
+ requireScope(extra, "admin");
2557
+ requireRole(extra, Role.ADMIN);
2558
+ const payload = getExtra(extra);
2559
+ const ec = payload.dineway;
2560
+ const locals = actorLocals(payload);
2561
+ const actor = resolveActorIdentity(locals);
2562
+ const { hitl_request_id, ...settingsInput } = args;
2563
+ try {
2564
+ const action = await new SettingsHitlPayloadBuilder(ec.db).buildUpdateSettingsRequest(settingsInput);
2565
+ const decision = await new RiskPolicyEvaluator({
2566
+ db: ec.db,
2567
+ handlers: ec
2568
+ }).evaluateWorkflowHitl({
2569
+ actor,
2570
+ hitlRequestId: hitl_request_id,
2571
+ action
2572
+ });
2573
+ if (!decision.allowed) {
2574
+ const { created, request } = await ensureWorkflowHitlRequest(extra, decision.action);
2575
+ return jsonResult({
2576
+ status: "hitl_required",
2577
+ created,
2578
+ message: decision.message,
2579
+ request
2580
+ });
2581
+ }
2582
+ const { handleSettingsUpdate } = await import("../../../settings-Dzgswvg4.mjs").then((n) => n.r);
2583
+ const result = await handleSettingsUpdate(ec.db, ec.storage, settingsInput);
2584
+ if (!result.success) return unwrap(result);
2585
+ await logSiteActivitySafely(ec.db, locals, {
2586
+ actionType: "settings.updated",
2587
+ subjectType: "site_settings",
2588
+ subjectId: "site",
2589
+ scope: "site",
2590
+ sourceType: "mcp_tool",
2591
+ sourceName: "settings_update",
2592
+ summary: action.summary ?? "Updated site settings",
2593
+ detail: {
2594
+ changedKeys: Object.keys(settingsInput).toSorted(),
2595
+ touchesSeo: Object.hasOwn(settingsInput, "seo"),
2596
+ hitlRequestId: decision.required ? decision.hitlRequest.id : null
2597
+ }
2598
+ });
2599
+ return unwrap(result);
2600
+ } catch (error) {
2601
+ return errorResult(error);
2602
+ }
2603
+ });
2604
+ return server;
2605
+ }
2606
+
2607
+ //#endregion
2608
+ //#region src/astro/routes/api/mcp.ts
2609
+ /**
2610
+ * MCP Streamable HTTP endpoint
2611
+ *
2612
+ * Exposes an MCP server at /_dineway/api/mcp using the Streamable HTTP
2613
+ * transport (Web Standard variant). The server runs stateless — each
2614
+ * request creates a fresh transport, so no session tracking is needed.
2615
+ * Authentication is handled by the existing Dineway auth middleware.
2616
+ *
2617
+ * POST /_dineway/api/mcp — JSON-RPC tool calls
2618
+ * GET /_dineway/api/mcp — SSE stream (not used in stateless mode)
2619
+ * DELETE /_dineway/api/mcp — Session close (not used in stateless mode)
2620
+ */
2621
+ const prerender = false;
2622
+ const POST = async ({ request, locals }) => {
2623
+ const { dineway, user } = locals;
2624
+ if (!dineway) return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
2625
+ if (!user) return apiError("UNAUTHORIZED", "Authentication required", 401);
2626
+ const server = createMcpServer();
2627
+ try {
2628
+ const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
2629
+ await server.connect(transport);
2630
+ return await transport.handleRequest(request, { authInfo: {
2631
+ token: "",
2632
+ clientId: "dineway-admin",
2633
+ scopes: [],
2634
+ extra: {
2635
+ dineway,
2636
+ userId: user.id,
2637
+ userRole: user.role,
2638
+ tokenScopes: locals.tokenScopes,
2639
+ authToken: locals.authToken
2640
+ }
2641
+ } });
2642
+ } catch (error) {
2643
+ console.error("[MCP]", error);
2644
+ await server.close().catch(() => {});
2645
+ return new Response(JSON.stringify({
2646
+ jsonrpc: "2.0",
2647
+ error: {
2648
+ code: -32603,
2649
+ message: "Internal server error"
2650
+ },
2651
+ id: null
2652
+ }), {
2653
+ status: 500,
2654
+ headers: {
2655
+ "Content-Type": "application/json",
2656
+ "Cache-Control": "private, no-store"
2657
+ }
2658
+ });
2659
+ }
2660
+ };
2661
+ /**
2662
+ * GET — SSE stream. Not used in stateless mode.
2663
+ */
2664
+ const GET = async () => {
2665
+ return new Response(JSON.stringify({
2666
+ jsonrpc: "2.0",
2667
+ error: {
2668
+ code: -32e3,
2669
+ message: "Method not allowed. This is a stateless MCP endpoint — use POST."
2670
+ },
2671
+ id: null
2672
+ }), {
2673
+ status: 405,
2674
+ headers: {
2675
+ "Content-Type": "application/json",
2676
+ "Cache-Control": "private, no-store"
2677
+ }
2678
+ });
2679
+ };
2680
+ /**
2681
+ * DELETE — Session close. Not used in stateless mode.
2682
+ */
2683
+ const DELETE = async () => {
2684
+ return new Response(JSON.stringify({
2685
+ jsonrpc: "2.0",
2686
+ error: {
2687
+ code: -32e3,
2688
+ message: "Method not allowed. This is a stateless MCP endpoint."
2689
+ },
2690
+ id: null
2691
+ }), {
2692
+ status: 405,
2693
+ headers: {
2694
+ "Content-Type": "application/json",
2695
+ "Cache-Control": "private, no-store"
2696
+ }
2697
+ });
2698
+ };
2699
+
2700
+ //#endregion
2701
+ export { DELETE, GET, POST, prerender };