@contentrain/mcp 1.9.0 → 1.10.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 (39) hide show
  1. package/README.md +10 -3
  2. package/dist/{annotations-DSzewhL4.mjs → annotations-D3tlsF38.mjs} +39 -4
  3. package/dist/annotations-D3tlsF38.mjs.map +1 -0
  4. package/dist/{availability-Bj9zOfEn.mjs → availability-sWWRz2W0.mjs} +8 -2
  5. package/dist/availability-sWWRz2W0.mjs.map +1 -0
  6. package/dist/{content-manager-BExZy6Eu.d.mts → content-manager-DcmNwvmq.d.mts} +2 -2
  7. package/dist/{content-manager-BExZy6Eu.d.mts.map → content-manager-DcmNwvmq.d.mts.map} +1 -1
  8. package/dist/core/config.d.mts +1 -1
  9. package/dist/core/content-manager.d.mts +1 -1
  10. package/dist/core/context.d.mts +1 -1
  11. package/dist/core/contracts/index.d.mts +2 -2
  12. package/dist/core/model-manager.d.mts +1 -1
  13. package/dist/core/ops/index.d.mts +2 -2
  14. package/dist/core/overlay-reader.d.mts +1 -1
  15. package/dist/core/validator/index.d.mts +1 -1
  16. package/dist/index-DDX-qYNw.d.mts +2 -0
  17. package/dist/index.mjs +2 -2
  18. package/dist/providers/github/index.d.mts +1 -1
  19. package/dist/providers/gitlab/index.d.mts +1 -1
  20. package/dist/providers/local/index.d.mts +1 -1
  21. package/dist/server/http/index.d.mts +1 -1
  22. package/dist/server/http/index.mjs +2 -2
  23. package/dist/{server-B6_fzRmK.mjs → server-DtabGAY6.mjs} +134 -4
  24. package/dist/server-DtabGAY6.mjs.map +1 -0
  25. package/dist/{server-B1KfvywF.d.mts → server-vA1tM7DA.d.mts} +2 -2
  26. package/dist/{server-B1KfvywF.d.mts.map → server-vA1tM7DA.d.mts.map} +1 -1
  27. package/dist/server.d.mts +1 -1
  28. package/dist/server.mjs +2 -2
  29. package/dist/tools/annotations.d.mts +3 -3
  30. package/dist/tools/annotations.d.mts.map +1 -1
  31. package/dist/tools/annotations.mjs +1 -1
  32. package/dist/tools/availability.d.mts +3 -0
  33. package/dist/tools/availability.d.mts.map +1 -1
  34. package/dist/tools/availability.mjs +1 -1
  35. package/package.json +3 -3
  36. package/dist/annotations-DSzewhL4.mjs.map +0 -1
  37. package/dist/availability-Bj9zOfEn.mjs.map +0 -1
  38. package/dist/index-6yasXNLQ.d.mts +0 -2
  39. package/dist/server-B6_fzRmK.mjs.map +0 -1
package/README.md CHANGED
@@ -71,9 +71,9 @@ All write operations are designed around git-backed safety:
71
71
 
72
72
  ## Tool Surface
73
73
 
