@collage-dam/mcp-server 0.1.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/.env.example +56 -0
- package/CHANGELOG.md +90 -0
- package/LICENSE +21 -0
- package/README.md +512 -0
- package/dist/client.d.ts +497 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +1162 -0
- package/dist/client.js.map +1 -0
- package/dist/conventions/confirmation.d.ts +89 -0
- package/dist/conventions/confirmation.d.ts.map +1 -0
- package/dist/conventions/confirmation.js +132 -0
- package/dist/conventions/confirmation.js.map +1 -0
- package/dist/conventions/dry-run/batch-executor.d.ts +36 -0
- package/dist/conventions/dry-run/batch-executor.d.ts.map +1 -0
- package/dist/conventions/dry-run/batch-executor.js +89 -0
- package/dist/conventions/dry-run/batch-executor.js.map +1 -0
- package/dist/conventions/dry-run/diff-renderer.d.ts +34 -0
- package/dist/conventions/dry-run/diff-renderer.d.ts.map +1 -0
- package/dist/conventions/dry-run/diff-renderer.js +158 -0
- package/dist/conventions/dry-run/diff-renderer.js.map +1 -0
- package/dist/conventions/dry-run/index.d.ts +13 -0
- package/dist/conventions/dry-run/index.d.ts.map +1 -0
- package/dist/conventions/dry-run/index.js +10 -0
- package/dist/conventions/dry-run/index.js.map +1 -0
- package/dist/conventions/dry-run/mutating-tool.d.ts +64 -0
- package/dist/conventions/dry-run/mutating-tool.d.ts.map +1 -0
- package/dist/conventions/dry-run/mutating-tool.js +88 -0
- package/dist/conventions/dry-run/mutating-tool.js.map +1 -0
- package/dist/conventions/dry-run/summary.d.ts +66 -0
- package/dist/conventions/dry-run/summary.d.ts.map +1 -0
- package/dist/conventions/dry-run/summary.js +185 -0
- package/dist/conventions/dry-run/summary.js.map +1 -0
- package/dist/conventions/dry-run/types.d.ts +597 -0
- package/dist/conventions/dry-run/types.d.ts.map +1 -0
- package/dist/conventions/dry-run/types.js +108 -0
- package/dist/conventions/dry-run/types.js.map +1 -0
- package/dist/conventions/dry-run/with-dry-run.d.ts +66 -0
- package/dist/conventions/dry-run/with-dry-run.d.ts.map +1 -0
- package/dist/conventions/dry-run/with-dry-run.js +219 -0
- package/dist/conventions/dry-run/with-dry-run.js.map +1 -0
- package/dist/conventions/env.d.ts +49 -0
- package/dist/conventions/env.d.ts.map +1 -0
- package/dist/conventions/env.js +84 -0
- package/dist/conventions/env.js.map +1 -0
- package/dist/conventions/errors.d.ts +68 -0
- package/dist/conventions/errors.d.ts.map +1 -0
- package/dist/conventions/errors.js +81 -0
- package/dist/conventions/errors.js.map +1 -0
- package/dist/conventions/logger.d.ts +28 -0
- package/dist/conventions/logger.d.ts.map +1 -0
- package/dist/conventions/logger.js +105 -0
- package/dist/conventions/logger.js.map +1 -0
- package/dist/conventions/pagination.d.ts +37 -0
- package/dist/conventions/pagination.d.ts.map +1 -0
- package/dist/conventions/pagination.js +53 -0
- package/dist/conventions/pagination.js.map +1 -0
- package/dist/conventions/rate-limiter.d.ts +54 -0
- package/dist/conventions/rate-limiter.d.ts.map +1 -0
- package/dist/conventions/rate-limiter.js +143 -0
- package/dist/conventions/rate-limiter.js.map +1 -0
- package/dist/conventions/response-budget.d.ts +66 -0
- package/dist/conventions/response-budget.d.ts.map +1 -0
- package/dist/conventions/response-budget.js +89 -0
- package/dist/conventions/response-budget.js.map +1 -0
- package/dist/conventions/schema-version.d.ts +27 -0
- package/dist/conventions/schema-version.d.ts.map +1 -0
- package/dist/conventions/schema-version.js +29 -0
- package/dist/conventions/schema-version.js.map +1 -0
- package/dist/conventions/state-store-redis.d.ts +32 -0
- package/dist/conventions/state-store-redis.d.ts.map +1 -0
- package/dist/conventions/state-store-redis.js +77 -0
- package/dist/conventions/state-store-redis.js.map +1 -0
- package/dist/conventions/state-store.d.ts +46 -0
- package/dist/conventions/state-store.d.ts.map +1 -0
- package/dist/conventions/state-store.js +105 -0
- package/dist/conventions/state-store.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +421 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/collection-audit.d.ts +13 -0
- package/dist/prompts/collection-audit.d.ts.map +1 -0
- package/dist/prompts/collection-audit.js +168 -0
- package/dist/prompts/collection-audit.js.map +1 -0
- package/dist/prompts/create-distribution.d.ts +15 -0
- package/dist/prompts/create-distribution.d.ts.map +1 -0
- package/dist/prompts/create-distribution.js +111 -0
- package/dist/prompts/create-distribution.js.map +1 -0
- package/dist/prompts/helpers.d.ts +20 -0
- package/dist/prompts/helpers.d.ts.map +1 -0
- package/dist/prompts/helpers.js +53 -0
- package/dist/prompts/helpers.js.map +1 -0
- package/dist/prompts/library-health-audit.d.ts +13 -0
- package/dist/prompts/library-health-audit.d.ts.map +1 -0
- package/dist/prompts/library-health-audit.js +131 -0
- package/dist/prompts/library-health-audit.js.map +1 -0
- package/dist/prompts/usage-insights.d.ts +13 -0
- package/dist/prompts/usage-insights.d.ts.map +1 -0
- package/dist/prompts/usage-insights.js +98 -0
- package/dist/prompts/usage-insights.js.map +1 -0
- package/dist/prompts/wrap-prompt-as-tool.d.ts +48 -0
- package/dist/prompts/wrap-prompt-as-tool.d.ts.map +1 -0
- package/dist/prompts/wrap-prompt-as-tool.js +61 -0
- package/dist/prompts/wrap-prompt-as-tool.js.map +1 -0
- package/dist/resources/asset-by-id.d.ts +4 -0
- package/dist/resources/asset-by-id.d.ts.map +1 -0
- package/dist/resources/asset-by-id.js +27 -0
- package/dist/resources/asset-by-id.js.map +1 -0
- package/dist/resources/collections.d.ts +5 -0
- package/dist/resources/collections.d.ts.map +1 -0
- package/dist/resources/collections.js +48 -0
- package/dist/resources/collections.js.map +1 -0
- package/dist/resources/custom-fields.d.ts +4 -0
- package/dist/resources/custom-fields.d.ts.map +1 -0
- package/dist/resources/custom-fields.js +30 -0
- package/dist/resources/custom-fields.js.map +1 -0
- package/dist/resources/folders.d.ts +5 -0
- package/dist/resources/folders.d.ts.map +1 -0
- package/dist/resources/folders.js +73 -0
- package/dist/resources/folders.js.map +1 -0
- package/dist/resources/helpers.d.ts +17 -0
- package/dist/resources/helpers.d.ts.map +1 -0
- package/dist/resources/helpers.js +59 -0
- package/dist/resources/helpers.js.map +1 -0
- package/dist/resources/portals.d.ts +5 -0
- package/dist/resources/portals.d.ts.map +1 -0
- package/dist/resources/portals.js +81 -0
- package/dist/resources/portals.js.map +1 -0
- package/dist/resources/recent-and-dashboard.d.ts +5 -0
- package/dist/resources/recent-and-dashboard.d.ts.map +1 -0
- package/dist/resources/recent-and-dashboard.js +42 -0
- package/dist/resources/recent-and-dashboard.js.map +1 -0
- package/dist/tools/asset-selection.d.ts +102 -0
- package/dist/tools/asset-selection.d.ts.map +1 -0
- package/dist/tools/asset-selection.js +133 -0
- package/dist/tools/asset-selection.js.map +1 -0
- package/dist/tools/audit/audit-folder-structure.d.ts +108 -0
- package/dist/tools/audit/audit-folder-structure.d.ts.map +1 -0
- package/dist/tools/audit/audit-folder-structure.js +260 -0
- package/dist/tools/audit/audit-folder-structure.js.map +1 -0
- package/dist/tools/audit/audit-naming-conventions.d.ts +83 -0
- package/dist/tools/audit/audit-naming-conventions.d.ts.map +1 -0
- package/dist/tools/audit/audit-naming-conventions.js +238 -0
- package/dist/tools/audit/audit-naming-conventions.js.map +1 -0
- package/dist/tools/audit/audit-tagging-hygiene.d.ts +77 -0
- package/dist/tools/audit/audit-tagging-hygiene.d.ts.map +1 -0
- package/dist/tools/audit/audit-tagging-hygiene.js +402 -0
- package/dist/tools/audit/audit-tagging-hygiene.js.map +1 -0
- package/dist/tools/audit/detect-duplicates.d.ts +62 -0
- package/dist/tools/audit/detect-duplicates.d.ts.map +1 -0
- package/dist/tools/audit/detect-duplicates.js +0 -0
- package/dist/tools/audit/detect-duplicates.js.map +1 -0
- package/dist/tools/audit/types.d.ts +526 -0
- package/dist/tools/audit/types.d.ts.map +1 -0
- package/dist/tools/audit/types.js +188 -0
- package/dist/tools/audit/types.js.map +1 -0
- package/dist/tools/bulk-move-assets.d.ts +78 -0
- package/dist/tools/bulk-move-assets.d.ts.map +1 -0
- package/dist/tools/bulk-move-assets.js +122 -0
- package/dist/tools/bulk-move-assets.js.map +1 -0
- package/dist/tools/bulk-normalize-filenames.d.ts +62 -0
- package/dist/tools/bulk-normalize-filenames.d.ts.map +1 -0
- package/dist/tools/bulk-normalize-filenames.js +237 -0
- package/dist/tools/bulk-normalize-filenames.js.map +1 -0
- package/dist/tools/bulk-rename-assets.d.ts +79 -0
- package/dist/tools/bulk-rename-assets.d.ts.map +1 -0
- package/dist/tools/bulk-rename-assets.js +139 -0
- package/dist/tools/bulk-rename-assets.js.map +1 -0
- package/dist/tools/bulk-tags.d.ts +107 -0
- package/dist/tools/bulk-tags.d.ts.map +1 -0
- package/dist/tools/bulk-tags.js +220 -0
- package/dist/tools/bulk-tags.js.map +1 -0
- package/dist/tools/client-adapters.d.ts +76 -0
- package/dist/tools/client-adapters.d.ts.map +1 -0
- package/dist/tools/client-adapters.js +648 -0
- package/dist/tools/client-adapters.js.map +1 -0
- package/dist/tools/collection-membership.d.ts +90 -0
- package/dist/tools/collection-membership.d.ts.map +1 -0
- package/dist/tools/collection-membership.js +195 -0
- package/dist/tools/collection-membership.js.map +1 -0
- package/dist/tools/create-collection.d.ts +63 -0
- package/dist/tools/create-collection.d.ts.map +1 -0
- package/dist/tools/create-collection.js +151 -0
- package/dist/tools/create-collection.js.map +1 -0
- package/dist/tools/create-folder.d.ts +46 -0
- package/dist/tools/create-folder.d.ts.map +1 -0
- package/dist/tools/create-folder.js +83 -0
- package/dist/tools/create-folder.js.map +1 -0
- package/dist/tools/create-share-link.d.ts +107 -0
- package/dist/tools/create-share-link.d.ts.map +1 -0
- package/dist/tools/create-share-link.js +239 -0
- package/dist/tools/create-share-link.js.map +1 -0
- package/dist/tools/get-asset-details.d.ts +401 -0
- package/dist/tools/get-asset-details.d.ts.map +1 -0
- package/dist/tools/get-asset-details.js +56 -0
- package/dist/tools/get-asset-details.js.map +1 -0
- package/dist/tools/get-collection.d.ts +126 -0
- package/dist/tools/get-collection.d.ts.map +1 -0
- package/dist/tools/get-collection.js +52 -0
- package/dist/tools/get-collection.js.map +1 -0
- package/dist/tools/get-embed-code.d.ts +195 -0
- package/dist/tools/get-embed-code.d.ts.map +1 -0
- package/dist/tools/get-embed-code.js +214 -0
- package/dist/tools/get-embed-code.js.map +1 -0
- package/dist/tools/insights/analyze-share-links.d.ts +159 -0
- package/dist/tools/insights/analyze-share-links.d.ts.map +1 -0
- package/dist/tools/insights/analyze-share-links.js +314 -0
- package/dist/tools/insights/analyze-share-links.js.map +1 -0
- package/dist/tools/insights/insight-cache.d.ts +36 -0
- package/dist/tools/insights/insight-cache.d.ts.map +1 -0
- package/dist/tools/insights/insight-cache.js +98 -0
- package/dist/tools/insights/insight-cache.js.map +1 -0
- package/dist/tools/insights/report-asset-activation.d.ts +149 -0
- package/dist/tools/insights/report-asset-activation.d.ts.map +1 -0
- package/dist/tools/insights/report-asset-activation.js +380 -0
- package/dist/tools/insights/report-asset-activation.js.map +1 -0
- package/dist/tools/insights/report-stale-assets.d.ts +120 -0
- package/dist/tools/insights/report-stale-assets.d.ts.map +1 -0
- package/dist/tools/insights/report-stale-assets.js +281 -0
- package/dist/tools/insights/report-stale-assets.js.map +1 -0
- package/dist/tools/insights/report-top-assets.d.ts +139 -0
- package/dist/tools/insights/report-top-assets.d.ts.map +1 -0
- package/dist/tools/insights/report-top-assets.js +407 -0
- package/dist/tools/insights/report-top-assets.js.map +1 -0
- package/dist/tools/list-categories.d.ts +127 -0
- package/dist/tools/list-categories.d.ts.map +1 -0
- package/dist/tools/list-categories.js +68 -0
- package/dist/tools/list-categories.js.map +1 -0
- package/dist/tools/list-collections.d.ts +127 -0
- package/dist/tools/list-collections.d.ts.map +1 -0
- package/dist/tools/list-collections.js +53 -0
- package/dist/tools/list-collections.js.map +1 -0
- package/dist/tools/list-custom-fields.d.ts +125 -0
- package/dist/tools/list-custom-fields.d.ts.map +1 -0
- package/dist/tools/list-custom-fields.js +51 -0
- package/dist/tools/list-custom-fields.js.map +1 -0
- package/dist/tools/list-share-links.d.ts +192 -0
- package/dist/tools/list-share-links.d.ts.map +1 -0
- package/dist/tools/list-share-links.js +92 -0
- package/dist/tools/list-share-links.js.map +1 -0
- package/dist/tools/list-workspaces.d.ts +88 -0
- package/dist/tools/list-workspaces.d.ts.map +1 -0
- package/dist/tools/list-workspaces.js +71 -0
- package/dist/tools/list-workspaces.js.map +1 -0
- package/dist/tools/move-asset.d.ts +48 -0
- package/dist/tools/move-asset.d.ts.map +1 -0
- package/dist/tools/move-asset.js +85 -0
- package/dist/tools/move-asset.js.map +1 -0
- package/dist/tools/rename-asset.d.ts +88 -0
- package/dist/tools/rename-asset.d.ts.map +1 -0
- package/dist/tools/rename-asset.js +100 -0
- package/dist/tools/rename-asset.js.map +1 -0
- package/dist/tools/rename-folder.d.ts +55 -0
- package/dist/tools/rename-folder.d.ts.map +1 -0
- package/dist/tools/rename-folder.js +101 -0
- package/dist/tools/rename-folder.js.map +1 -0
- package/dist/tools/revoke-share-link.d.ts +55 -0
- package/dist/tools/revoke-share-link.d.ts.map +1 -0
- package/dist/tools/revoke-share-link.js +77 -0
- package/dist/tools/revoke-share-link.js.map +1 -0
- package/dist/tools/search/facets.d.ts +34 -0
- package/dist/tools/search/facets.d.ts.map +1 -0
- package/dist/tools/search/facets.js +147 -0
- package/dist/tools/search/facets.js.map +1 -0
- package/dist/tools/search/filter-builder.d.ts +33 -0
- package/dist/tools/search/filter-builder.d.ts.map +1 -0
- package/dist/tools/search/filter-builder.js +111 -0
- package/dist/tools/search/filter-builder.js.map +1 -0
- package/dist/tools/search/search-assets.d.ts +41 -0
- package/dist/tools/search/search-assets.d.ts.map +1 -0
- package/dist/tools/search/search-assets.js +162 -0
- package/dist/tools/search/search-assets.js.map +1 -0
- package/dist/tools/search/search-collections.d.ts +35 -0
- package/dist/tools/search/search-collections.d.ts.map +1 -0
- package/dist/tools/search/search-collections.js +103 -0
- package/dist/tools/search/search-collections.js.map +1 -0
- package/dist/tools/search/types.d.ts +1047 -0
- package/dist/tools/search/types.d.ts.map +1 -0
- package/dist/tools/search/types.js +216 -0
- package/dist/tools/search/types.js.map +1 -0
- package/dist/tools/update-asset-metadata.d.ts +78 -0
- package/dist/tools/update-asset-metadata.d.ts.map +1 -0
- package/dist/tools/update-asset-metadata.js +203 -0
- package/dist/tools/update-asset-metadata.js.map +1 -0
- package/dist/tools/update-collection.d.ts +69 -0
- package/dist/tools/update-collection.d.ts.map +1 -0
- package/dist/tools/update-collection.js +142 -0
- package/dist/tools/update-collection.js.map +1 -0
- package/dist/tools/view-category-contents.d.ts +231 -0
- package/dist/tools/view-category-contents.d.ts.map +1 -0
- package/dist/tools/view-category-contents.js +97 -0
- package/dist/tools/view-category-contents.js.map +1 -0
- package/dist/types.d.ts +1326 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +288 -0
- package/dist/types.js.map +1 -0
- package/dist/typesense.d.ts +84 -0
- package/dist/typesense.d.ts.map +1 -0
- package/dist/typesense.js +243 -0
- package/dist/typesense.js.map +1 -0
- package/docs/api-field-verification.md +244 -0
- package/docs/deployment-runbook.md +446 -0
- package/docs/security-review.md +195 -0
- package/docs/typesense-filter-schema.md +262 -0
- package/docs/verified-endpoints.md +38 -0
- package/package.json +72 -0
package/.env.example
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Collage MCP Server — Environment Variables
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# Copy this file to .env and fill in the values.
|
|
5
|
+
# See README.md and docs/deployment-runbook.md for details on obtaining each
|
|
6
|
+
# credential and tuning the optional settings.
|
|
7
|
+
|
|
8
|
+
# -----------------------------------------------------------------------------
|
|
9
|
+
# Required
|
|
10
|
+
# -----------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
# JWT Bearer token for the Collage REST API. Extract from the Authorization
|
|
13
|
+
# header on any damapi.collage.inc request after signing into app.collage.inc.
|
|
14
|
+
# See README.md → "Getting credentials" for the step-by-step. The token is
|
|
15
|
+
# bound to the signed-in user's session — for production deployments, use a
|
|
16
|
+
# dedicated service-account user whose session you control.
|
|
17
|
+
COLLAGE_API_KEY=
|
|
18
|
+
|
|
19
|
+
# Full numeric workspace ID (the value that appears in app.collage.inc URLs
|
|
20
|
+
# after the host: /<workspace_id>/dam-dashboard). Example: 1927483178.
|
|
21
|
+
COLLAGE_WORKSPACE_ID=
|
|
22
|
+
|
|
23
|
+
# HMAC secret used to sign dry-run confirmation tokens. Any high-entropy
|
|
24
|
+
# string (32+ bytes recommended). Generate with: openssl rand -hex 32
|
|
25
|
+
# Rotating this value invalidates every in-flight confirmation token by
|
|
26
|
+
# design — that's the intended way to revoke stale plans.
|
|
27
|
+
MCP_CONFIRMATION_SECRET=
|
|
28
|
+
|
|
29
|
+
# -----------------------------------------------------------------------------
|
|
30
|
+
# Optional — overrides for staging / self-hosted setups
|
|
31
|
+
# -----------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
# Base URL for the Collage REST API (Laravel backend).
|
|
34
|
+
COLLAGE_API_URL=https://damapi.collage.inc/api/v1/
|
|
35
|
+
|
|
36
|
+
# Base URL for the Collage search proxy (Nuxt /typesense/search middleware).
|
|
37
|
+
# This is a different host than COLLAGE_API_URL — search lives on the
|
|
38
|
+
# frontend deploy, not the Laravel API. The default is correct for production.
|
|
39
|
+
# Override only for staging or if Collage shifts the proxy host.
|
|
40
|
+
COLLAGE_SEARCH_BASE=https://app.collage.inc
|
|
41
|
+
|
|
42
|
+
# Search request timeout in milliseconds (default 15000).
|
|
43
|
+
COLLAGE_SEARCH_TIMEOUT_MS=15000
|
|
44
|
+
|
|
45
|
+
# Maximum requests per second to the Collage REST API. Token-bucket
|
|
46
|
+
# rate-limited at this rate per server process; 429s are retried with backoff.
|
|
47
|
+
COLLAGE_MAX_RPS=10
|
|
48
|
+
|
|
49
|
+
# Redis connection string. Required only when running the streamable-HTTP
|
|
50
|
+
# transport (the stdio transport uses an in-memory state store). Locally:
|
|
51
|
+
# `docker compose up redis` provides a redis:7-alpine instance on this URL.
|
|
52
|
+
REDIS_URL=redis://localhost:6379
|
|
53
|
+
|
|
54
|
+
# Logging verbosity: debug | info | warn | error
|
|
55
|
+
# pino logs go to stderr; stdout is reserved for MCP framing.
|
|
56
|
+
LOG_LEVEL=info
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `listAllCategoriesRecursive()` — full folder tree walk via BFS over
|
|
12
|
+
`sub-category-list`, bounded by `maxNodes` / `maxDepth`. Closes the
|
|
13
|
+
gap where audits and resource readers missed nested-folder content
|
|
14
|
+
because upstream `all-category-list` returns roots only.
|
|
15
|
+
- Four new MCP resources: `collage://assets/{id}`, `collage://collections`,
|
|
16
|
+
`collage://collections/{id}`, `collage://custom-fields`. Resource
|
|
17
|
+
surface is now 10 of 13 specced.
|
|
18
|
+
- `tests/contract/search.contract.test.ts` — live BREZ coverage for the
|
|
19
|
+
Nuxt search proxy (happy path + UNAUTHORIZED branch).
|
|
20
|
+
- `tests/unit/resources/new-resources.test.ts` — unit coverage for the
|
|
21
|
+
four new resource readers.
|
|
22
|
+
- `docs/api-field-verification.md` (canonical reference for ~22
|
|
23
|
+
endpoints with Admin-Frontend citations and contract-test references).
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- **Search transport**: dropped the official `typesense` npm client.
|
|
27
|
+
`src/typesense.ts` now POSTs to the Nuxt `/typesense/search` proxy on
|
|
28
|
+
`app.collage.inc` using the existing Collage Bearer token. Workspace
|
|
29
|
+
scoping is enforced server-side; no Typesense Cloud credentials
|
|
30
|
+
required. See `docs/deployment-runbook.md` for env changes.
|
|
31
|
+
- **Env**: `TYPESENSE_API_KEY` and `TYPESENSE_HOST` are no longer
|
|
32
|
+
required. New optional `COLLAGE_SEARCH_BASE` (default
|
|
33
|
+
`https://app.collage.inc`).
|
|
34
|
+
- The public `list_categories` tool now returns the full tree, not just
|
|
35
|
+
roots — uses the recursive walk under the hood.
|
|
36
|
+
- `enumerateAssets()`, `buildFolderByIdReader`, `buildFoldersListReader`,
|
|
37
|
+
and `getCurrentFolderName` migrated to the recursive walk.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
- `library_health_audit` no longer misses assets in nested folders.
|
|
41
|
+
- `collage://folders/{id}` no longer returns NOT_FOUND for nested
|
|
42
|
+
folder ids.
|
|
43
|
+
- `getCurrentFolderName` no longer returns `null` for nested folders
|
|
44
|
+
during rename flows.
|
|
45
|
+
- `search_assets` with `projection: "verbose"` now returns hydrated
|
|
46
|
+
hits. `VerboseAssetHitSchema` typed `breadcrumb` as `z.string()`, but
|
|
47
|
+
the `digital_assets` index returns it as a string array — every
|
|
48
|
+
verbose row failed validation and was silently dropped by
|
|
49
|
+
`projectHits()`, surfacing as `total>0` with `hits: []`. Schema now
|
|
50
|
+
accepts either shape; regression test added. (F#5)
|
|
51
|
+
|
|
52
|
+
### Known limitations
|
|
53
|
+
- The Nuxt search proxy strips `facet_by` before forwarding to
|
|
54
|
+
Typesense; `facets.tags`, `facets.file_type`, and
|
|
55
|
+
`facets.date_histogram` always return empty arrays. `next_step_hints`
|
|
56
|
+
fall back to per-hit `countUntagged()`. Tracked separately for
|
|
57
|
+
upstream proxy fix.
|
|
58
|
+
- Three resources (`collage://tags`, `collage://analytics`,
|
|
59
|
+
`collage://portals/{id}/users`) are blocked on upstream endpoint
|
|
60
|
+
design issues — none are workspace-wide / fit the resource shape.
|
|
61
|
+
|
|
62
|
+
## [0.1.0] — Initial pre-release (2026-04-14 → 2026-05-04)
|
|
63
|
+
|
|
64
|
+
First Phase 1 release. Surface:
|
|
65
|
+
|
|
66
|
+
- **30 tools** — read (`list_workspaces`, `list_collections`,
|
|
67
|
+
`get_asset_details`, `list_categories`, `view_category_contents`,
|
|
68
|
+
`list_share_links`, `get_embed_code`, `search_assets`,
|
|
69
|
+
`search_collections`, `audit_tagging_hygiene`, `list_custom_fields`,
|
|
70
|
+
`get_collection`) and write (`rename_asset`, `update_asset_metadata`,
|
|
71
|
+
`bulk_set_tags`, `bulk_add_tags`, `bulk_remove_tags`,
|
|
72
|
+
`create_collection`, `update_collection`, `add_to_collection`,
|
|
73
|
+
`remove_from_collection`, `create_share_link`, `revoke_share_link`,
|
|
74
|
+
`create_folder`, `rename_folder`, `move_asset`). Plus four
|
|
75
|
+
prompt-aliases (`start_*`).
|
|
76
|
+
- **10 resources** — `collage://folders` (+ `{id}`),
|
|
77
|
+
`collage://portals` (+ `{id}`), `collage://assets/recent`,
|
|
78
|
+
`collage://dashboard`, `collage://assets/{id}`,
|
|
79
|
+
`collage://collections` (+ `{id}`), `collage://custom-fields`.
|
|
80
|
+
- **4 prompts** — `library_health_audit`, `collection_audit`,
|
|
81
|
+
`create_distribution`, `usage_insights`. Each guides the LLM through
|
|
82
|
+
a multi-step playbook against the underlying tools/resources.
|
|
83
|
+
- **Mutation safety** — every write tool gated by the dry-run
|
|
84
|
+
framework: HMAC-signed confirmation tokens, per-tool TTL, soft-expiry
|
|
85
|
+
auto-refresh on stable plan fingerprints, divergence detection on
|
|
86
|
+
conflicting mutations.
|
|
87
|
+
- **Transport** — stdio (production) and streamable-HTTP with Redis
|
|
88
|
+
state store (dev / multi-client).
|
|
89
|
+
- **Test coverage** — 518 unit + 34 contract tests against BREZ
|
|
90
|
+
workspace `1927483178`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Collage, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
# @collage-dam/mcp-server
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@collage-dam/mcp-server)
|
|
4
|
+
|
|
5
|
+
MCP server for the [Collage](https://collage.inc) Digital Asset Management
|
|
6
|
+
platform. Lets MCP clients (Claude Desktop, Cursor, etc.) read and operate
|
|
7
|
+
on a Collage workspace through a typed tool surface.
|
|
8
|
+
|
|
9
|
+
The package name on npm is `@collage-dam/mcp-server`; the CLI binary it
|
|
10
|
+
installs is `collage-mcp`.
|
|
11
|
+
|
|
12
|
+
> **Status:** pre-1.0. Phase 1 ships read-only smoke tools. Mutating tools
|
|
13
|
+
> arrive once the dry-run framework spec lands.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
- Node.js >= 20
|
|
20
|
+
- A Collage account with API access (see "Getting credentials" below)
|
|
21
|
+
- Redis (only required when running the streamable-HTTP transport — `docker compose up redis` is provided)
|
|
22
|
+
|
|
23
|
+
> Search now flows through Collage's Nuxt `/typesense/search` proxy on
|
|
24
|
+
> `app.collage.inc`. **No separate Typesense credentials are needed** —
|
|
25
|
+
> the existing Collage Bearer token is the only auth required.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Quickstart (install from npm)
|
|
30
|
+
|
|
31
|
+
For end users who just want to run the server:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# One-shot via npx (no install — pulls latest)
|
|
35
|
+
COLLAGE_API_KEY=... \
|
|
36
|
+
COLLAGE_WORKSPACE_ID=... \
|
|
37
|
+
MCP_CONFIRMATION_SECRET=... \
|
|
38
|
+
npx @collage-dam/mcp-server
|
|
39
|
+
|
|
40
|
+
# Or install globally and run as `collage-mcp`
|
|
41
|
+
npm install -g @collage-dam/mcp-server
|
|
42
|
+
collage-mcp
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
See [Getting credentials](#getting-credentials) below for the env vars,
|
|
46
|
+
and [Wiring into Claude Desktop](#wiring-into-claude-desktop) for client
|
|
47
|
+
config snippets.
|
|
48
|
+
|
|
49
|
+
## Quickstart (local development)
|
|
50
|
+
|
|
51
|
+
For contributing to the server or running unreleased code:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
git clone https://github.com/southleft/collage-mcp.git
|
|
55
|
+
cd collage-mcp
|
|
56
|
+
npm install
|
|
57
|
+
cp .env.example .env
|
|
58
|
+
# fill in COLLAGE_API_KEY, COLLAGE_WORKSPACE_ID, MCP_CONFIRMATION_SECRET
|
|
59
|
+
npm run dev # stdio transport against the configured workspace
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Getting credentials
|
|
65
|
+
|
|
66
|
+
### `COLLAGE_API_KEY`
|
|
67
|
+
|
|
68
|
+
Collage does **not** have a dedicated "API Keys" UI. The token is the JWT
|
|
69
|
+
issued by `POST /api/v1/login` and stored client-side after a normal web
|
|
70
|
+
sign-in. To extract it:
|
|
71
|
+
|
|
72
|
+
1. Sign into `app.collage.inc` with a workspace user account.
|
|
73
|
+
2. Open DevTools → **Network** tab.
|
|
74
|
+
3. Click any request to `damapi.collage.inc/api/v1/...`.
|
|
75
|
+
4. Copy the `Authorization` header value and strip the leading `Bearer `.
|
|
76
|
+
5. The remainder is the raw JWT — paste it into `COLLAGE_API_KEY`.
|
|
77
|
+
|
|
78
|
+
Alternative: DevTools → **Application** → Local Storage →
|
|
79
|
+
`https://app.collage.inc` → `auth._token.local` (Nuxt-Auth default).
|
|
80
|
+
Same value, also prefixed with `Bearer `.
|
|
81
|
+
|
|
82
|
+
> ⚠️ **JWT caveat.** This token is bound to the signed-in user, not a
|
|
83
|
+
> dedicated service account. It expires when the user's session does and
|
|
84
|
+
> rotates on every fresh login. For long-lived MCP deployments, use a
|
|
85
|
+
> dedicated workspace user whose session you control. The MCP server
|
|
86
|
+
> redacts the token from its own logs; anything that imports your `.env`
|
|
87
|
+
> directly is on its own — use `redactSensitiveFields()` if you log
|
|
88
|
+
> request bodies.
|
|
89
|
+
|
|
90
|
+
### `COLLAGE_WORKSPACE_ID`
|
|
91
|
+
|
|
92
|
+
The numeric workspace ID from any URL inside the Collage admin —
|
|
93
|
+
`https://app.collage.inc/<workspace_id>/dam-dashboard`. The BREZ demo
|
|
94
|
+
workspace is `6307594138`.
|
|
95
|
+
|
|
96
|
+
### `COLLAGE_SEARCH_BASE` (optional)
|
|
97
|
+
|
|
98
|
+
Search no longer requires Typesense credentials. The MCP server POSTs to
|
|
99
|
+
the Nuxt `/typesense/search` proxy on `app.collage.inc`, which verifies
|
|
100
|
+
the inbound `COLLAGE_API_KEY` Bearer token via Laravel's `/user`
|
|
101
|
+
endpoint and resolves the workspace scope server-side.
|
|
102
|
+
|
|
103
|
+
The default base URL is `https://app.collage.inc`. Override only for
|
|
104
|
+
staging or self-hosted Collage deployments where the frontend is on a
|
|
105
|
+
different host.
|
|
106
|
+
|
|
107
|
+
### `MCP_CONFIRMATION_SECRET`
|
|
108
|
+
|
|
109
|
+
Any high-entropy string (32+ bytes recommended). Used to HMAC-sign
|
|
110
|
+
dry-run confirmation tokens. Rotate by changing the value; in-flight
|
|
111
|
+
confirmations are invalidated by design.
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
openssl rand -hex 32 # generate one
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Smoke test (stdio)
|
|
120
|
+
|
|
121
|
+
After filling `.env`, run:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
npm run dev
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The server connects over stdio and waits for an MCP client. Logs go to
|
|
128
|
+
stderr; stdout is reserved for MCP framing.
|
|
129
|
+
|
|
130
|
+
### Wiring into Claude Desktop
|
|
131
|
+
|
|
132
|
+
Add the following to your Claude Desktop config file (macOS:
|
|
133
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"mcpServers": {
|
|
138
|
+
"collage": {
|
|
139
|
+
"command": "npx",
|
|
140
|
+
"args": ["-y", "@collage-dam/mcp-server"],
|
|
141
|
+
"env": {
|
|
142
|
+
"COLLAGE_API_KEY": "...",
|
|
143
|
+
"COLLAGE_WORKSPACE_ID": "1927483178",
|
|
144
|
+
"MCP_CONFIRMATION_SECRET": "..."
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Restart Claude Desktop, then ask it to call `list_workspaces`. A
|
|
152
|
+
successful response returns a single workspace with `has_access: true`.
|
|
153
|
+
|
|
154
|
+
If you've cloned the repo for local development, point Claude Desktop at
|
|
155
|
+
the built file instead:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"mcpServers": {
|
|
160
|
+
"collage": {
|
|
161
|
+
"command": "node",
|
|
162
|
+
"args": ["/absolute/path/to/collage-mcp/dist/index.js"],
|
|
163
|
+
"env": { "...": "..." }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
…or run the TypeScript source directly via `tsx`:
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"command": "npx",
|
|
174
|
+
"args": ["tsx", "/absolute/path/to/collage-mcp/src/index.ts"]
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Wiring into Cursor
|
|
179
|
+
|
|
180
|
+
Cursor reads MCP servers from `~/.cursor/mcp.json` (or
|
|
181
|
+
`<project>/.cursor/mcp.json` for project-scoped servers). The shape is
|
|
182
|
+
identical to Claude Desktop:
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"mcpServers": {
|
|
187
|
+
"collage": {
|
|
188
|
+
"command": "npx",
|
|
189
|
+
"args": ["-y", "@collage-dam/mcp-server"],
|
|
190
|
+
"env": {
|
|
191
|
+
"COLLAGE_API_KEY": "...",
|
|
192
|
+
"COLLAGE_WORKSPACE_ID": "1927483178",
|
|
193
|
+
"MCP_CONFIRMATION_SECRET": "..."
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Restart Cursor after editing. Tools appear under the chat composer.
|
|
201
|
+
|
|
202
|
+
### Wiring into raw stdio (any MCP client)
|
|
203
|
+
|
|
204
|
+
For any MCP client that spawns a server over stdio, the canonical
|
|
205
|
+
command after `npm run build`:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
node /absolute/path/to/collage-mcp/dist/index.js
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The server reads its config from environment variables — set them in
|
|
212
|
+
the parent shell or via your client's config block. Logs go to stderr
|
|
213
|
+
(fd 2); stdout is reserved for MCP framing. Anything that writes to
|
|
214
|
+
stdout in the server process WILL corrupt the protocol.
|
|
215
|
+
|
|
216
|
+
### Wiring via `npx` (after publish)
|
|
217
|
+
|
|
218
|
+
Once published to npm:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
npx @collage-dam/mcp-server
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The CLI binary is named `collage-mcp`, so you can also install globally
|
|
225
|
+
and invoke directly:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
npm install -g @collage-dam/mcp-server
|
|
229
|
+
collage-mcp
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The same env vars are read from the parent shell.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Invoking guided workflows
|
|
237
|
+
|
|
238
|
+
Four MCP **prompts** ship as named guided workflows — they orchestrate
|
|
239
|
+
the underlying tools and resources end-to-end so a user can trigger a
|
|
240
|
+
complex flow by name instead of stitching tool calls themselves:
|
|
241
|
+
|
|
242
|
+
| Prompt | What it does |
|
|
243
|
+
| --- | --- |
|
|
244
|
+
| `library_health_audit` | Workspace-wide audit (metadata / tags / structure). Returns a scored report with non-executed mutation proposals. |
|
|
245
|
+
| `collection_audit` | Single-collection deep-dive (with pattern-derived suggestions) or portfolio-wide overview. |
|
|
246
|
+
| `create_distribution` | Conversational "build a share link for an audience" workflow. |
|
|
247
|
+
| `usage_insights` | Engagement summary across share links and assets (degraded MVP slice). |
|
|
248
|
+
|
|
249
|
+
### How to invoke
|
|
250
|
+
|
|
251
|
+
The MCP protocol exposes prompts via `prompts/list` + `prompts/get`, and
|
|
252
|
+
the server registers all four correctly. **However, current Claude
|
|
253
|
+
Desktop builds do not surface MCP prompts in the slash menu or `+`
|
|
254
|
+
attachment menu** — a UI gap on the client side. Until Anthropic ships
|
|
255
|
+
prompt UI, two fallback paths cover the same workflows:
|
|
256
|
+
|
|
257
|
+
#### Path A — `start_*` tool aliases (recommended for Claude Desktop)
|
|
258
|
+
|
|
259
|
+
Each prompt has a thin wrapper tool that re-emits the same playbook body
|
|
260
|
+
through the standard tool surface (which Claude Desktop *does* surface):
|
|
261
|
+
|
|
262
|
+
- `start_library_health_audit`
|
|
263
|
+
- `start_collection_audit`
|
|
264
|
+
- `start_create_distribution`
|
|
265
|
+
- `start_usage_insights`
|
|
266
|
+
|
|
267
|
+
In a fresh Claude Desktop chat: *"Run the start_library_health_audit
|
|
268
|
+
tool with default arguments."* Claude approves, calls the tool, gets the
|
|
269
|
+
playbook back, and walks the orchestration. Tool descriptions are
|
|
270
|
+
explicitly marked `TEMPORARY FALLBACK` so the sunset path is clear.
|
|
271
|
+
|
|
272
|
+
#### Path B — `npm run prompt -- <name>` CLI helper
|
|
273
|
+
|
|
274
|
+
For local QA or copy-paste into any MCP client:
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
npm run prompt # list available prompts
|
|
278
|
+
npm run prompt -- library_health_audit # default args, copy to clipboard (macOS)
|
|
279
|
+
npm run prompt -- create_distribution '{"audience":"Q2 dealer kit"}'
|
|
280
|
+
npm run prompt -- usage_insights --no-copy # print only
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Output goes to stdout. On macOS the playbook is copied to the system
|
|
284
|
+
clipboard via `pbcopy` — paste into a fresh chat to seed the workflow.
|
|
285
|
+
|
|
286
|
+
Both paths emit the same playbook body; the underlying prompt is the
|
|
287
|
+
source of truth in either case.
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Available surface (Phase 1)
|
|
292
|
+
|
|
293
|
+
The server exposes **three MCP primitives**: tools (actions), resources
|
|
294
|
+
(read-only URI-addressable state), and prompts (guided workflows).
|
|
295
|
+
|
|
296
|
+
### Tools (30)
|
|
297
|
+
|
|
298
|
+
Read tools: `list_workspaces`, `list_collections`, `get_collection`,
|
|
299
|
+
`get_asset_details`, `list_custom_fields`, `list_categories`,
|
|
300
|
+
`view_category_contents`, `list_share_links`, `get_embed_code`,
|
|
301
|
+
`search_assets`, `search_collections`, `audit_tagging_hygiene`.
|
|
302
|
+
|
|
303
|
+
Mutating tools (each gated by the dry-run framework — see *Mutation
|
|
304
|
+
safety* below): `rename_asset`, `update_asset_metadata`, `bulk_set_tags`,
|
|
305
|
+
`bulk_add_tags`, `bulk_remove_tags`, `create_collection`,
|
|
306
|
+
`update_collection`, `add_to_collection`, `remove_from_collection`,
|
|
307
|
+
`create_share_link`, `revoke_share_link`, `create_folder`,
|
|
308
|
+
`rename_folder`, `move_asset`.
|
|
309
|
+
|
|
310
|
+
Prompt tool-aliases (UI fallback — see *Invoking guided workflows*
|
|
311
|
+
above): `start_library_health_audit`, `start_collection_audit`,
|
|
312
|
+
`start_create_distribution`, `start_usage_insights`.
|
|
313
|
+
|
|
314
|
+
### Resources (10)
|
|
315
|
+
|
|
316
|
+
`collage://folders`, `collage://folders/{id}`, `collage://portals`,
|
|
317
|
+
`collage://portals/{id}`, `collage://assets/recent`,
|
|
318
|
+
`collage://dashboard`, `collage://assets/{id}`, `collage://collections`,
|
|
319
|
+
`collage://collections/{id}`, `collage://custom-fields`. All read-only
|
|
320
|
+
and JSON-shaped, with cursor pagination and the shared 12K-token
|
|
321
|
+
response budget applied to list shapes.
|
|
322
|
+
|
|
323
|
+
The folder tree resource walks `sub-category-list` recursively under
|
|
324
|
+
the hood — upstream `all-category-list` returns roots only by design,
|
|
325
|
+
so the resource flattens the full tree with `parent_id` per row.
|
|
326
|
+
|
|
327
|
+
### Prompts (4)
|
|
328
|
+
|
|
329
|
+
`library_health_audit`, `collection_audit`, `create_distribution`,
|
|
330
|
+
`usage_insights`. See the *Invoking guided workflows* section above.
|
|
331
|
+
|
|
332
|
+
More tools, resources, and prompts land as the remaining specs ship —
|
|
333
|
+
see the project portal for the live roadmap.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Mutation safety
|
|
338
|
+
|
|
339
|
+
Every mutating tool (`rename_asset`, `update_asset_metadata`, `bulk_*_tags`,
|
|
340
|
+
`create_collection`, `add_to_collection`, `create_share_link`, `create_folder`,
|
|
341
|
+
`rename_folder`, `move_asset`, etc.) is gated by the **dry-run framework**:
|
|
342
|
+
|
|
343
|
+
1. **Plan.** First call returns a structured preview — change list, risk
|
|
344
|
+
flags, aggregates, plus a signed `confirmation_token`.
|
|
345
|
+
2. **Execute.** Caller echoes the token back to actually perform the
|
|
346
|
+
mutation. Tokens are single-use and HMAC-signed.
|
|
347
|
+
|
|
348
|
+
### Confirmation token TTL
|
|
349
|
+
|
|
350
|
+
The token has two lifetimes:
|
|
351
|
+
|
|
352
|
+
| Window | Default | Behaviour |
|
|
353
|
+
| --- | --- | --- |
|
|
354
|
+
| **Soft expiry** (`confirmation.ttl_seconds`) | 15 min | Visible to the caller in the preview response. Past this window the framework re-plans before executing — see auto-refresh below. |
|
|
355
|
+
| **Memo expiry** (plan-store TTL) | 60 min | The plan record itself stays in the ephemeral state store this long. Past this window the token is genuinely dead. |
|
|
356
|
+
|
|
357
|
+
Per-tool overrides go through `confirmationTtlMs` on the `MutatingTool`
|
|
358
|
+
constructor — useful for very-long-running review workflows.
|
|
359
|
+
|
|
360
|
+
### Auto-refresh
|
|
361
|
+
|
|
362
|
+
When an execute call arrives after the soft expiry but the plan record is
|
|
363
|
+
still in the memo store, the framework:
|
|
364
|
+
|
|
365
|
+
1. Re-runs `plan(input)` with the same input the caller supplied
|
|
366
|
+
2. Computes a stable fingerprint of the new change list (sha256 over a
|
|
367
|
+
canonicalised `[id, operation, before, after]` projection — sorted by
|
|
368
|
+
id, tool-internal `metadata` excluded)
|
|
369
|
+
3. Compares against the prior plan's fingerprint
|
|
370
|
+
4. **Match** → emits a `dry_run.auto_refresh` log entry and continues
|
|
371
|
+
execute on the prior plan (intent unchanged; no caller action needed)
|
|
372
|
+
5. **Mismatch** → returns `CONFIRMATION_STATE_DIVERGED` with both plan
|
|
373
|
+
summaries in `error.cause.prior_plan` and `error.cause.new_plan`. The
|
|
374
|
+
calling LLM should re-run the preview and re-confirm with the user.
|
|
375
|
+
|
|
376
|
+
This means a normal preview-then-think-then-confirm pause is forgiving up
|
|
377
|
+
to one hour, **provided the underlying workspace state hasn't changed
|
|
378
|
+
under the user**. If someone else mutates the same asset/folder/collection
|
|
379
|
+
in the gap, the divergence error catches it — no silent overwrite.
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## Verified endpoints
|
|
384
|
+
|
|
385
|
+
The complete list of upstream endpoints we have observed working against a
|
|
386
|
+
real workspace lives in [`docs/verified-endpoints.md`](docs/verified-endpoints.md)
|
|
387
|
+
plus the much fuller reference doc at
|
|
388
|
+
[`docs/api-field-verification.md`](docs/api-field-verification.md) (~22
|
|
389
|
+
endpoints with Admin-Frontend source citations and contract-test
|
|
390
|
+
references). Anything not in those files is unproven — wrappers should
|
|
391
|
+
only be written after a contract test passes against the live API.
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Known limitations
|
|
396
|
+
|
|
397
|
+
### Search facets are not currently surfaced
|
|
398
|
+
|
|
399
|
+
The Nuxt `/typesense/search` proxy that fronts Collage's Typesense node
|
|
400
|
+
strips the `facet_by` parameter before forwarding to Typesense. Effect
|
|
401
|
+
on `search_assets`:
|
|
402
|
+
|
|
403
|
+
- `facets.tags`, `facets.file_type`, and `facets.date_histogram`
|
|
404
|
+
always return empty arrays.
|
|
405
|
+
- `facets.score_distribution` is hit-derived (synthesised from returned
|
|
406
|
+
hits) and still works.
|
|
407
|
+
- `next_step_hints` degrades gracefully — the `untagged_assets_found`
|
|
408
|
+
hint logic falls back to per-hit `countUntagged()` and is independent
|
|
409
|
+
of facet counts.
|
|
410
|
+
|
|
411
|
+
A proxy-side fix is requested upstream. The MCP surface is otherwise
|
|
412
|
+
fully functional.
|
|
413
|
+
|
|
414
|
+
### Three resources are blocked on upstream endpoint design
|
|
415
|
+
|
|
416
|
+
- `collage://tags` — Collage has no workspace-wide tag-listing endpoint;
|
|
417
|
+
the closest (`get-common-tag-list`) returns the *intersection* of tags
|
|
418
|
+
across a given asset list, not all tags. Awaiting Backend-API change.
|
|
419
|
+
- `collage://analytics` — `analytics/summary` is per-asset only, not a
|
|
420
|
+
workspace overview. Awaiting Backend-API change or scope reframe.
|
|
421
|
+
- `collage://portals/{id}/users` — endpoint TBD.
|
|
422
|
+
|
|
423
|
+
The MCP server registers 10 of the 13 specced resources; the three
|
|
424
|
+
above will ship once upstream lands.
|
|
425
|
+
|
|
426
|
+
### Thumbnail indexing race condition (search side)
|
|
427
|
+
|
|
428
|
+
When an asset is uploaded, its thumbnail URL may not appear in the
|
|
429
|
+
Typesense index for up to ~24 hours after upload. Search hits returned
|
|
430
|
+
during that window may have `thumbnail_file: null` or a stale value.
|
|
431
|
+
This is upstream behaviour — the MCP server passes through whatever
|
|
432
|
+
the index returns.
|
|
433
|
+
|
|
434
|
+
If you need a reliable thumbnail URL for a freshly-uploaded asset,
|
|
435
|
+
fetch the asset directly via `get_asset_details` (POST `view-detail`)
|
|
436
|
+
which reads from the live database rather than the search index.
|
|
437
|
+
|
|
438
|
+
### Prompts UI not yet surfaced in Claude Desktop
|
|
439
|
+
|
|
440
|
+
MCP defines a `prompts/list` + `prompts/get` primitive that this server
|
|
441
|
+
implements correctly. Current Claude Desktop builds do not yet surface
|
|
442
|
+
prompts in their slash menu or attachment UI.
|
|
443
|
+
|
|
444
|
+
Two fallback paths cover the same workflows in the meantime — see the
|
|
445
|
+
*Invoking guided workflows* section above. Both surface tool-aliases
|
|
446
|
+
(`start_*`) that re-emit the same playbook body through the standard
|
|
447
|
+
tool surface, which Claude Desktop *does* render.
|
|
448
|
+
|
|
449
|
+
### Confirmation token TTLs
|
|
450
|
+
|
|
451
|
+
Mutation safety uses two TTL windows (soft expiry 15 min, memo expiry
|
|
452
|
+
60 min). If the calling LLM pauses for human review longer than the
|
|
453
|
+
soft expiry, the framework auto-refreshes the plan when execute
|
|
454
|
+
arrives — but only if the underlying workspace state hasn't changed.
|
|
455
|
+
If another mutation has touched the same target in the gap, you'll
|
|
456
|
+
get `CONFIRMATION_STATE_DIVERGED` with both plan summaries in
|
|
457
|
+
`error.cause` for the LLM to reconcile. See *Mutation safety* above
|
|
458
|
+
for the full lifecycle.
|
|
459
|
+
|
|
460
|
+
### Streamable-HTTP transport requires Redis
|
|
461
|
+
|
|
462
|
+
The default stdio transport uses an in-memory state store and runs
|
|
463
|
+
without external dependencies. The streamable-HTTP transport is
|
|
464
|
+
multi-client capable but requires Redis for the dry-run plan store.
|
|
465
|
+
Use `docker compose up redis` for local development; for production
|
|
466
|
+
deployments, see [`docs/deployment-runbook.md`](docs/deployment-runbook.md)
|
|
467
|
+
for state-store provisioning guidance.
|
|
468
|
+
|
|
469
|
+
---
|
|
470
|
+
|
|
471
|
+
## Project layout
|
|
472
|
+
|
|
473
|
+
```
|
|
474
|
+
src/
|
|
475
|
+
index.ts MCP server entrypoint (stdio)
|
|
476
|
+
client.ts Collage REST HTTP client (Bearer JWT, rate limited)
|
|
477
|
+
typesense.ts Typesense Cloud direct client + admin-key guard
|
|
478
|
+
types.ts Phase-1 zod schemas (asset, category, collection, custom field)
|
|
479
|
+
conventions/ Shared infra: env, errors, logger, pagination,
|
|
480
|
+
rate-limiter, response-budget, state store, etc.
|
|
481
|
+
tools/ One file per registered MCP tool
|
|
482
|
+
|
|
483
|
+
tests/
|
|
484
|
+
unit/ Fast, hermetic. Run on every commit.
|
|
485
|
+
contract/ Hits live API. Gated by INTEGRATION=1.
|
|
486
|
+
integration/ End-to-end MCP transport tests (planned).
|
|
487
|
+
|
|
488
|
+
scripts/
|
|
489
|
+
check-schema-version.ts Schema-version drift guard.
|
|
490
|
+
docs/
|
|
491
|
+
verified-endpoints.md Source of truth for which upstream endpoints work.
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
## Scripts
|
|
497
|
+
|
|
498
|
+
```
|
|
499
|
+
npm run dev # stdio transport, hot-reload via tsx watch
|
|
500
|
+
npm run build # tsc → dist/
|
|
501
|
+
npm start # run the built server
|
|
502
|
+
npm test # unit tests (fast, hermetic)
|
|
503
|
+
npm run test:integration # contract tests (live API; needs INTEGRATION=1 + env)
|
|
504
|
+
npm run lint # tsc --noEmit
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## Contributing
|
|
510
|
+
|
|
511
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow,
|
|
512
|
+
testing conventions, and how to add a new tool / wrapper / contract test.
|