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.
- package/README.md +63 -17
- package/dist/activity-events-BsMaXdJa.mjs +540 -0
- package/dist/allowed-origins-DG86sH8U.mjs +68 -0
- package/dist/api/route-utils.d.mts +41 -0
- package/dist/api/route-utils.mjs +26 -0
- package/dist/api/schemas/index.d.mts +3 -0
- package/dist/api/schemas/index.mjs +6 -0
- package/dist/api/schemas/setup.d.mts +42 -0
- package/dist/api/schemas/setup.mjs +39 -0
- package/dist/api-Cmy8Rjk5.mjs +2704 -0
- package/dist/api-tokens-Bu3ez1MO.mjs +153 -0
- package/dist/api-tokens-DzloJxuh.mjs +3 -0
- package/dist/{apply-iVSqz2qs.mjs → apply-Co5imxxT.mjs} +15 -689
- package/dist/astro/index.d.mts +10 -6
- package/dist/astro/index.mjs +86 -11
- package/dist/astro/middleware/auth.d.mts +10 -7
- package/dist/astro/middleware/auth.mjs +19 -104
- package/dist/astro/middleware/redirect.mjs +24 -14
- package/dist/astro/middleware/request-context.mjs +9 -6
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.mjs +86 -145
- package/dist/astro/routes/PluginRegistry.d.mts +14 -0
- package/dist/astro/routes/PluginRegistry.mjs +24 -0
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.d.mts +14 -0
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +65 -0
- package/dist/astro/routes/api/admin/allowed-domains/index.d.mts +14 -0
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +65 -0
- package/dist/astro/routes/api/admin/api-tokens/_id_.d.mts +10 -0
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +33 -0
- package/dist/astro/routes/api/admin/api-tokens/index.d.mts +16 -0
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +59 -0
- package/dist/astro/routes/api/admin/briefing.d.mts +7 -0
- package/dist/astro/routes/api/admin/briefing.mjs +71 -0
- package/dist/astro/routes/api/admin/bylines/_id_/index.d.mts +9 -0
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +74 -0
- package/dist/astro/routes/api/admin/bylines/index.d.mts +8 -0
- package/dist/astro/routes/api/admin/bylines/index.mjs +61 -0
- package/dist/astro/routes/api/admin/comments/_id_/status.d.mts +7 -0
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +80 -0
- package/dist/astro/routes/api/admin/comments/_id_.d.mts +14 -0
- package/dist/astro/routes/api/admin/comments/_id_.mjs +46 -0
- package/dist/astro/routes/api/admin/comments/bulk.d.mts +7 -0
- package/dist/astro/routes/api/admin/comments/bulk.mjs +36 -0
- package/dist/astro/routes/api/admin/comments/counts.d.mts +7 -0
- package/dist/astro/routes/api/admin/comments/counts.mjs +24 -0
- package/dist/astro/routes/api/admin/comments/index.d.mts +10 -0
- package/dist/astro/routes/api/admin/comments/index.mjs +40 -0
- package/dist/astro/routes/api/admin/context/_id_/history.d.mts +7 -0
- package/dist/astro/routes/api/admin/context/_id_/history.mjs +45 -0
- package/dist/astro/routes/api/admin/context/_id_/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/context/_id_/index.mjs +45 -0
- package/dist/astro/routes/api/admin/context/_id_/review.d.mts +7 -0
- package/dist/astro/routes/api/admin/context/_id_/review.mjs +60 -0
- package/dist/astro/routes/api/admin/context/_id_/supersede.d.mts +7 -0
- package/dist/astro/routes/api/admin/context/_id_/supersede.mjs +63 -0
- package/dist/astro/routes/api/admin/context/diff.d.mts +7 -0
- package/dist/astro/routes/api/admin/context/diff.mjs +49 -0
- package/dist/astro/routes/api/admin/context/index.d.mts +8 -0
- package/dist/astro/routes/api/admin/context/index.mjs +71 -0
- package/dist/astro/routes/api/admin/context/stale.d.mts +7 -0
- package/dist/astro/routes/api/admin/context/stale.mjs +49 -0
- package/dist/astro/routes/api/admin/hitl-requests/_id_/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/hitl-requests/_id_/index.mjs +51 -0
- package/dist/astro/routes/api/admin/hitl-requests/_id_/resolve.d.mts +7 -0
- package/dist/astro/routes/api/admin/hitl-requests/_id_/resolve.mjs +67 -0
- package/dist/astro/routes/api/admin/hitl-requests/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/hitl-requests/index.mjs +55 -0
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.d.mts +7 -0
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +98 -0
- package/dist/astro/routes/api/admin/hooks/exclusive/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +33 -0
- package/dist/astro/routes/api/admin/oauth-clients/_id_.d.mts +18 -0
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +79 -0
- package/dist/astro/routes/api/admin/oauth-clients/index.d.mts +14 -0
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +58 -0
- package/dist/astro/routes/api/admin/plugins/_id_/disable.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +89 -0
- package/dist/astro/routes/api/admin/plugins/_id_/enable.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +89 -0
- package/dist/astro/routes/api/admin/plugins/_id_/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +54 -0
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +98 -0
- package/dist/astro/routes/api/admin/plugins/_id_/update.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +131 -0
- package/dist/astro/routes/api/admin/plugins/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/index.mjs +52 -0
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +36 -0
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +54 -0
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +128 -0
- package/dist/astro/routes/api/admin/plugins/marketplace/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +61 -0
- package/dist/astro/routes/api/admin/plugins/updates.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/updates.mjs +52 -0
- package/dist/astro/routes/api/admin/review-requests/_id_/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/review-requests/_id_/index.mjs +26 -0
- package/dist/astro/routes/api/admin/review-requests/_id_/resolve.d.mts +7 -0
- package/dist/astro/routes/api/admin/review-requests/_id_/resolve.mjs +97 -0
- package/dist/astro/routes/api/admin/review-requests/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/review-requests/index.mjs +31 -0
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +54 -0
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.d.mts +7 -0
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +36 -0
- package/dist/astro/routes/api/admin/themes/marketplace/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +70 -0
- package/dist/astro/routes/api/admin/users/_id_/disable.d.mts +7 -0
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +38 -0
- package/dist/astro/routes/api/admin/users/_id_/enable.d.mts +7 -0
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +29 -0
- package/dist/astro/routes/api/admin/users/_id_/index.d.mts +8 -0
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +104 -0
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.d.mts +7 -0
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +43 -0
- package/dist/astro/routes/api/admin/users/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/users/index.mjs +54 -0
- package/dist/astro/routes/api/auth/dev-bypass.d.mts +8 -0
- package/dist/astro/routes/api/auth/dev-bypass.mjs +81 -0
- package/dist/astro/routes/api/auth/invite/accept.d.mts +7 -0
- package/dist/astro/routes/api/auth/invite/accept.mjs +31 -0
- package/dist/astro/routes/api/auth/invite/complete.d.mts +7 -0
- package/dist/astro/routes/api/auth/invite/complete.mjs +54 -0
- package/dist/astro/routes/api/auth/invite/index.d.mts +7 -0
- package/dist/astro/routes/api/auth/invite/index.mjs +51 -0
- package/dist/astro/routes/api/auth/invite/register-options.d.mts +7 -0
- package/dist/astro/routes/api/auth/invite/register-options.mjs +44 -0
- package/dist/astro/routes/api/auth/logout.d.mts +7 -0
- package/dist/astro/routes/api/auth/logout.mjs +24 -0
- package/dist/astro/routes/api/auth/magic-link/send.d.mts +7 -0
- package/dist/astro/routes/api/auth/magic-link/send.mjs +48 -0
- package/dist/astro/routes/api/auth/magic-link/verify.d.mts +7 -0
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +32 -0
- package/dist/astro/routes/api/auth/me.d.mts +13 -0
- package/dist/astro/routes/api/auth/me.mjs +41 -0
- package/dist/astro/routes/api/auth/mode.d.mts +7 -0
- package/dist/astro/routes/api/auth/mode.mjs +28 -0
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.d.mts +7 -0
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +114 -0
- package/dist/astro/routes/api/auth/oauth/_provider_.d.mts +7 -0
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +58 -0
- package/dist/astro/routes/api/auth/passkey/_id_.d.mts +14 -0
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +62 -0
- package/dist/astro/routes/api/auth/passkey/index.d.mts +7 -0
- package/dist/astro/routes/api/auth/passkey/index.mjs +25 -0
- package/dist/astro/routes/api/auth/passkey/options.d.mts +7 -0
- package/dist/astro/routes/api/auth/passkey/options.mjs +46 -0
- package/dist/astro/routes/api/auth/passkey/register/options.d.mts +7 -0
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +44 -0
- package/dist/astro/routes/api/auth/passkey/register/verify.d.mts +7 -0
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +59 -0
- package/dist/astro/routes/api/auth/passkey/verify.d.mts +7 -0
- package/dist/astro/routes/api/auth/passkey/verify.mjs +47 -0
- package/dist/astro/routes/api/auth/signup/complete.d.mts +7 -0
- package/dist/astro/routes/api/auth/signup/complete.mjs +55 -0
- package/dist/astro/routes/api/auth/signup/request.d.mts +7 -0
- package/dist/astro/routes/api/auth/signup/request.mjs +44 -0
- package/dist/astro/routes/api/auth/signup/verify.d.mts +7 -0
- package/dist/astro/routes/api/auth/signup/verify.mjs +32 -0
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.d.mts +14 -0
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +193 -0
- package/dist/astro/routes/api/content/_collection_/_id_/compare.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +17 -0
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +36 -0
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +39 -0
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +31 -0
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +78 -0
- package/dist/astro/routes/api/content/_collection_/_id_/publish.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +92 -0
- package/dist/astro/routes/api/content/_collection_/_id_/restore.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +36 -0
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +19 -0
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.d.mts +8 -0
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +75 -0
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.d.mts +14 -0
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +85 -0
- package/dist/astro/routes/api/content/_collection_/_id_/translations.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +40 -0
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +36 -0
- package/dist/astro/routes/api/content/_collection_/_id_.d.mts +9 -0
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +114 -0
- package/dist/astro/routes/api/content/_collection_/index.d.mts +8 -0
- package/dist/astro/routes/api/content/_collection_/index.mjs +74 -0
- package/dist/astro/routes/api/content/_collection_/trash.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/trash.mjs +23 -0
- package/dist/astro/routes/api/dashboard.d.mts +7 -0
- package/dist/astro/routes/api/dashboard.mjs +26 -0
- package/dist/astro/routes/api/dev/emails.d.mts +8 -0
- package/dist/astro/routes/api/dev/emails.mjs +17 -0
- package/dist/astro/routes/api/health.d.mts +7 -0
- package/dist/astro/routes/api/health.mjs +34 -0
- package/dist/astro/routes/api/import/probe.d.mts +17 -0
- package/dist/astro/routes/api/import/probe.mjs +33 -0
- package/dist/astro/routes/api/import/wordpress/analyze.d.mts +87 -0
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +305 -0
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +37 -0
- package/dist/astro/routes/api/import/wordpress/execute.mjs +197 -0
- package/dist/astro/routes/api/import/wordpress/media.d.mts +35 -0
- package/dist/astro/routes/api/import/wordpress/media.mjs +222 -0
- package/dist/astro/routes/api/import/wordpress/prepare.d.mts +19 -0
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +155 -0
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +21 -0
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +289 -0
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +15 -0
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +69 -0
- package/dist/astro/routes/api/import/wordpress-plugin/callback.d.mts +7 -0
- package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +28 -0
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +19 -0
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +268 -0
- package/dist/astro/routes/api/manifest.d.mts +7 -0
- package/dist/astro/routes/api/manifest.mjs +50 -0
- package/dist/astro/routes/api/mcp.d.mts +15 -0
- package/dist/astro/routes/api/mcp.mjs +2700 -0
- package/dist/astro/routes/api/media/_id_/confirm.d.mts +10 -0
- package/dist/astro/routes/api/media/_id_/confirm.mjs +59 -0
- package/dist/astro/routes/api/media/_id_.d.mts +22 -0
- package/dist/astro/routes/api/media/_id_.mjs +81 -0
- package/dist/astro/routes/api/media/file/_...key_.d.mts +7 -0
- package/dist/astro/routes/api/media/file/_...key_.mjs +49 -0
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.d.mts +14 -0
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +49 -0
- package/dist/astro/routes/api/media/providers/_providerId_/index.d.mts +14 -0
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +72 -0
- package/dist/astro/routes/api/media/providers/index.d.mts +10 -0
- package/dist/astro/routes/api/media/providers/index.mjs +18 -0
- package/dist/astro/routes/api/media/upload-url.d.mts +10 -0
- package/dist/astro/routes/api/media/upload-url.mjs +82 -0
- package/dist/astro/routes/api/media.d.mts +16 -0
- package/dist/astro/routes/api/media.mjs +137 -0
- package/dist/astro/routes/api/menus/_name_/items.d.mts +9 -0
- package/{src/astro/routes/api/menus/[name]/items.ts → dist/astro/routes/api/menus/_name_/items.mjs} +63 -105
- package/dist/astro/routes/api/menus/_name_/reorder.d.mts +7 -0
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +77 -0
- package/dist/astro/routes/api/menus/_name_.d.mts +9 -0
- package/dist/astro/routes/api/menus/_name_.mjs +123 -0
- package/dist/astro/routes/api/menus/index.d.mts +8 -0
- package/dist/astro/routes/api/menus/index.mjs +84 -0
- package/dist/astro/routes/api/oauth/authorize.d.mts +8 -0
- package/dist/astro/routes/api/oauth/authorize.mjs +265 -0
- package/dist/astro/routes/api/oauth/device/authorize.d.mts +7 -0
- package/dist/astro/routes/api/oauth/device/authorize.mjs +30 -0
- package/dist/astro/routes/api/oauth/device/code.d.mts +7 -0
- package/dist/astro/routes/api/oauth/device/code.mjs +34 -0
- package/dist/astro/routes/api/oauth/device/token.d.mts +7 -0
- package/dist/astro/routes/api/oauth/device/token.mjs +45 -0
- package/dist/astro/routes/api/oauth/register.d.mts +8 -0
- package/dist/astro/routes/api/oauth/register.mjs +115 -0
- package/dist/astro/routes/api/oauth/token/refresh.d.mts +7 -0
- package/dist/astro/routes/api/oauth/token/refresh.mjs +28 -0
- package/dist/astro/routes/api/oauth/token/revoke.d.mts +7 -0
- package/dist/astro/routes/api/oauth/token/revoke.mjs +25 -0
- package/dist/astro/routes/api/oauth/token.d.mts +8 -0
- package/dist/astro/routes/api/oauth/token.mjs +138 -0
- package/dist/astro/routes/api/openapi.json.d.mts +7 -0
- package/dist/astro/routes/api/openapi.json.mjs +2638 -0
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.d.mts +11 -0
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +77 -0
- package/dist/astro/routes/api/redirects/404s/index.d.mts +9 -0
- package/dist/astro/routes/api/redirects/404s/index.mjs +62 -0
- package/dist/astro/routes/api/redirects/404s/summary.d.mts +7 -0
- package/dist/astro/routes/api/redirects/404s/summary.mjs +34 -0
- package/dist/astro/routes/api/redirects/_id_.d.mts +9 -0
- package/dist/astro/routes/api/redirects/_id_.mjs +152 -0
- package/dist/astro/routes/api/redirects/index.d.mts +8 -0
- package/dist/astro/routes/api/redirects/index.mjs +97 -0
- package/dist/astro/routes/api/revisions/_revisionId_/index.d.mts +7 -0
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +16 -0
- package/dist/astro/routes/api/revisions/_revisionId_/restore.d.mts +7 -0
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +23 -0
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.d.mts +9 -0
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +98 -0
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.d.mts +8 -0
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +80 -0
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.d.mts +7 -0
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +67 -0
- package/dist/astro/routes/api/schema/collections/_slug_/index.d.mts +9 -0
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +97 -0
- package/dist/astro/routes/api/schema/collections/index.d.mts +8 -0
- package/dist/astro/routes/api/schema/collections/index.mjs +77 -0
- package/dist/astro/routes/api/schema/index.d.mts +7 -0
- package/dist/astro/routes/api/schema/index.mjs +79 -0
- package/dist/astro/routes/api/schema/orphans/_slug_.d.mts +7 -0
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +58 -0
- package/dist/astro/routes/api/schema/orphans/index.d.mts +7 -0
- package/dist/astro/routes/api/schema/orphans/index.mjs +53 -0
- package/dist/astro/routes/api/search/enable.d.mts +15 -0
- package/dist/astro/routes/api/search/enable.mjs +55 -0
- package/dist/astro/routes/api/search/index.d.mts +16 -0
- package/dist/astro/routes/api/search/index.mjs +52 -0
- package/dist/astro/routes/api/search/rebuild.d.mts +13 -0
- package/dist/astro/routes/api/search/rebuild.mjs +48 -0
- package/dist/astro/routes/api/search/stats.d.mts +10 -0
- package/dist/astro/routes/api/search/stats.mjs +28 -0
- package/dist/astro/routes/api/search/suggest.d.mts +15 -0
- package/dist/astro/routes/api/search/suggest.mjs +43 -0
- package/dist/astro/routes/api/sections/_slug_.d.mts +9 -0
- package/dist/astro/routes/api/sections/_slug_.mjs +156 -0
- package/dist/astro/routes/api/sections/index.d.mts +8 -0
- package/dist/astro/routes/api/sections/index.mjs +99 -0
- package/dist/astro/routes/api/settings/email.d.mts +17 -0
- package/dist/astro/routes/api/settings/email.mjs +102 -0
- package/dist/astro/routes/api/settings.d.mts +20 -0
- package/dist/astro/routes/api/settings.mjs +101 -0
- package/dist/astro/routes/api/setup/admin-verify.d.mts +7 -0
- package/dist/astro/routes/api/setup/admin-verify.mjs +67 -0
- package/dist/astro/routes/api/setup/admin.d.mts +7 -0
- package/dist/astro/routes/api/setup/admin.mjs +68 -0
- package/dist/astro/routes/api/setup/dev-bypass.d.mts +8 -0
- package/dist/astro/routes/api/setup/dev-bypass.mjs +137 -0
- package/dist/astro/routes/api/setup/dev-reset.d.mts +7 -0
- package/dist/astro/routes/api/setup/dev-reset.mjs +22 -0
- package/dist/astro/routes/api/setup/index.d.mts +7 -0
- package/dist/astro/routes/api/setup/index.mjs +93 -0
- package/dist/astro/routes/api/setup/status.d.mts +7 -0
- package/dist/astro/routes/api/setup/status.mjs +57 -0
- package/dist/astro/routes/api/snapshot.d.mts +7 -0
- package/dist/astro/routes/api/snapshot.mjs +227 -0
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.d.mts +18 -0
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +189 -0
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.d.mts +14 -0
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +113 -0
- package/dist/astro/routes/api/taxonomies/index.d.mts +14 -0
- package/dist/astro/routes/api/taxonomies/index.mjs +103 -0
- package/dist/astro/routes/api/themes/preview.d.mts +7 -0
- package/dist/astro/routes/api/themes/preview.mjs +47 -0
- package/dist/astro/routes/api/typegen.d.mts +17 -0
- package/dist/astro/routes/api/typegen.mjs +75 -0
- package/dist/astro/routes/api/well-known/auth.d.mts +7 -0
- package/dist/astro/routes/api/well-known/auth.mjs +42 -0
- package/dist/astro/routes/api/well-known/oauth-authorization-server.d.mts +7 -0
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +33 -0
- package/dist/astro/routes/api/well-known/oauth-protected-resource.d.mts +7 -0
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +21 -0
- package/dist/astro/routes/api/widget-areas/_name_/reorder.d.mts +7 -0
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +88 -0
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.d.mts +8 -0
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +158 -0
- package/dist/astro/routes/api/widget-areas/_name_/widgets.d.mts +7 -0
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +104 -0
- package/dist/astro/routes/api/widget-areas/_name_.d.mts +8 -0
- package/dist/astro/routes/api/widget-areas/_name_.mjs +99 -0
- package/dist/astro/routes/api/widget-areas/index.d.mts +8 -0
- package/dist/astro/routes/api/widget-areas/index.mjs +108 -0
- package/dist/astro/routes/api/widget-components.d.mts +7 -0
- package/dist/astro/routes/api/widget-components.mjs +15 -0
- package/dist/astro/routes/robots.txt.d.mts +7 -0
- package/dist/astro/routes/robots.txt.mjs +60 -0
- package/dist/astro/routes/sitemap-_collection_.xml.d.mts +7 -0
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +70 -0
- package/dist/astro/routes/sitemap.xml.d.mts +7 -0
- package/dist/astro/routes/sitemap.xml.mjs +63 -0
- package/dist/astro/types.d.mts +41 -9
- package/dist/auth/providers/github-admin.d.mts +9 -0
- package/dist/auth/providers/github-admin.mjs +27 -0
- package/dist/auth/providers/github.d.mts +12 -0
- package/dist/auth/providers/github.mjs +17 -0
- package/dist/auth/providers/google-admin.d.mts +9 -0
- package/dist/auth/providers/google-admin.mjs +43 -0
- package/dist/auth/providers/google.d.mts +12 -0
- package/dist/auth/providers/google.mjs +17 -0
- package/dist/auth-control-guard-DKUe_1oa.mjs +13 -0
- package/dist/authorize-BBj8C6Y8.mjs +36 -0
- package/dist/briefing-BrXCuMEE.mjs +1294 -0
- package/dist/briefing-ClWw4mc9.mjs +29 -0
- package/dist/{byline-OhH2dlRu.mjs → byline-naZxOPSa.mjs} +3 -3
- package/dist/{bylines-BGpD9_hy.mjs → bylines-BcOPh6Ej.mjs} +20 -53
- package/dist/bylines-HfUKum_j.d.mts +2023 -0
- package/dist/{cache-BdSY-gQN.mjs → cache-DEbQ13c9.mjs} +21 -11
- package/dist/challenge-store-DHMgBGOq.mjs +48 -0
- package/dist/cli/index.mjs +142 -22
- package/dist/client/external-auth-headers.d.mts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.mjs +3 -3
- package/dist/comment-DFO-gWDH.mjs +246 -0
- package/dist/comments-Gy3zLBaP.mjs +186 -0
- package/dist/components-DND2rd3D.mjs +107 -0
- package/dist/{content-DWi4d0rT.mjs → content-CyLkb-qH.mjs} +33 -44
- package/dist/context-bE5Kyvcj.mjs +184 -0
- package/dist/context-nxMyOe3p.mjs +849 -0
- package/dist/context-route-helpers-D-6uCQ0S.mjs +45 -0
- package/dist/context-types-C-LwdAxx.mjs +23 -0
- package/dist/cron-DGzVTtJp.mjs +263 -0
- package/dist/dashboard-DqnYU8EU.mjs +120 -0
- package/dist/db/index.d.mts +3 -3
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/libsql.mjs +3 -3
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/db/sqlite.mjs +1 -2
- package/dist/device-flow-7AhWNwCK.mjs +487 -0
- package/dist/email-console-CgLVZbcn.mjs +36 -0
- package/dist/entity-aliases-C0v-yNET.mjs +51 -0
- package/dist/error-DEGjx2Xw.mjs +435 -0
- package/dist/escape-mNZr4t2A.mjs +8 -0
- package/dist/experimental-workflows-DldxJlqV.mjs +38 -0
- package/dist/fts-manager-B1pTNEG_.mjs +297 -0
- package/dist/hash-CDX7M0ze.mjs +32 -0
- package/dist/hitl-requests-Bx3Bkk9l.mjs +118 -0
- package/dist/hitl-route-helpers-DMmJRS7B.mjs +96 -0
- package/dist/import-DD3f2jkc.mjs +243 -0
- package/dist/import-DVZcYlDp.mjs +1323 -0
- package/dist/index-CkljPf5F.d.mts +227 -0
- package/dist/index.d.mts +15 -11
- package/dist/index.mjs +60 -22
- package/dist/{loader-sMG4TZ-u.mjs → loader-PZnPxFLc.mjs} +42 -5
- package/dist/{manifest-schema-D1MSVnoI.mjs → manifest-schema-DYoCQ5np.mjs} +22 -10
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +2 -1
- package/dist/media/local-runtime.d.mts +11 -7
- package/dist/media/local-runtime.mjs +3 -3
- package/dist/{media-DMTr80Gv.mjs → media-_7Fxdu45.mjs} +1 -1
- package/dist/menus-BacxVCCo.mjs +312 -0
- package/dist/menus-CrzHokKj.mjs +3502 -0
- package/dist/normalize-C49G_o1k.mjs +126 -0
- package/dist/oauth-authorization-C1qiw4hd.mjs +283 -0
- package/dist/oauth-clients-CvWatf5p.mjs +298 -0
- package/dist/oauth-state-store-hSdzxsEe.mjs +48 -0
- package/dist/oauth-user-lookup-B4OcmsLV.mjs +25 -0
- package/dist/options-z8VVg1Ll.mjs +114 -0
- package/dist/page/index.d.mts +2 -2
- package/dist/parse-BeQXIt1U.mjs +88 -0
- package/dist/passkey-config-Daqs5fjq.mjs +42 -0
- package/dist/{patterns-CrCYkMBb.mjs → patterns-K0DLqWir.mjs} +53 -1
- package/dist/{placeholder-Cp8g5Emj.mjs → placeholder-C2P5fKa4.mjs} +1 -126
- package/dist/plugins/adapt-sandbox-entry.d.mts +9 -5
- package/dist/plugins/adapt-sandbox-entry.mjs +4 -4
- package/dist/preview-C_4DyVox.mjs +788 -0
- package/dist/public-url-BB_umF5G.mjs +71 -0
- package/dist/{query-kDmwCsHh.mjs → query-RiobVwB5.mjs} +93 -19
- package/dist/rate-limit-CbJoj_fT.mjs +112 -0
- package/dist/{redirect-DnEWAkVg.mjs → redirect-CGl64yOX.mjs} +9 -5
- package/dist/redirect-ClSmMOtC.mjs +16 -0
- package/dist/redirects-B69T59hK.mjs +499 -0
- package/dist/redirects-CqaxraTO.mjs +1070 -0
- package/dist/{registry-C0zjeB9P.mjs → registry-C-_hxLqa.mjs} +26 -294
- package/dist/request-meta-Bd0mQfiS.mjs +130 -0
- package/dist/review-requests-C2DIHwlJ.mjs +148 -0
- package/dist/review-requests-DIyjw-K_.mjs +79 -0
- package/dist/{runner-CFI6B6J2.d.mts → runner-9eIQXuc2.d.mts} +1 -1
- package/dist/{index-yvc6E_17.d.mts → runtime-C4-7y7xK.d.mts} +1539 -2007
- package/dist/runtime.d.mts +10 -6
- package/dist/runtime.mjs +3 -3
- package/dist/schema-BNpI53of.mjs +40 -0
- package/dist/search-DM6CVti3.mjs +337 -0
- package/dist/secrets-dI8zzTV7.mjs +160 -0
- package/dist/sections-DZFyAQXd.mjs +338 -0
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +18 -13
- package/dist/seo/index.d.mts +1 -1
- package/dist/seo-BBgTCOYU.mjs +85 -0
- package/dist/seo-CUQctrog.mjs +129 -0
- package/dist/service-CSfcQguB.mjs +194 -0
- package/dist/settings-4XnpVMOS.mjs +223 -0
- package/dist/settings-Bw93cLfe.mjs +50 -0
- package/dist/setup-complete-DidsDQ1e.mjs +21 -0
- package/dist/setup-nonce-pml1PMKo.mjs +17 -0
- package/dist/sidecar-client-vzwV98K4.mjs +66 -0
- package/dist/site-activity-B8FjLIVh.mjs +104 -0
- package/dist/site-context-Bpu_Paur.mjs +4122 -0
- package/dist/site-url-CYIcO0Tj.mjs +12 -0
- package/dist/slugify-PDTDtMXp.mjs +30 -0
- package/dist/ssrf-CmM76lLV.mjs +248 -0
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +2 -2
- package/dist/{taxonomies-1s5PaS_8.mjs → taxonomies-BvBgfzn3.mjs} +11 -7
- package/dist/taxonomies-CpqGcIJD.mjs +355 -0
- package/dist/taxonomy-D5cbhc8u.mjs +165 -0
- package/dist/{tokens-CJz9ubV6.mjs → tokens-DLTo4dO2.mjs} +1 -1
- package/dist/{transport-DB5eDN4x.mjs → transport-C9e_h-BF.mjs} +5 -4
- package/dist/trusted-proxy-Bi0Cuk5n.mjs +30 -0
- package/dist/{types-BawVha09.mjs → types-Bs6lTBBW.mjs} +1 -1
- package/dist/types-C982qI5I.d.mts +344 -0
- package/dist/types-D4XVOt01.d.mts +165 -0
- package/dist/{types-Cj0KMIZV.d.mts → types-DgfUZqcd.d.mts} +54 -16
- package/dist/{types-BuMDPy5C.d.mts → types-IPACEM14.d.mts} +6 -0
- package/dist/user-CcXq-zoL.mjs +154 -0
- package/dist/utils-D2in-zwy.mjs +285 -0
- package/dist/{validate-BZ5wnLLp.mjs → validate-BJgA6TW_.mjs} +1 -1
- package/dist/{validate-IPf8n4Fj.d.mts → validate-JCZihRIa.d.mts} +3 -3
- package/dist/version-DH53KCQd.mjs +6 -0
- package/dist/widgets-B7Q_7bxN.mjs +104 -0
- package/dist/wordpress-slugs-BevajWrC.mjs +14 -0
- package/dist/zod-generator-DBVP8D0P.mjs +132 -0
- package/locals.d.ts +1 -6
- package/package.json +67 -11
- package/src/components/DinewayHead.astro +8 -4
- package/src/components/DinewayImage.astro +7 -5
- package/src/components/DinewayMedia.astro +9 -3
- package/src/components/Gallery.astro +5 -3
- package/src/components/Image.astro +5 -1
- package/src/components/InlinePortableTextEditor.tsx +68 -19
- package/dist/error-BmL6QipT.mjs +0 -30
- package/dist/search-DxopAWxs.mjs +0 -11200
- package/dist/version-BPz1imu2.mjs +0 -6
- package/src/astro/routes/PluginRegistry.tsx +0 -21
- package/src/astro/routes/api/admin/allowed-domains/[domain].ts +0 -112
- package/src/astro/routes/api/admin/allowed-domains/index.ts +0 -108
- package/src/astro/routes/api/admin/api-tokens/[id].ts +0 -44
- package/src/astro/routes/api/admin/api-tokens/index.ts +0 -90
- package/src/astro/routes/api/admin/briefing.ts +0 -76
- package/src/astro/routes/api/admin/bylines/[id]/index.ts +0 -90
- package/src/astro/routes/api/admin/bylines/index.ts +0 -74
- package/src/astro/routes/api/admin/comments/[id]/status.ts +0 -120
- package/src/astro/routes/api/admin/comments/[id].ts +0 -64
- package/src/astro/routes/api/admin/comments/bulk.ts +0 -42
- package/src/astro/routes/api/admin/comments/counts.ts +0 -30
- package/src/astro/routes/api/admin/comments/index.ts +0 -46
- package/src/astro/routes/api/admin/context/[id]/history.ts +0 -35
- package/src/astro/routes/api/admin/context/[id]/index.ts +0 -35
- package/src/astro/routes/api/admin/context/[id]/review.ts +0 -57
- package/src/astro/routes/api/admin/context/[id]/supersede.ts +0 -58
- package/src/astro/routes/api/admin/context/diff.ts +0 -35
- package/src/astro/routes/api/admin/context/index.ts +0 -69
- package/src/astro/routes/api/admin/context/stale.ts +0 -35
- package/src/astro/routes/api/admin/hitl-requests/[id]/index.ts +0 -38
- package/src/astro/routes/api/admin/hitl-requests/[id]/resolve.ts +0 -54
- package/src/astro/routes/api/admin/hitl-requests/index.ts +0 -38
- package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +0 -132
- package/src/astro/routes/api/admin/hooks/exclusive/index.ts +0 -51
- package/src/astro/routes/api/admin/oauth-clients/[id].ts +0 -137
- package/src/astro/routes/api/admin/oauth-clients/index.ts +0 -95
- package/src/astro/routes/api/admin/plugins/[id]/disable.ts +0 -91
- package/src/astro/routes/api/admin/plugins/[id]/enable.ts +0 -91
- package/src/astro/routes/api/admin/plugins/[id]/index.ts +0 -38
- package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +0 -98
- package/src/astro/routes/api/admin/plugins/[id]/update.ts +0 -154
- package/src/astro/routes/api/admin/plugins/index.ts +0 -32
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/icon.ts +0 -62
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/index.ts +0 -33
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +0 -135
- package/src/astro/routes/api/admin/plugins/marketplace/index.ts +0 -38
- package/src/astro/routes/api/admin/plugins/updates.ts +0 -28
- package/src/astro/routes/api/admin/review-requests/[id]/index.ts +0 -35
- package/src/astro/routes/api/admin/review-requests/[id]/resolve.ts +0 -52
- package/src/astro/routes/api/admin/review-requests/index.ts +0 -35
- package/src/astro/routes/api/admin/themes/marketplace/[id]/index.ts +0 -33
- package/src/astro/routes/api/admin/themes/marketplace/[id]/thumbnail.ts +0 -62
- package/src/astro/routes/api/admin/themes/marketplace/index.ts +0 -45
- package/src/astro/routes/api/admin/users/[id]/disable.ts +0 -72
- package/src/astro/routes/api/admin/users/[id]/enable.ts +0 -48
- package/src/astro/routes/api/admin/users/[id]/index.ts +0 -166
- package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +0 -72
- package/src/astro/routes/api/admin/users/index.ts +0 -66
- package/src/astro/routes/api/auth/dev-bypass.ts +0 -139
- package/src/astro/routes/api/auth/invite/accept.ts +0 -52
- package/src/astro/routes/api/auth/invite/complete.ts +0 -86
- package/src/astro/routes/api/auth/invite/index.ts +0 -99
- package/src/astro/routes/api/auth/invite/register-options.ts +0 -73
- package/src/astro/routes/api/auth/logout.ts +0 -40
- package/src/astro/routes/api/auth/magic-link/send.ts +0 -90
- package/src/astro/routes/api/auth/magic-link/verify.ts +0 -71
- package/src/astro/routes/api/auth/me.ts +0 -60
- package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +0 -221
- package/src/astro/routes/api/auth/oauth/[provider].ts +0 -120
- package/src/astro/routes/api/auth/passkey/[id].ts +0 -124
- package/src/astro/routes/api/auth/passkey/index.ts +0 -54
- package/src/astro/routes/api/auth/passkey/options.ts +0 -85
- package/src/astro/routes/api/auth/passkey/register/options.ts +0 -88
- package/src/astro/routes/api/auth/passkey/register/verify.ts +0 -119
- package/src/astro/routes/api/auth/passkey/verify.ts +0 -72
- package/src/astro/routes/api/auth/signup/complete.ts +0 -87
- package/src/astro/routes/api/auth/signup/request.ts +0 -89
- package/src/astro/routes/api/auth/signup/verify.ts +0 -53
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +0 -310
- package/src/astro/routes/api/content/[collection]/[id]/compare.ts +0 -28
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +0 -68
- package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +0 -77
- package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +0 -42
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +0 -107
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +0 -100
- package/src/astro/routes/api/content/[collection]/[id]/restore.ts +0 -64
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +0 -31
- package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +0 -129
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +0 -143
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +0 -50
- package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +0 -69
- package/src/astro/routes/api/content/[collection]/[id].ts +0 -173
- package/src/astro/routes/api/content/[collection]/index.ts +0 -103
- package/src/astro/routes/api/content/[collection]/trash.ts +0 -33
- package/src/astro/routes/api/dashboard.ts +0 -32
- package/src/astro/routes/api/dev/emails.ts +0 -36
- package/src/astro/routes/api/health.ts +0 -54
- package/src/astro/routes/api/import/probe.ts +0 -47
- package/src/astro/routes/api/import/wordpress/analyze.ts +0 -523
- package/src/astro/routes/api/import/wordpress/execute.ts +0 -330
- package/src/astro/routes/api/import/wordpress/media.ts +0 -338
- package/src/astro/routes/api/import/wordpress/prepare.ts +0 -212
- package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +0 -425
- package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +0 -111
- package/src/astro/routes/api/import/wordpress-plugin/callback.ts +0 -58
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +0 -399
- package/src/astro/routes/api/manifest.ts +0 -75
- package/src/astro/routes/api/mcp.ts +0 -125
- package/src/astro/routes/api/media/[id]/confirm.ts +0 -93
- package/src/astro/routes/api/media/[id].ts +0 -145
- package/src/astro/routes/api/media/file/[...key].ts +0 -79
- package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +0 -91
- package/src/astro/routes/api/media/providers/[providerId]/index.ts +0 -111
- package/src/astro/routes/api/media/providers/index.ts +0 -30
- package/src/astro/routes/api/media/upload-url.ts +0 -146
- package/src/astro/routes/api/media.ts +0 -204
- package/src/astro/routes/api/menus/[name]/reorder.ts +0 -79
- package/src/astro/routes/api/menus/[name].ts +0 -145
- package/src/astro/routes/api/menus/index.ts +0 -91
- package/src/astro/routes/api/oauth/authorize.ts +0 -430
- package/src/astro/routes/api/oauth/device/authorize.ts +0 -45
- package/src/astro/routes/api/oauth/device/code.ts +0 -56
- package/src/astro/routes/api/oauth/device/token.ts +0 -70
- package/src/astro/routes/api/oauth/register.ts +0 -182
- package/src/astro/routes/api/oauth/token/refresh.ts +0 -38
- package/src/astro/routes/api/oauth/token/revoke.ts +0 -38
- package/src/astro/routes/api/oauth/token.ts +0 -195
- package/src/astro/routes/api/openapi.json.ts +0 -33
- package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +0 -109
- package/src/astro/routes/api/redirects/404s/index.ts +0 -72
- package/src/astro/routes/api/redirects/404s/summary.ts +0 -33
- package/src/astro/routes/api/redirects/[id].ts +0 -183
- package/src/astro/routes/api/redirects/index.ts +0 -100
- package/src/astro/routes/api/revisions/[revisionId]/index.ts +0 -29
- package/src/astro/routes/api/revisions/[revisionId]/restore.ts +0 -62
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +0 -104
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +0 -67
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +0 -45
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +0 -107
- package/src/astro/routes/api/schema/collections/index.ts +0 -61
- package/src/astro/routes/api/schema/index.ts +0 -109
- package/src/astro/routes/api/schema/orphans/[slug].ts +0 -36
- package/src/astro/routes/api/schema/orphans/index.ts +0 -26
- package/src/astro/routes/api/search/enable.ts +0 -64
- package/src/astro/routes/api/search/index.ts +0 -52
- package/src/astro/routes/api/search/rebuild.ts +0 -72
- package/src/astro/routes/api/search/stats.ts +0 -35
- package/src/astro/routes/api/search/suggest.ts +0 -50
- package/src/astro/routes/api/sections/[slug].ts +0 -203
- package/src/astro/routes/api/sections/index.ts +0 -107
- package/src/astro/routes/api/settings/email.ts +0 -150
- package/src/astro/routes/api/settings.ts +0 -116
- package/src/astro/routes/api/setup/admin-verify.ts +0 -122
- package/src/astro/routes/api/setup/admin.ts +0 -104
- package/src/astro/routes/api/setup/dev-bypass.ts +0 -200
- package/src/astro/routes/api/setup/dev-reset.ts +0 -40
- package/src/astro/routes/api/setup/index.ts +0 -128
- package/src/astro/routes/api/setup/status.ts +0 -122
- package/src/astro/routes/api/snapshot.ts +0 -76
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +0 -232
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +0 -131
- package/src/astro/routes/api/taxonomies/index.ts +0 -114
- package/src/astro/routes/api/themes/preview.ts +0 -78
- package/src/astro/routes/api/typegen.ts +0 -114
- package/src/astro/routes/api/well-known/auth.ts +0 -71
- package/src/astro/routes/api/well-known/oauth-authorization-server.ts +0 -48
- package/src/astro/routes/api/well-known/oauth-protected-resource.ts +0 -39
- package/src/astro/routes/api/widget-areas/[name]/reorder.ts +0 -114
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +0 -213
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +0 -126
- package/src/astro/routes/api/widget-areas/[name].ts +0 -135
- package/src/astro/routes/api/widget-areas/index.ts +0 -149
- package/src/astro/routes/api/widget-components.ts +0 -22
- package/src/astro/routes/robots.txt.ts +0 -81
- package/src/astro/routes/sitemap-[collection].xml.ts +0 -104
- package/src/astro/routes/sitemap.xml.ts +0 -92
- /package/dist/{adapters-C2ypTrZZ.d.mts → adapters-BLDldpJg.d.mts} +0 -0
- /package/{src → dist}/astro/routes/admin.astro +0 -0
- /package/dist/{base64-F8-DUraK.mjs → base64-Cz-aU0X1.mjs} +0 -0
- /package/dist/{chunks--4F8ddV4.mjs → chunks-D_jVet6z.mjs} +0 -0
- /package/dist/{config-BXwuX8Bx.mjs → config-CAMFxGaV.mjs} +0 -0
- /package/dist/{db-errors-CEqD7qH9.mjs → db-errors-DKUg_NgF.mjs} +0 -0
- /package/dist/{default-VjJyuuG9.mjs → default-C3PZN-bz.mjs} +0 -0
- /package/dist/{load-Coc9HpHH.mjs → load-D-9NhLmF.mjs} +0 -0
- /package/dist/{mode-47goXBBK.mjs → mode-C80mAZQv.mjs} +0 -0
- /package/dist/{placeholder--wOi4TbO.d.mts → placeholder-CHkLckzK.d.mts} +0 -0
- /package/dist/{request-cache-Dk5qPSOx.mjs → request-cache-DHMRr2Lf.mjs} +0 -0
- /package/dist/{transaction-Cn2rjY78.mjs → transaction-x2tJQ-A1.mjs} +0 -0
- /package/dist/{transport-Wge_IzKl.d.mts → transport-6RefuBdV.d.mts} +0 -0
- /package/dist/{types-griIBQOQ.mjs → types-B9gKVOHk.mjs} +0 -0
- /package/dist/{types-CWbdtiux.d.mts → types-B9qVtiHb.d.mts} +0 -0
- /package/dist/{types-COeOq9nK.mjs → types-DL7Y8D_t.mjs} +0 -0
- /package/dist/{types-BzcUjoqg.d.mts → types-Djdp0cZO.d.mts} +0 -0
- /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 };
|