74
- 19 MCP tools with [annotations](https://spec.modelcontextprotocol.io/specification/2025-03-26/server/tools/#annotations) (`readOnlyHint`, `destructiveHint`, `idempotentHint`, and `openWorldHint: false` — every tool operates on the configured repository only) for client safety hints.
74
+ 24 MCP tools — 19 core + 5 media — with [annotations](https://spec.modelcontextprotocol.io/specification/2025-03-26/server/tools/#annotations) (`readOnlyHint`, `destructiveHint`, `idempotentHint`, and `openWorldHint: false` everywhere except `contentrain_media_ingest`, which fetches a caller-supplied URL server-side) for client safety hints.
75
75
 
76
- **Tool listing is capability-aware.** `tools/list` only advertises tools the resolved provider + `projectRoot` pair can actually satisfy. A local stdio server lists all 19; a session driven by a remote provider (GitHub/GitLab, no local checkout) lists only the remote-safe subset — `status`, `describe`, `describe_format`, `model_save`, `model_delete`, `content_save`, `content_delete`, `content_list`, `validate`. The requirement map lives in `TOOL_REQUIREMENTS` (`@contentrain/mcp/tools/availability`).
76
+ **Tool listing is capability-aware.** `tools/list` only advertises tools the resolved provider + `projectRoot` pair can actually satisfy. A local stdio server lists the 19 core tools; a session driven by a remote provider (GitHub/GitLab, no local checkout) lists only the remote-safe subset — `status`, `describe`, `describe_format`, `model_save`, `model_delete`, `content_save`, `content_delete`, `content_list`, `validate`. The requirement map lives in `TOOL_REQUIREMENTS` (`@contentrain/mcp/tools/availability`).
77
77
 
78
78
  | Tool | Purpose | Read-only | Destructive |
79
79
  | --- | --- | --- | --- |
@@ -96,6 +96,13 @@ All write operations are designed around git-backed safety:
96
96
  | `contentrain_scan` | Graph- and candidate-based hardcoded string scan | Yes | — |
97
97
  | `contentrain_apply` | Normalize extract/reuse execution with dry-run support | — | — |
98
98
  | `contentrain_bulk` | Bulk locale copy, status updates, and deletes | — | — |
99
+ | `contentrain_media_list` | List media assets (search, tag filter, cursor pagination) | Yes | — |
100
+ | `contentrain_media_get` | Get one media asset by id | Yes | — |
101
+ | `contentrain_media_ingest` | Ingest an asset from a source URL (provider fetches server-side) | — | — |
102
+ | `contentrain_media_update` | Update asset metadata (alt, tags, filename) | — | — |
103
+ | `contentrain_media_delete` | Delete an asset from the media stack | — | **Yes** |
104
+
105
+ The five `contentrain_media_*` tools are a deterministic passthrough to the provider's optional media facet (`RepoProvider.media`) and are registered **only when the provider exposes one** (e.g. Studio MCP Cloud). Local stdio servers and plain GitHub/GitLab providers never list them. Ingest is URL-based (MCP has no binary channel); the provider implementation owns SSRF/MIME/size policy for the fetch.
99
106
 
100
107
  ## Quick Start
101
108
 
@@ -257,7 +264,7 @@ These are intended for Contentrain tooling and advanced integrations, not for di
257
264
  Key design decisions in this package:
258
265
 
259
266
  - local-first **by default** — stdio transport + LocalProvider works without any network dependency
260
- - provider-agnostic engine — the same 19 tools run over LocalProvider, GitHubProvider, or GitLabProvider behind a single `RepoProvider` contract
267
+ - provider-agnostic engine — the same core tools run over LocalProvider, GitHubProvider, or GitLabProvider behind a single `RepoProvider` contract; media tools ride the provider's optional media facet
261
268
  - remote provider SDKs (`@octokit/rest`, `@gitbeaker/rest`) are optional peer dependencies — pulled in only when their provider is used
262
269
  - JSON-only content storage
263
270
  - git-backed write workflow (worktree transaction locally, single atomic commit over the Git Data / REST APIs remotely)
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * Centralized tool annotations registry.
4
4
  * MCP clients use these hints to distinguish read-only vs. write vs. destructive tools.
5
- * `openWorldHint` is `false` across the board: every tool operates on the
6
- * configured content repository only — none reaches out to arbitrary
7
- * external systems.
5
+ * `openWorldHint` is `false` everywhere except `contentrain_media_ingest`,
6
+ * which fetches a caller-supplied external URL server-side; every other
7
+ * tool operates on the configured content repository only.
8
8
  *
9
9
  * Also serves as the **single source of truth for the tool name list**. Consumers
10
10
  * that need to enumerate every registered tool (e.g. parity tests in
@@ -144,6 +144,41 @@ const TOOL_ANNOTATIONS = {
144
144
  destructiveHint: false,
145
145
  idempotentHint: false,
146
146
  openWorldHint: false
147
+ },
148
+ contentrain_media_list: {
149
+ title: "List Media Assets",
150
+ readOnlyHint: true,
151
+ destructiveHint: false,
152
+ idempotentHint: true,
153
+ openWorldHint: false
154
+ },
155
+ contentrain_media_get: {
156
+ title: "Get Media Asset",
157
+ readOnlyHint: true,
158
+ destructiveHint: false,
159
+ idempotentHint: true,
160
+ openWorldHint: false
161
+ },
162
+ contentrain_media_ingest: {
163
+ title: "Ingest Media From URL",
164
+ readOnlyHint: false,
165
+ destructiveHint: false,
166
+ idempotentHint: false,
167
+ openWorldHint: true
168
+ },
169
+ contentrain_media_update: {
170
+ title: "Update Media Metadata",
171
+ readOnlyHint: false,
172
+ destructiveHint: false,
173
+ idempotentHint: true,
174
+ openWorldHint: false
175
+ },
176
+ contentrain_media_delete: {
177
+ title: "Delete Media Asset",
178
+ readOnlyHint: false,
179
+ destructiveHint: true,
180
+ idempotentHint: false,
181
+ openWorldHint: false
147
182
  }
148
183
  };
149
184
  /**
@@ -157,4 +192,4 @@ const TOOL_NAMES = Object.freeze(Object.keys(TOOL_ANNOTATIONS));
157
192
  //#endregion
158
193
  export { TOOL_NAMES as n, TOOL_ANNOTATIONS as t };
159
194
 
160
- //# sourceMappingURL=annotations-DSzewhL4.mjs.map
195
+ //# sourceMappingURL=annotations-D3tlsF38.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"annotations-D3tlsF38.mjs","names":[],"sources":["../src/tools/annotations.ts"],"sourcesContent":["import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js'\n\n/**\n * Centralized tool annotations registry.\n * MCP clients use these hints to distinguish read-only vs. write vs. destructive tools.\n * `openWorldHint` is `false` everywhere except `contentrain_media_ingest`,\n * which fetches a caller-supplied external URL server-side; every other\n * tool operates on the configured content repository only.\n *\n * Also serves as the **single source of truth for the tool name list**. Consumers\n * that need to enumerate every registered tool (e.g. parity tests in\n * `@contentrain/rules` / `@contentrain/skills`) should import `TOOL_NAMES` below\n * rather than hardcoding the list.\n */\nexport const TOOL_ANNOTATIONS: Record<string, ToolAnnotations> = {\n // ─── Context (read-only) ───\n contentrain_status: {\n title: 'Project Status',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n contentrain_describe: {\n title: 'Describe Model',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n contentrain_describe_format: {\n title: 'Describe Format',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n contentrain_doctor: {\n title: 'Project Health Report',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n\n // ─── Setup (write + git) ───\n contentrain_init: {\n title: 'Initialize Project',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n contentrain_scaffold: {\n title: 'Scaffold Template',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n\n // ─── Model (write + git) ───\n contentrain_model_save: {\n title: 'Save Model',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n contentrain_model_delete: {\n title: 'Delete Model',\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: false,\n openWorldHint: false,\n },\n\n // ─── Content (mixed) ───\n contentrain_content_save: {\n title: 'Save Content',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n contentrain_content_delete: {\n title: 'Delete Content',\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: false,\n openWorldHint: false,\n },\n contentrain_content_list: {\n title: 'List Content',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n\n // ─── Workflow (mixed) ───\n contentrain_validate: {\n title: 'Validate Project',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n contentrain_submit: {\n title: 'Submit Branches',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n contentrain_merge: {\n title: 'Merge Branch',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n contentrain_branch_list: {\n title: 'List Branches',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n contentrain_branch_delete: {\n title: 'Delete Branch',\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: false,\n openWorldHint: false,\n },\n\n // ─── Normalize (mixed) ───\n contentrain_scan: {\n title: 'Scan Source Code',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n contentrain_apply: {\n title: 'Apply Normalize',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n\n // ─── Bulk (write + git) ───\n contentrain_bulk: {\n title: 'Bulk Operations',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n\n // ─── Media (provider media facet — registered only when RepoProvider.media is present) ───\n contentrain_media_list: {\n title: 'List Media Assets',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n contentrain_media_get: {\n title: 'Get Media Asset',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n contentrain_media_ingest: {\n title: 'Ingest Media From URL',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n contentrain_media_update: {\n title: 'Update Media Metadata',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n contentrain_media_delete: {\n title: 'Delete Media Asset',\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: false,\n openWorldHint: false,\n },\n}\n\n/**\n * Canonical list of every registered MCP tool name, derived from the\n * single source of truth above. Re-exported here with a stable name so\n * parity tests in sibling packages (`@contentrain/rules`,\n * `@contentrain/skills`) can assert against it without depending on\n * `TOOL_ANNOTATIONS` internals.\n */\nexport const TOOL_NAMES: readonly string[] = Object.freeze(Object.keys(TOOL_ANNOTATIONS))\n"],"mappings":";;;;;;;;;;;;;AAcA,MAAa,mBAAoD;CAE/D,oBAAoB;EAClB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CACD,sBAAsB;EACpB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CACD,6BAA6B;EAC3B,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CACD,oBAAoB;EAClB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CAGD,kBAAkB;EAChB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CACD,sBAAsB;EACpB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CAGD,wBAAwB;EACtB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CACD,0BAA0B;EACxB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CAGD,0BAA0B;EACxB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CACD,4BAA4B;EAC1B,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CACD,0BAA0B;EACxB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CAGD,sBAAsB;EACpB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CACD,oBAAoB;EAClB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CACD,mBAAmB;EACjB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CACD,yBAAyB;EACvB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CACD,2BAA2B;EACzB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CAGD,kBAAkB;EAChB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CACD,mBAAmB;EACjB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CAGD,kBAAkB;EAChB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CAGD,wBAAwB;EACtB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CACD,uBAAuB;EACrB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CACD,0BAA0B;EACxB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CACD,0BAA0B;EACxB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CACD,0BAA0B;EACxB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAChB;CACF;;;;;;;;AASD,MAAa,aAAgC,OAAO,OAAO,OAAO,KAAK,iBAAiB,CAAC"}
@@ -27,7 +27,12 @@ const TOOL_REQUIREMENTS = {
27
27
  contentrain_apply: {
28
28
  projectRoot: true,
29
29
  capabilities: ["sourceRead"]
30
- }
30
+ },
31
+ contentrain_media_list: { media: true },
32
+ contentrain_media_get: { media: true },
33
+ contentrain_media_ingest: { media: true },
34
+ contentrain_media_update: { media: true },
35
+ contentrain_media_delete: { media: true }
31
36
  };
32
37
  /**
33
38
  * Whether a tool can ever succeed for this provider + projectRoot pair.
@@ -39,9 +44,10 @@ function isToolAvailable(name, provider, projectRoot) {
39
44
  const req = TOOL_REQUIREMENTS[name];
40
45
  if (!req) return true;
41
46
  if (req.projectRoot && !projectRoot) return false;
47
+ if (req.media && !provider.media) return false;
42
48
  return (req.capabilities ?? []).every((cap) => provider.capabilities[cap]);
43
49
  }
44
50
  //#endregion
45
51
  export { isToolAvailable as n, TOOL_REQUIREMENTS as t };
46
52
 
47
- //# sourceMappingURL=availability-Bj9zOfEn.mjs.map
53
+ //# sourceMappingURL=availability-sWWRz2W0.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"availability-sWWRz2W0.mjs","names":[],"sources":["../src/tools/availability.ts"],"sourcesContent":["import type { ProviderCapabilities } from '@contentrain/types'\n\n/**\n * Declarative availability requirements for capability-gated tools.\n *\n * This is the single source of truth `createServer` uses to decide which\n * tools to register for a given provider + projectRoot combination. Tools\n * absent from this map are always available. The requirements mirror the\n * call-time guards inside each tool handler (`capabilityError`) — those\n * guards remain as defense in depth, but a client talking to a remote\n * provider no longer sees tools that could never succeed in `tools/list`.\n *\n * `contentrain_validate` is intentionally NOT listed: read-only validate\n * works over any provider; only `fix: true` needs a local worktree and\n * keeps its call-time guard.\n */\nexport interface ToolRequirements {\n /** Tool needs a local project root on disk (worktree git transactions, IDE config, AST scans). */\n projectRoot?: boolean\n /** Provider capability flags that must all be `true`. */\n capabilities?: readonly (keyof ProviderCapabilities)[]\n /** Tool needs the provider's optional media facet (`RepoProvider.media`). */\n media?: boolean\n}\n\nexport const TOOL_REQUIREMENTS: Readonly<Record<string, ToolRequirements>> = {\n contentrain_init: { projectRoot: true },\n contentrain_scaffold: { projectRoot: true },\n contentrain_doctor: { projectRoot: true },\n contentrain_bulk: { projectRoot: true },\n contentrain_submit: { projectRoot: true, capabilities: ['localWorktree', 'pushRemote'] },\n contentrain_merge: { projectRoot: true, capabilities: ['localWorktree'] },\n contentrain_branch_list: { projectRoot: true, capabilities: ['localWorktree'] },\n contentrain_branch_delete: { projectRoot: true, capabilities: ['localWorktree'] },\n contentrain_scan: { projectRoot: true, capabilities: ['astScan'] },\n // Registration requires the extract path (sourceRead); reuse mode\n // additionally needs sourceWrite, which stays a call-time check because\n // it depends on the `mode` input.\n contentrain_apply: { projectRoot: true, capabilities: ['sourceRead'] },\n // Media tools exist only where the provider exposes a media stack\n // (Studio MCP Cloud). Local and plain remote providers never list them.\n contentrain_media_list: { media: true },\n contentrain_media_get: { media: true },\n contentrain_media_ingest: { media: true },\n contentrain_media_update: { media: true },\n contentrain_media_delete: { media: true },\n}\n\n/**\n * Whether a tool can ever succeed for this provider + projectRoot pair.\n * Used by `createServer` to filter registration; also exported for\n * embedders (e.g. Studio) that want to reason about the effective tool\n * surface without spinning up a server.\n */\nexport function isToolAvailable(\n name: string,\n provider: { capabilities: ProviderCapabilities, media?: unknown },\n projectRoot: string | undefined,\n): boolean {\n const req = TOOL_REQUIREMENTS[name]\n if (!req) return true\n if (req.projectRoot && !projectRoot) return false\n if (req.media && !provider.media) return false\n return (req.capabilities ?? []).every(cap => provider.capabilities[cap])\n}\n"],"mappings":";AAyBA,MAAa,oBAAgE;CAC3E,kBAAkB,EAAE,aAAa,MAAM;CACvC,sBAAsB,EAAE,aAAa,MAAM;CAC3C,oBAAoB,EAAE,aAAa,MAAM;CACzC,kBAAkB,EAAE,aAAa,MAAM;CACvC,oBAAoB;EAAE,aAAa;EAAM,cAAc,CAAC,iBAAiB,aAAa;EAAE;CACxF,mBAAmB;EAAE,aAAa;EAAM,cAAc,CAAC,gBAAgB;EAAE;CACzE,yBAAyB;EAAE,aAAa;EAAM,cAAc,CAAC,gBAAgB;EAAE;CAC/E,2BAA2B;EAAE,aAAa;EAAM,cAAc,CAAC,gBAAgB;EAAE;CACjF,kBAAkB;EAAE,aAAa;EAAM,cAAc,CAAC,UAAU;EAAE;CAIlE,mBAAmB;EAAE,aAAa;EAAM,cAAc,CAAC,aAAa;EAAE;CAGtE,wBAAwB,EAAE,OAAO,MAAM;CACvC,uBAAuB,EAAE,OAAO,MAAM;CACtC,0BAA0B,EAAE,OAAO,MAAM;CACzC,0BAA0B,EAAE,OAAO,MAAM;CACzC,0BAA0B,EAAE,OAAO,MAAM;CAC1C;;;;;;;AAQD,SAAgB,gBACd,MACA,UACA,aACS;CACT,MAAM,MAAM,kBAAkB;AAC9B,KAAI,CAAC,IAAK,QAAO;AACjB,KAAI,IAAI,eAAe,CAAC,YAAa,QAAO;AAC5C,KAAI,IAAI,SAAS,CAAC,SAAS,MAAO,QAAO;AACzC,SAAQ,IAAI,gBAAgB,EAAE,EAAE,OAAM,QAAO,SAAS,aAAa,KAAK"}
@@ -1,4 +1,4 @@
1
- import { d as RepoReader } from "./index-6yasXNLQ.mjs";
1
+ import { _ as RepoReader } from "./index-DDX-qYNw.mjs";
2
2
  import { ContentrainConfig, LocaleStrategy, ModelDefinition, Vocabulary, parseMarkdownFrontmatter as parseFrontmatter, serializeMarkdownFrontmatter as serializeFrontmatter, validateEntryId as validateEntryId$1, validateLocale as validateLocale$1, validateSlug as validateSlug$1 } from "@contentrain/types";
3
3
 
4
4
  //#region src/core/content-manager.d.ts
@@ -60,4 +60,4 @@ declare function readContent(projectRoot: string, model: ModelDefinition, opts:
60
60
  }): Promise<unknown>;
61
61
  //#endregion
62
62
  export { writeContent as _, deleteContent as a, readContent as c, resolveLocaleStrategy as d, resolveMdFilePath as f, validateSlug$1 as g, validateLocale$1 as h, WriteResult as i, resolveContentDir as l, validateEntryId$1 as m, DeleteOpts as n, listContent as o, serializeFrontmatter as p, ListOpts as r, parseFrontmatter as s, ContentEntry as t, resolveJsonFilePath as u };
63
- //# sourceMappingURL=content-manager-BExZy6Eu.d.mts.map
63
+ //# sourceMappingURL=content-manager-DcmNwvmq.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"content-manager-BExZy6Eu.d.mts","names":[],"sources":["../src/core/content-manager.ts"],"mappings":";;;;iBAgBgB,iBAAA,CAAkB,WAAA,UAAqB,KAAA,EAAO,eAAA;AAAA,iBAO9C,qBAAA,CAAsB,KAAA,EAAO,eAAA,GAAkB,cAAA;;iBAK/C,mBAAA,CAAoB,GAAA,UAAa,KAAA,EAAO,eAAA,EAAiB,MAAA;;iBAczD,iBAAA,CAAkB,GAAA,UAAa,KAAA,EAAO,eAAA,EAAiB,MAAA,UAAgB,IAAA;AAAA,UAetE,YAAA;EACf,EAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA,EAAM,MAAA;AAAA;AAAA,UAGS,WAAA;EACf,MAAA;EACA,EAAA;EACA,IAAA;EACA,MAAA;EACA,UAAA;AAAA;AAAA,UAGe,UAAA;EACf,EAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA;AAAA;AAAA,UAGe,QAAA;EACf,MAAA;EACA,MAAA,GAAS,MAAA;EACT,OAAA;EACA,KAAA;EACA,MAAA;AAAA;AAAA,iBAsBoB,YAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,OAAA,EAAS,YAAA,IACT,MAAA,EAAQ,iBAAA,EACR,UAAA,GAAa,UAAA,UACZ,OAAA,CAAQ,WAAA;AAAA,iBAyIW,aAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,IAAA,EAAM,UAAA,GACL,OAAA;;;;;AApMH;;;;;;;;;;;iBAoUgB,WAAA,CACd,WAAA,UACA,KAAA,EAAO,eAAA,EACP,IAAA,EAAM,QAAA,EACN,MAAA,EAAQ,iBAAA,GACP,OAAA;AAAA,iBACa,WAAA,CACd,MAAA,EAAQ,UAAA,EACR,KAAA,EAAO,eAAA,EACP,IAAA,EAAM,QAAA,EACN,MAAA,EAAQ,iBAAA,GACP,OAAA;AAAA,iBA0QmB,WAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,IAAA;EAAQ,MAAA;EAAgB,OAAA;EAAkB,IAAA;AAAA,IACzC,OAAA"}
1
+ {"version":3,"file":"content-manager-DcmNwvmq.d.mts","names":[],"sources":["../src/core/content-manager.ts"],"mappings":";;;;iBAgBgB,iBAAA,CAAkB,WAAA,UAAqB,KAAA,EAAO,eAAA;AAAA,iBAO9C,qBAAA,CAAsB,KAAA,EAAO,eAAA,GAAkB,cAAA;;iBAK/C,mBAAA,CAAoB,GAAA,UAAa,KAAA,EAAO,eAAA,EAAiB,MAAA;;iBAczD,iBAAA,CAAkB,GAAA,UAAa,KAAA,EAAO,eAAA,EAAiB,MAAA,UAAgB,IAAA;AAAA,UAetE,YAAA;EACf,EAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA,EAAM,MAAA;AAAA;AAAA,UAGS,WAAA;EACf,MAAA;EACA,EAAA;EACA,IAAA;EACA,MAAA;EACA,UAAA;AAAA;AAAA,UAGe,UAAA;EACf,EAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA;AAAA;AAAA,UAGe,QAAA;EACf,MAAA;EACA,MAAA,GAAS,MAAA;EACT,OAAA;EACA,KAAA;EACA,MAAA;AAAA;AAAA,iBAsBoB,YAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,OAAA,EAAS,YAAA,IACT,MAAA,EAAQ,iBAAA,EACR,UAAA,GAAa,UAAA,UACZ,OAAA,CAAQ,WAAA;AAAA,iBAyIW,aAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,IAAA,EAAM,UAAA,GACL,OAAA;;;;;AApMH;;;;;;;;;;;iBAoUgB,WAAA,CACd,WAAA,UACA,KAAA,EAAO,eAAA,EACP,IAAA,EAAM,QAAA,EACN,MAAA,EAAQ,iBAAA,GACP,OAAA;AAAA,iBACa,WAAA,CACd,MAAA,EAAQ,UAAA,EACR,KAAA,EAAO,eAAA,EACP,IAAA,EAAM,QAAA,EACN,MAAA,EAAQ,iBAAA,GACP,OAAA;AAAA,iBA0QmB,WAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,IAAA;EAAQ,MAAA;EAAgB,OAAA;EAAkB,IAAA;AAAA,IACzC,OAAA"}
@@ -1,4 +1,4 @@
1
- import { d as RepoReader } from "../index-6yasXNLQ.mjs";
1
+ import { _ as RepoReader } from "../index-DDX-qYNw.mjs";
2
2
  import { ContentrainConfig, Vocabulary } from "@contentrain/types";
3
3
 
4
4
  //#region src/core/config.d.ts
@@ -1,2 +1,2 @@
1
- import { _ as writeContent, a as deleteContent, c as readContent, d as resolveLocaleStrategy, f as resolveMdFilePath, g as validateSlug, h as validateLocale, i as WriteResult, l as resolveContentDir, m as validateEntryId, n as DeleteOpts, o as listContent, p as serializeFrontmatter, r as ListOpts, s as parseFrontmatter, t as ContentEntry, u as resolveJsonFilePath } from "../content-manager-BExZy6Eu.mjs";
1
+ import { _ as writeContent, a as deleteContent, c as readContent, d as resolveLocaleStrategy, f as resolveMdFilePath, g as validateSlug, h as validateLocale, i as WriteResult, l as resolveContentDir, m as validateEntryId, n as DeleteOpts, o as listContent, p as serializeFrontmatter, r as ListOpts, s as parseFrontmatter, t as ContentEntry, u as resolveJsonFilePath } from "../content-manager-DcmNwvmq.mjs";
2
2
  export { ContentEntry, DeleteOpts, ListOpts, WriteResult, deleteContent, listContent, parseFrontmatter, readContent, resolveContentDir, resolveJsonFilePath, resolveLocaleStrategy, resolveMdFilePath, serializeFrontmatter, validateEntryId, validateLocale, validateSlug, writeContent };
@@ -1,4 +1,4 @@
1
- import { a as FileChange, d as RepoReader } from "../index-6yasXNLQ.mjs";
1
+ import { _ as RepoReader, a as FileChange } from "../index-DDX-qYNw.mjs";
2
2
  import { ContextJson, ContextSource } from "@contentrain/types";
3
3
 
4
4
  //#region src/core/context.d.ts
@@ -1,2 +1,2 @@
1
- import { a as FileChange, c as MergeResult, d as RepoReader, f as RepoWriter, i as CommitAuthor, l as ProviderCapabilities, n as Branch, o as FileDiff, r as Commit, s as LOCAL_CAPABILITIES, t as ApplyPlanInput, u as RepoProvider } from "../../index-6yasXNLQ.mjs";
2
- export { ApplyPlanInput, Branch, Commit, CommitAuthor, FileChange, FileDiff, LOCAL_CAPABILITIES, MergeResult, ProviderCapabilities, RepoProvider, RepoReader, RepoWriter };
1
+ import { _ as RepoReader, a as FileChange, c as MediaAsset, d as MediaListResult, f as MediaProvider, g as RepoProvider, h as ProviderCapabilities, i as CommitAuthor, l as MediaIngestInput, m as MergeResult, n as Branch, o as FileDiff, p as MediaUpdateInput, r as Commit, s as LOCAL_CAPABILITIES, t as ApplyPlanInput, u as MediaListOptions, v as RepoWriter } from "../../index-DDX-qYNw.mjs";
2
+ export { ApplyPlanInput, Branch, Commit, CommitAuthor, FileChange, FileDiff, LOCAL_CAPABILITIES, MediaAsset, MediaIngestInput, MediaListOptions, MediaListResult, MediaProvider, MediaUpdateInput, MergeResult, ProviderCapabilities, RepoProvider, RepoReader, RepoWriter };
@@ -1,4 +1,4 @@
1
- import { d as RepoReader } from "../index-6yasXNLQ.mjs";
1
+ import { _ as RepoReader } from "../index-DDX-qYNw.mjs";
2
2
  import { ModelDefinition, ModelSummary, ModelSummary as ModelSummary$1 } from "@contentrain/types";
3
3
  import { z } from "zod";
4
4
 
@@ -1,5 +1,5 @@
1
- import { a as FileChange, d as RepoReader } from "../../index-6yasXNLQ.mjs";
2
- import { t as ContentEntry } from "../../content-manager-BExZy6Eu.mjs";
1
+ import { _ as RepoReader, a as FileChange } from "../../index-DDX-qYNw.mjs";
2
+ import { t as ContentEntry } from "../../content-manager-DcmNwvmq.mjs";
3
3
  import { ContentrainConfig, ModelDefinition, Vocabulary } from "@contentrain/types";
4
4
 
5
5
  //#region src/core/ops/apply-to-worktree.d.ts
@@ -1,4 +1,4 @@
1
- import { a as FileChange, d as RepoReader } from "../index-6yasXNLQ.mjs";
1
+ import { _ as RepoReader, a as FileChange } from "../index-DDX-qYNw.mjs";
2
2
 
3
3
  //#region src/core/overlay-reader.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { d as RepoReader } from "../../index-6yasXNLQ.mjs";
1
+ import { _ as RepoReader } from "../../index-DDX-qYNw.mjs";
2
2
  import { EntryMeta, FieldDef, ModelDefinition, ValidationError, ValidationResult } from "@contentrain/types";
3
3
 
4
4
  //#region src/core/validator/entry.d.ts
@@ -0,0 +1,2 @@
1
+ import { ApplyPlanInput, Branch, Commit, CommitAuthor, FileChange, FileDiff, LOCAL_CAPABILITIES, MediaAsset, MediaIngestInput, MediaListOptions, MediaListResult, MediaProvider, MediaUpdateInput, MergeResult, ProviderCapabilities as ProviderCapabilities$1, RepoProvider, RepoReader, RepoWriter } from "@contentrain/types";
2
+ export { RepoReader as _, FileChange as a, MediaAsset as c, MediaListResult as d, MediaProvider as f, RepoProvider as g, ProviderCapabilities$1 as h, CommitAuthor as i, MediaIngestInput as l, MergeResult as m, Branch as n, FileDiff as o, MediaUpdateInput as p, Commit as r, LOCAL_CAPABILITIES as s, ApplyPlanInput as t, MediaListOptions as u, RepoWriter as v };
package/dist/index.mjs CHANGED
@@ -12,8 +12,8 @@ import "./id-DV_T9Ic8.mjs";
12
12
  import "./transaction-qliKvBRL.mjs";
13
13
  import "./local-BZEUWBoC.mjs";
14
14
  import "./detect-wVSI9VuY.mjs";
15
- import "./annotations-DSzewhL4.mjs";
16
- import { n as createServer } from "./server-B6_fzRmK.mjs";
15
+ import "./annotations-D3tlsF38.mjs";
16
+ import { n as createServer } from "./server-DtabGAY6.mjs";
17
17
  import "./validator-8vaPxSba.mjs";
18
18
  import "./scan-config-BGUflS8t.mjs";
19
19
  import "./graph-builder-CRUX_8mA.mjs";
@@ -1,4 +1,4 @@
1
- import { c as MergeResult, d as RepoReader, i as CommitAuthor, l as ProviderCapabilities, n as Branch, o as FileDiff, r as Commit, t as ApplyPlanInput, u as RepoProvider } from "../../index-6yasXNLQ.mjs";
1
+ import { _ as RepoReader, g as RepoProvider, h as ProviderCapabilities, i as CommitAuthor, m as MergeResult, n as Branch, o as FileDiff, r as Commit, t as ApplyPlanInput } from "../../index-DDX-qYNw.mjs";
2
2
  import { Octokit } from "@octokit/rest";
3
3
 
4
4
  //#region src/providers/github/app-auth.d.ts
@@ -1,4 +1,4 @@
1
- import { c as MergeResult, d as RepoReader, i as CommitAuthor, l as ProviderCapabilities, n as Branch, o as FileDiff, r as Commit, t as ApplyPlanInput, u as RepoProvider } from "../../index-6yasXNLQ.mjs";
1
+ import { _ as RepoReader, g as RepoProvider, h as ProviderCapabilities, i as CommitAuthor, m as MergeResult, n as Branch, o as FileDiff, r as Commit, t as ApplyPlanInput } from "../../index-DDX-qYNw.mjs";
2
2
  import { Gitlab } from "@gitbeaker/rest";
3
3
 
4
4
  //#region src/providers/gitlab/capabilities.d.ts
@@ -1,4 +1,4 @@
1
- import { a as FileChange, c as MergeResult, d as RepoReader, i as CommitAuthor, l as ProviderCapabilities$1, n as Branch, o as FileDiff, r as Commit, u as RepoProvider } from "../../index-6yasXNLQ.mjs";
1
+ import { _ as RepoReader, a as FileChange, g as RepoProvider, h as ProviderCapabilities$1, i as CommitAuthor, m as MergeResult, n as Branch, o as FileDiff, r as Commit } from "../../index-DDX-qYNw.mjs";
2
2
  import { SyncResult, WorkflowMode } from "@contentrain/types";
3
3
 
4
4
  //#region src/providers/local/types.d.ts
@@ -1,4 +1,4 @@
1
- import { r as ToolProvider } from "../../server-B1KfvywF.mjs";
1
+ import { r as ToolProvider } from "../../server-vA1tM7DA.mjs";
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import http from "node:http";
4
4
 
@@ -11,8 +11,8 @@ import "../../id-DV_T9Ic8.mjs";
11
11
  import "../../transaction-qliKvBRL.mjs";
12
12
  import "../../local-BZEUWBoC.mjs";
13
13
  import "../../detect-wVSI9VuY.mjs";
14
- import "../../annotations-DSzewhL4.mjs";
15
- import { n as createServer } from "../../server-B6_fzRmK.mjs";
14
+ import "../../annotations-D3tlsF38.mjs";
15
+ import { n as createServer } from "../../server-DtabGAY6.mjs";
16
16
  import "../../validator-8vaPxSba.mjs";
17
17
  import "../../scan-config-BGUflS8t.mjs";
18
18
  import "../../graph-builder-CRUX_8mA.mjs";
@@ -8,9 +8,9 @@ import { n as readContext } from "./context-CYN__o3Q.mjs";
8
8
  import { a as deleteRemoteBranch, c as pruneMergedRemoteBranches, i as cleanupMergedBranches, n as checkBranchHealth, s as listRemoteCrBranches } from "./branch-lifecycle-D7tXOE4f.mjs";
9
9
  import { i as mergeBranch, n as createTransaction, r as ensureContentBranch, t as buildBranchName } from "./transaction-qliKvBRL.mjs";
10
10
  import { n as isMerged, t as LocalProvider } from "./local-BZEUWBoC.mjs";
11
- import { n as isToolAvailable, t as TOOL_REQUIREMENTS } from "./availability-Bj9zOfEn.mjs";
11
+ import { n as isToolAvailable, t as TOOL_REQUIREMENTS } from "./availability-sWWRz2W0.mjs";
12
12
  import { t as detectStack } from "./detect-wVSI9VuY.mjs";
13
- import { t as TOOL_ANNOTATIONS } from "./annotations-DSzewhL4.mjs";
13
+ import { t as TOOL_ANNOTATIONS } from "./annotations-D3tlsF38.mjs";
14
14
  import { n as listTemplates, t as getTemplate } from "./templates-D6EllixQ.mjs";
15
15
  import { t as validateProject } from "./validator-8vaPxSba.mjs";
16
16
  import { t as OverlayReader } from "./overlay-reader-BOb105gS.mjs";
@@ -2368,8 +2368,137 @@ function registerDoctorTools(server, _provider, projectRoot) {
2368
2368
  });
2369
2369
  }
