You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(mcp): media facet — the 1.10.0 media tools over MCP Cloud (#146)
Implements RepoProvider.media on the loopback provider so
contentrain_media_list/get/ingest/update/delete work on both MCP Cloud
surfaces, with policy at the proxy and mechanism at the loopback.
- server/utils/mcp-media-facet.ts: the contract MediaProvider as a
deterministic passthrough over Studio's ee media stack — cursor
pagination, tenant isolation (get→null / update+delete→404 on foreign
assets), and the ingest quota dance (fetchRemoteMedia SSRF/MIME/size →
reserveStorageIfAllowed → upload source:'agent' → reconcile/release).
Returns null when useMediaProvider() is null (CE / no R2) so the
tools stay hidden.
- proxy: STUDIO_HEADERS + x-cr-{project-id,workspace-id,media-owner,plan},
injected ONLY when mediaEligible (media stack + plan media.upload +
cdn_enabled + owner). Media tools are excluded from the empty-allowlist
"unrestricted" rule — a key needs media_enabled or an explicit listing,
so existing keys never silently gain URL ingest / destructive delete.
- loopback: resolveProvider builds the facet from the four proxy headers;
git.ts carries RepoProvider.media; the tenant fingerprint gains the
project id so an eligibility flip re-inits the session within a request.
- key surface: mcp_cloud_keys.media_enabled (migration 018, default false)
+ create route + panel "Allow media tools" toggle + Media badge.
- OAuth: advertisedScopes() adds media:read/media:write only where the
media stack is configured; the 4 well-known routes inject availability;
scope→tool mapping + step-up already routed media (from #145).
- deps already at 1.10.0 (#145). openWorldHint present on all 24 tools.
- tests: facet unit suite (18), fingerprint + scope/metadata units,
proxy eligibility matrix + key media gate + oauth media path, contract
media_enabled column case. lint/typecheck clean; unit+integration+nuxt
1048, contract 105, managed e2e 12/12 (media absent without R2).
Co-authored-by: Contentrain <mcp@contentrain.io>
"media.file_type_not_allowed": "File type not allowed: {type}",
143
+
"media.invalid_cursor": "Invalid pagination cursor. Start a new listing without a cursor.",
143
144
"media.library_upgrade": "Asset Manager is available on all plans. {plan} includes {mediaStorage} storage. Upgrade to {toPlan} for {toMediaStorage} storage and custom variants.",
144
145
"media.list_failed": "Failed to list media assets: {detail}",
145
146
"media.no_file_provided": "No file was provided for upload.",
147
+
"media.rename_not_supported": "Renaming media assets is not supported over MCP. Update alt text or tags instead.",
146
148
"media.storage_not_configured": "Media storage is not configured. Contact your administrator to set up R2 storage credentials.",
147
149
"media.update_failed": "Failed to update media asset: {detail}",
148
150
"media.upload_upgrade": "Media upload is available on all plans. {plan} includes {mediaStorage} storage and {maxFileSize} file size limit. Upgrade to {toPlan} for {toMediaStorage} storage and {toMaxFileSize} file limit.",
Copy file name to clipboardExpand all lines: .contentrain/content/system/ui-strings/en.json
+3Lines changed: 3 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -471,6 +471,9 @@
471
471
"mcp_cloud.key_created_title": "API key created",
472
472
"mcp_cloud.key_created_warning": "Copy your key now — it won't be shown again.",
473
473
"mcp_cloud.last_used": "Last used {when}",
474
+
"mcp_cloud.media_badge": "Media",
475
+
"mcp_cloud.media_enabled_hint": "Lets this key list, ingest (from a URL), update and delete media assets. Requires the media add-on and CDN delivery on the project.",
476
+
"mcp_cloud.media_enabled_label": "Allow media tools",
0 commit comments