dineway 0.1.9 → 0.1.11

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