2370
2370
  //#endregion
2371
+ //#region src/tools/media.ts
2372
+ /**
2373
+ * Media tools — a deterministic passthrough to the provider's optional
2374
+ * media facet (`RepoProvider.media`). Registration is capability-aware:
2375
+ * `createServer` skips all five tools when the provider has no media
2376
+ * stack, so the guard below is defense in depth only.
2377
+ *
2378
+ * The facet closes the discovery loop for external agents: list assets →
2379
+ * pick a `media/...` path → reference it via `contentrain_content_save`
2380
+ * (which normalizes it to an absolute delivery URL when `mediaBaseUrl`
2381
+ * is set). Ingest is URL-based — MCP has no binary channel — and the
2382
+ * provider implementation owns SSRF/MIME/size policy for the fetch.
2383
+ */
2384
+ function mediaUnavailable(tool) {
2385
+ return {
2386
+ content: [{
2387
+ type: "text",
2388
+ text: JSON.stringify({
2389
+ error: `${tool} requires a provider with a media stack.`,
2390
+ capability_required: "media",
2391
+ hint: "Media tools are available when MCP is driven by a media-capable provider (e.g. Studio MCP Cloud). Local and plain git providers do not expose one."
2392
+ })
2393
+ }],
2394
+ isError: true
2395
+ };
2396
+ }
2397
+ function mediaError(error, stage) {
2398
+ return {
2399
+ content: [{
2400
+ type: "text",
2401
+ text: JSON.stringify({ ...normalizeOperationError(error, stage) })
2402
+ }],
2403
+ isError: true
2404
+ };
2405
+ }
2406
+ function ok(payload) {
2407
+ return { content: [{
2408
+ type: "text",
2409
+ text: JSON.stringify(payload)
2410
+ }] };
2411
+ }
2412
+ function registerMediaTools(server, provider, _projectRoot) {
2413
+ server.tool("contentrain_media_list", "List media assets from the provider media stack. Returns storage paths (media/...) usable in media/image/file fields via contentrain_content_save, plus delivery URLs when available. Supports search, tag filter, and cursor pagination.", {
2414
+ search: z.string().optional().describe("Substring match on filename/path/alt"),
2415
+ tag: z.string().optional().describe("Only assets carrying this tag"),
2416
+ limit: z.number().int().min(1).max(100).optional().describe("Page size. Default: provider-defined"),
2417
+ cursor: z.string().optional().describe("Opaque cursor from a previous response")
2418
+ }, TOOL_ANNOTATIONS["contentrain_media_list"], async (input) => {
2419
+ if (!provider.media) return mediaUnavailable("contentrain_media_list");
2420
+ try {
2421
+ const result = await provider.media.list(input);
2422
+ return ok({
2423
+ assets: result.assets,
2424
+ next_cursor: result.nextCursor,
2425
+ total: result.total
2426
+ });
2427
+ } catch (error) {
2428
+ return mediaError(error, "media_list");
2429
+ }
2430
+ });
2431
+ server.tool("contentrain_media_get", "Get a single media asset by id, including its storage path, delivery URL, and metadata.", { id: z.string().describe("Asset id (from contentrain_media_list)") }, TOOL_ANNOTATIONS["contentrain_media_get"], async (input) => {
2432
+ if (!provider.media) return mediaUnavailable("contentrain_media_get");
2433
+ try {
2434
+ const asset = await provider.media.get(input.id);
2435
+ if (!asset) return {
2436
+ content: [{
2437
+ type: "text",
2438
+ text: JSON.stringify({
2439
+ error: `Media asset not found: ${input.id}`,
2440
+ hint: "Use contentrain_media_list to discover asset ids."
2441
+ })
2442
+ }],
2443
+ isError: true
2444
+ };
2445
+ return ok({ asset });
2446
+ } catch (error) {
2447
+ return mediaError(error, "media_get");
2448
+ }
2449
+ });
2450
+ server.tool("contentrain_media_ingest", "Ingest a media asset from a source URL. The provider fetches the URL server-side under its own SSRF/MIME/size policy and stores the asset in the media stack. Returns the stored asset with its media/... path.", {
2451
+ url: z.string().url().describe("Source URL to fetch server-side"),
2452
+ filename: z.string().optional().describe("Target filename override"),
2453
+ alt: z.string().optional().describe("Alt text for the asset"),
2454
+ tags: z.array(z.string()).max(20).optional().describe("Tags to attach")
2455
+ }, TOOL_ANNOTATIONS["contentrain_media_ingest"], async (input) => {
2456
+ if (!provider.media) return mediaUnavailable("contentrain_media_ingest");
2457
+ try {
2458
+ return ok({
2459
+ status: "ingested",
2460
+ asset: await provider.media.ingest(input)
2461
+ });
2462
+ } catch (error) {
2463
+ return mediaError(error, "media_ingest");
2464
+ }
2465
+ });
2466
+ server.tool("contentrain_media_update", "Update metadata (alt text, tags, filename) of an existing media asset. Does not touch the binary.", {
2467
+ id: z.string().describe("Asset id to update"),
2468
+ alt: z.string().optional().describe("New alt text"),
2469
+ tags: z.array(z.string()).max(20).optional().describe("Replacement tag list"),
2470
+ filename: z.string().optional().describe("New filename")
2471
+ }, TOOL_ANNOTATIONS["contentrain_media_update"], async (input) => {
2472
+ if (!provider.media) return mediaUnavailable("contentrain_media_update");
2473
+ try {
2474
+ const { id, ...patch } = input;
2475
+ return ok({
2476
+ status: "updated",
2477
+ asset: await provider.media.update(id, patch)
2478
+ });
2479
+ } catch (error) {
2480
+ return mediaError(error, "media_update");
2481
+ }
2482
+ });
2483
+ server.tool("contentrain_media_delete", "Delete a media asset from the media stack. Content entries referencing its path are NOT rewritten — check references before deleting.", {
2484
+ id: z.string().describe("Asset id to delete"),
2485
+ confirm: z.literal(true).describe("Must be true to confirm deletion")
2486
+ }, TOOL_ANNOTATIONS["contentrain_media_delete"], async (input) => {
2487
+ if (!provider.media) return mediaUnavailable("contentrain_media_delete");
2488
+ try {
2489
+ await provider.media.delete(input.id);
2490
+ return ok({
2491
+ status: "deleted",
2492
+ id: input.id
2493
+ });
2494
+ } catch (error) {
2495
+ return mediaError(error, "media_delete");
2496
+ }
2497
+ });
2498
+ }
2499
+ //#endregion
2371
2500
  //#region package.json
2372
- var version = "1.9.0";
2501
+ var version = "1.10.0";
2373
2502
  //#endregion
2374
2503
  //#region src/server.ts
2375
2504
  /**
@@ -2410,6 +2539,7 @@ function createServer(input) {
2410
2539
  registerNormalizeTools(server, provider, projectRoot);
2411
2540
  registerBulkTools(server, provider, projectRoot);
2412
2541
  registerDoctorTools(server, provider, projectRoot);
2542
+ registerMediaTools(server, provider, projectRoot);
2413
2543
  return server;
2414
2544
  }
2415
2545
  function resolveServerContext(input) {
@@ -2438,4 +2568,4 @@ function resolveServerContext(input) {
2438
2568
  //#endregion
2439
2569
  export { createServer as n, DEFAULT_INSTRUCTIONS as t };
2440
2570
 
2441
- //# sourceMappingURL=server-B6_fzRmK.mjs.map
2571
+ //# sourceMappingURL=server-DtabGAY6.mjs.map