@collage-dam/mcp-server
v0.4.1
Published
Model Context Protocol (MCP) server for the Collage Digital Asset Management platform. Exposes Collage workspaces — assets, folders, Collages, share links, audit prompts — to AI clients (Claude Desktop, Cursor, ChatGPT) through a typed tool surface.
Readme
@collage-dam/mcp-server
MCP server for the Collage Digital Asset Management platform. Lets MCP clients (Claude Desktop, Cursor, etc.) read and operate on a Collage workspace through a typed tool surface.
The package name on npm is @collage-dam/mcp-server; the CLI binary it
installs is collage-mcp.
Status: pre-1.0. Phase 1 ships read-only smoke tools. Mutating tools arrive once the dry-run framework spec lands.
Requirements
- Node.js >= 20
- A Collage account with API access (see "Getting credentials" below)
- Redis (only required when running the streamable-HTTP transport —
docker compose up redisis provided)
Search now flows through Collage's Nuxt
/typesense/searchproxy onapp.collage.inc. No separate Typesense credentials are needed — the existing Collage Bearer token is the only auth required.
Quickstart (install from npm)
For end users who just want to run the server:
# One-shot via npx (no install — pulls latest)
COLLAGE_API_KEY=... \
COLLAGE_WORKSPACE_ID=... \
MCP_CONFIRMATION_SECRET=... \
npx @collage-dam/mcp-server
# Or install globally and run as `collage-mcp`
npm install -g @collage-dam/mcp-server
collage-mcpSee Getting credentials below for the env vars, and Wiring into Claude Desktop for client config snippets.
Quickstart (local development)
For contributing to the server or running unreleased code:
git clone https://github.com/southleft/collage-mcp.git
cd collage-mcp
npm install
cp .env.example .env
# fill in COLLAGE_API_KEY, COLLAGE_WORKSPACE_ID, MCP_CONFIRMATION_SECRET
npm run dev # stdio transport against the configured workspaceGetting credentials
COLLAGE_API_KEY
Collage does not have a dedicated "API Keys" UI. The token is the JWT
issued by POST /api/v1/login and stored client-side after a normal web
sign-in. To extract it:
- Sign into
app.collage.incwith a workspace user account. - Open DevTools → Network tab.
- Click any request to
damapi.collage.inc/api/v1/.... - Copy the
Authorizationheader value and strip the leadingBearer. - The remainder is the raw JWT — paste it into
COLLAGE_API_KEY.
Alternative: DevTools → Application → Local Storage →
https://app.collage.inc → auth._token.local (Nuxt-Auth default).
Same value, also prefixed with Bearer .
⚠️ JWT caveat. This token is bound to the signed-in user, not a dedicated service account. It expires when the user's session does and rotates on every fresh login. For long-lived MCP deployments, use a dedicated workspace user whose session you control. The MCP server redacts the token from its own logs; anything that imports your
.envdirectly is on its own — useredactSensitiveFields()if you log request bodies.
COLLAGE_WORKSPACE_ID
The numeric workspace ID from any URL inside the Collage admin —
https://app.collage.inc/<workspace_id>/dam-dashboard. The BREZ demo
workspace is 6307594138.
COLLAGE_SEARCH_BASE (optional)
Search no longer requires Typesense credentials. The MCP server POSTs to
the Nuxt /typesense/search proxy on app.collage.inc, which verifies
the inbound COLLAGE_API_KEY Bearer token via Laravel's /user
endpoint and resolves the workspace scope server-side.
The default base URL is https://app.collage.inc. Override only for
staging or self-hosted Collage deployments where the frontend is on a
different host.
MCP_CONFIRMATION_SECRET
Any high-entropy string (32+ bytes recommended). Used to HMAC-sign dry-run confirmation tokens. Rotate by changing the value; in-flight confirmations are invalidated by design.
openssl rand -hex 32 # generate oneSmoke test (stdio)
After filling .env, run:
npm run devThe server connects over stdio and waits for an MCP client. Logs go to stderr; stdout is reserved for MCP framing.
Wiring into Claude Desktop
Add the following to your Claude Desktop config file (macOS:
~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"collage": {
"command": "npx",
"args": ["-y", "@collage-dam/mcp-server"],
"env": {
"COLLAGE_API_KEY": "...",
"COLLAGE_WORKSPACE_ID": "1927483178",
"MCP_CONFIRMATION_SECRET": "..."
}
}
}
}Restart Claude Desktop, then ask it to call list_workspaces. A
successful response returns a single workspace with has_access: true.
Troubleshooting — "Server disconnected" /
Cannot find module 'node:path'. Claude Desktop does not inherit your interactive shell'sPATH, so thenpxabove resolvesnodefrom whatever the app sees first. If that happens to be a Node older than the required v20,npxcrashes before the server starts (thenode:module scheme didn't exist until Node 14.18/16), and Claude Desktop reports the server as disconnected.Fix: make sure the
nodeon your loginPATHis ≥ 20 (node --version). If you use a version manager (nvm, asdf, volta) and still hit this, point the config at an explicit modern node instead of barenpx:{ "command": "/absolute/path/to/node", "args": ["/absolute/path/to/npm/bin/npx-cli.js", "-y", "@collage-dam/mcp-server"] }— or install globally on a Node ≥ 20 runtime and use
"command": "collage-mcp".
If you've cloned the repo for local development, point Claude Desktop at the built file instead:
{
"mcpServers": {
"collage": {
"command": "node",
"args": ["/absolute/path/to/collage-mcp/dist/index.js"],
"env": { "...": "..." }
}
}
}…or run the TypeScript source directly via tsx:
{
"command": "npx",
"args": ["tsx", "/absolute/path/to/collage-mcp/src/index.ts"]
}Wiring into Cursor
Cursor reads MCP servers from ~/.cursor/mcp.json (or
<project>/.cursor/mcp.json for project-scoped servers). The shape is
identical to Claude Desktop:
{
"mcpServers": {
"collage": {
"command": "npx",
"args": ["-y", "@collage-dam/mcp-server"],
"env": {
"COLLAGE_API_KEY": "...",
"COLLAGE_WORKSPACE_ID": "1927483178",
"MCP_CONFIRMATION_SECRET": "..."
}
}
}
}Restart Cursor after editing. Tools appear under the chat composer.
Wiring into raw stdio (any MCP client)
For any MCP client that spawns a server over stdio, the canonical
command after npm run build:
node /absolute/path/to/collage-mcp/dist/index.jsThe server reads its config from environment variables — set them in the parent shell or via your client's config block. Logs go to stderr (fd 2); stdout is reserved for MCP framing. Anything that writes to stdout in the server process WILL corrupt the protocol.
Wiring via npx (after publish)
Once published to npm:
npx @collage-dam/mcp-serverThe CLI binary is named collage-mcp, so you can also install globally
and invoke directly:
npm install -g @collage-dam/mcp-server
collage-mcpThe same env vars are read from the parent shell.
Invoking guided workflows
Four MCP prompts ship as named guided workflows — they orchestrate the underlying tools and resources end-to-end so a user can trigger a complex flow by name instead of stitching tool calls themselves:
| Prompt | What it does |
| --- | --- |
| library_health_audit | Workspace-wide audit (metadata / tags / structure). Returns a scored report with non-executed mutation proposals. |
| collection_audit | Single-collection deep-dive (with pattern-derived suggestions) or portfolio-wide overview. |
| create_distribution | Conversational "build a share link for an audience" workflow. |
| usage_insights | Engagement summary across share links and assets (degraded MVP slice). |
How to invoke
The MCP protocol exposes prompts via prompts/list + prompts/get, and
the server registers all four correctly. However, current Claude
Desktop builds do not surface MCP prompts in the slash menu or +
attachment menu — a UI gap on the client side. Until Anthropic ships
prompt UI, two fallback paths cover the same workflows:
Path A — start_* tool aliases (recommended for Claude Desktop)
Each prompt has a thin wrapper tool that re-emits the same playbook body through the standard tool surface (which Claude Desktop does surface):
start_library_health_auditstart_collection_auditstart_create_distributionstart_usage_insights
In a fresh Claude Desktop chat: "Run the start_library_health_audit
tool with default arguments." Claude approves, calls the tool, gets the
playbook back, and walks the orchestration. Tool descriptions are
explicitly marked TEMPORARY FALLBACK so the sunset path is clear.
Path B — npm run prompt -- <name> CLI helper
For local QA or copy-paste into any MCP client:
npm run prompt # list available prompts
npm run prompt -- library_health_audit # default args, copy to clipboard (macOS)
npm run prompt -- create_distribution '{"audience":"Q2 dealer kit"}'
npm run prompt -- usage_insights --no-copy # print onlyOutput goes to stdout. On macOS the playbook is copied to the system
clipboard via pbcopy — paste into a fresh chat to seed the workflow.
Both paths emit the same playbook body; the underlying prompt is the source of truth in either case.
Available surface (Phase 1)
The server exposes three MCP primitives: tools (actions), resources (read-only URI-addressable state), and prompts (guided workflows).
Tools (30)
Read tools: list_workspaces, list_collages, get_collage,
get_asset_details, list_custom_fields, list_folders,
view_folder_contents, list_share_links, get_embed_code,
search_assets, search_collages, audit_tagging_hygiene.
Mutating tools (each gated by the dry-run framework — see Mutation
safety below): rename_asset, update_asset_metadata, bulk_set_tags,
bulk_add_tags, bulk_remove_tags, create_collage,
update_collage, add_to_collage, remove_from_collage,
create_share_link, revoke_share_link, create_folder,
rename_folder, move_asset.
Prompt tool-aliases (UI fallback — see Invoking guided workflows
above): start_library_health_audit, start_collection_audit,
start_create_distribution, start_usage_insights.
Resources (10)
collage://folders, collage://folders/{id}, collage://portals,
collage://portals/{id}, collage://assets/recent,
collage://dashboard, collage://assets/{id}, collage://collections,
collage://collections/{id}, collage://custom-fields. All read-only
and JSON-shaped, with cursor pagination and the shared 12K-token
response budget applied to list shapes.
The folder tree resource walks sub-category-list recursively under
the hood — upstream all-category-list returns roots only by design,
so the resource flattens the full tree with parent_id per row.
Prompts (4)
library_health_audit, collection_audit, create_distribution,
usage_insights. See the Invoking guided workflows section above.
More tools, resources, and prompts land as the remaining specs ship — see the project portal for the live roadmap.
Mutation safety
Every mutating tool (rename_asset, update_asset_metadata, bulk_*_tags,
create_collection, add_to_collection, create_share_link, create_folder,
rename_folder, move_asset, etc.) is gated by the dry-run framework:
- Plan. First call returns a structured preview — change list, risk
flags, aggregates, plus a signed
confirmation_token. - Execute. Caller echoes the token back to actually perform the mutation. Tokens are single-use and HMAC-signed.
Confirmation token TTL
The token has two lifetimes:
| Window | Default | Behaviour |
| --- | --- | --- |
| 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. |
| 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. |
Per-tool overrides go through confirmationTtlMs on the MutatingTool
constructor — useful for very-long-running review workflows.
Auto-refresh
When an execute call arrives after the soft expiry but the plan record is still in the memo store, the framework:
- Re-runs
plan(input)with the same input the caller supplied - Computes a stable fingerprint of the new change list (sha256 over a
canonicalised
[id, operation, before, after]projection — sorted by id, tool-internalmetadataexcluded) - Compares against the prior plan's fingerprint
- Match → emits a
dry_run.auto_refreshlog entry and continues execute on the prior plan (intent unchanged; no caller action needed) - Mismatch → returns
CONFIRMATION_STATE_DIVERGEDwith both plan summaries inerror.cause.prior_plananderror.cause.new_plan. The calling LLM should re-run the preview and re-confirm with the user.
This means a normal preview-then-think-then-confirm pause is forgiving up to one hour, provided the underlying workspace state hasn't changed under the user. If someone else mutates the same asset/folder/collection in the gap, the divergence error catches it — no silent overwrite.
Verified endpoints
The complete list of upstream endpoints we have observed working against a
real workspace lives in docs/verified-endpoints.md
plus the much fuller reference doc at
docs/api-field-verification.md (~22
endpoints with Admin-Frontend source citations and contract-test
references). Anything not in those files is unproven — wrappers should
only be written after a contract test passes against the live API.
Known limitations
Search facets are not currently surfaced
The Nuxt /typesense/search proxy that fronts Collage's Typesense node
strips the facet_by parameter before forwarding to Typesense. Effect
on search_assets:
facets.tags,facets.file_type, andfacets.date_histogramalways return empty arrays.facets.score_distributionis hit-derived (synthesised from returned hits) and still works.next_step_hintsdegrades gracefully — theuntagged_assets_foundhint logic falls back to per-hitcountUntagged()and is independent of facet counts.
A proxy-side fix is requested upstream. The MCP surface is otherwise fully functional.
Three resources are blocked on upstream endpoint design
collage://tags— Collage has no workspace-wide tag-listing endpoint; the closest (get-common-tag-list) returns the intersection of tags across a given asset list, not all tags. Awaiting Backend-API change.collage://analytics—analytics/summaryis per-asset only, not a workspace overview. Awaiting Backend-API change or scope reframe.collage://portals/{id}/users— endpoint TBD.
The MCP server registers 10 of the 13 specced resources; the three above will ship once upstream lands.
Thumbnail indexing race condition (search side)
When an asset is uploaded, its thumbnail URL may not appear in the
Typesense index for up to ~24 hours after upload. Search hits returned
during that window may have thumbnail_file: null or a stale value.
This is upstream behaviour — the MCP server passes through whatever
the index returns.
If you need a reliable thumbnail URL for a freshly-uploaded asset,
fetch the asset directly via get_asset_details (POST view-detail)
which reads from the live database rather than the search index.
Prompts UI not yet surfaced in Claude Desktop
MCP defines a prompts/list + prompts/get primitive that this server
implements correctly. Current Claude Desktop builds do not yet surface
prompts in their slash menu or attachment UI.
Two fallback paths cover the same workflows in the meantime — see the
Invoking guided workflows section above. Both surface tool-aliases
(start_*) that re-emit the same playbook body through the standard
tool surface, which Claude Desktop does render.
Confirmation token TTLs
Mutation safety uses two TTL windows (soft expiry 15 min, memo expiry
60 min). If the calling LLM pauses for human review longer than the
soft expiry, the framework auto-refreshes the plan when execute
arrives — but only if the underlying workspace state hasn't changed.
If another mutation has touched the same target in the gap, you'll
get CONFIRMATION_STATE_DIVERGED with both plan summaries in
error.cause for the LLM to reconcile. See Mutation safety above
for the full lifecycle.
Streamable-HTTP transport requires Redis
The default stdio transport uses an in-memory state store and runs
without external dependencies. The streamable-HTTP transport is
multi-client capable but requires Redis for the dry-run plan store.
Use docker compose up redis for local development; for production
deployments, see docs/deployment-runbook.md
for state-store provisioning guidance.
Running under streamable HTTP
# 1. Start local Redis (compose file lives at the repo root).
docker compose up -d redis
# 2. Export env and pick the transport. CLI flag wins over MCP_TRANSPORT.
export COLLAGE_API_KEY=...
export COLLAGE_WORKSPACE_ID=...
export MCP_CONFIRMATION_SECRET=...
export REDIS_URL=redis://127.0.0.1:6379
# Optional overrides — defaults shown.
export MCP_HTTP_HOST=127.0.0.1
export MCP_HTTP_PORT=3333
npm run dev -- --transport=http
# or, equivalently:
MCP_TRANSPORT=http npm run devThe server logs collage-mcp ready (streamable-http) to stderr on
success. From another shell you can poke initialize directly:
curl -sS -X POST http://127.0.0.1:3333/ \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {"name": "curl", "version": "0"}
}
}'If REDIS_URL is unset while MCP_TRANSPORT=http, startup fails with
a descriptive validation error pointing at this section.
Project layout
src/
index.ts MCP server entrypoint (stdio)
client.ts Collage REST HTTP client (Bearer JWT, rate limited)
typesense.ts Typesense Cloud direct client + admin-key guard
types.ts Phase-1 zod schemas (asset, category, collection, custom field)
conventions/ Shared infra: env, errors, logger, pagination,
rate-limiter, response-budget, state store, etc.
tools/ One file per registered MCP tool
tests/
unit/ Fast, hermetic. Run on every commit.
contract/ Hits live API. Gated by INTEGRATION=1.
integration/ End-to-end MCP transport tests (planned).
scripts/
check-schema-version.ts Schema-version drift guard.
docs/
verified-endpoints.md Source of truth for which upstream endpoints work.Scripts
npm run dev # stdio transport, hot-reload via tsx watch
npm run build # tsc → dist/
npm start # run the built server
npm test # unit tests (fast, hermetic)
npm run test:integration # contract tests (live API; needs INTEGRATION=1 + env)
npm run lint # tsc --noEmitHandoff
See docs/developer-handoff.md for the architecture map, capability surface, documentation index, open Collage-side dependencies, and the handoff sequence — the orientation doc for taking ownership.
Contributing
See CONTRIBUTING.md for the development workflow, testing conventions, and how to add a new tool / wrapper / contract test.
