@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.
Files changed (306) hide show
  1. package/.env.example +56 -0
  2. package/CHANGELOG.md +90 -0
  3. package/LICENSE +21 -0
  4. package/README.md +512 -0
  5. package/dist/client.d.ts +497 -0
  6. package/dist/client.d.ts.map +1 -0
  7. package/dist/client.js +1162 -0
  8. package/dist/client.js.map +1 -0
  9. package/dist/conventions/confirmation.d.ts +89 -0
  10. package/dist/conventions/confirmation.d.ts.map +1 -0
  11. package/dist/conventions/confirmation.js +132 -0
  12. package/dist/conventions/confirmation.js.map +1 -0
  13. package/dist/conventions/dry-run/batch-executor.d.ts +36 -0
  14. package/dist/conventions/dry-run/batch-executor.d.ts.map +1 -0
  15. package/dist/conventions/dry-run/batch-executor.js +89 -0
  16. package/dist/conventions/dry-run/batch-executor.js.map +1 -0
  17. package/dist/conventions/dry-run/diff-renderer.d.ts +34 -0
  18. package/dist/conventions/dry-run/diff-renderer.d.ts.map +1 -0
  19. package/dist/conventions/dry-run/diff-renderer.js +158 -0
  20. package/dist/conventions/dry-run/diff-renderer.js.map +1 -0
  21. package/dist/conventions/dry-run/index.d.ts +13 -0
  22. package/dist/conventions/dry-run/index.d.ts.map +1 -0
  23. package/dist/conventions/dry-run/index.js +10 -0
  24. package/dist/conventions/dry-run/index.js.map +1 -0
  25. package/dist/conventions/dry-run/mutating-tool.d.ts +64 -0
  26. package/dist/conventions/dry-run/mutating-tool.d.ts.map +1 -0
  27. package/dist/conventions/dry-run/mutating-tool.js +88 -0
  28. package/dist/conventions/dry-run/mutating-tool.js.map +1 -0
  29. package/dist/conventions/dry-run/summary.d.ts +66 -0
  30. package/dist/conventions/dry-run/summary.d.ts.map +1 -0
  31. package/dist/conventions/dry-run/summary.js +185 -0
  32. package/dist/conventions/dry-run/summary.js.map +1 -0
  33. package/dist/conventions/dry-run/types.d.ts +597 -0
  34. package/dist/conventions/dry-run/types.d.ts.map +1 -0
  35. package/dist/conventions/dry-run/types.js +108 -0
  36. package/dist/conventions/dry-run/types.js.map +1 -0
  37. package/dist/conventions/dry-run/with-dry-run.d.ts +66 -0
  38. package/dist/conventions/dry-run/with-dry-run.d.ts.map +1 -0
  39. package/dist/conventions/dry-run/with-dry-run.js +219 -0
  40. package/dist/conventions/dry-run/with-dry-run.js.map +1 -0
  41. package/dist/conventions/env.d.ts +49 -0
  42. package/dist/conventions/env.d.ts.map +1 -0
  43. package/dist/conventions/env.js +84 -0
  44. package/dist/conventions/env.js.map +1 -0
  45. package/dist/conventions/errors.d.ts +68 -0
  46. package/dist/conventions/errors.d.ts.map +1 -0
  47. package/dist/conventions/errors.js +81 -0
  48. package/dist/conventions/errors.js.map +1 -0
  49. package/dist/conventions/logger.d.ts +28 -0
  50. package/dist/conventions/logger.d.ts.map +1 -0
  51. package/dist/conventions/logger.js +105 -0
  52. package/dist/conventions/logger.js.map +1 -0
  53. package/dist/conventions/pagination.d.ts +37 -0
  54. package/dist/conventions/pagination.d.ts.map +1 -0
  55. package/dist/conventions/pagination.js +53 -0
  56. package/dist/conventions/pagination.js.map +1 -0
  57. package/dist/conventions/rate-limiter.d.ts +54 -0
  58. package/dist/conventions/rate-limiter.d.ts.map +1 -0
  59. package/dist/conventions/rate-limiter.js +143 -0
  60. package/dist/conventions/rate-limiter.js.map +1 -0
  61. package/dist/conventions/response-budget.d.ts +66 -0
  62. package/dist/conventions/response-budget.d.ts.map +1 -0
  63. package/dist/conventions/response-budget.js +89 -0
  64. package/dist/conventions/response-budget.js.map +1 -0
  65. package/dist/conventions/schema-version.d.ts +27 -0
  66. package/dist/conventions/schema-version.d.ts.map +1 -0
  67. package/dist/conventions/schema-version.js +29 -0
  68. package/dist/conventions/schema-version.js.map +1 -0
  69. package/dist/conventions/state-store-redis.d.ts +32 -0
  70. package/dist/conventions/state-store-redis.d.ts.map +1 -0
  71. package/dist/conventions/state-store-redis.js +77 -0
  72. package/dist/conventions/state-store-redis.js.map +1 -0
  73. package/dist/conventions/state-store.d.ts +46 -0
  74. package/dist/conventions/state-store.d.ts.map +1 -0
  75. package/dist/conventions/state-store.js +105 -0
  76. package/dist/conventions/state-store.js.map +1 -0
  77. package/dist/index.d.ts +5 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +421 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/prompts/collection-audit.d.ts +13 -0
  82. package/dist/prompts/collection-audit.d.ts.map +1 -0
  83. package/dist/prompts/collection-audit.js +168 -0
  84. package/dist/prompts/collection-audit.js.map +1 -0
  85. package/dist/prompts/create-distribution.d.ts +15 -0
  86. package/dist/prompts/create-distribution.d.ts.map +1 -0
  87. package/dist/prompts/create-distribution.js +111 -0
  88. package/dist/prompts/create-distribution.js.map +1 -0
  89. package/dist/prompts/helpers.d.ts +20 -0
  90. package/dist/prompts/helpers.d.ts.map +1 -0
  91. package/dist/prompts/helpers.js +53 -0
  92. package/dist/prompts/helpers.js.map +1 -0
  93. package/dist/prompts/library-health-audit.d.ts +13 -0
  94. package/dist/prompts/library-health-audit.d.ts.map +1 -0
  95. package/dist/prompts/library-health-audit.js +131 -0
  96. package/dist/prompts/library-health-audit.js.map +1 -0
  97. package/dist/prompts/usage-insights.d.ts +13 -0
  98. package/dist/prompts/usage-insights.d.ts.map +1 -0
  99. package/dist/prompts/usage-insights.js +98 -0
  100. package/dist/prompts/usage-insights.js.map +1 -0
  101. package/dist/prompts/wrap-prompt-as-tool.d.ts +48 -0
  102. package/dist/prompts/wrap-prompt-as-tool.d.ts.map +1 -0
  103. package/dist/prompts/wrap-prompt-as-tool.js +61 -0
  104. package/dist/prompts/wrap-prompt-as-tool.js.map +1 -0
  105. package/dist/resources/asset-by-id.d.ts +4 -0
  106. package/dist/resources/asset-by-id.d.ts.map +1 -0
  107. package/dist/resources/asset-by-id.js +27 -0
  108. package/dist/resources/asset-by-id.js.map +1 -0
  109. package/dist/resources/collections.d.ts +5 -0
  110. package/dist/resources/collections.d.ts.map +1 -0
  111. package/dist/resources/collections.js +48 -0
  112. package/dist/resources/collections.js.map +1 -0
  113. package/dist/resources/custom-fields.d.ts +4 -0
  114. package/dist/resources/custom-fields.d.ts.map +1 -0
  115. package/dist/resources/custom-fields.js +30 -0
  116. package/dist/resources/custom-fields.js.map +1 -0
  117. package/dist/resources/folders.d.ts +5 -0
  118. package/dist/resources/folders.d.ts.map +1 -0
  119. package/dist/resources/folders.js +73 -0
  120. package/dist/resources/folders.js.map +1 -0
  121. package/dist/resources/helpers.d.ts +17 -0
  122. package/dist/resources/helpers.d.ts.map +1 -0
  123. package/dist/resources/helpers.js +59 -0
  124. package/dist/resources/helpers.js.map +1 -0
  125. package/dist/resources/portals.d.ts +5 -0
  126. package/dist/resources/portals.d.ts.map +1 -0
  127. package/dist/resources/portals.js +81 -0
  128. package/dist/resources/portals.js.map +1 -0
  129. package/dist/resources/recent-and-dashboard.d.ts +5 -0
  130. package/dist/resources/recent-and-dashboard.d.ts.map +1 -0
  131. package/dist/resources/recent-and-dashboard.js +42 -0
  132. package/dist/resources/recent-and-dashboard.js.map +1 -0
  133. package/dist/tools/asset-selection.d.ts +102 -0
  134. package/dist/tools/asset-selection.d.ts.map +1 -0
  135. package/dist/tools/asset-selection.js +133 -0
  136. package/dist/tools/asset-selection.js.map +1 -0
  137. package/dist/tools/audit/audit-folder-structure.d.ts +108 -0
  138. package/dist/tools/audit/audit-folder-structure.d.ts.map +1 -0
  139. package/dist/tools/audit/audit-folder-structure.js +260 -0
  140. package/dist/tools/audit/audit-folder-structure.js.map +1 -0
  141. package/dist/tools/audit/audit-naming-conventions.d.ts +83 -0
  142. package/dist/tools/audit/audit-naming-conventions.d.ts.map +1 -0
  143. package/dist/tools/audit/audit-naming-conventions.js +238 -0
  144. package/dist/tools/audit/audit-naming-conventions.js.map +1 -0
  145. package/dist/tools/audit/audit-tagging-hygiene.d.ts +77 -0
  146. package/dist/tools/audit/audit-tagging-hygiene.d.ts.map +1 -0
  147. package/dist/tools/audit/audit-tagging-hygiene.js +402 -0
  148. package/dist/tools/audit/audit-tagging-hygiene.js.map +1 -0
  149. package/dist/tools/audit/detect-duplicates.d.ts +62 -0
  150. package/dist/tools/audit/detect-duplicates.d.ts.map +1 -0
  151. package/dist/tools/audit/detect-duplicates.js +0 -0
  152. package/dist/tools/audit/detect-duplicates.js.map +1 -0
  153. package/dist/tools/audit/types.d.ts +526 -0
  154. package/dist/tools/audit/types.d.ts.map +1 -0
  155. package/dist/tools/audit/types.js +188 -0
  156. package/dist/tools/audit/types.js.map +1 -0
  157. package/dist/tools/bulk-move-assets.d.ts +78 -0
  158. package/dist/tools/bulk-move-assets.d.ts.map +1 -0
  159. package/dist/tools/bulk-move-assets.js +122 -0
  160. package/dist/tools/bulk-move-assets.js.map +1 -0
  161. package/dist/tools/bulk-normalize-filenames.d.ts +62 -0
  162. package/dist/tools/bulk-normalize-filenames.d.ts.map +1 -0
  163. package/dist/tools/bulk-normalize-filenames.js +237 -0
  164. package/dist/tools/bulk-normalize-filenames.js.map +1 -0
  165. package/dist/tools/bulk-rename-assets.d.ts +79 -0
  166. package/dist/tools/bulk-rename-assets.d.ts.map +1 -0
  167. package/dist/tools/bulk-rename-assets.js +139 -0
  168. package/dist/tools/bulk-rename-assets.js.map +1 -0
  169. package/dist/tools/bulk-tags.d.ts +107 -0
  170. package/dist/tools/bulk-tags.d.ts.map +1 -0
  171. package/dist/tools/bulk-tags.js +220 -0
  172. package/dist/tools/bulk-tags.js.map +1 -0
  173. package/dist/tools/client-adapters.d.ts +76 -0
  174. package/dist/tools/client-adapters.d.ts.map +1 -0
  175. package/dist/tools/client-adapters.js +648 -0
  176. package/dist/tools/client-adapters.js.map +1 -0
  177. package/dist/tools/collection-membership.d.ts +90 -0
  178. package/dist/tools/collection-membership.d.ts.map +1 -0
  179. package/dist/tools/collection-membership.js +195 -0
  180. package/dist/tools/collection-membership.js.map +1 -0
  181. package/dist/tools/create-collection.d.ts +63 -0
  182. package/dist/tools/create-collection.d.ts.map +1 -0
  183. package/dist/tools/create-collection.js +151 -0
  184. package/dist/tools/create-collection.js.map +1 -0
  185. package/dist/tools/create-folder.d.ts +46 -0
  186. package/dist/tools/create-folder.d.ts.map +1 -0
  187. package/dist/tools/create-folder.js +83 -0
  188. package/dist/tools/create-folder.js.map +1 -0
  189. package/dist/tools/create-share-link.d.ts +107 -0
  190. package/dist/tools/create-share-link.d.ts.map +1 -0
  191. package/dist/tools/create-share-link.js +239 -0
  192. package/dist/tools/create-share-link.js.map +1 -0
  193. package/dist/tools/get-asset-details.d.ts +401 -0
  194. package/dist/tools/get-asset-details.d.ts.map +1 -0
  195. package/dist/tools/get-asset-details.js +56 -0
  196. package/dist/tools/get-asset-details.js.map +1 -0
  197. package/dist/tools/get-collection.d.ts +126 -0
  198. package/dist/tools/get-collection.d.ts.map +1 -0
  199. package/dist/tools/get-collection.js +52 -0
  200. package/dist/tools/get-collection.js.map +1 -0
  201. package/dist/tools/get-embed-code.d.ts +195 -0
  202. package/dist/tools/get-embed-code.d.ts.map +1 -0
  203. package/dist/tools/get-embed-code.js +214 -0
  204. package/dist/tools/get-embed-code.js.map +1 -0
  205. package/dist/tools/insights/analyze-share-links.d.ts +159 -0
  206. package/dist/tools/insights/analyze-share-links.d.ts.map +1 -0
  207. package/dist/tools/insights/analyze-share-links.js +314 -0
  208. package/dist/tools/insights/analyze-share-links.js.map +1 -0
  209. package/dist/tools/insights/insight-cache.d.ts +36 -0
  210. package/dist/tools/insights/insight-cache.d.ts.map +1 -0
  211. package/dist/tools/insights/insight-cache.js +98 -0
  212. package/dist/tools/insights/insight-cache.js.map +1 -0
  213. package/dist/tools/insights/report-asset-activation.d.ts +149 -0
  214. package/dist/tools/insights/report-asset-activation.d.ts.map +1 -0
  215. package/dist/tools/insights/report-asset-activation.js +380 -0
  216. package/dist/tools/insights/report-asset-activation.js.map +1 -0
  217. package/dist/tools/insights/report-stale-assets.d.ts +120 -0
  218. package/dist/tools/insights/report-stale-assets.d.ts.map +1 -0
  219. package/dist/tools/insights/report-stale-assets.js +281 -0
  220. package/dist/tools/insights/report-stale-assets.js.map +1 -0
  221. package/dist/tools/insights/report-top-assets.d.ts +139 -0
  222. package/dist/tools/insights/report-top-assets.d.ts.map +1 -0
  223. package/dist/tools/insights/report-top-assets.js +407 -0
  224. package/dist/tools/insights/report-top-assets.js.map +1 -0
  225. package/dist/tools/list-categories.d.ts +127 -0
  226. package/dist/tools/list-categories.d.ts.map +1 -0
  227. package/dist/tools/list-categories.js +68 -0
  228. package/dist/tools/list-categories.js.map +1 -0
  229. package/dist/tools/list-collections.d.ts +127 -0
  230. package/dist/tools/list-collections.d.ts.map +1 -0
  231. package/dist/tools/list-collections.js +53 -0
  232. package/dist/tools/list-collections.js.map +1 -0
  233. package/dist/tools/list-custom-fields.d.ts +125 -0
  234. package/dist/tools/list-custom-fields.d.ts.map +1 -0
  235. package/dist/tools/list-custom-fields.js +51 -0
  236. package/dist/tools/list-custom-fields.js.map +1 -0
  237. package/dist/tools/list-share-links.d.ts +192 -0
  238. package/dist/tools/list-share-links.d.ts.map +1 -0
  239. package/dist/tools/list-share-links.js +92 -0
  240. package/dist/tools/list-share-links.js.map +1 -0
  241. package/dist/tools/list-workspaces.d.ts +88 -0
  242. package/dist/tools/list-workspaces.d.ts.map +1 -0
  243. package/dist/tools/list-workspaces.js +71 -0
  244. package/dist/tools/list-workspaces.js.map +1 -0
  245. package/dist/tools/move-asset.d.ts +48 -0
  246. package/dist/tools/move-asset.d.ts.map +1 -0
  247. package/dist/tools/move-asset.js +85 -0
  248. package/dist/tools/move-asset.js.map +1 -0
  249. package/dist/tools/rename-asset.d.ts +88 -0
  250. package/dist/tools/rename-asset.d.ts.map +1 -0
  251. package/dist/tools/rename-asset.js +100 -0
  252. package/dist/tools/rename-asset.js.map +1 -0
  253. package/dist/tools/rename-folder.d.ts +55 -0
  254. package/dist/tools/rename-folder.d.ts.map +1 -0
  255. package/dist/tools/rename-folder.js +101 -0
  256. package/dist/tools/rename-folder.js.map +1 -0
  257. package/dist/tools/revoke-share-link.d.ts +55 -0
  258. package/dist/tools/revoke-share-link.d.ts.map +1 -0
  259. package/dist/tools/revoke-share-link.js +77 -0
  260. package/dist/tools/revoke-share-link.js.map +1 -0
  261. package/dist/tools/search/facets.d.ts +34 -0
  262. package/dist/tools/search/facets.d.ts.map +1 -0
  263. package/dist/tools/search/facets.js +147 -0
  264. package/dist/tools/search/facets.js.map +1 -0
  265. package/dist/tools/search/filter-builder.d.ts +33 -0
  266. package/dist/tools/search/filter-builder.d.ts.map +1 -0
  267. package/dist/tools/search/filter-builder.js +111 -0
  268. package/dist/tools/search/filter-builder.js.map +1 -0
  269. package/dist/tools/search/search-assets.d.ts +41 -0
  270. package/dist/tools/search/search-assets.d.ts.map +1 -0
  271. package/dist/tools/search/search-assets.js +162 -0
  272. package/dist/tools/search/search-assets.js.map +1 -0
  273. package/dist/tools/search/search-collections.d.ts +35 -0
  274. package/dist/tools/search/search-collections.d.ts.map +1 -0
  275. package/dist/tools/search/search-collections.js +103 -0
  276. package/dist/tools/search/search-collections.js.map +1 -0
  277. package/dist/tools/search/types.d.ts +1047 -0
  278. package/dist/tools/search/types.d.ts.map +1 -0
  279. package/dist/tools/search/types.js +216 -0
  280. package/dist/tools/search/types.js.map +1 -0
  281. package/dist/tools/update-asset-metadata.d.ts +78 -0
  282. package/dist/tools/update-asset-metadata.d.ts.map +1 -0
  283. package/dist/tools/update-asset-metadata.js +203 -0
  284. package/dist/tools/update-asset-metadata.js.map +1 -0
  285. package/dist/tools/update-collection.d.ts +69 -0
  286. package/dist/tools/update-collection.d.ts.map +1 -0
  287. package/dist/tools/update-collection.js +142 -0
  288. package/dist/tools/update-collection.js.map +1 -0
  289. package/dist/tools/view-category-contents.d.ts +231 -0
  290. package/dist/tools/view-category-contents.d.ts.map +1 -0
  291. package/dist/tools/view-category-contents.js +97 -0
  292. package/dist/tools/view-category-contents.js.map +1 -0
  293. package/dist/types.d.ts +1326 -0
  294. package/dist/types.d.ts.map +1 -0
  295. package/dist/types.js +288 -0
  296. package/dist/types.js.map +1 -0
  297. package/dist/typesense.d.ts +84 -0
  298. package/dist/typesense.d.ts.map +1 -0
  299. package/dist/typesense.js +243 -0
  300. package/dist/typesense.js.map +1 -0
  301. package/docs/api-field-verification.md +244 -0
  302. package/docs/deployment-runbook.md +446 -0
  303. package/docs/security-review.md +195 -0
  304. package/docs/typesense-filter-schema.md +262 -0
  305. package/docs/verified-endpoints.md +38 -0
  306. 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
+ [![npm](https://img.shields.io/npm/v/@collage-dam/mcp-server.svg)](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.