@aphexcms/cms-core 9.7.0 → 9.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-chat-state.svelte.d.ts +38 -0
- package/dist/agent-chat-state.svelte.d.ts.map +1 -0
- package/dist/agent-chat-state.svelte.js +86 -0
- package/dist/ai/content-workspace-tools.d.ts +3 -0
- package/dist/ai/content-workspace-tools.d.ts.map +1 -0
- package/dist/ai/content-workspace-tools.js +50 -0
- package/dist/ai/default-system-prompt.d.ts +2 -0
- package/dist/ai/default-system-prompt.d.ts.map +1 -0
- package/dist/ai/default-system-prompt.js +15 -0
- package/dist/ai/index.d.ts +2 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +4 -0
- package/dist/ai/interfaces/ai-provider.d.ts +67 -0
- package/dist/ai/interfaces/ai-provider.d.ts.map +1 -0
- package/dist/ai/interfaces/ai-provider.js +16 -0
- package/dist/ai/run-agent-turn.d.ts +29 -0
- package/dist/ai/run-agent-turn.d.ts.map +1 -0
- package/dist/ai/run-agent-turn.js +166 -0
- package/dist/api/agent-chat.d.ts +27 -0
- package/dist/api/agent-chat.d.ts.map +1 -0
- package/dist/api/agent-chat.js +64 -0
- package/dist/api/documents.d.ts +8 -3
- package/dist/api/documents.d.ts.map +1 -1
- package/dist/api/documents.js +6 -6
- package/dist/api/schemas/agent-chat.d.ts +45 -0
- package/dist/api/schemas/agent-chat.d.ts.map +1 -0
- package/dist/api/schemas/agent-chat.js +35 -0
- package/dist/api/schemas/agent-operations.d.ts +13 -0
- package/dist/api/schemas/agent-operations.d.ts.map +1 -0
- package/dist/api/schemas/agent-operations.js +15 -0
- package/dist/api/schemas/documents.d.ts +22 -0
- package/dist/api/schemas/documents.d.ts.map +1 -1
- package/dist/api/schemas/documents.js +20 -2
- package/dist/chunk-recovery.d.ts +33 -0
- package/dist/chunk-recovery.d.ts.map +1 -0
- package/dist/chunk-recovery.js +131 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/ui.d.ts +1 -0
- package/dist/client/ui.d.ts.map +1 -1
- package/dist/client/ui.js +1 -0
- package/dist/components/AdminApp.svelte +37 -3
- package/dist/components/AdminApp.svelte.d.ts.map +1 -1
- package/dist/components/admin/ActivityView.svelte +251 -42
- package/dist/components/admin/ActivityView.svelte.d.ts.map +1 -1
- package/dist/components/admin/AgentChat.svelte +977 -0
- package/dist/components/admin/AgentChat.svelte.d.ts +17 -0
- package/dist/components/admin/AgentChat.svelte.d.ts.map +1 -0
- package/dist/components/admin/DocumentEditor.svelte +273 -60
- package/dist/components/admin/DocumentEditor.svelte.d.ts +6 -0
- package/dist/components/admin/DocumentEditor.svelte.d.ts.map +1 -1
- package/dist/components/admin/ObjectModal.svelte +4 -2
- package/dist/components/admin/fields/ArrayField.svelte +4 -1
- package/dist/components/admin/fields/ReferenceField.svelte +12 -1
- package/dist/components/admin/fields/ReferenceField.svelte.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/layout/Sidebar.svelte +26 -2
- package/dist/components/layout/Sidebar.svelte.d.ts +1 -0
- package/dist/components/layout/Sidebar.svelte.d.ts.map +1 -1
- package/dist/db/interfaces/agent-change-sets.d.ts +17 -0
- package/dist/db/interfaces/agent-change-sets.d.ts.map +1 -0
- package/dist/db/interfaces/agent-change-sets.js +1 -0
- package/dist/db/interfaces/document.d.ts +25 -3
- package/dist/db/interfaces/document.d.ts.map +1 -1
- package/dist/db/interfaces/document.js +19 -1
- package/dist/db/interfaces/index.d.ts +4 -1
- package/dist/db/interfaces/index.d.ts.map +1 -1
- package/dist/db/interfaces/index.js +1 -1
- package/dist/document-refresh.svelte.d.ts +2 -0
- package/dist/document-refresh.svelte.d.ts.map +1 -1
- package/dist/document-refresh.svelte.js +15 -0
- package/dist/document-workspace-registry.svelte.d.ts +15 -0
- package/dist/document-workspace-registry.svelte.d.ts.map +1 -0
- package/dist/document-workspace-registry.svelte.js +30 -0
- package/dist/field-validation/utils.d.ts.map +1 -1
- package/dist/field-validation/utils.js +211 -29
- package/dist/hooks.d.ts +2 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +2 -0
- package/dist/lib/agent-chat-state.svelte.d.ts +38 -0
- package/dist/lib/agent-chat-state.svelte.d.ts.map +1 -0
- package/dist/lib/agent-chat-state.svelte.js +87 -0
- package/dist/lib/agent-chat-state.svelte.js.map +1 -0
- package/dist/lib/ai/content-workspace-tools.d.ts +3 -0
- package/dist/lib/ai/content-workspace-tools.d.ts.map +1 -0
- package/dist/lib/ai/content-workspace-tools.js +51 -0
- package/dist/lib/ai/content-workspace-tools.js.map +1 -0
- package/dist/lib/ai/default-system-prompt.d.ts +2 -0
- package/dist/lib/ai/default-system-prompt.d.ts.map +1 -0
- package/dist/lib/ai/default-system-prompt.js +16 -0
- package/dist/lib/ai/default-system-prompt.js.map +1 -0
- package/dist/lib/ai/index.d.ts +2 -0
- package/dist/lib/ai/index.d.ts.map +1 -0
- package/dist/lib/ai/index.js +5 -0
- package/dist/lib/ai/index.js.map +1 -0
- package/dist/lib/ai/interfaces/ai-provider.d.ts +67 -0
- package/dist/lib/ai/interfaces/ai-provider.d.ts.map +1 -0
- package/dist/lib/ai/interfaces/ai-provider.js +17 -0
- package/dist/lib/ai/interfaces/ai-provider.js.map +1 -0
- package/dist/lib/ai/run-agent-turn.d.ts +29 -0
- package/dist/lib/ai/run-agent-turn.d.ts.map +1 -0
- package/dist/lib/ai/run-agent-turn.js +167 -0
- package/dist/lib/ai/run-agent-turn.js.map +1 -0
- package/dist/lib/api/agent-chat.d.ts +27 -0
- package/dist/lib/api/agent-chat.d.ts.map +1 -0
- package/dist/lib/api/agent-chat.js +65 -0
- package/dist/lib/api/agent-chat.js.map +1 -0
- package/dist/lib/api/documents.d.ts +8 -3
- package/dist/lib/api/documents.d.ts.map +1 -1
- package/dist/lib/api/documents.js +6 -6
- package/dist/lib/api/documents.js.map +1 -1
- package/dist/lib/api/schemas/agent-chat.d.ts +45 -0
- package/dist/lib/api/schemas/agent-chat.d.ts.map +1 -0
- package/dist/lib/api/schemas/agent-chat.js +36 -0
- package/dist/lib/api/schemas/agent-chat.js.map +1 -0
- package/dist/lib/api/schemas/agent-operations.d.ts +13 -0
- package/dist/lib/api/schemas/agent-operations.d.ts.map +1 -0
- package/dist/lib/api/schemas/agent-operations.js +16 -0
- package/dist/lib/api/schemas/agent-operations.js.map +1 -0
- package/dist/lib/api/schemas/documents.d.ts +22 -0
- package/dist/lib/api/schemas/documents.d.ts.map +1 -1
- package/dist/lib/api/schemas/documents.js +20 -2
- package/dist/lib/api/schemas/documents.js.map +1 -1
- package/dist/lib/chunk-recovery.d.ts +33 -0
- package/dist/lib/chunk-recovery.d.ts.map +1 -0
- package/dist/lib/chunk-recovery.js +132 -0
- package/dist/lib/chunk-recovery.js.map +1 -0
- package/dist/lib/client/index.d.ts +1 -0
- package/dist/lib/client/index.d.ts.map +1 -1
- package/dist/lib/client/index.js +1 -0
- package/dist/lib/client/index.js.map +1 -1
- package/dist/lib/client/ui.d.ts +1 -0
- package/dist/lib/client/ui.d.ts.map +1 -1
- package/dist/lib/client/ui.js +1 -0
- package/dist/lib/client/ui.js.map +1 -1
- package/dist/lib/components/index.d.ts +1 -0
- package/dist/lib/components/index.d.ts.map +1 -1
- package/dist/lib/components/index.js +1 -0
- package/dist/lib/components/index.js.map +1 -1
- package/dist/lib/db/interfaces/agent-change-sets.d.ts +17 -0
- package/dist/lib/db/interfaces/agent-change-sets.d.ts.map +1 -0
- package/dist/lib/db/interfaces/agent-change-sets.js +2 -0
- package/dist/lib/db/interfaces/agent-change-sets.js.map +1 -0
- package/dist/lib/db/interfaces/document.d.ts +25 -3
- package/dist/lib/db/interfaces/document.d.ts.map +1 -1
- package/dist/lib/db/interfaces/document.js +19 -1
- package/dist/lib/db/interfaces/document.js.map +1 -1
- package/dist/lib/db/interfaces/index.d.ts +4 -1
- package/dist/lib/db/interfaces/index.d.ts.map +1 -1
- package/dist/lib/db/interfaces/index.js +1 -1
- package/dist/lib/db/interfaces/index.js.map +1 -1
- package/dist/lib/document-refresh.svelte.d.ts +2 -0
- package/dist/lib/document-refresh.svelte.d.ts.map +1 -1
- package/dist/lib/document-refresh.svelte.js +15 -0
- package/dist/lib/document-refresh.svelte.js.map +1 -1
- package/dist/lib/document-workspace-registry.svelte.d.ts +15 -0
- package/dist/lib/document-workspace-registry.svelte.d.ts.map +1 -0
- package/dist/lib/document-workspace-registry.svelte.js +31 -0
- package/dist/lib/document-workspace-registry.svelte.js.map +1 -0
- package/dist/lib/field-validation/utils.d.ts.map +1 -1
- package/dist/lib/field-validation/utils.js +211 -29
- package/dist/lib/field-validation/utils.js.map +1 -1
- package/dist/lib/hooks.d.ts +2 -0
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/hooks.js +2 -0
- package/dist/lib/hooks.js.map +1 -1
- package/dist/lib/local-api/collection-api.d.ts +7 -2
- package/dist/lib/local-api/collection-api.d.ts.map +1 -1
- package/dist/lib/local-api/collection-api.js +17 -10
- package/dist/lib/local-api/collection-api.js.map +1 -1
- package/dist/lib/mcp/tools.d.ts +39 -4
- package/dist/lib/mcp/tools.d.ts.map +1 -1
- package/dist/lib/mcp/tools.js +546 -362
- package/dist/lib/mcp/tools.js.map +1 -1
- package/dist/lib/plugins/resolver.d.ts +8 -1
- package/dist/lib/plugins/resolver.d.ts.map +1 -1
- package/dist/lib/plugins/resolver.js +14 -1
- package/dist/lib/plugins/resolver.js.map +1 -1
- package/dist/lib/plugins/types.d.ts +23 -2
- package/dist/lib/plugins/types.d.ts.map +1 -1
- package/dist/lib/plugins/types.js.map +1 -1
- package/dist/lib/preview/stega.d.ts +2 -2
- package/dist/lib/preview/stega.d.ts.map +1 -1
- package/dist/lib/preview/stega.js +52 -29
- package/dist/lib/preview/stega.js.map +1 -1
- package/dist/lib/schema-utils/validator.d.ts.map +1 -1
- package/dist/lib/schema-utils/validator.js +5 -2
- package/dist/lib/schema-utils/validator.js.map +1 -1
- package/dist/lib/server/api/index.d.ts.map +1 -1
- package/dist/lib/server/api/index.js +8 -0
- package/dist/lib/server/api/index.js.map +1 -1
- package/dist/lib/server/api/routes/agent-change-sets.d.ts +4 -0
- package/dist/lib/server/api/routes/agent-change-sets.d.ts.map +1 -0
- package/dist/lib/server/api/routes/agent-change-sets.js +132 -0
- package/dist/lib/server/api/routes/agent-change-sets.js.map +1 -0
- package/dist/lib/server/api/routes/agent-chat.d.ts +14 -0
- package/dist/lib/server/api/routes/agent-chat.d.ts.map +1 -0
- package/dist/lib/server/api/routes/agent-chat.js +224 -0
- package/dist/lib/server/api/routes/agent-chat.js.map +1 -0
- package/dist/lib/server/api/routes/document-versions.d.ts.map +1 -1
- package/dist/lib/server/api/routes/document-versions.js +16 -2
- package/dist/lib/server/api/routes/document-versions.js.map +1 -1
- package/dist/lib/server/api/routes/documents-by-id.d.ts.map +1 -1
- package/dist/lib/server/api/routes/documents-by-id.js +11 -1
- package/dist/lib/server/api/routes/documents-by-id.js.map +1 -1
- package/dist/lib/server/api/routes/documents-publish.d.ts.map +1 -1
- package/dist/lib/server/api/routes/documents-publish.js +36 -3
- package/dist/lib/server/api/routes/documents-publish.js.map +1 -1
- package/dist/lib/server/api/routes/jobs.d.ts.map +1 -1
- package/dist/lib/server/api/routes/jobs.js +4 -2
- package/dist/lib/server/api/routes/jobs.js.map +1 -1
- package/dist/lib/server/api/routes/resolve-created-by.d.ts +13 -0
- package/dist/lib/server/api/routes/resolve-created-by.d.ts.map +1 -0
- package/dist/lib/server/api/routes/resolve-created-by.js +31 -0
- package/dist/lib/server/api/routes/resolve-created-by.js.map +1 -0
- package/dist/lib/server/index.d.ts +1 -0
- package/dist/lib/server/index.d.ts.map +1 -1
- package/dist/lib/server/index.js +1 -0
- package/dist/lib/server/index.js.map +1 -1
- package/dist/lib/services/version-service.d.ts +4 -4
- package/dist/lib/services/version-service.d.ts.map +1 -1
- package/dist/lib/services/version-service.js +15 -11
- package/dist/lib/services/version-service.js.map +1 -1
- package/dist/lib/type-gen.d.ts.map +1 -1
- package/dist/lib/type-gen.js +11 -0
- package/dist/lib/type-gen.js.map +1 -1
- package/dist/lib/types/agent-change-sets.d.ts +70 -0
- package/dist/lib/types/agent-change-sets.d.ts.map +1 -0
- package/dist/lib/types/agent-change-sets.js +7 -0
- package/dist/lib/types/agent-change-sets.js.map +1 -0
- package/dist/lib/types/agent-stream.d.ts +60 -0
- package/dist/lib/types/agent-stream.d.ts.map +1 -0
- package/dist/lib/types/agent-stream.js +12 -0
- package/dist/lib/types/agent-stream.js.map +1 -0
- package/dist/lib/types/agent-tools.d.ts +65 -0
- package/dist/lib/types/agent-tools.d.ts.map +1 -0
- package/dist/lib/types/agent-tools.js +2 -0
- package/dist/lib/types/agent-tools.js.map +1 -0
- package/dist/lib/types/config.d.ts +19 -0
- package/dist/lib/types/config.d.ts.map +1 -1
- package/dist/lib/types/document-workspace.d.ts +53 -0
- package/dist/lib/types/document-workspace.d.ts.map +1 -0
- package/dist/lib/types/document-workspace.js +15 -0
- package/dist/lib/types/document-workspace.js.map +1 -0
- package/dist/lib/types/document.d.ts +5 -0
- package/dist/lib/types/document.d.ts.map +1 -1
- package/dist/lib/types/index.d.ts +2 -0
- package/dist/lib/types/index.d.ts.map +1 -1
- package/dist/lib/types/index.js +2 -0
- package/dist/lib/types/index.js.map +1 -1
- package/dist/lib/types/schemas.js +1 -1
- package/dist/lib/types/schemas.js.map +1 -1
- package/dist/lib/utils/fetch-remote-file.d.ts +11 -0
- package/dist/lib/utils/fetch-remote-file.d.ts.map +1 -0
- package/dist/lib/utils/fetch-remote-file.js +169 -0
- package/dist/lib/utils/fetch-remote-file.js.map +1 -0
- package/dist/local-api/collection-api.d.ts +7 -2
- package/dist/local-api/collection-api.d.ts.map +1 -1
- package/dist/local-api/collection-api.js +17 -10
- package/dist/mcp/tools.d.ts +39 -4
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +546 -362
- package/dist/plugins/resolver.d.ts +8 -1
- package/dist/plugins/resolver.d.ts.map +1 -1
- package/dist/plugins/resolver.js +14 -1
- package/dist/plugins/types.d.ts +23 -2
- package/dist/plugins/types.d.ts.map +1 -1
- package/dist/preview/stega.d.ts +2 -2
- package/dist/preview/stega.d.ts.map +1 -1
- package/dist/preview/stega.js +52 -29
- package/dist/schema-utils/validator.d.ts.map +1 -1
- package/dist/schema-utils/validator.js +5 -2
- package/dist/server/api/index.d.ts.map +1 -1
- package/dist/server/api/index.js +8 -0
- package/dist/server/api/routes/agent-change-sets.d.ts +4 -0
- package/dist/server/api/routes/agent-change-sets.d.ts.map +1 -0
- package/dist/server/api/routes/agent-change-sets.js +131 -0
- package/dist/server/api/routes/agent-chat.d.ts +14 -0
- package/dist/server/api/routes/agent-chat.d.ts.map +1 -0
- package/dist/server/api/routes/agent-chat.js +223 -0
- package/dist/server/api/routes/document-versions.d.ts.map +1 -1
- package/dist/server/api/routes/document-versions.js +16 -2
- package/dist/server/api/routes/documents-by-id.d.ts.map +1 -1
- package/dist/server/api/routes/documents-by-id.js +11 -1
- package/dist/server/api/routes/documents-publish.d.ts.map +1 -1
- package/dist/server/api/routes/documents-publish.js +36 -3
- package/dist/server/api/routes/jobs.d.ts.map +1 -1
- package/dist/server/api/routes/jobs.js +4 -2
- package/dist/server/api/routes/resolve-created-by.d.ts +13 -0
- package/dist/server/api/routes/resolve-created-by.d.ts.map +1 -0
- package/dist/server/api/routes/resolve-created-by.js +30 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/services/version-service.d.ts +4 -4
- package/dist/services/version-service.d.ts.map +1 -1
- package/dist/services/version-service.js +15 -11
- package/dist/type-gen.d.ts.map +1 -1
- package/dist/type-gen.js +11 -0
- package/dist/types/agent-change-sets.d.ts +70 -0
- package/dist/types/agent-change-sets.d.ts.map +1 -0
- package/dist/types/agent-change-sets.js +6 -0
- package/dist/types/agent-stream.d.ts +60 -0
- package/dist/types/agent-stream.d.ts.map +1 -0
- package/dist/types/agent-stream.js +11 -0
- package/dist/types/agent-tools.d.ts +65 -0
- package/dist/types/agent-tools.d.ts.map +1 -0
- package/dist/types/agent-tools.js +1 -0
- package/dist/types/config.d.ts +19 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/document-workspace.d.ts +53 -0
- package/dist/types/document-workspace.d.ts.map +1 -0
- package/dist/types/document-workspace.js +14 -0
- package/dist/types/document.d.ts +5 -0
- package/dist/types/document.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/schemas.js +1 -1
- package/dist/utils/fetch-remote-file.d.ts +11 -0
- package/dist/utils/fetch-remote-file.d.ts.map +1 -0
- package/dist/utils/fetch-remote-file.js +168 -0
- package/package.json +5 -2
package/dist/lib/mcp/tools.js
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Content agent tools — transport-agnostic.
|
|
2
2
|
//
|
|
3
|
-
// Each tool is
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
3
|
+
// Each tool is an `AgentToolDefinition` (name/description/capabilities/zod schema — the
|
|
4
|
+
// Milestone 2 contract, see references/content-copilot-phase-1-plan.md) paired with an
|
|
5
|
+
// `execute` function. Unlike the old per-request `McpTool[]` registry, these are defined
|
|
6
|
+
// ONCE, statically — `execute` receives `{ aphexCMS, context }` as a call-time argument
|
|
7
|
+
// (matching how `aphex/event/consumer`/`aphex/job/handler` plugin parts already work) rather
|
|
8
|
+
// than a closure bound at registration time, so the exact same tool can be invoked by MCP,
|
|
9
|
+
// a future in-admin agent panel, or a plugin-contributed executor, through one execution
|
|
10
|
+
// service. `buildContentTools()` is a thin per-request adapter that wraps these into the
|
|
11
|
+
// MCP SDK's expected shape — the MCP route (routes/mcp.ts) is the only remaining MCP-specific
|
|
12
|
+
// code in this file.
|
|
10
13
|
import { z } from 'zod';
|
|
11
14
|
import { VALID_FIELD_TYPES, RESERVED_FIELDS, validateSchemaReferences } from '../schema-utils/validator.js';
|
|
12
15
|
import { validateDocumentData } from '../field-validation/utils.js';
|
|
13
16
|
import { validateFile } from '../utils/mime-detect.js';
|
|
17
|
+
import { fetchRemoteFile } from '../utils/fetch-remote-file.js';
|
|
14
18
|
import { fieldWriteShape } from '../type-gen.js';
|
|
19
|
+
import { hasCapability, resolveCapabilities } from '../types/capabilities.js';
|
|
20
|
+
import { contentWorkspaceTools } from '../ai/content-workspace-tools.js';
|
|
15
21
|
import { DEFAULT_BLOCK_STYLES, DEFAULT_BLOCK_DECORATORS, DEFAULT_BLOCK_LISTS } from '../components/admin/fields/richtext/block-defaults.js';
|
|
16
|
-
const ok = (data) => ({
|
|
17
|
-
|
|
18
|
-
});
|
|
19
|
-
const fail = (message) => ({
|
|
20
|
-
content: [{ type: 'text', text: message }],
|
|
21
|
-
isError: true
|
|
22
|
-
});
|
|
22
|
+
const ok = (data) => ({ success: true, data });
|
|
23
|
+
const fail = (message) => ({ success: false, error: message });
|
|
23
24
|
function asString(args, key) {
|
|
24
25
|
const v = args[key];
|
|
25
26
|
return typeof v === 'string' && v.length > 0 ? v : null;
|
|
@@ -123,149 +124,178 @@ function buildWriteShapes(schema, allSchemas) {
|
|
|
123
124
|
return { writeShapes, shapeLegend };
|
|
124
125
|
}
|
|
125
126
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
127
|
+
* The content-plane tools, safe to expose against a live instance: all writes go through
|
|
128
|
+
* LocalAPI, so a read-only API key is rejected by the permission layer, not by this
|
|
129
|
+
* registry. `requiredCapabilities` here is the advertisement/execution gate for the new
|
|
130
|
+
* agent-tool contract; document tools additionally get real enforcement downstream from
|
|
131
|
+
* `CollectionAPI`'s own `PermissionChecker` (unchanged) — `asset.read`/`asset.upload` have no
|
|
132
|
+
* such downstream check, so `list_assets`/`upload_asset` enforce it directly in `execute`.
|
|
129
133
|
*/
|
|
130
|
-
export
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const orgId = context.organizationId;
|
|
134
|
-
return [
|
|
135
|
-
{
|
|
134
|
+
export const contentAgentTools = [
|
|
135
|
+
{
|
|
136
|
+
definition: {
|
|
136
137
|
name: 'describe_cms',
|
|
137
138
|
description: 'Orientation for building against this CMS: all content types and their relationships, the valid field-type vocabulary, and what this API key is allowed to do. Call this first. All data is derived live from the running config — never stale. For exact field/schema TypeScript signatures, read the SchemaType and Field types from the `@aphexcms/cms-core` package (and the real schemas in src/lib/schemaTypes/*.ts).',
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
for (const s of schemas)
|
|
143
|
-
collectReferences(s.name, s.fields, edges);
|
|
144
|
-
const documentTypes = schemas
|
|
145
|
-
.filter((s) => s.type === 'document')
|
|
146
|
-
.map((s) => ({
|
|
147
|
-
name: s.name,
|
|
148
|
-
title: s.title,
|
|
149
|
-
singleton: s.type === 'document' ? (s.singleton ?? false) : false,
|
|
150
|
-
fieldCount: s.fields.length
|
|
151
|
-
}));
|
|
152
|
-
const objectTypes = schemas
|
|
153
|
-
.filter((s) => s.type === 'object')
|
|
154
|
-
.map((s) => ({ name: s.name, title: s.title, fieldCount: s.fields.length }));
|
|
155
|
-
const auth = context.auth;
|
|
156
|
-
const capabilities = auth?.type === 'api_key'
|
|
157
|
-
? {
|
|
158
|
-
authType: 'api_key',
|
|
159
|
-
canWrite: auth.permissions.includes('write'),
|
|
160
|
-
permissions: auth.permissions,
|
|
161
|
-
capabilities: auth.capabilities
|
|
162
|
-
}
|
|
163
|
-
: auth?.type === 'session'
|
|
164
|
-
? { authType: 'session', canWrite: true, capabilities: auth.capabilities }
|
|
165
|
-
: { authType: 'unknown', canWrite: false };
|
|
166
|
-
return ok({
|
|
167
|
-
organizationId: orgId,
|
|
168
|
-
documentTypes,
|
|
169
|
-
objectTypes,
|
|
170
|
-
referenceGraph: edges,
|
|
171
|
-
validFieldTypes: VALID_FIELD_TYPES,
|
|
172
|
-
reservedFieldNames: RESERVED_FIELDS,
|
|
173
|
-
capabilities,
|
|
174
|
-
typeReference: "Import SchemaType/Field from '@aphexcms/cms-core' for exact per-field-type props and validation Rule API; TypeScript enforces them. Read existing schemas in src/lib/schemaTypes/*.ts as working examples."
|
|
175
|
-
});
|
|
176
|
-
}
|
|
139
|
+
mutates: false,
|
|
140
|
+
requiredCapabilities: [],
|
|
141
|
+
execution: 'server',
|
|
142
|
+
inputSchema: z.object({})
|
|
177
143
|
},
|
|
178
|
-
{
|
|
144
|
+
execute: async (_input, { aphexCMS, context }) => {
|
|
145
|
+
const schemas = aphexCMS.config.schemaTypes;
|
|
146
|
+
const orgId = context.organizationId;
|
|
147
|
+
const edges = [];
|
|
148
|
+
for (const s of schemas)
|
|
149
|
+
collectReferences(s.name, s.fields, edges);
|
|
150
|
+
const documentTypes = schemas
|
|
151
|
+
.filter((s) => s.type === 'document')
|
|
152
|
+
.map((s) => ({
|
|
153
|
+
name: s.name,
|
|
154
|
+
title: s.title,
|
|
155
|
+
singleton: s.type === 'document' ? (s.singleton ?? false) : false,
|
|
156
|
+
fieldCount: s.fields.length
|
|
157
|
+
}));
|
|
158
|
+
const objectTypes = schemas
|
|
159
|
+
.filter((s) => s.type === 'object')
|
|
160
|
+
.map((s) => ({ name: s.name, title: s.title, fieldCount: s.fields.length }));
|
|
161
|
+
const auth = context.auth;
|
|
162
|
+
const capabilities = auth?.type === 'api_key'
|
|
163
|
+
? {
|
|
164
|
+
authType: 'api_key',
|
|
165
|
+
canWrite: auth.permissions.includes('write'),
|
|
166
|
+
permissions: auth.permissions,
|
|
167
|
+
capabilities: auth.capabilities
|
|
168
|
+
}
|
|
169
|
+
: auth?.type === 'session'
|
|
170
|
+
? { authType: 'session', canWrite: true, capabilities: auth.capabilities }
|
|
171
|
+
: { authType: 'unknown', canWrite: false };
|
|
172
|
+
return ok({
|
|
173
|
+
organizationId: orgId,
|
|
174
|
+
documentTypes,
|
|
175
|
+
objectTypes,
|
|
176
|
+
referenceGraph: edges,
|
|
177
|
+
validFieldTypes: VALID_FIELD_TYPES,
|
|
178
|
+
reservedFieldNames: RESERVED_FIELDS,
|
|
179
|
+
capabilities,
|
|
180
|
+
typeReference: "Import SchemaType/Field from '@aphexcms/cms-core' for exact per-field-type props and validation Rule API; TypeScript enforces them. Read existing schemas in src/lib/schemaTypes/*.ts as working examples."
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
definition: {
|
|
179
186
|
name: 'list_collections',
|
|
180
187
|
description: 'List the document collections (content types) available in this CMS, with their names and titles.',
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const schema = api.getCollectionSchema(name);
|
|
186
|
-
return { name, title: schema?.title ?? name, singleton: schema?.singleton ?? false };
|
|
187
|
-
});
|
|
188
|
-
return ok({ collections });
|
|
189
|
-
}
|
|
188
|
+
mutates: false,
|
|
189
|
+
requiredCapabilities: [],
|
|
190
|
+
execution: 'server',
|
|
191
|
+
inputSchema: z.object({})
|
|
190
192
|
},
|
|
191
|
-
{
|
|
193
|
+
execute: async (_input, { aphexCMS }) => {
|
|
194
|
+
const api = aphexCMS.localAPI;
|
|
195
|
+
const names = api.getCollectionNames();
|
|
196
|
+
const collections = names.map((name) => {
|
|
197
|
+
const schema = api.getCollectionSchema(name);
|
|
198
|
+
return { name, title: schema?.title ?? name, singleton: schema?.singleton ?? false };
|
|
199
|
+
});
|
|
200
|
+
return ok({ collections });
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
definition: {
|
|
192
205
|
name: 'get_schema',
|
|
193
206
|
description: "Get the field schema for one collection, so you know the shape to use when creating or updating its documents. Returns { schema, portableText? } — `portableText` is present when the type has rich-text (block) fields and links the open Portable Text spec plus this schema's allowed styles/marks/custom block types.",
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return fail('Missing required string argument: collection');
|
|
199
|
-
const schema = api.getCollectionSchema(collection);
|
|
200
|
-
if (!schema)
|
|
201
|
-
return fail(`Unknown collection: ${collection}`);
|
|
202
|
-
const portableText = portableTextGuide(schema);
|
|
203
|
-
const { writeShapes, shapeLegend } = buildWriteShapes(schema, aphexCMS.config.schemaTypes);
|
|
204
|
-
return ok({
|
|
205
|
-
schema,
|
|
206
|
-
writeShapes,
|
|
207
|
-
...(Object.keys(shapeLegend).length > 0 ? { shapeLegend } : {}),
|
|
208
|
-
...(portableText ? { portableText } : {})
|
|
209
|
-
});
|
|
210
|
-
}
|
|
207
|
+
mutates: false,
|
|
208
|
+
requiredCapabilities: [],
|
|
209
|
+
execution: 'server',
|
|
210
|
+
inputSchema: z.object({ collection: z.string().describe('Collection name') })
|
|
211
211
|
},
|
|
212
|
-
{
|
|
212
|
+
execute: async (args, { aphexCMS }) => {
|
|
213
|
+
const api = aphexCMS.localAPI;
|
|
214
|
+
const collection = asString(args, 'collection');
|
|
215
|
+
if (!collection)
|
|
216
|
+
return fail('Missing required string argument: collection');
|
|
217
|
+
const schema = api.getCollectionSchema(collection);
|
|
218
|
+
if (!schema)
|
|
219
|
+
return fail(`Unknown collection: ${collection}`);
|
|
220
|
+
const portableText = portableTextGuide(schema);
|
|
221
|
+
const { writeShapes, shapeLegend } = buildWriteShapes(schema, aphexCMS.config.schemaTypes);
|
|
222
|
+
return ok({
|
|
223
|
+
schema,
|
|
224
|
+
writeShapes,
|
|
225
|
+
...(Object.keys(shapeLegend).length > 0 ? { shapeLegend } : {}),
|
|
226
|
+
...(portableText ? { portableText } : {})
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
definition: {
|
|
213
232
|
name: 'validate_document',
|
|
214
233
|
description: 'Dry-run: validate document `data` against its collection schema WITHOUT saving, using the same validator as create/update. Returns field-level errors so you can fix them before create_document/update_document.',
|
|
215
|
-
|
|
234
|
+
mutates: false,
|
|
235
|
+
requiredCapabilities: [],
|
|
236
|
+
execution: 'server',
|
|
237
|
+
inputSchema: z.object({
|
|
216
238
|
collection: z.string().describe('Collection name'),
|
|
217
239
|
data: z.record(z.string(), z.unknown()).describe('Document field values to validate')
|
|
218
|
-
}
|
|
219
|
-
handler: async (args) => {
|
|
220
|
-
const collection = asString(args, 'collection');
|
|
221
|
-
const data = asRecord(args, 'data');
|
|
222
|
-
if (!collection || !data)
|
|
223
|
-
return fail('Missing required arguments: collection (string), data (object)');
|
|
224
|
-
const schema = api.getCollectionSchema(collection);
|
|
225
|
-
if (!schema)
|
|
226
|
-
return fail(`Unknown collection: ${collection}`);
|
|
227
|
-
try {
|
|
228
|
-
const result = await validateDocumentData(schema, data);
|
|
229
|
-
return ok({ isValid: result.isValid, errors: result.errors });
|
|
230
|
-
}
|
|
231
|
-
catch (err) {
|
|
232
|
-
return fail(`Validation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
240
|
+
})
|
|
235
241
|
},
|
|
236
|
-
{
|
|
242
|
+
execute: async (args, { aphexCMS }) => {
|
|
243
|
+
const api = aphexCMS.localAPI;
|
|
244
|
+
const collection = asString(args, 'collection');
|
|
245
|
+
const data = asRecord(args, 'data');
|
|
246
|
+
if (!collection || !data)
|
|
247
|
+
return fail('Missing required arguments: collection (string), data (object)');
|
|
248
|
+
const schema = api.getCollectionSchema(collection);
|
|
249
|
+
if (!schema)
|
|
250
|
+
return fail(`Unknown collection: ${collection}`);
|
|
251
|
+
try {
|
|
252
|
+
const result = await validateDocumentData(schema, data);
|
|
253
|
+
return ok({ isValid: result.isValid, errors: result.errors });
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
return fail(`Validation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
definition: {
|
|
237
262
|
name: 'validate_schema',
|
|
238
263
|
description: 'Validate a proposed schema definition (structure, field types, references, reserved field names) against the current CMS, WITHOUT writing a file. Use before writing a schema .ts file. Pass the schema as JSON — validation-rule functions are not needed for structural validation.',
|
|
239
|
-
|
|
264
|
+
mutates: false,
|
|
265
|
+
requiredCapabilities: [],
|
|
266
|
+
execution: 'server',
|
|
267
|
+
inputSchema: z.object({
|
|
240
268
|
schema: z
|
|
241
269
|
.record(z.string(), z.unknown())
|
|
242
270
|
.describe('Proposed SchemaType as JSON (type, name, title, fields, …)')
|
|
243
|
-
}
|
|
244
|
-
handler: async (args) => {
|
|
245
|
-
const proposed = asRecord(args, 'schema');
|
|
246
|
-
if (!proposed)
|
|
247
|
-
return fail('Missing required argument: schema (object)');
|
|
248
|
-
// Validate the proposed schema alongside the existing ones so its
|
|
249
|
-
// references resolve. `proposed` is external JSON asserted into SchemaType
|
|
250
|
-
// at this boundary; validateSchemaReferences is what actually checks it.
|
|
251
|
-
const all = [
|
|
252
|
-
...aphexCMS.config.schemaTypes,
|
|
253
|
-
proposed
|
|
254
|
-
];
|
|
255
|
-
try {
|
|
256
|
-
validateSchemaReferences(all);
|
|
257
|
-
return ok({ isValid: true, errors: [] });
|
|
258
|
-
}
|
|
259
|
-
catch (err) {
|
|
260
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
261
|
-
return ok({ isValid: false, errors: message.split('\n') });
|
|
262
|
-
}
|
|
263
|
-
}
|
|
271
|
+
})
|
|
264
272
|
},
|
|
265
|
-
{
|
|
273
|
+
execute: async (args, { aphexCMS }) => {
|
|
274
|
+
const proposed = asRecord(args, 'schema');
|
|
275
|
+
if (!proposed)
|
|
276
|
+
return fail('Missing required argument: schema (object)');
|
|
277
|
+
// Validate the proposed schema alongside the existing ones so its
|
|
278
|
+
// references resolve. `proposed` is external JSON asserted into SchemaType
|
|
279
|
+
// at this boundary; validateSchemaReferences is what actually checks it.
|
|
280
|
+
const all = [...aphexCMS.config.schemaTypes, proposed];
|
|
281
|
+
try {
|
|
282
|
+
validateSchemaReferences(all);
|
|
283
|
+
return ok({ isValid: true, errors: [] });
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
287
|
+
return ok({ isValid: false, errors: message.split('\n') });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
definition: {
|
|
266
293
|
name: 'query_documents',
|
|
267
294
|
description: 'Query documents in a collection. Supports where filters, sorting, pagination, and draft/published perspective.',
|
|
268
|
-
|
|
295
|
+
mutates: false,
|
|
296
|
+
requiredCapabilities: ['document.read'],
|
|
297
|
+
execution: 'server',
|
|
298
|
+
inputSchema: z.object({
|
|
269
299
|
collection: z.string().describe('Collection name'),
|
|
270
300
|
where: z
|
|
271
301
|
.record(z.string(), z.unknown())
|
|
@@ -281,239 +311,317 @@ export function buildContentTools({ aphexCMS, context }) {
|
|
|
281
311
|
.enum(['draft', 'published'])
|
|
282
312
|
.optional()
|
|
283
313
|
.describe('Which content to read (default draft)')
|
|
284
|
-
}
|
|
285
|
-
handler: async (args) => {
|
|
286
|
-
const collection = asString(args, 'collection');
|
|
287
|
-
if (!collection)
|
|
288
|
-
return fail('Missing required string argument: collection');
|
|
289
|
-
const col = api.getCollection(collection);
|
|
290
|
-
if (!col)
|
|
291
|
-
return fail(`Unknown collection: ${collection}`);
|
|
292
|
-
// `where` is arbitrary filter JSON from the MCP client. WhereTyped permits
|
|
293
|
-
// dynamic field keys; assert the parsed object into it at this external
|
|
294
|
-
// boundary rather than validating every possible filter shape.
|
|
295
|
-
const where = (asRecord(args, 'where') ?? undefined);
|
|
296
|
-
const limit = typeof args.limit === 'number' ? args.limit : undefined;
|
|
297
|
-
const offset = typeof args.offset === 'number' ? args.offset : undefined;
|
|
298
|
-
const sort = asString(args, 'sort') ?? undefined;
|
|
299
|
-
try {
|
|
300
|
-
const result = await col.find(context, {
|
|
301
|
-
where,
|
|
302
|
-
limit,
|
|
303
|
-
offset,
|
|
304
|
-
sort,
|
|
305
|
-
perspective: perspectiveArg(args.perspective)
|
|
306
|
-
});
|
|
307
|
-
return ok(result);
|
|
308
|
-
}
|
|
309
|
-
catch (err) {
|
|
310
|
-
return fail(`Query failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
314
|
+
})
|
|
313
315
|
},
|
|
314
|
-
{
|
|
316
|
+
execute: async (args, { aphexCMS, context }) => {
|
|
317
|
+
const api = aphexCMS.localAPI;
|
|
318
|
+
const collection = asString(args, 'collection');
|
|
319
|
+
if (!collection)
|
|
320
|
+
return fail('Missing required string argument: collection');
|
|
321
|
+
const col = api.getCollection(collection);
|
|
322
|
+
if (!col)
|
|
323
|
+
return fail(`Unknown collection: ${collection}`);
|
|
324
|
+
// `where` is arbitrary filter JSON from the MCP client. WhereTyped permits
|
|
325
|
+
// dynamic field keys; assert the parsed object into it at this external
|
|
326
|
+
// boundary rather than validating every possible filter shape.
|
|
327
|
+
const where = (asRecord(args, 'where') ?? undefined);
|
|
328
|
+
const limit = typeof args.limit === 'number' ? args.limit : undefined;
|
|
329
|
+
const offset = typeof args.offset === 'number' ? args.offset : undefined;
|
|
330
|
+
const sort = asString(args, 'sort') ?? undefined;
|
|
331
|
+
try {
|
|
332
|
+
const result = await col.find(context, {
|
|
333
|
+
where,
|
|
334
|
+
limit,
|
|
335
|
+
offset,
|
|
336
|
+
sort,
|
|
337
|
+
perspective: perspectiveArg(args.perspective)
|
|
338
|
+
});
|
|
339
|
+
return ok(result);
|
|
340
|
+
}
|
|
341
|
+
catch (err) {
|
|
342
|
+
return fail(`Query failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
definition: {
|
|
315
348
|
name: 'get_document',
|
|
316
349
|
description: 'Get a single document by id from a collection.',
|
|
317
|
-
|
|
350
|
+
mutates: false,
|
|
351
|
+
requiredCapabilities: ['document.read'],
|
|
352
|
+
execution: 'server',
|
|
353
|
+
inputSchema: z.object({
|
|
318
354
|
collection: z.string().describe('Collection name'),
|
|
319
355
|
id: z.string().describe('Document id'),
|
|
320
356
|
perspective: z.enum(['draft', 'published']).optional()
|
|
321
|
-
}
|
|
322
|
-
handler: async (args) => {
|
|
323
|
-
const collection = asString(args, 'collection');
|
|
324
|
-
const id = asString(args, 'id');
|
|
325
|
-
if (!collection || !id)
|
|
326
|
-
return fail('Missing required string arguments: collection, id');
|
|
327
|
-
const col = api.getCollection(collection);
|
|
328
|
-
if (!col)
|
|
329
|
-
return fail(`Unknown collection: ${collection}`);
|
|
330
|
-
try {
|
|
331
|
-
const doc = await col.findByID(context, id, {
|
|
332
|
-
perspective: perspectiveArg(args.perspective)
|
|
333
|
-
});
|
|
334
|
-
if (!doc)
|
|
335
|
-
return fail(`Document not found: ${collection}/${id}`);
|
|
336
|
-
return ok(doc);
|
|
337
|
-
}
|
|
338
|
-
catch (err) {
|
|
339
|
-
return fail(`Get failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
357
|
+
})
|
|
342
358
|
},
|
|
343
|
-
{
|
|
359
|
+
execute: async (args, { aphexCMS, context }) => {
|
|
360
|
+
const api = aphexCMS.localAPI;
|
|
361
|
+
const collection = asString(args, 'collection');
|
|
362
|
+
const id = asString(args, 'id');
|
|
363
|
+
if (!collection || !id)
|
|
364
|
+
return fail('Missing required string arguments: collection, id');
|
|
365
|
+
const col = api.getCollection(collection);
|
|
366
|
+
if (!col)
|
|
367
|
+
return fail(`Unknown collection: ${collection}`);
|
|
368
|
+
try {
|
|
369
|
+
const doc = await col.findByID(context, id, {
|
|
370
|
+
perspective: perspectiveArg(args.perspective)
|
|
371
|
+
});
|
|
372
|
+
if (!doc)
|
|
373
|
+
return fail(`Document not found: ${collection}/${id}`);
|
|
374
|
+
return ok(doc);
|
|
375
|
+
}
|
|
376
|
+
catch (err) {
|
|
377
|
+
return fail(`Get failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
definition: {
|
|
344
383
|
name: 'create_document',
|
|
345
384
|
description: 'Create a document in a collection. Pass field values in `data` (matching the collection schema). Set publish:true to publish immediately, otherwise it is saved as a draft.',
|
|
346
|
-
|
|
385
|
+
mutates: true,
|
|
386
|
+
requiredCapabilities: ['document.create'],
|
|
387
|
+
execution: 'server',
|
|
388
|
+
inputSchema: z.object({
|
|
347
389
|
collection: z.string().describe('Collection name'),
|
|
348
390
|
data: z
|
|
349
391
|
.record(z.string(), z.unknown())
|
|
350
392
|
.describe('Field values matching the collection schema'),
|
|
351
393
|
publish: z.boolean().optional().describe('Publish immediately (default false)')
|
|
352
|
-
}
|
|
353
|
-
handler: async (args) => {
|
|
354
|
-
const collection = asString(args, 'collection');
|
|
355
|
-
const data = asRecord(args, 'data');
|
|
356
|
-
if (!collection || !data)
|
|
357
|
-
return fail('Missing required arguments: collection (string), data (object)');
|
|
358
|
-
const col = api.getCollection(collection);
|
|
359
|
-
if (!col)
|
|
360
|
-
return fail(`Unknown collection: ${collection}`);
|
|
361
|
-
try {
|
|
362
|
-
const result = await col.create(context, data, { publish: args.publish === true });
|
|
363
|
-
return ok(result);
|
|
364
|
-
}
|
|
365
|
-
catch (err) {
|
|
366
|
-
return fail(`Create failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
394
|
+
})
|
|
369
395
|
},
|
|
370
|
-
{
|
|
396
|
+
execute: async (args, { aphexCMS, context }) => {
|
|
397
|
+
const api = aphexCMS.localAPI;
|
|
398
|
+
const collection = asString(args, 'collection');
|
|
399
|
+
const data = asRecord(args, 'data');
|
|
400
|
+
if (!collection || !data)
|
|
401
|
+
return fail('Missing required arguments: collection (string), data (object)');
|
|
402
|
+
const col = api.getCollection(collection);
|
|
403
|
+
if (!col)
|
|
404
|
+
return fail(`Unknown collection: ${collection}`);
|
|
405
|
+
try {
|
|
406
|
+
const result = await col.create(context, data, { publish: args.publish === true });
|
|
407
|
+
return ok(result);
|
|
408
|
+
}
|
|
409
|
+
catch (err) {
|
|
410
|
+
return fail(`Create failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
definition: {
|
|
371
416
|
name: 'update_document',
|
|
372
|
-
description:
|
|
373
|
-
|
|
417
|
+
description: "Update fields on an existing document. Only include the fields you want to change in `data`. Set publish:true to publish the result. Pass `expectedRevision` (from a prior get_document/update_document/publish_document call's `document._meta.revision`) to guard against overwriting a change made since you last read it — a mismatch fails the call instead of silently overwriting. If `content_patch_fields`/`content_save_draft` are also available, they target the document currently open in the admin editor — prefer those for edits to that specific document, since this tool writes straight to the database and the open editor will not reflect the change until the user reloads.",
|
|
418
|
+
mutates: true,
|
|
419
|
+
requiredCapabilities: ['document.update'],
|
|
420
|
+
execution: 'server',
|
|
421
|
+
inputSchema: z.object({
|
|
374
422
|
collection: z.string().describe('Collection name'),
|
|
375
423
|
id: z.string().describe('Document id'),
|
|
376
424
|
data: z.record(z.string(), z.unknown()).describe('Partial field values to update'),
|
|
377
|
-
publish: z.boolean().optional().describe('Publish after updating (default false)')
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
if (!collection || !id || !data)
|
|
384
|
-
return fail('Missing required arguments: collection, id (strings), data (object)');
|
|
385
|
-
const col = api.getCollection(collection);
|
|
386
|
-
if (!col)
|
|
387
|
-
return fail(`Unknown collection: ${collection}`);
|
|
388
|
-
try {
|
|
389
|
-
const result = await col.update(context, id, data, { publish: args.publish === true });
|
|
390
|
-
if (!result)
|
|
391
|
-
return fail(`Document not found: ${collection}/${id}`);
|
|
392
|
-
return ok(result);
|
|
393
|
-
}
|
|
394
|
-
catch (err) {
|
|
395
|
-
return fail(`Update failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
425
|
+
publish: z.boolean().optional().describe('Publish after updating (default false)'),
|
|
426
|
+
expectedRevision: z
|
|
427
|
+
.number()
|
|
428
|
+
.optional()
|
|
429
|
+
.describe('CAS guard — the revision you last read; mismatch fails instead of overwriting')
|
|
430
|
+
})
|
|
398
431
|
},
|
|
399
|
-
{
|
|
432
|
+
execute: async (args, { aphexCMS, context }) => {
|
|
433
|
+
const api = aphexCMS.localAPI;
|
|
434
|
+
const collection = asString(args, 'collection');
|
|
435
|
+
const id = asString(args, 'id');
|
|
436
|
+
const data = asRecord(args, 'data');
|
|
437
|
+
if (!collection || !id || !data)
|
|
438
|
+
return fail('Missing required arguments: collection, id (strings), data (object)');
|
|
439
|
+
const col = api.getCollection(collection);
|
|
440
|
+
if (!col)
|
|
441
|
+
return fail(`Unknown collection: ${collection}`);
|
|
442
|
+
try {
|
|
443
|
+
const result = await col.update(context, id, data, {
|
|
444
|
+
publish: args.publish === true,
|
|
445
|
+
expectedRevision: typeof args.expectedRevision === 'number' ? args.expectedRevision : undefined
|
|
446
|
+
});
|
|
447
|
+
if (!result)
|
|
448
|
+
return fail(`Document not found: ${collection}/${id}`);
|
|
449
|
+
return ok(result);
|
|
450
|
+
}
|
|
451
|
+
catch (err) {
|
|
452
|
+
return fail(`Update failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
definition: {
|
|
400
458
|
name: 'publish_document',
|
|
401
|
-
description:
|
|
402
|
-
|
|
459
|
+
description: "Publish a document (copies its current draft to the published perspective). Pass `expectedRevision` (from a prior read/write's `document._meta.revision`) to guard against publishing over a change made since you last read it.",
|
|
460
|
+
mutates: true,
|
|
461
|
+
requiredCapabilities: ['document.publish'],
|
|
462
|
+
execution: 'server',
|
|
463
|
+
inputSchema: z.object({
|
|
403
464
|
collection: z.string().describe('Collection name'),
|
|
404
|
-
id: z.string().describe('Document id')
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
return fail('Missing required string arguments: collection, id');
|
|
411
|
-
const col = api.getCollection(collection);
|
|
412
|
-
if (!col)
|
|
413
|
-
return fail(`Unknown collection: ${collection}`);
|
|
414
|
-
try {
|
|
415
|
-
const doc = await col.publish(context, id);
|
|
416
|
-
if (!doc)
|
|
417
|
-
return fail(`Document not found: ${collection}/${id}`);
|
|
418
|
-
return ok(doc);
|
|
419
|
-
}
|
|
420
|
-
catch (err) {
|
|
421
|
-
return fail(`Publish failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
465
|
+
id: z.string().describe('Document id'),
|
|
466
|
+
expectedRevision: z
|
|
467
|
+
.number()
|
|
468
|
+
.optional()
|
|
469
|
+
.describe('CAS guard — the revision you last read; mismatch fails instead of overwriting')
|
|
470
|
+
})
|
|
424
471
|
},
|
|
425
|
-
{
|
|
472
|
+
execute: async (args, { aphexCMS, context }) => {
|
|
473
|
+
const api = aphexCMS.localAPI;
|
|
474
|
+
const collection = asString(args, 'collection');
|
|
475
|
+
const id = asString(args, 'id');
|
|
476
|
+
if (!collection || !id)
|
|
477
|
+
return fail('Missing required string arguments: collection, id');
|
|
478
|
+
const col = api.getCollection(collection);
|
|
479
|
+
if (!col)
|
|
480
|
+
return fail(`Unknown collection: ${collection}`);
|
|
481
|
+
try {
|
|
482
|
+
const doc = await col.publish(context, id, {
|
|
483
|
+
expectedRevision: typeof args.expectedRevision === 'number' ? args.expectedRevision : undefined
|
|
484
|
+
});
|
|
485
|
+
if (!doc)
|
|
486
|
+
return fail(`Document not found: ${collection}/${id}`);
|
|
487
|
+
return ok(doc);
|
|
488
|
+
}
|
|
489
|
+
catch (err) {
|
|
490
|
+
return fail(`Publish failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
definition: {
|
|
426
496
|
name: 'get_singleton',
|
|
427
497
|
description: 'Get a singleton document (a type where exactly one exists, e.g. site settings — flagged `singleton: true` in describe_cms). No id needed; the canonical row is resolved (and lazily created empty on first access). Use this instead of get_document for singletons.',
|
|
428
|
-
|
|
498
|
+
mutates: false,
|
|
499
|
+
requiredCapabilities: ['document.read'],
|
|
500
|
+
execution: 'server',
|
|
501
|
+
inputSchema: z.object({
|
|
429
502
|
collection: z.string().describe('Singleton collection name'),
|
|
430
503
|
perspective: z.enum(['draft', 'published']).optional()
|
|
431
|
-
}
|
|
432
|
-
handler: async (args) => {
|
|
433
|
-
const collection = asString(args, 'collection');
|
|
434
|
-
if (!collection)
|
|
435
|
-
return fail('Missing required string argument: collection');
|
|
436
|
-
const col = api.getCollection(collection);
|
|
437
|
-
if (!col)
|
|
438
|
-
return fail(`Unknown collection: ${collection}`);
|
|
439
|
-
try {
|
|
440
|
-
const doc = await col.get(context, { perspective: perspectiveArg(args.perspective) });
|
|
441
|
-
return ok(doc);
|
|
442
|
-
}
|
|
443
|
-
catch (err) {
|
|
444
|
-
// SingletonOperationError when the type isn't a singleton — surface its message.
|
|
445
|
-
return fail(err instanceof Error ? err.message : String(err));
|
|
446
|
-
}
|
|
447
|
-
}
|
|
504
|
+
})
|
|
448
505
|
},
|
|
449
|
-
{
|
|
506
|
+
execute: async (args, { aphexCMS, context }) => {
|
|
507
|
+
const api = aphexCMS.localAPI;
|
|
508
|
+
const collection = asString(args, 'collection');
|
|
509
|
+
if (!collection)
|
|
510
|
+
return fail('Missing required string argument: collection');
|
|
511
|
+
const col = api.getCollection(collection);
|
|
512
|
+
if (!col)
|
|
513
|
+
return fail(`Unknown collection: ${collection}`);
|
|
514
|
+
try {
|
|
515
|
+
const doc = await col.get(context, { perspective: perspectiveArg(args.perspective) });
|
|
516
|
+
return ok(doc);
|
|
517
|
+
}
|
|
518
|
+
catch (err) {
|
|
519
|
+
// SingletonOperationError when the type isn't a singleton — surface its message.
|
|
520
|
+
return fail(err instanceof Error ? err.message : String(err));
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
definition: {
|
|
450
526
|
name: 'update_singleton',
|
|
451
527
|
description: 'Update a singleton document (e.g. site settings). No id needed — the canonical row is resolved by type. Include only the fields to change in `data`. Set publish:true to publish the result. Use this instead of update_document for singletons.',
|
|
452
|
-
|
|
528
|
+
mutates: true,
|
|
529
|
+
requiredCapabilities: ['document.update'],
|
|
530
|
+
execution: 'server',
|
|
531
|
+
inputSchema: z.object({
|
|
453
532
|
collection: z.string().describe('Singleton collection name'),
|
|
454
533
|
data: z.record(z.string(), z.unknown()).describe('Partial field values to update'),
|
|
455
534
|
publish: z.boolean().optional().describe('Publish after updating (default false)')
|
|
456
|
-
}
|
|
457
|
-
handler: async (args) => {
|
|
458
|
-
const collection = asString(args, 'collection');
|
|
459
|
-
const data = asRecord(args, 'data');
|
|
460
|
-
if (!collection || !data)
|
|
461
|
-
return fail('Missing required arguments: collection (string), data (object)');
|
|
462
|
-
const col = api.getCollection(collection);
|
|
463
|
-
if (!col)
|
|
464
|
-
return fail(`Unknown collection: ${collection}`);
|
|
465
|
-
const id = col.getSingletonId(context);
|
|
466
|
-
if (!id)
|
|
467
|
-
return fail(`'${collection}' is not a singleton. Use update_document instead.`);
|
|
468
|
-
try {
|
|
469
|
-
// Ensure the canonical row exists (get lazily creates it), then update.
|
|
470
|
-
await col.get(context);
|
|
471
|
-
const result = await col.update(context, id, data, { publish: args.publish === true });
|
|
472
|
-
if (!result)
|
|
473
|
-
return fail(`Failed to update singleton '${collection}'.`);
|
|
474
|
-
return ok(result);
|
|
475
|
-
}
|
|
476
|
-
catch (err) {
|
|
477
|
-
return fail(`Update failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
535
|
+
})
|
|
480
536
|
},
|
|
481
|
-
{
|
|
537
|
+
execute: async (args, { aphexCMS, context }) => {
|
|
538
|
+
const api = aphexCMS.localAPI;
|
|
539
|
+
const collection = asString(args, 'collection');
|
|
540
|
+
const data = asRecord(args, 'data');
|
|
541
|
+
if (!collection || !data)
|
|
542
|
+
return fail('Missing required arguments: collection (string), data (object)');
|
|
543
|
+
const col = api.getCollection(collection);
|
|
544
|
+
if (!col)
|
|
545
|
+
return fail(`Unknown collection: ${collection}`);
|
|
546
|
+
const id = col.getSingletonId(context);
|
|
547
|
+
if (!id)
|
|
548
|
+
return fail(`'${collection}' is not a singleton. Use update_document instead.`);
|
|
549
|
+
try {
|
|
550
|
+
// Ensure the canonical row exists (get lazily creates it), then update.
|
|
551
|
+
await col.get(context);
|
|
552
|
+
const result = await col.update(context, id, data, { publish: args.publish === true });
|
|
553
|
+
if (!result)
|
|
554
|
+
return fail(`Failed to update singleton '${collection}'.`);
|
|
555
|
+
return ok(result);
|
|
556
|
+
}
|
|
557
|
+
catch (err) {
|
|
558
|
+
return fail(`Update failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
definition: {
|
|
482
564
|
name: 'list_assets',
|
|
483
565
|
description: 'List media assets (images and files) in this organization, optionally filtered.',
|
|
484
|
-
|
|
566
|
+
mutates: false,
|
|
567
|
+
requiredCapabilities: ['asset.read'],
|
|
568
|
+
execution: 'server',
|
|
569
|
+
inputSchema: z.object({
|
|
485
570
|
search: z.string().optional().describe('Filter by filename/text'),
|
|
486
571
|
assetType: z.enum(['image', 'file']).optional(),
|
|
487
572
|
limit: z.number().optional(),
|
|
488
573
|
offset: z.number().optional()
|
|
489
|
-
}
|
|
490
|
-
handler: async (args) => {
|
|
491
|
-
const search = asString(args, 'search') ?? undefined;
|
|
492
|
-
const assetType = args.assetType === 'image' || args.assetType === 'file' ? args.assetType : undefined;
|
|
493
|
-
const limit = typeof args.limit === 'number' ? args.limit : undefined;
|
|
494
|
-
const offset = typeof args.offset === 'number' ? args.offset : undefined;
|
|
495
|
-
try {
|
|
496
|
-
const assets = await assetService.findAssets(orgId, { search, assetType, limit, offset });
|
|
497
|
-
return ok({ assets, count: assets.length });
|
|
498
|
-
}
|
|
499
|
-
catch (err) {
|
|
500
|
-
return fail(`List assets failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
574
|
+
})
|
|
503
575
|
},
|
|
504
|
-
{
|
|
576
|
+
execute: async (args, { aphexCMS, context }) => {
|
|
577
|
+
if (!context.auth || !hasCapability(context.auth, 'asset.read')) {
|
|
578
|
+
return fail("Forbidden: 'asset.read' capability required.");
|
|
579
|
+
}
|
|
580
|
+
const { assetService } = aphexCMS;
|
|
581
|
+
const orgId = context.organizationId;
|
|
582
|
+
const search = asString(args, 'search') ?? undefined;
|
|
583
|
+
const assetType = args.assetType === 'image' || args.assetType === 'file' ? args.assetType : undefined;
|
|
584
|
+
const limit = typeof args.limit === 'number' ? args.limit : undefined;
|
|
585
|
+
const offset = typeof args.offset === 'number' ? args.offset : undefined;
|
|
586
|
+
try {
|
|
587
|
+
const assets = await assetService.findAssets(orgId, { search, assetType, limit, offset });
|
|
588
|
+
return ok({ assets, count: assets.length });
|
|
589
|
+
}
|
|
590
|
+
catch (err) {
|
|
591
|
+
return fail(`List assets failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
definition: {
|
|
505
597
|
name: 'upload_asset',
|
|
506
|
-
description: 'Upload an image or file
|
|
507
|
-
'
|
|
508
|
-
'
|
|
509
|
-
'
|
|
510
|
-
'
|
|
511
|
-
|
|
512
|
-
|
|
598
|
+
description: 'Upload an image or file and get back a ready-to-reference value. Provide either ' +
|
|
599
|
+
'`data` (base64 file contents) or `url` (fetched server-side — use this for an image ' +
|
|
600
|
+
'found via search or a link you were given, not `data`, since you cannot produce raw ' +
|
|
601
|
+
'file bytes yourself). The response includes `imageValue` and `fileValue` — drop the ' +
|
|
602
|
+
'matching one straight into a document field (e.g. a blog post `coverImage`, an author ' +
|
|
603
|
+
'`avatar`, or an inline `image` block) via update_document. File type is verified from ' +
|
|
604
|
+
'the actual bytes, not the declared name or URL.',
|
|
605
|
+
mutates: true,
|
|
606
|
+
requiredCapabilities: ['asset.upload'],
|
|
607
|
+
execution: 'server',
|
|
608
|
+
inputSchema: z.object({
|
|
609
|
+
data: z
|
|
610
|
+
.string()
|
|
611
|
+
.min(1)
|
|
612
|
+
.optional()
|
|
613
|
+
.describe('Base64-encoded file contents (no data: URI prefix).'),
|
|
614
|
+
url: z
|
|
615
|
+
.string()
|
|
616
|
+
.url()
|
|
617
|
+
.optional()
|
|
618
|
+
.describe('An http(s) URL to fetch the file from. Provide exactly one of `data`/`url`.'),
|
|
513
619
|
filename: z
|
|
514
620
|
.string()
|
|
515
621
|
.min(1)
|
|
516
|
-
.
|
|
622
|
+
.optional()
|
|
623
|
+
.describe('Original filename, e.g. "cover.png". Its extension helps typing. Required with ' +
|
|
624
|
+
'`data`; derived from the URL when omitted with `url`.'),
|
|
517
625
|
mimeType: z
|
|
518
626
|
.string()
|
|
519
627
|
.optional()
|
|
@@ -521,53 +629,129 @@ export function buildContentTools({ aphexCMS, context }) {
|
|
|
521
629
|
alt: z.string().optional().describe('Default alt text, shared across every placement.'),
|
|
522
630
|
title: z.string().optional(),
|
|
523
631
|
description: z.string().optional()
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
632
|
+
})
|
|
633
|
+
},
|
|
634
|
+
execute: async (args, { aphexCMS, context }) => {
|
|
635
|
+
if (!context.auth || !hasCapability(context.auth, 'asset.upload')) {
|
|
636
|
+
return fail("Forbidden: 'asset.upload' capability required.");
|
|
637
|
+
}
|
|
638
|
+
const { assetService } = aphexCMS;
|
|
639
|
+
const orgId = context.organizationId;
|
|
640
|
+
const base64 = asString(args, 'data');
|
|
641
|
+
const url = asString(args, 'url');
|
|
642
|
+
if (!base64 && !url)
|
|
643
|
+
return fail("Provide either 'data' or 'url'.");
|
|
644
|
+
if (base64 && url)
|
|
645
|
+
return fail("Provide only one of 'data' or 'url', not both.");
|
|
646
|
+
let buffer;
|
|
647
|
+
let sniffedMime = null;
|
|
648
|
+
let filename = asString(args, 'filename');
|
|
649
|
+
if (url) {
|
|
533
650
|
try {
|
|
534
|
-
|
|
651
|
+
const remote = await fetchRemoteFile(url);
|
|
652
|
+
buffer = remote.buffer;
|
|
653
|
+
sniffedMime = remote.contentType;
|
|
535
654
|
}
|
|
536
|
-
catch {
|
|
537
|
-
return fail(
|
|
655
|
+
catch (err) {
|
|
656
|
+
return fail(`Fetching 'url' failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
538
657
|
}
|
|
539
|
-
if (
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const validation = validateFile(buffer, filename, declaredMime);
|
|
543
|
-
if (!validation.valid) {
|
|
544
|
-
return fail(`Upload rejected: ${validation.error ?? 'file failed validation.'}`);
|
|
658
|
+
if (!filename) {
|
|
659
|
+
const last = new URL(url).pathname.split('/').filter(Boolean).pop();
|
|
660
|
+
filename = last && last.includes('.') ? last : 'upload';
|
|
545
661
|
}
|
|
546
|
-
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
547
664
|
try {
|
|
548
|
-
|
|
549
|
-
buffer,
|
|
550
|
-
originalFilename: filename,
|
|
551
|
-
mimeType,
|
|
552
|
-
size: buffer.length,
|
|
553
|
-
alt: asString(args, 'alt') ?? undefined,
|
|
554
|
-
title: asString(args, 'title') ?? undefined,
|
|
555
|
-
description: asString(args, 'description') ?? undefined,
|
|
556
|
-
createdBy: context.user?.id
|
|
557
|
-
});
|
|
558
|
-
const ref = { _type: 'reference', _ref: asset.id };
|
|
559
|
-
return ok({
|
|
560
|
-
asset,
|
|
561
|
-
// Referenceable field values — use the one matching the target field's type.
|
|
562
|
-
imageValue: { _type: 'image', asset: ref },
|
|
563
|
-
fileValue: { _type: 'file', asset: ref }
|
|
564
|
-
});
|
|
665
|
+
buffer = Buffer.from(base64, 'base64');
|
|
565
666
|
}
|
|
566
|
-
catch
|
|
567
|
-
return fail(
|
|
667
|
+
catch {
|
|
668
|
+
return fail("'data' is not valid base64.");
|
|
568
669
|
}
|
|
569
670
|
}
|
|
671
|
+
if (!filename)
|
|
672
|
+
return fail("'filename' is required.");
|
|
673
|
+
if (buffer.length === 0)
|
|
674
|
+
return fail('File contents decoded to zero bytes.');
|
|
675
|
+
const declaredMime = asString(args, 'mimeType') ?? sniffedMime ?? '';
|
|
676
|
+
const validation = validateFile(buffer, filename, declaredMime);
|
|
677
|
+
if (!validation.valid) {
|
|
678
|
+
return fail(`Upload rejected: ${validation.error ?? 'file failed validation.'}`);
|
|
679
|
+
}
|
|
680
|
+
const mimeType = validation.detectedMimeType || declaredMime || 'application/octet-stream';
|
|
681
|
+
try {
|
|
682
|
+
const asset = await assetService.uploadAsset(orgId, {
|
|
683
|
+
buffer,
|
|
684
|
+
originalFilename: filename,
|
|
685
|
+
mimeType,
|
|
686
|
+
size: buffer.length,
|
|
687
|
+
alt: asString(args, 'alt') ?? undefined,
|
|
688
|
+
title: asString(args, 'title') ?? undefined,
|
|
689
|
+
description: asString(args, 'description') ?? undefined,
|
|
690
|
+
createdBy: context.user?.id
|
|
691
|
+
});
|
|
692
|
+
const ref = { _type: 'reference', _ref: asset.id };
|
|
693
|
+
return ok({
|
|
694
|
+
asset,
|
|
695
|
+
// Referenceable field values — use the one matching the target field's type.
|
|
696
|
+
imageValue: { _type: 'image', asset: ref },
|
|
697
|
+
fileValue: { _type: 'file', asset: ref }
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
catch (err) {
|
|
701
|
+
return fail(`Upload failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
702
|
+
}
|
|
570
703
|
}
|
|
571
|
-
|
|
704
|
+
}
|
|
705
|
+
];
|
|
706
|
+
function toMcpResult(result) {
|
|
707
|
+
if (result.success) {
|
|
708
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
|
|
709
|
+
}
|
|
710
|
+
return { content: [{ type: 'text', text: result.error ?? 'Tool failed' }], isError: true };
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* The full set of tools this caller can see: core built-ins plus any
|
|
714
|
+
* plugin-contributed `aphex/agent/tool` parts their capabilities unlock
|
|
715
|
+
* (`partResolver.agentToolsForCapabilities`), plus the workspace-bridge tools when
|
|
716
|
+
* `documentContext` is given — the one shared list MCP, the in-admin agent runtime
|
|
717
|
+
* (`ai/run-agent-turn.ts`), and any other future tool-calling transport all resolve from.
|
|
718
|
+
* Core built-ins always win a name collision, since they're the platform's own contract.
|
|
719
|
+
*/
|
|
720
|
+
export function resolveAgentTools({ aphexCMS, context }, opts) {
|
|
721
|
+
const coreNames = new Set(contentAgentTools.map((t) => t.definition.name));
|
|
722
|
+
const callerCapabilities = context.auth ? [...resolveCapabilities(context.auth)] : [];
|
|
723
|
+
const pluginTools = aphexCMS.partResolver
|
|
724
|
+
.agentToolsForCapabilities(callerCapabilities)
|
|
725
|
+
.filter((t) => !coreNames.has(t.definition.name));
|
|
726
|
+
const base = [...contentAgentTools, ...pluginTools];
|
|
727
|
+
if (opts?.documentContext) {
|
|
728
|
+
// `update_document` writes straight to the DB and bypasses the open editor. Its
|
|
729
|
+
// description already told the model to prefer the workspace tools here, but a prompt
|
|
730
|
+
// preference is not a guarantee — the model still reached for it (users had to explicitly
|
|
731
|
+
// ask for a refresh). Removing it as a *choice* while a document is bridged is the actual
|
|
732
|
+
// fix: the only path left for editing that document is the one that stays in sync.
|
|
733
|
+
return [
|
|
734
|
+
...base.filter((t) => t.definition.name !== 'update_document'),
|
|
735
|
+
...contentWorkspaceTools
|
|
736
|
+
];
|
|
737
|
+
}
|
|
738
|
+
// Defense in depth: without a live document to bridge into, a `workspace`-execution tool
|
|
739
|
+
// (core or plugin-contributed) must never be advertised — there's nothing on the other
|
|
740
|
+
// end to resolve it, and `run-agent-turn.ts` would otherwise pause a turn forever.
|
|
741
|
+
return base.filter((t) => t.definition.execution !== 'workspace');
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Adapt `resolveAgentTools` into the MCP SDK's expected shape for one authenticated
|
|
745
|
+
* request. All the actual tool logic lives in `contentAgentTools`/plugin parts above —
|
|
746
|
+
* this is purely a transport-shape + result-shape conversion.
|
|
747
|
+
*/
|
|
748
|
+
export function buildContentTools(deps) {
|
|
749
|
+
const { aphexCMS, context } = deps;
|
|
750
|
+
return resolveAgentTools(deps).map(({ definition, execute }) => ({
|
|
751
|
+
name: definition.name,
|
|
752
|
+
description: definition.description,
|
|
753
|
+
inputSchema: definition.inputSchema.shape,
|
|
754
|
+
handler: async (args) => toMcpResult(await execute(args, { aphexCMS, context }))
|
|
755
|
+
}));
|
|
572
756
|
}
|
|
573
757
|
//# sourceMappingURL=tools.js.map
|