From b301d4d1217aeeb4bce7b96154ab9ddb895c9e67 Mon Sep 17 00:00:00 2001 From: Eitan Peer Date: Sun, 5 Jul 2026 17:20:29 +0300 Subject: [PATCH 1/6] Initial release: Cloudinary power for Kiro OAuth-connected remote MCP servers (asset management, environment config, structured metadata, analysis), steering files for upload, transformations, asset management, analysis, and agentic account provisioning per AUTH.md. Co-Authored-By: Claude Fable 5 --- .gitignore | 2 + POWER.md | 65 +++++++++++++++++ README.md | 42 +++++++++++ mcp.json | 20 ++++++ steering/account-provisioning.md | 57 +++++++++++++++ steering/analysis.md | 67 ++++++++++++++++++ steering/asset-management.md | 75 ++++++++++++++++++++ steering/transformations.md | 115 +++++++++++++++++++++++++++++++ steering/upload.md | 97 ++++++++++++++++++++++++++ 9 files changed, 540 insertions(+) create mode 100644 .gitignore create mode 100644 POWER.md create mode 100644 README.md create mode 100644 mcp.json create mode 100644 steering/account-provisioning.md create mode 100644 steering/analysis.md create mode 100644 steering/asset-management.md create mode 100644 steering/transformations.md create mode 100644 steering/upload.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c051e52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.claude/ +.DS_Store diff --git a/POWER.md b/POWER.md new file mode 100644 index 0000000..ce77ee9 --- /dev/null +++ b/POWER.md @@ -0,0 +1,65 @@ +--- +name: "cloudinary" +displayName: "Cloudinary" +description: "Upload, manage, transform, optimize, and analyze images and videos with Cloudinary. Connects to Cloudinary's remote MCP servers via OAuth — no API keys to paste — and can even provision a new Cloudinary account for you." +keywords: ["cloudinary", "image", "video", "media", "upload", "transformation", "optimization", "cdn", "dam", "thumbnail", "resize", "crop", "responsive images", "asset management"] +author: "Cloudinary" +--- + +# Cloudinary Power + +This power connects Kiro to Cloudinary — the media platform for uploading, storing, transforming, optimizing, and delivering images and videos. It wires up Cloudinary's four remote MCP servers (asset management, environment configuration, structured metadata, and media analysis) and provides steering for the most common media workflows. + +Authentication is OAuth: the user signs in to Cloudinary in a browser and picks a product environment. No API keys or secrets are ever stored in this power's configuration. + +# Onboarding + +## Step 1 — Determine whether the user has a Cloudinary account + +Ask the user if they already have a Cloudinary account (or check: if any `cloudinary-*` MCP server is already connected and authorized, they do — skip to Step 3). + +- **Has an account (or is willing to sign up in the browser)** → go to Step 2. +- **No account, and wants you to set one up for them** → go to Step 2b (agentic provisioning). + +## Step 2 — Connect via OAuth (existing account) + +The four Cloudinary MCP servers in `mcp.json` are OAuth 2.1 protected resources supporting dynamic client registration. When Kiro first connects, the user is sent to the browser to sign in to Cloudinary, consent, and **select the product environment (cloud)** the tokens will act on. + +1. Trigger a connection to `cloudinary-asset-management` first — it covers upload plus asset administration and is enough for most workflows. +2. Have the user complete the browser sign-in and pick their product environment. +3. Verify the connection with a lightweight call (e.g., list a few assets or fetch usage). +4. Connect the other servers (`cloudinary-environment-config`, `cloudinary-structured-metadata`, `cloudinary-analysis`) lazily — only when a workflow needs them, since each requires its own consent. + +Notes: +- Tokens are scoped and short-lived, refreshed automatically via `offline_access`. They authenticate **MCP calls only** — they cannot be used against Cloudinary's REST APIs directly. +- If the user works with multiple product environments, the environment was chosen at consent time; to switch environments, re-authorize the server. + +## Step 2b — Provision a new account (no Cloudinary account yet) + +If the user has no Cloudinary account, you can create one for them from just their email address. Load `steering/account-provisioning.md` and follow it. In short: + +1. `POST https://api.cloudinary.com/v1_1/provisioning/agents/accounts` with the user's email and your agent metadata (no authentication required). +2. Tell the user to open the verification email from Cloudinary, click the link, and **set a password** (the link expires in ~24 hours). The account and its credentials stay **inert** until they do. +3. Once claimed, **return to Step 2** and connect via OAuth — the user signs in with the password they just set. Do not use the root `api_key`/`api_secret` from the provisioning response unless the user explicitly needs direct REST access; OAuth delegation is the preferred path. +4. If provisioning returns a 400 saying the email has already been taken, the user already has an account — go to Step 2 instead. + +## Step 3 — Confirm setup + +Run one simple end-to-end check: upload a small test asset or list existing assets via `cloudinary-asset-management`, and show the user the result. Mention that they can ask for uploads, transformations, optimization advice, asset search, metadata management, and media analysis. + +# When to Load Steering Files + +Load the steering file that matches the user's task. Load more than one when the task spans areas (e.g., "upload and tag product images" → upload + asset management). + +- Uploading images or videos (from disk, URLs, or in app code), upload presets, public IDs, eager transformations → `steering/upload.md` +- Transforming, resizing, cropping, optimizing media; building delivery URLs; responsive images; overlays; generative AI edits; video thumbnails/streaming → `steering/transformations.md` +- Searching, organizing, tagging assets; folders; renaming/deleting; structured metadata fields; upload presets, named transformations, and webhooks configuration → `steering/asset-management.md` +- AI analysis of media: auto-tagging, captioning, moderation, content analysis tasks → `steering/analysis.md` +- Creating a Cloudinary account for a user who doesn't have one; claim/verification flow; provisioning errors → `steering/account-provisioning.md` + +# General Guidance + +- **Prefer MCP tools for operating on the user's Cloudinary account** (uploading assets, searching, configuring); **generate SDK code** when the user is building application features (in-app uploads, URL generation in their codebase). +- Always recommend `f_auto` and `q_auto` in delivery URLs unless the user has a specific reason not to. +- Never ask the user for their `api_key`/`api_secret` — OAuth handles authentication. If application code needs credentials (e.g., `CLOUDINARY_URL` for an SDK), direct the user to copy them from their Cloudinary Console into their own environment/secrets manager; never inline secrets in code or commit them. +- Asset operations like delete and rename affect delivered URLs; warn the user and consider `invalidate: true` for CDN cache invalidation. diff --git a/README.md b/README.md new file mode 100644 index 0000000..fd00a08 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# Cloudinary Power for Kiro + +A [Kiro power](https://kiro.dev/docs/powers/) that connects Kiro to [Cloudinary](https://cloudinary.com) — upload, manage, transform, optimize, and analyze images and videos directly from your agent. + +## What it does + +- Wires up Cloudinary's four remote MCP servers over **OAuth** (browser sign-in, no API keys pasted into config): + - **Asset management** — upload assets and manage your media library (Upload + Admin API capabilities) + - **Environment config** — upload presets, named transformations, streaming profiles, webhooks + - **Structured metadata** — define and manage metadata fields + - **Analysis** — AI media analysis (tagging, captioning, moderation) +- Ships steering files with Cloudinary best practices for uploads, transformations & optimization, asset management, and media analysis. +- Supports **agentic account provisioning**: if you don't have a Cloudinary account, Kiro can create one from your email — you claim it by verifying the email and setting a password, then sign in via OAuth. See [Cloudinary's AUTH.md](https://github.com/cloudinary/auth.md) for the underlying agentic-registration flow. + +## Install + +1. Open Kiro → **Powers** panel → **Add Custom Power** +2. **Import power from GitHub** and paste this repository's URL (or **Import power from a folder** if you cloned it) +3. On first use, Kiro opens your browser to sign in to Cloudinary and choose a product environment + +## Layout + +``` +. +├── POWER.md ← power manifest: activation keywords, onboarding, steering routing +├── mcp.json ← the four remote Cloudinary MCP servers (OAuth) +└── steering/ + ├── upload.md + ├── transformations.md + ├── asset-management.md + ├── analysis.md + └── account-provisioning.md +``` + +## Try it + +Ask Kiro things like: + +- "Upload everything in ./assets to Cloudinary and tag it `launch`" +- "Give me a responsive, optimized delivery URL for hero.jpg cropped to 16:9" +- "Find all videos larger than 50 MB uploaded this month" +- "Auto-caption the images in the products folder and store the captions as metadata" diff --git a/mcp.json b/mcp.json new file mode 100644 index 0000000..15d4f99 --- /dev/null +++ b/mcp.json @@ -0,0 +1,20 @@ +{ + "mcpServers": { + "cloudinary-asset-management": { + "url": "https://asset-management.mcp.cloudinary.com/mcp", + "oauthScopes": ["openid", "email", "offline_access", "asset_management", "upload"] + }, + "cloudinary-environment-config": { + "url": "https://environment-config.mcp.cloudinary.com/mcp", + "oauthScopes": ["openid", "email", "offline_access", "asset_management"] + }, + "cloudinary-structured-metadata": { + "url": "https://structured-metadata.mcp.cloudinary.com/mcp", + "oauthScopes": ["openid", "email", "offline_access", "asset_management"] + }, + "cloudinary-analysis": { + "url": "https://analysis.mcp.cloudinary.com/sse", + "oauthScopes": ["openid", "email", "offline_access", "media_analysis", "query_analysis_tasks"] + } + } +} diff --git a/steering/account-provisioning.md b/steering/account-provisioning.md new file mode 100644 index 0000000..3ca97e9 --- /dev/null +++ b/steering/account-provisioning.md @@ -0,0 +1,57 @@ +# Provisioning a Cloudinary Account for the User + +When this file applies: the user wants to use Cloudinary but has no account, and has asked you (or agreed) to set one up on their behalf. This is Cloudinary's **agentic registration** flow: you register an account from the user's email, the user claims it by verifying their email and setting a password, and then you connect via OAuth like any existing account. If the user already has an account, skip this file entirely and connect the MCP servers via OAuth. + +## Step 1 — Register + +`POST` the agent account-creation endpoint. No authentication is required. + +```http +POST https://api.cloudinary.com/v1_1/provisioning/agents/accounts +Content-Type: application/json + +{ + "email": "user@example.com", + "agent_framework": "kiro", + "agent_llm_model": "", + "agent_goal": "", + "sdk_framework": "" +} +``` + +Field constraints: `email` required; `agent_framework` and `agent_llm_model` required, 2–100 chars; `agent_goal` required, 2–300 chars; `sdk_framework` optional, 2–100 chars. + +A `200` response includes the new account's product environment with root `api_key` / `api_secret` and a `CLOUDINARY_URL` string, plus a `guidance` block. + +**Important:** these credentials are **inert** — the environment is created disabled and nothing works until the user completes Step 2. You generally do **not** need to store them: the preferred path after the claim is OAuth (Step 3). Surface the `guidance` text to the user. + +## Step 2 — Claim ceremony (email verification) + +Cloudinary emails the user a verification link. Tell the user to: + +1. Open the email Cloudinary sent to their address. +2. Click the verification link, **set a password**, and confirm. + +This activates the product environment and gives the user a normal Cloudinary login. The link expires in ~24 hours — if it lapses, the user can request a new one from the sign-in page. + +There is **no pollable completion signal** for the claim. The user completing the OAuth sign-in in Step 3 is itself the confirmation. (Only if you must use root credentials directly — see fallback — detect activation by retrying a real API call with backoff until it succeeds.) + +## Step 3 — Connect via OAuth (preferred) + +Once the user has claimed the account, return to the power's onboarding Step 2: connect the `cloudinary-*` MCP servers. The user signs in with the password they just set and selects the product environment. You receive scoped, short-lived tokens and never handle the root secret. + +**Fallback — root credentials.** Only if the user explicitly needs direct REST API access beyond what the MCP servers expose (MCP tokens do not work against `https://api.cloudinary.com/...`), the root `api_key`/`api_secret` from Step 1 can be used with HTTP Basic auth or via `CLOUDINARY_URL` in an SDK. Treat the secret as full-access and long-lived: never log it, never put it in client-side code, and have the user store it in their own secrets manager. Prefer OAuth for everything else. + +## Errors + +Cloudinary's standard error envelope: `{ "error": { "category", "message", "code?", "details?" } }`. `code` is optional — the validation and duplicate-email 400s carry only `category` + `message`. + +| Status | Meaning | What to do | +| --- | --- | --- | +| 400 (validation) | Missing/oversized field or invalid UTF-8 | Fix the request body. | +| 400 (email taken) | Message contains `{"email":["has already been taken"]}` | The user already has an account — don't retry; connect via OAuth instead. | +| 403 `agent_registration_disabled` | Agent signup temporarily off | Don't retry tightly; ask the user to sign up at cloudinary.com themselves. | +| 403 `geo_location_not_permitted` | Region not allowed | Inform the user; do not retry. | +| 403 (generic "Invalid request") | Request blocked (e.g., IP gating) | Do not probe further. | +| 429 `ip_rate_limit_exceeded` | Per-IP signup cap (default 10/day) | Back off; retry later. | +| 5xx | Transient | Retry with exponential backoff. | diff --git a/steering/analysis.md b/steering/analysis.md new file mode 100644 index 0000000..080c56a --- /dev/null +++ b/steering/analysis.md @@ -0,0 +1,67 @@ +# Cloudinary Media Analysis + +**When this file applies:** Load this guidance when the user wants to understand, tag, caption, moderate, classify, or extract information from images and videos stored in (or reachable by) Cloudinary — e.g. "tag these product images", "generate captions", "check assets for inappropriate content", "detect watermarks/logos/text", or "make our media searchable". It covers the Cloudinary **Analysis MCP server** (`https://analysis.mcp.cloudinary.com/sse`, scopes: `media_analysis`, `query_analysis_tasks`), which wraps Cloudinary's Analyze API, and how it relates to upload-time add-ons handled by the Asset Management server. + +## What the Analysis server offers + +The Analyze API runs AI models against an asset identified by `uri` (any public URL) or `asset_id` (an asset already in the Cloudinary account). Main analysis types: + +- **AI Vision (prompt-driven, most flexible):** + - `ai_vision_general` — ask open-ended questions about an image ("what brand is this?", "describe the scene"). + - `ai_vision_tagging` — you supply candidate tag names with natural-language definitions; the model decides which apply. + - `ai_vision_moderation` — you supply yes/no policy questions ("does this contain alcohol?"); returns per-question verdicts. +- **Cloudinary content-analysis models:** `captioning` (descriptive alt-text style caption), `coco` / `lvis` / `unidet` (object detection: 80 common / thousands of / unified categories), `cld_fashion` (garment attributes), `cld_text` (text presence and location), `human_anatomy`, `shop_classifier` (product shot vs. natural photo), `watermark_detection`, `image_quality` (IQA score 0–1 with low/medium/high band). +- **Third-party models:** `google_tagging` (general labels), `google_logo_detection` (brand logos). + +Decision guide: prefer `ai_vision_*` when the user's need is expressed in natural language or is domain-specific; prefer a dedicated model when it matches exactly (captions → `captioning`, quality gating → `image_quality`, generic labels → `google_tagging`/`lvis`). + +## Async task pattern + +Analysis can run synchronously (result in the response) or asynchronously: + +1. Start the analysis with `async: true` (or when the tool defaults to async). You get back a `task_id` with status `pending`. +2. Poll with the **`query_analysis_tasks`** capability (GET task by `task_id`) until status is `completed` or `failed`. Statuses: `pending` → `processing` → `completed` / `failed`. +3. Alternatively pass a `notification_url` to receive a webhook on completion instead of polling. + +Rules: +- Do not busy-poll; check status between other work or at modest intervals, and surface the `task_id` to the user for long jobs. +- For batches, start all tasks first, then poll — don't serialize start/wait/start/wait. +- On `failed`, report the error; don't silently retry more than once. + +## Analysis server vs. upload-time add-ons + +There are two ways to get the same classes of insight — pick deliberately: + +- **Use the Analysis server (Analyze API)** for assets that already exist, for ad-hoc/exploratory analysis, for external URLs not yet uploaded, for prompt-driven AI Vision tasks, and for backfilling metadata across an existing library. +- **Use upload-time parameters** (via the Asset Management / Upload API, not this server) when analysis should happen automatically as part of ingestion: + - `detection` + `auto_tagging: <0–1 confidence>` — AI Content Analysis add-on models (`cld-fashion`, `coco`, `lvis`, `unidet`, `cld-text`, `captioning`, `iqa`, `watermark-detection`, ...); auto-applies tags above the confidence threshold. Also available retroactively via the Admin API `update` method. + - `moderation` — `aws_rek` (image), `aws_rek_video`, `google_video_moderation`, `webpurify`, `perception_point` (malware), `duplicate:`, `manual`. Chain with pipes (e.g. `"aws_rek|duplicate:0"`); `manual` must be last. Moderation lifecycle: `queued` → `pending` → `approved` / `rejected` (or `aborted` after an earlier rejection). Override with Admin API `update` + `moderation_status`. + +Rule of thumb: continuous pipeline → upload-time add-on parameters; one-off, retroactive, or question-shaped analysis → this Analysis server. + +## Feeding results back into searchability + +Analysis results are returned to you — they are not automatically stored on the asset (except upload-time `auto_tagging`/`detection`, which persist tags and detection data). To make results durable and searchable: + +- Write tags from tagging/object-detection output onto the asset (Asset Management server: add tags / update asset). +- Store captions in `context` metadata (commonly `alt` or `caption`) so they serve as alt text and search fodder. +- Store quality scores, moderation verdicts, or classifications in structured metadata fields when the account defines them. +- After writing, the Search API can find assets by those tags/metadata — this is the standard "analyze → enrich → search" loop. + +## Add-ons, credits, and limits + +- Most analysis types require an **active add-on subscription** for the corresponding feature (AI Content Analysis, Google Auto Tagging, Amazon Rekognition, WebPurify, Perception Point, Duplicate Image Detection, etc.). A request for an unsubscribed feature fails — tell the user which add-on to enable in the Cloudinary Console rather than retrying. +- Analyze API responses include a `limits` object (`used_by_request`, `remaining`, `limit`, `reset_time`). Check it before large batches; a 429 means quota exhaustion — report remaining quota and reset time instead of hammering. +- Add-on operations consume plan credits / tiered add-on units. For bulk jobs over many assets, warn the user about consumption and confirm before proceeding. +- Delivery URLs relying on AI Content Analysis results (e.g. object-aware cropping) must generally be signed or eagerly generated. + +## Quick reference + +| Goal | Do this | +|---|---| +| Describe / caption an image | `captioning` analysis; store result in `context.alt` | +| Custom domain tags | `ai_vision_tagging` with tag definitions; write tags back | +| Policy check on existing asset | `ai_vision_moderation` with yes/no questions | +| Moderate all future uploads | Upload-time `moderation` parameter (add-on) — not this server | +| Auto-tag at upload | Upload-time `detection` + `auto_tagging` threshold | +| Check a running job | `query_analysis_tasks` with the `task_id` | diff --git a/steering/asset-management.md b/steering/asset-management.md new file mode 100644 index 0000000..26abf16 --- /dev/null +++ b/steering/asset-management.md @@ -0,0 +1,75 @@ +# Cloudinary Asset Management & Admin Operations + +**When this file applies:** Load this guidance whenever the user asks to find, organize, tag, move, rename, or delete Cloudinary assets, define or edit structured metadata fields, or configure the product environment (upload presets, named transformations, streaming profiles, webhooks). These workflows map to three Cloudinary MCP servers: **asset-management** (Admin API: assets, search, folders, tags, settings), **structured-metadata** (metadata field definitions and values), and **environment-config** (presets, transformations, streaming profiles, webhook notifications). Prefer the most specific server for the task; do not use asset-management tools to define metadata fields or presets when a dedicated server exists. + +## Finding assets: always prefer Search over listing + +Use the Search method with a filter expression instead of paginating `resources` listings whenever the user's request implies *criteria* (tags, folder, size, date, metadata). Listing is only appropriate for "show me everything" browsing. Search is one API call; listing-then-filtering wastes rate limit and tokens. + +### Search expression syntax (Lucene-like) + +- `field:value` — tokenized/contains match; `field=value` — exact match. `tags:shirt` matches partial token hits; `tags=shirt` requires the exact tag. +- Boolean operators must be ALL CAPS: `AND`, `OR`, `NOT` (also `&&`, `||`, `!`, and `+`/`-` prefixes). Adjacent terms default to OR. Group with parentheses. +- Ranges: `width:{200 TO 1024}` (also `[ ]` inclusive), relative dates like `2d`, `4w`; comparison ops `>`, `>=`, `<` work for numbers/dates. +- Wildcards: `*` in values (e.g., `public_id:products/*`). Quote values containing spaces or special characters; escape `"` and `*` with `\`. +- Useful fields: `public_id`, `folder`/`asset_folder`, `tags`, `context`, `metadata`, `resource_type`, `format`, `width`, `height`, `aspect_ratio`, `bytes`, `duration`, `created_at`, `uploaded_at`, `transparent`, `moderation_status`. +- Structured metadata: query by field external_id, e.g. `metadata.department=marketing` or presence check `metadata=sku`. + +Example expressions: + +``` +tags=sale AND resource_type:image +folder:products/* AND format:png AND transparent:true +resource_type:video AND duration:{30 TO 120} AND bytes>10000000 +uploaded_at:[4w TO 1w] AND tags=hero AND width>=1920 +context.alt:"summer campaign" OR tags=summer-2026 +metadata.approval_status=approved AND NOT tags=archived +``` + +Notes: results exclude assets pending moderation unless you add `moderation_status:pending`; use `max_results` (cap 500) and `next_cursor` for paging; request `sort_by` and only needed `with_field` values to keep responses small. + +## Tags vs context vs structured metadata — choosing the right one + +| Mechanism | Nature | Use when | +|---|---|---| +| **Tags** | Flat labels, many per asset | Grouping, bulk operations (delete/list by tag), quick categorical filtering. No values, no validation. | +| **Context** | Free-form key-value strings per asset | Per-asset descriptive text (`alt`, `caption`, ad-hoc attributes). No schema, no validation, not shared across assets. | +| **Structured metadata** | Account-level typed schema, values per asset | Governed fields: validated, typed, searchable, mandatory-able, with predefined option lists. Use for anything a team standardizes on (SKU, department, rights, approval status). | + +Rule of thumb: if the user wants controlled vocabulary, validation, or reporting — structured metadata. If they want quick grouping for bulk ops — tags. If it's one-off descriptive text — context. + +## Structured metadata fields (structured-metadata server) + +- Field types: `string`, `integer`, `date`, `enum` (single-select), `set` (multi-select). Max 100 fields per environment. +- Defining a field requires `external_id` (stable identifier — used in search expressions), `label`, `type`; optional `mandatory` and `default_value`. +- `enum`/`set` fields require a `datasource` of up to 3,000 entries, each with its own `external_id` and `value`. Update the datasource to add/remove options; never delete an option that assets still reference without confirming intent. +- Validation is supported: numeric ranges (`greater_than`/`less_than`), regex patterns for strings, and combined `and` rules. Conditional metadata rules can enable fields, activate values, or set mandatory based on other fields' values. +- Set values on assets via the metadata update tools (or at upload via presets). Changes to metadata trigger webhook notifications if configured. + +## Folders: fixed vs dynamic mode + +Determine the environment's folder mode before moving assets — behavior differs fundamentally: + +- **Fixed folder mode:** the folder is part of the public ID (`products/shoes/sneaker-01`). Moving an asset means **renaming** its public ID, which changes its delivery URL (breaking change for consumers). +- **Dynamic folder mode:** `asset_folder` is independent of the public ID. Moving an asset updates `asset_folder` only; delivery URLs are unaffected. Also supports a separate `display_name`. +- Folder ops: create folders explicitly, delete only empty folders (folders with many assets require deleting/moving contents first). + +## Renaming, moving, deleting — and the CDN caveat + +- **Rename** changes an asset's public ID (fixed mode: also its folder/URL). Use `overwrite: true` only when the user explicitly accepts replacing an existing target. +- **Delete** by public IDs, asset IDs, prefix, or tag. Deleting by tag/prefix is bulk and irreversible — restate scope and confirm before running. `keep_original: true` deletes only derived versions. +- **CDN invalidation:** deleting or overwriting an asset does NOT purge CDN caches by default — old versions keep being served until cache TTL expires. Pass `invalidate: true` on rename/delete/overwrite when the user needs the change visible immediately, and warn that invalidation propagation across the CDN takes several minutes to about an hour. + +## Environment configuration (environment-config server) + +- **Upload presets:** named bundles of upload options (folder, tags, context/metadata defaults, incoming/eager transformations, allowed formats, moderation, auto-tagging). *Signed* presets support all parameters (backend use); *unsigned* presets are for client-side uploads — protect them with `allowed_formats`, `max_file_size`, and `disallow_public_id`. Prefer editing an existing preset over creating near-duplicates; list existing presets first. +- **Named transformations:** saved, reusable transformation definitions referenced as `t_` in URLs. Changing a named transformation affects every URL using it — check usage and prefer creating a new name over mutating one in production. Avoid `f_auto` inside eager/incoming transformations; use `eager_async: true` for video. +- **Streaming profiles:** predefined ladder configurations for adaptive bitrate video; create/update via this server rather than hand-building per-asset transformations. +- **Webhook notifications:** configure notification URLs and event-type triggers (upload, delete, moderation, metadata changes). Advise users to verify notification signatures on their receiving endpoint. Metadata-change notifications indicate origin (API vs console) and change type. + +## Rate limits and operational discipline + +- Admin API operations are rate-limited per hour (500/hr on free plans, 2,000+/hr on paid). Every call counts; `versions=true` costs extra. +- Check `X-FeatureRateLimit-Remaining` / `X-FeatureRateLimit-Reset` headers when doing bulk work; batch operations (delete by tag/prefix, bulk tag updates) instead of looping per-asset calls. +- Never poll listings in a loop to "watch" for changes — recommend webhooks instead. +- For destructive or URL-breaking operations (bulk delete, rename in fixed folder mode, editing a shared named transformation or preset), summarize the blast radius and get user confirmation first. diff --git a/steering/transformations.md b/steering/transformations.md new file mode 100644 index 0000000..4e2b309 --- /dev/null +++ b/steering/transformations.md @@ -0,0 +1,115 @@ +# Cloudinary Transformations & Optimization + +**When this file applies:** Use this guidance whenever you are constructing Cloudinary delivery URLs, writing code that renders Cloudinary-hosted images or videos (img/video tags, SDK calls, srcset attributes), or advising on media optimization in a project connected to Cloudinary via MCP. It covers URL-based transformations for images and video, optimization defaults, responsive delivery, and generative AI edits. + +## Delivery URL anatomy + +``` +https://res.cloudinary.com//////. +``` + +- `asset_type`: `image`, `video`, or `raw`. `delivery_type`: usually `upload`. +- `` is optional. `` (e.g. `v1712345678`) is optional and busts CDN cache. +- Extension is optional; with `f_auto` you can omit it (or keep the original — `f_auto` overrides it). +- Public IDs are **case-sensitive** and may contain folder paths (`products/shoes/red-sneaker`). Never change their casing or guess an extension. + +Example: `https://res.cloudinary.com/demo/image/upload/c_fill,w_300,h_200,g_auto/q_auto/f_auto/sample` + +## Transformation syntax rules + +- **Comma-separate** parameters within one component: `c_fill,w_300,h_200,g_auto`. +- **Slash-separate** chained components; each applies to the output of the previous one, left to right. **Order matters** — cropping then rounding corners is not the same as the reverse. +- **One action per component.** Action parameters (`c_`, `e_`, `l_`, `a_`) each get their own component. Qualifiers (`w_`, `h_`, `g_`, `ar_`, `x_`, `y_`, `co_`) must sit in the same component as the action they modify — a `g_auto` in its own component does nothing. +- Identical URLs hit the same cached derived asset; keep parameter order consistent (SDKs sort alphabetically) to maximize cache hits. + +## Optimization defaults — always apply + +Append `q_auto/f_auto` (or `q_auto,f_auto` as the final component) to **every** delivery URL unless the user explicitly needs a fixed format/quality: + +- `f_auto` — serves AVIF/WebP/JPEG XL/etc. per browser support. Transparent images fall back to PNG when needed. +- `q_auto` — content-aware compression. Levels: `q_auto:best`, `q_auto:good` (default), `q_auto:eco`, `q_auto:low`. Honors the browser `Save-Data` header (switches to eco). +- Don't set a numeric quality (`q_80`) alongside or before `f_auto` — it defeats per-format tuning. Let `q_auto` decide; only override with a level, not a number. +- `f_auto` is ineffective when buried inside a named transformation — keep it in the URL itself. + +## Resize & crop modes — choosing the right one + +| Mode | Behavior | Use when | +|---|---|---| +| `c_fill` | Scale + crop to exactly fill w×h | Fixed-size slots (cards, heroes); pair with `g_auto` | +| `c_lfill` | Like fill, but never upscales | Fill without quality loss on small originals | +| `c_fit` | Scale to fit within w×h, no crop | Whole image must be visible; container tolerates letterbox | +| `c_lfit` | Like fit, but never upscales | Responsive max-size delivery | +| `c_limit` | Alias behavior of lfit: shrink only if larger | Capping dimensions safely — good default for user content | +| `c_scale` | Force to w×h (or one dim, other auto) | Simple resizing; may distort if both dims given | +| `c_crop` | Extract region, no scaling | Cutting a region at original resolution | +| `c_thumb` | Crop toward detected face(s), then scale | Avatars/profile photos; use with `g_face` and optional `z_` zoom | +| `c_pad` / `c_lpad` | Fit then pad with background to exact w×h | Exact dimensions with full image visible; set `b_` (or `b_gen_fill`) | +| `c_auto` | AI picks the best crop strategy | When unsure and content varies | + +**Gravity** (qualifier for fill/crop/thumb/pad): +- `g_auto` — AI keeps the most interesting content in frame. Default choice for `c_fill`. +- `g_face` / `g_faces` — center on one/all faces. Best for people. +- Compass: `g_north`, `g_south_east`, `g_center`, etc. — for deterministic positioning and overlay placement. + +**Aspect ratio:** `ar_16:9`, `ar_1:1`, or decimal `ar_1.5`. Give `ar_` plus one dimension instead of hard-coding both: `c_fill,ar_1:1,w_400,g_auto`. + +## Responsive sizing & DPR + +- Always deliver images at their final rendered size — resize server-side, never with CSS alone. +- `dpr_2` (or `dpr_auto` with client hints) serves high-density variants: `c_fill,w_300,dpr_2` renders 600px wide for a 300px CSS slot. +- For `srcset`, generate width variants by changing only `w_` and keep everything else identical: + ```html + + ``` +- 3–5 breakpoints is usually enough; each distinct URL is a billable derived transformation. + +## Named transformations + +- `t_` applies a preset saved in the account: `/t_product_card/sample.jpg`. +- Prefer them for transformations reused across a codebase — one place to update, shorter URLs, and they can hide transformation details in strict-transformation setups. +- Can be chained with inline components: `/t_product_card/f_auto/...` (keep `f_auto` outside the named transformation). + +## Overlays & text basics + +- Image overlay: `l_` — use `:` instead of `/` for folders (`l_logos:acme`). Position and close with a separate `fl_layer_apply` component when styling the layer: + `/l_logos:acme,w_100/fl_layer_apply,g_south_east,x_20,y_20/` +- Text overlay: `l_text:_:` with styling qualifiers: + `/l_text:Arial_60_bold:Sale%2050%25/co_white,g_south,y_40/` +- URL-encode overlay text (spaces `%20`, `%` as `%25`, commas as `%2C`). + +## Video transformations + +Base: `https://res.cloudinary.com//video/upload//.` + +- Resize/crop works like images: `c_fill,w_640,h_360,g_auto`. `q_auto`/`f_auto` apply too. +- **Adaptive streaming:** use a streaming profile + HLS/DASH format: `sp_auto/f_m3u8` (or `sp_hd`, and `.m3u8`/`.mpd` extensions). Prefer ABR streaming over progressive MP4 for anything longer than a short clip. +- **Thumbnails from video:** request an image format with a start offset: + `/video/upload/so_2.5,c_fill,w_400,h_225,g_auto/q_auto/f_auto/my-video.jpg` — `so_` picks the frame (seconds, or `so_auto`). +- **Trimming:** `so_` (start), `eo_` (end), `du_` (duration): `so_5,du_10`. +- On-the-fly limits: ~60 min output for ABR, ~30 min for progressive; longer runs asynchronously. + +## Generative AI transforms (consume extra credits) + +These count against quota at a **higher special rate** than standard transformations — mention this when suggesting them, and prefer eager/pre-generated derivatives since first requests may return HTTP 423/420 while processing. + +- `e_gen_remove:prompt_` — remove an object and inpaint: `e_gen_remove:prompt_the%20stick`. Multiple: `prompt_(phone;keyboard)`. Avoid on faces/hands. +- `e_gen_replace:from_;to_` — swap an object: `e_gen_replace:from_the%20lamp;to_a%20plant`. +- `b_gen_fill` — generative background used with padding crops to extend an image to a new aspect ratio: `ar_16:9,b_gen_fill,c_pad,w_1200`. Great for portrait→landscape. +- Also available: `e_gen_background_replace[:prompt_...]`, `e_gen_recolor:prompt_;to-color_`, `e_gen_restore`. Add `;seed_` for reproducible variations. Not supported on animated or fetched images; most require non-transparent input. + +## Common mistakes to avoid + +1. **Wrong chain order.** Components run left to right — `e_grayscale` before an overlay grays only the base; after `fl_layer_apply` it grays everything. +2. **Splitting one action across components.** `c_fill,w_300/g_auto` silently ignores the gravity; qualifiers must live with their action: `c_fill,w_300,g_auto`. +3. **Cramming chained actions into one component.** Two effects like `e_blur,e_grayscale` in one component is invalid — chain them: `e_blur:200/e_grayscale`. +4. **Fixed quality with `f_auto`.** `q_80,f_auto` locks quality tuned for one format; use `q_auto,f_auto`. +5. **Changing public_id casing or extension.** IDs are case-sensitive; `Sample.jpg` ≠ `sample.jpg`. Don't append an extension you haven't verified — with `f_auto` omit it. +6. **Forgetting URL encoding** in text overlays and gen-AI prompts (spaces, commas, slashes). +7. **Upscaling small originals** with `c_fill`/`c_scale` — use `c_limit`/`c_lfill`/`c_lfit` when the source size is unknown. +8. **CSS-only resizing** — shipping a 4000px original into a 300px slot; always resize in the URL. +9. **`f_auto` inside a named transformation** — it won't negotiate formats there; keep it in the delivery URL. +10. **Unbounded gen-AI usage** — generative transforms are billed at a premium; cache/eagerly generate rather than composing unique prompts per request. diff --git a/steering/upload.md b/steering/upload.md new file mode 100644 index 0000000..36c1cae --- /dev/null +++ b/steering/upload.md @@ -0,0 +1,97 @@ +# Cloudinary: Uploading Assets + +**When this file applies:** Load this guidance whenever the task involves getting files into Cloudinary — uploading images, videos, or raw files via the Cloudinary asset-management MCP tools (Upload API / Admin API), configuring upload presets, or writing application code that uploads to Cloudinary. It covers choosing identifiers, folders, presets, upload-time metadata, transformations, and large-file handling. + +## MCP tools vs. generating SDK code + +- **Use the MCP upload tools** for one-off or operator-driven tasks: seeding a media library, migrating a batch of files, uploading a test asset, fixing a specific asset. The user asks *you* to upload; you call the tool. +- **Generate application code** when the user is building an app that uploads at runtime (user uploads, server pipelines). Do not route their app's traffic through MCP. Write server-side SDK code (signed) or client-side unsigned uploads with an unsigned preset. + +Server-side Node.js example (credentials come from the `CLOUDINARY_URL` env var, format `cloudinary://:@` — never hardcode secrets): + +```js +import { v2 as cloudinary } from "cloudinary"; +// CLOUDINARY_URL in env configures the SDK automatically + +const result = await cloudinary.uploader.upload("local-or-remote-file.jpg", { + asset_folder: "products", + use_filename: true, + unique_filename: false, + overwrite: false, + resource_type: "auto", + tags: ["catalog", "summer"], + context: { alt: "Red sneaker, side view" }, +}); +console.log(result.public_id, result.secure_url); +``` + +For Python: `cloudinary.uploader.upload(file, **options)` with the same option names. + +## public_id: explicit vs. auto-generated + +- If you omit `public_id`, Cloudinary assigns a random ID (e.g. `8jsb1xofxdqamu2rzwt9q`). Fine for user-generated content; bad for assets referenced by predictable URLs. +- Set an explicit `public_id` when the app or user needs stable, readable delivery URLs. Rules: max 255 chars, no leading/trailing space or `/`, no `? & # \ % < >`. Omit the file extension for image/video; **include** it for `raw`. +- `use_filename: true` derives the public_id from the uploaded filename. Combined with: + - `unique_filename: true` (default) → filename + random suffix (collision-safe). + - `unique_filename: false` → exact normalized filename (predictable, but can collide). +- Decision rule: explicit `public_id` for curated/app assets; `use_filename: true, unique_filename: false` for mirroring an existing file tree; defaults for untrusted user uploads. + +## Overwrite behavior + +- Default is effectively "don't clobber": uploading with an existing public_id only replaces the asset if `overwrite: true`. +- Overwriting may clear existing tags, context, and structured metadata — re-send them in the same call if they must survive. +- After overwriting, add `invalidate: true` to purge cached CDN copies (propagation takes seconds to minutes). + +## Folders + +Product environments run in one of two folder modes — check before choosing a strategy: + +- **Dynamic folders (newer accounts):** set `asset_folder` to place the asset in the Media Library. Slashes in `public_id` do *not* create folders; display location and public_id are independent. +- **Fixed folders (legacy):** slashes in `public_id` (or the `folder` param) define both the URL path and the Media Library folder. + +If unsure of the mode, query the environment config via the Admin/config tools or ask the user rather than guessing. + +## Upload presets + +- A preset is a named, server-stored bundle of upload options; pass `upload_preset: ""` in the call. +- **Signed presets** (server-side): all parameters allowed. Precedence: parameters in the request override the preset, except `eager` and incoming `transformation`, which are **merged**. +- **Unsigned presets** (browser/client-side, no signature): only a small whitelist of request parameters is accepted; everything else must live in the preset. Preset values win, except `context`/`metadata` (merged) and `public_id` (request wins unless `disallow_public_id` is set). +- When generating client-upload code, recommend hardening the unsigned preset: `allowed_formats`, `max_file_size`, `disallow_public_id`, and an incoming transformation to cap dimensions. +- Default presets (e.g. `ml_default`) can auto-apply per resource type from console settings — changing them affects production workflows; warn before editing. + +## Tags, context, metadata at upload time + +Set these in the upload call rather than with follow-up API calls — it's atomic and cheaper: + +- `tags: ["a", "b"]` — flat labels for grouping, search, and bulk operations. +- `context: { alt: "...", caption: "..." }` — free-form key-value pairs (contextual metadata). +- `metadata: { "field-id": "value" }` — structured metadata; values must conform to the fields defined in the environment, otherwise the upload errors. +- Auto-tagging: `categorization: "google_tagging", auto_tagging: 0.6` applies detected tags above the confidence threshold. + +## Eager vs. on-the-fly transformations + +- Default to **on-the-fly**: store the original untouched and put transformations in the delivery URL. This is Cloudinary's core model; don't pre-generate derivatives without a reason. +- Use `eager: [{ width: 400, height: 300, crop: "pad" }, ...]` when specific derived versions must exist immediately (strict first-request latency, or on-the-fly is restricted). For video, prefer `eager_async: true` with `eager_notification_url` — video derivation is slow. +- An **incoming transformation** (top-level `transformation`/`width`/`crop` params) permanently alters the stored original (e.g. cap resolution, strip data). Use only when the original should not be kept as-is. + +## Large files and video (chunked upload) + +- Files **over 100 MB must be uploaded in chunks**. SDKs expose `upload_large` (e.g. `cloudinary.uploader.upload_large(file, { chunk_size: 20_000_000 })`); default chunk size 20 MB, minimum 5 MB. +- Videos commonly exceed 100 MB — reach for `upload_large` for any sizable video, and set `resource_type: "video"` explicitly (SDK default is `image`). +- Intermediate chunk responses return `done: false`; the final response has `done: true`. For files over ~20 GB, also set `async: true`. +- If an MCP upload tool hits size limits on a huge file, generate a short SDK/CLI script for the user instead of retrying through MCP. + +## Other options that matter + +- `resource_type`: SDK default is `image` (also covers PDFs). Use `"auto"` when file types are mixed or unknown; use `"raw"` for non-media files (include the extension in the public_id). +- `notification_url`: webhook that receives the upload result — pair with `async: true` or eager-async processing so the app isn't blocked on long operations. +- `type`: `upload` (public, default) vs `private`/`authenticated` for access-restricted originals. +- Remote sources: the `file` argument accepts local paths, remote HTTPS URLs, and data URIs — for assets already hosted elsewhere, pass the URL instead of downloading first. + +## Quick checklist before uploading + +1. One-off task → MCP tool; app runtime → generate SDK code with `CLOUDINARY_URL`. +2. Decide public_id strategy (explicit / use_filename / random) and overwrite intent. +3. Fixed vs dynamic folder mode → `public_id` path vs `asset_folder`. +4. Attach tags/context/metadata in the same call. +5. Video or >100 MB → chunked upload, explicit `resource_type`, consider eager async + notification_url. From ca9c8d9ebd8b3f4d9bc38ca1f7f3caa6cc1e1e62 Mon Sep 17 00:00:00 2001 From: Eitan Peer Date: Sun, 5 Jul 2026 17:54:59 +0300 Subject: [PATCH 2/6] Align with official Kiro powers conventions; move power into cloudinary/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Power files now live in cloudinary/ so the imported folder name matches the power name (Kiro labels local imports by directory name) - mcp.json: bare url entries (Kiro handles OAuth/DCR; oauthScopes is not part of the powers schema — no official power uses it) - POWER.md: add Overview, Available MCP Servers with exact tool names, MCP Configuration, Troubleshooting, and License/Support sections; rewrite onboarding as tool-availability detection with a read-only connectivity check; trim keywords to 7 per power-builder spec - account-provisioning: redact root credentials at the tool boundary - analysis: correct task-polling tool name (tasks-get-status) - add MIT LICENSE Co-Authored-By: Claude Fable 5 --- LICENSE | 21 ++ POWER.md | 65 ------ README.md | 21 +- cloudinary/POWER.md | 198 ++++++++++++++++++ cloudinary/mcp.json | 16 ++ .../steering}/account-provisioning.md | 11 +- {steering => cloudinary/steering}/analysis.md | 6 +- .../steering}/asset-management.md | 0 .../steering}/transformations.md | 0 {steering => cloudinary/steering}/upload.md | 0 mcp.json | 20 -- 11 files changed, 260 insertions(+), 98 deletions(-) create mode 100644 LICENSE delete mode 100644 POWER.md create mode 100644 cloudinary/POWER.md create mode 100644 cloudinary/mcp.json rename {steering => cloudinary/steering}/account-provisioning.md (80%) rename {steering => cloudinary/steering}/analysis.md (93%) rename {steering => cloudinary/steering}/asset-management.md (100%) rename {steering => cloudinary/steering}/transformations.md (100%) rename {steering => cloudinary/steering}/upload.md (100%) delete mode 100644 mcp.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..26d6bfe --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Cloudinary + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/POWER.md b/POWER.md deleted file mode 100644 index ce77ee9..0000000 --- a/POWER.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -name: "cloudinary" -displayName: "Cloudinary" -description: "Upload, manage, transform, optimize, and analyze images and videos with Cloudinary. Connects to Cloudinary's remote MCP servers via OAuth — no API keys to paste — and can even provision a new Cloudinary account for you." -keywords: ["cloudinary", "image", "video", "media", "upload", "transformation", "optimization", "cdn", "dam", "thumbnail", "resize", "crop", "responsive images", "asset management"] -author: "Cloudinary" ---- - -# Cloudinary Power - -This power connects Kiro to Cloudinary — the media platform for uploading, storing, transforming, optimizing, and delivering images and videos. It wires up Cloudinary's four remote MCP servers (asset management, environment configuration, structured metadata, and media analysis) and provides steering for the most common media workflows. - -Authentication is OAuth: the user signs in to Cloudinary in a browser and picks a product environment. No API keys or secrets are ever stored in this power's configuration. - -# Onboarding - -## Step 1 — Determine whether the user has a Cloudinary account - -Ask the user if they already have a Cloudinary account (or check: if any `cloudinary-*` MCP server is already connected and authorized, they do — skip to Step 3). - -- **Has an account (or is willing to sign up in the browser)** → go to Step 2. -- **No account, and wants you to set one up for them** → go to Step 2b (agentic provisioning). - -## Step 2 — Connect via OAuth (existing account) - -The four Cloudinary MCP servers in `mcp.json` are OAuth 2.1 protected resources supporting dynamic client registration. When Kiro first connects, the user is sent to the browser to sign in to Cloudinary, consent, and **select the product environment (cloud)** the tokens will act on. - -1. Trigger a connection to `cloudinary-asset-management` first — it covers upload plus asset administration and is enough for most workflows. -2. Have the user complete the browser sign-in and pick their product environment. -3. Verify the connection with a lightweight call (e.g., list a few assets or fetch usage). -4. Connect the other servers (`cloudinary-environment-config`, `cloudinary-structured-metadata`, `cloudinary-analysis`) lazily — only when a workflow needs them, since each requires its own consent. - -Notes: -- Tokens are scoped and short-lived, refreshed automatically via `offline_access`. They authenticate **MCP calls only** — they cannot be used against Cloudinary's REST APIs directly. -- If the user works with multiple product environments, the environment was chosen at consent time; to switch environments, re-authorize the server. - -## Step 2b — Provision a new account (no Cloudinary account yet) - -If the user has no Cloudinary account, you can create one for them from just their email address. Load `steering/account-provisioning.md` and follow it. In short: - -1. `POST https://api.cloudinary.com/v1_1/provisioning/agents/accounts` with the user's email and your agent metadata (no authentication required). -2. Tell the user to open the verification email from Cloudinary, click the link, and **set a password** (the link expires in ~24 hours). The account and its credentials stay **inert** until they do. -3. Once claimed, **return to Step 2** and connect via OAuth — the user signs in with the password they just set. Do not use the root `api_key`/`api_secret` from the provisioning response unless the user explicitly needs direct REST access; OAuth delegation is the preferred path. -4. If provisioning returns a 400 saying the email has already been taken, the user already has an account — go to Step 2 instead. - -## Step 3 — Confirm setup - -Run one simple end-to-end check: upload a small test asset or list existing assets via `cloudinary-asset-management`, and show the user the result. Mention that they can ask for uploads, transformations, optimization advice, asset search, metadata management, and media analysis. - -# When to Load Steering Files - -Load the steering file that matches the user's task. Load more than one when the task spans areas (e.g., "upload and tag product images" → upload + asset management). - -- Uploading images or videos (from disk, URLs, or in app code), upload presets, public IDs, eager transformations → `steering/upload.md` -- Transforming, resizing, cropping, optimizing media; building delivery URLs; responsive images; overlays; generative AI edits; video thumbnails/streaming → `steering/transformations.md` -- Searching, organizing, tagging assets; folders; renaming/deleting; structured metadata fields; upload presets, named transformations, and webhooks configuration → `steering/asset-management.md` -- AI analysis of media: auto-tagging, captioning, moderation, content analysis tasks → `steering/analysis.md` -- Creating a Cloudinary account for a user who doesn't have one; claim/verification flow; provisioning errors → `steering/account-provisioning.md` - -# General Guidance - -- **Prefer MCP tools for operating on the user's Cloudinary account** (uploading assets, searching, configuring); **generate SDK code** when the user is building application features (in-app uploads, URL generation in their codebase). -- Always recommend `f_auto` and `q_auto` in delivery URLs unless the user has a specific reason not to. -- Never ask the user for their `api_key`/`api_secret` — OAuth handles authentication. If application code needs credentials (e.g., `CLOUDINARY_URL` for an SDK), direct the user to copy them from their Cloudinary Console into their own environment/secrets manager; never inline secrets in code or commit them. -- Asset operations like delete and rename affect delivered URLs; warn the user and consider `invalidate: true` for CDN cache invalidation. diff --git a/README.md b/README.md index fd00a08..9a26ca5 100644 --- a/README.md +++ b/README.md @@ -15,21 +15,24 @@ A [Kiro power](https://kiro.dev/docs/powers/) that connects Kiro to [Cloudinary] ## Install 1. Open Kiro → **Powers** panel → **Add Custom Power** -2. **Import power from GitHub** and paste this repository's URL (or **Import power from a folder** if you cloned it) +2. **Import power from GitHub** using this repository's `cloudinary/` folder URL (or **Import power from a folder** and select the `cloudinary/` directory if you cloned it) 3. On first use, Kiro opens your browser to sign in to Cloudinary and choose a product environment ## Layout ``` . -├── POWER.md ← power manifest: activation keywords, onboarding, steering routing -├── mcp.json ← the four remote Cloudinary MCP servers (OAuth) -└── steering/ - ├── upload.md - ├── transformations.md - ├── asset-management.md - ├── analysis.md - └── account-provisioning.md +├── README.md +├── LICENSE +└── cloudinary/ ← the power; import this folder in Kiro + ├── POWER.md ← power manifest: activation keywords, onboarding, steering routing + ├── mcp.json ← the four remote Cloudinary MCP servers (OAuth) + └── steering/ + ├── upload.md + ├── transformations.md + ├── asset-management.md + ├── analysis.md + └── account-provisioning.md ``` ## Try it diff --git a/cloudinary/POWER.md b/cloudinary/POWER.md new file mode 100644 index 0000000..1f3ed9f --- /dev/null +++ b/cloudinary/POWER.md @@ -0,0 +1,198 @@ +--- +name: "cloudinary" +displayName: "Cloudinary" +description: "Upload, transform, optimize, and manage images and videos with Cloudinary. Search and analyze your media library with AI, and deliver optimized assets worldwide." +keywords: ["cloudinary", "image", "video", "upload", "transformation", "optimization", "asset management"] +author: "Cloudinary" +--- + +# Cloudinary Power + +## Overview + +Connect Kiro to Cloudinary — the media platform for uploading, storing, transforming, optimizing, and delivering images and videos. This power wires up Cloudinary's four remote MCP servers and provides steering for the most common media workflows. + +**Key capabilities:** + +- **Upload and manage assets**: upload images/videos/raw files, search with Lucene-like expressions, organize with folders, tags, and metadata +- **Transform and optimize**: build delivery URLs with resizing, cropping, overlays, `f_auto`/`q_auto` optimization, and generative AI edits +- **Configure the environment**: upload presets, named transformations, streaming profiles, triggers +- **AI media analysis**: auto-tagging, captioning, moderation, object detection, quality scoring +- **OAuth authentication**: no API keys pasted into config — the user signs in to Cloudinary in a browser and selects a product environment +- **Agentic account provisioning**: users without a Cloudinary account can have one created from their email, claimed via email verification + +## Onboarding + +### Step 1: Check connection state + +Check which Cloudinary tools are available: + +- **Connected**: tools like `search-assets` and `upload-asset` are present — skip to Step 3. +- **Not connected**: no `cloudinary-*` tools are available — the server needs authentication (Step 2). If the user has no Cloudinary account and wants you to create one, go to Step 2b first. + +### Step 2: Authenticate (existing account) + +Attempt `mcp_auth` on the `cloudinary-asset-management` server, or ask the user to approve the connection when Kiro prompts. The user's browser opens to sign in to Cloudinary, consent, and **select the product environment (cloud)** the tokens will act on. + +Notes: + +- Tokens are scoped and short-lived and refresh automatically. They authenticate **MCP calls only** — they cannot be used against Cloudinary's REST APIs directly. +- Each of the four servers requires its own one-time consent. Authorize `cloudinary-asset-management` now; authorize the others when a workflow first needs them rather than all upfront. +- The product environment was chosen at consent time. If assets seem missing, the user may have selected a different environment — re-authorize to switch. + +### Step 2b: Provision a new account (no Cloudinary account yet) + +If the user has no Cloudinary account, you can create one from just their email address. Load `steering/account-provisioning.md` and follow it exactly — including its credential-redaction rules. In short: register via the agent provisioning endpoint, have the user claim the account through the verification email (they set a password), then return to Step 2 and authenticate via OAuth. Never surface or store the root credentials from the provisioning response unless the user explicitly chooses the headless fallback documented there. + +### Step 3: Verify with a read-only call + +Confirm the connection with a **read-only** call: `get-usage-details`, or `search-assets` with a small `max_results`. Never upload a test asset as a connectivity check — don't write to the user's media library uninvited. + +Then tell the user what they can ask for: uploads, transformations and optimization, asset search and organization, metadata, environment configuration, and AI media analysis. + +## Available Steering Files + +- **upload** — Getting files into Cloudinary: public IDs, folders, presets, upload-time metadata, eager transformations, large files +- **transformations** — Delivery URLs: resizing/cropping, `f_auto`/`q_auto`, responsive images, overlays, video, generative AI edits +- **asset-management** — Search expressions, tags vs context vs structured metadata, folders, rename/delete, environment configuration +- **analysis** — AI analysis: tagging, captioning, moderation, async task pattern, feeding results back into searchability +- **account-provisioning** — Creating a Cloudinary account for a user who doesn't have one (agentic registration + claim ceremony) + +## When to Load Steering Files + +Load the steering file that matches the user's task. Load more than one when the task spans areas (e.g., "upload and tag product images" → upload + asset-management). + +- Uploading images or videos (via MCP tools or in app code), upload presets, public IDs, eager transformations → `upload.md` +- Transforming, resizing, cropping, optimizing media; delivery URLs; responsive images; overlays; generative AI edits; video thumbnails/streaming → `transformations.md` +- Searching, organizing, tagging assets; folders; renaming/deleting; structured metadata; presets, named transformations, triggers, streaming profiles → `asset-management.md` +- AI analysis of media: auto-tagging, captioning, moderation, object detection, quality scoring → `analysis.md` +- Creating a Cloudinary account for a user without one; claim/verification flow; provisioning errors → `account-provisioning.md` + +## Available MCP Servers + +### cloudinary-asset-management + +**Connection:** `https://asset-management.mcp.cloudinary.com/mcp` — OAuth (browser sign-in) +**Covers:** uploading and managing assets (Upload API + Admin API capabilities) + +| Tool | Purpose | +|------|---------| +| `upload-asset` | Upload images, videos, or raw files with optional transformations | +| `search-assets` | Query assets using Lucene-like search expressions | +| `visual-search-assets` | Find visually similar images by URL, asset ID, or description | +| `get-asset-details` | Fetch full details of a single asset | +| `asset-update` | Modify an asset's metadata, tags, and attributes | +| `asset-rename` | Change an asset's public ID | +| `delete-asset` | Delete an asset by asset ID | +| `delete-derived-assets` | Remove derived (transformed) versions | +| `list-images` / `list-videos` / `list-files` | List assets by type with basic filtering | +| `list-tags` | List tags in use | +| `create-folder` / `move-folder` / `delete-folder` / `search-folders` | Folder management | +| `create-asset-relations` / `delete-asset-relations` | Link/unlink related assets | +| `generate-archive` | Create downloadable ZIP/TGZ archives of assets | +| `download-asset-backup` | Retrieve a backed-up asset version | +| `transform-asset` | Generate derived transformations (explicit/eager) | +| `get-tx-reference` | Load official transformation syntax reference (once per session) | +| `get-usage-details` | Account usage metrics — good read-only connectivity check | + +### cloudinary-environment-config + +**Connection:** `https://environment-config.mcp.cloudinary.com/mcp` — OAuth (browser sign-in) +**Covers:** product-environment configuration + +Tools follow a list/get/create/update/delete pattern for: **transformations** (named), **upload-presets**, **upload-mappings**, **triggers** (plus `test-trigger`), and **streaming-profiles** (e.g., `list-upload-presets`, `create-transformation`, `update-streaming-profile`). + +### cloudinary-structured-metadata + +**Connection:** `https://structured-metadata.mcp.cloudinary.com/mcp` — OAuth (browser sign-in) +**Covers:** structured metadata schema + +Tools follow the same CRUD pattern for **metadata-fields** (`create-metadata-field`, `list-metadata-fields`, `get-metadata-field`, `update-metadata-field`, `delete-metadata-field`), **metadata-datasource-values** (update/delete), and **metadata-rules** (create/list/update/delete). + +### cloudinary-analysis + +**Connection:** `https://analysis.mcp.cloudinary.com/sse` — OAuth (browser sign-in) +**Covers:** AI media analysis + +| Tool | Purpose | +|------|---------| +| `analyze-ai-vision-general` | Open-ended questions about an image | +| `analyze-ai-vision-tagging` | Tag images against a custom taxonomy | +| `analyze-ai-vision-moderation` | Yes/no policy-compliance questions | +| `analyze-captioning` | Generate descriptive captions | +| `analyze-coco` / `analyze-lvis` / `analyze-unidet` | Object detection (80 / thousands / unified categories) | +| `analyze-google-tagging` / `analyze-google-logo-detection` | Google label and logo detection | +| `analyze-cld-fashion` / `analyze-cld-text` / `analyze-human-anatomy` | Fashion attributes, text presence, anatomy | +| `analyze-shop-classifier` | Studio product shot vs natural photo | +| `analyze-image-quality` | Image quality score | +| `analyze-watermark-detection` | Detect watermarks | +| `tasks-get-status` | Check status of an async analysis task | + +## MCP Configuration + +```json +{ + "mcpServers": { + "cloudinary-asset-management": { + "url": "https://asset-management.mcp.cloudinary.com/mcp" + }, + "cloudinary-environment-config": { + "url": "https://environment-config.mcp.cloudinary.com/mcp" + }, + "cloudinary-structured-metadata": { + "url": "https://structured-metadata.mcp.cloudinary.com/mcp" + }, + "cloudinary-analysis": { + "url": "https://analysis.mcp.cloudinary.com/sse" + } + } +} +``` + +## Best Practices + +- **Prefer MCP tools for operating on the user's Cloudinary account** (uploading, searching, configuring); **generate SDK code** when the user is building application features (in-app uploads, URL generation in their codebase). +- Always recommend `f_auto` and `q_auto` in delivery URLs unless the user has a specific reason not to. +- Never ask the user for their `api_key`/`api_secret` — OAuth handles authentication. If application code needs credentials (e.g., `CLOUDINARY_URL` for an SDK), direct the user to copy them from their Cloudinary Console into their own environment/secrets manager; never inline secrets in code or commit them. +- Asset operations like delete, rename, and overwrite affect delivered URLs and cached CDN copies; warn the user and use `invalidate: true` when the change must be visible immediately. +- For version-sensitive specifics (rate limits, add-on catalogs, new transformation parameters), verify against current docs via https://cloudinary.com/documentation/llms.txt rather than relying solely on steering content. + +## Troubleshooting + +### No Cloudinary tools available + +**Cause:** the MCP server isn't connected or authorized. +**Solution:** attempt `mcp_auth` on the server, or have the user check Kiro's MCP panel and approve the connection. The browser sign-in must complete, including product-environment selection. + +### Authentication errors (401 / token expired) + +**Cause:** the OAuth session behind the grant ended (e.g., the user signed out of Cloudinary). +**Solution:** re-authenticate the server. Tokens are short-lived by design and normally refresh automatically. + +### Assets the user expects are missing + +**Cause:** the token acts on the product environment selected at consent, which may not be the one the user is thinking of. +**Solution:** confirm which cloud they intended; re-authorize the server and select the right environment. + +### Analysis tool fails with an add-on or subscription error + +**Cause:** most analysis models require the corresponding add-on (AI Content Analysis, Google Auto Tagging, Amazon Rekognition, etc.). +**Solution:** tell the user which add-on to enable in the Cloudinary Console; don't retry until it's enabled. + +### Need direct REST API access + +**Cause:** MCP tokens work only against the MCP servers, not `https://api.cloudinary.com/...`. +**Solution:** for application code, use a Cloudinary SDK with credentials the user copies from their Console. Don't route app runtime traffic through MCP. + +### Provisioning returns 400 "email has already been taken" + +**Cause:** the user already has a Cloudinary account. +**Solution:** skip provisioning; authenticate via OAuth (Onboarding Step 2). + +## License and Support + +This power integrates with [Cloudinary](https://cloudinary.com). + +- [Documentation](https://cloudinary.com/documentation) +- [Support](https://support.cloudinary.com) +- [Privacy Policy](https://cloudinary.com/privacy) diff --git a/cloudinary/mcp.json b/cloudinary/mcp.json new file mode 100644 index 0000000..5ac584e --- /dev/null +++ b/cloudinary/mcp.json @@ -0,0 +1,16 @@ +{ + "mcpServers": { + "cloudinary-asset-management": { + "url": "https://asset-management.mcp.cloudinary.com/mcp" + }, + "cloudinary-environment-config": { + "url": "https://environment-config.mcp.cloudinary.com/mcp" + }, + "cloudinary-structured-metadata": { + "url": "https://structured-metadata.mcp.cloudinary.com/mcp" + }, + "cloudinary-analysis": { + "url": "https://analysis.mcp.cloudinary.com/sse" + } + } +} diff --git a/steering/account-provisioning.md b/cloudinary/steering/account-provisioning.md similarity index 80% rename from steering/account-provisioning.md rename to cloudinary/steering/account-provisioning.md index 3ca97e9..b970c39 100644 --- a/steering/account-provisioning.md +++ b/cloudinary/steering/account-provisioning.md @@ -23,7 +23,16 @@ Field constraints: `email` required; `agent_framework` and `agent_llm_model` req A `200` response includes the new account's product environment with root `api_key` / `api_secret` and a `CLOUDINARY_URL` string, plus a `guidance` block. -**Important:** these credentials are **inert** — the environment is created disabled and nothing works until the user completes Step 2. You generally do **not** need to store them: the preferred path after the claim is OAuth (Step 3). Surface the `guidance` text to the user. +**Redact the credentials at the tool boundary.** The response contains a full-access root secret; anything you print lands in the chat transcript and possibly in logs. Unless the user has explicitly chosen the headless fallback (Step 3), strip the credentials before the response enters the conversation — e.g.: + +```sh +curl -s -X POST https://api.cloudinary.com/v1_1/provisioning/agents/accounts \ + -H 'Content-Type: application/json' -d @request.json \ + | jq 'del(.product_environments[].api_key, .product_environments[].api_secret) + | del(.product_environments[].api_environment_variable)' +``` + +Never echo, display, or write the unredacted response to a file. These credentials are **inert** anyway — the environment is created disabled and nothing works until the user completes Step 2 — and the preferred path after the claim is OAuth (Step 3), which never needs them. Surface the `guidance` text to the user. ## Step 2 — Claim ceremony (email verification) diff --git a/steering/analysis.md b/cloudinary/steering/analysis.md similarity index 93% rename from steering/analysis.md rename to cloudinary/steering/analysis.md index 080c56a..b261878 100644 --- a/steering/analysis.md +++ b/cloudinary/steering/analysis.md @@ -4,7 +4,7 @@ ## What the Analysis server offers -The Analyze API runs AI models against an asset identified by `uri` (any public URL) or `asset_id` (an asset already in the Cloudinary account). Main analysis types: +The Analyze API runs AI models against an asset identified by `uri` (any public URL) or `asset_id` (an asset already in the Cloudinary account). The MCP tools are named `analyze-` with hyphens (e.g. `analyze-captioning`, `analyze-ai-vision-general`, `analyze-image-quality`), plus `tasks-get-status` for polling async tasks. Main analysis types: - **AI Vision (prompt-driven, most flexible):** - `ai_vision_general` — ask open-ended questions about an image ("what brand is this?", "describe the scene"). @@ -20,7 +20,7 @@ Decision guide: prefer `ai_vision_*` when the user's need is expressed in natura Analysis can run synchronously (result in the response) or asynchronously: 1. Start the analysis with `async: true` (or when the tool defaults to async). You get back a `task_id` with status `pending`. -2. Poll with the **`query_analysis_tasks`** capability (GET task by `task_id`) until status is `completed` or `failed`. Statuses: `pending` → `processing` → `completed` / `failed`. +2. Poll with the **`tasks-get-status`** tool (pass the `task_id`) until status is `completed` or `failed`. Statuses: `pending` → `processing` → `completed` / `failed`. 3. Alternatively pass a `notification_url` to receive a webhook on completion instead of polling. Rules: @@ -64,4 +64,4 @@ Analysis results are returned to you — they are not automatically stored on th | Policy check on existing asset | `ai_vision_moderation` with yes/no questions | | Moderate all future uploads | Upload-time `moderation` parameter (add-on) — not this server | | Auto-tag at upload | Upload-time `detection` + `auto_tagging` threshold | -| Check a running job | `query_analysis_tasks` with the `task_id` | +| Check a running job | `tasks-get-status` with the `task_id` | diff --git a/steering/asset-management.md b/cloudinary/steering/asset-management.md similarity index 100% rename from steering/asset-management.md rename to cloudinary/steering/asset-management.md diff --git a/steering/transformations.md b/cloudinary/steering/transformations.md similarity index 100% rename from steering/transformations.md rename to cloudinary/steering/transformations.md diff --git a/steering/upload.md b/cloudinary/steering/upload.md similarity index 100% rename from steering/upload.md rename to cloudinary/steering/upload.md diff --git a/mcp.json b/mcp.json deleted file mode 100644 index 15d4f99..0000000 --- a/mcp.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "mcpServers": { - "cloudinary-asset-management": { - "url": "https://asset-management.mcp.cloudinary.com/mcp", - "oauthScopes": ["openid", "email", "offline_access", "asset_management", "upload"] - }, - "cloudinary-environment-config": { - "url": "https://environment-config.mcp.cloudinary.com/mcp", - "oauthScopes": ["openid", "email", "offline_access", "asset_management"] - }, - "cloudinary-structured-metadata": { - "url": "https://structured-metadata.mcp.cloudinary.com/mcp", - "oauthScopes": ["openid", "email", "offline_access", "asset_management"] - }, - "cloudinary-analysis": { - "url": "https://analysis.mcp.cloudinary.com/sse", - "oauthScopes": ["openid", "email", "offline_access", "media_analysis", "query_analysis_tasks"] - } - } -} From 9999d7588d8e7701cd39e9a5e33d143eba3b1a60 Mon Sep 17 00:00:00 2001 From: Eitan Peer Date: Tue, 7 Jul 2026 14:16:50 +0300 Subject: [PATCH 3/6] Add SPDX license identifier to POWER.md for submission criteria Co-Authored-By: Claude Opus 4.8 --- cloudinary/POWER.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cloudinary/POWER.md b/cloudinary/POWER.md index 1f3ed9f..8729c79 100644 --- a/cloudinary/POWER.md +++ b/cloudinary/POWER.md @@ -191,7 +191,9 @@ Tools follow the same CRUD pattern for **metadata-fields** (`create-metadata-fie ## License and Support -This power integrates with [Cloudinary](https://cloudinary.com). +**License:** MIT (SPDX-License-Identifier: `MIT`) — see the `LICENSE` file at the repository root. + +This power integrates with [Cloudinary](https://cloudinary.com); the four MCP servers are operated by Cloudinary, so the links below cover both the power and the servers. - [Documentation](https://cloudinary.com/documentation) - [Support](https://support.cloudinary.com) From 2b99126941f57ad2c3700f6e496657dd924b1675 Mon Sep 17 00:00:00 2001 From: Eitan Peer Date: Tue, 7 Jul 2026 15:20:24 +0300 Subject: [PATCH 4/6] Fix doc inaccuracies found in docs review Cross-checked steering/manifest against current Cloudinary docs and corrected: - transformations.md: remove non-existent c_lfit crop mode; fold its behavior into c_limit (fit-but-only-scales-down) - upload.md: correct overwrite default (signed uploads default to true, not "don't clobber"); add missing '+' to public_id forbidden chars - POWER.md/README.md: soften "Cloudinary's four MCP servers" phrasing (more servers exist); this power wires up four of them - POWER.md/account-provisioning.md: drop undocumented token-lifecycle and "MCP-only" claims; note non-Analysis servers also support API-key auth - account-provisioning.md: replace fabricated provisioning error codes/messages with the three documented cases (400/403/429), correct the error envelope, add disposable-email restriction Co-Authored-By: Claude Opus 4.8 --- README.md | 4 ++-- cloudinary/POWER.md | 14 +++++++------- cloudinary/steering/account-provisioning.md | 16 ++++++---------- cloudinary/steering/transformations.md | 5 ++--- cloudinary/steering/upload.md | 5 +++-- 5 files changed, 20 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 9a26ca5..76fbe82 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A [Kiro power](https://kiro.dev/docs/powers/) that connects Kiro to [Cloudinary] ## What it does -- Wires up Cloudinary's four remote MCP servers over **OAuth** (browser sign-in, no API keys pasted into config): +- Wires up four of Cloudinary's remote MCP servers over **OAuth** (browser sign-in, no API keys pasted into config): - **Asset management** — upload assets and manage your media library (Upload + Admin API capabilities) - **Environment config** — upload presets, named transformations, streaming profiles, webhooks - **Structured metadata** — define and manage metadata fields @@ -26,7 +26,7 @@ A [Kiro power](https://kiro.dev/docs/powers/) that connects Kiro to [Cloudinary] ├── LICENSE └── cloudinary/ ← the power; import this folder in Kiro ├── POWER.md ← power manifest: activation keywords, onboarding, steering routing - ├── mcp.json ← the four remote Cloudinary MCP servers (OAuth) + ├── mcp.json ← four remote Cloudinary MCP servers (OAuth) └── steering/ ├── upload.md ├── transformations.md diff --git a/cloudinary/POWER.md b/cloudinary/POWER.md index 8729c79..1d8bdbc 100644 --- a/cloudinary/POWER.md +++ b/cloudinary/POWER.md @@ -10,7 +10,7 @@ author: "Cloudinary" ## Overview -Connect Kiro to Cloudinary — the media platform for uploading, storing, transforming, optimizing, and delivering images and videos. This power wires up Cloudinary's four remote MCP servers and provides steering for the most common media workflows. +Connect Kiro to Cloudinary — the media platform for uploading, storing, transforming, optimizing, and delivering images and videos. This power wires up four of Cloudinary's remote MCP servers and provides steering for the most common media workflows. **Key capabilities:** @@ -36,7 +36,7 @@ Attempt `mcp_auth` on the `cloudinary-asset-management` server, or ask the user Notes: -- Tokens are scoped and short-lived and refresh automatically. They authenticate **MCP calls only** — they cannot be used against Cloudinary's REST APIs directly. +- Authentication is handled by the OAuth flow — you never paste or handle raw API keys. (Cloudinary's non-Analysis MCP servers also accept API-key authentication via `CLOUDINARY_URL`/header credentials, but this power standardizes on OAuth so no secrets enter the config.) - Each of the four servers requires its own one-time consent. Authorize `cloudinary-asset-management` now; authorize the others when a workflow first needs them rather than all upfront. - The product environment was chosen at consent time. If assets seem missing, the user may have selected a different environment — re-authorize to switch. @@ -167,7 +167,7 @@ Tools follow the same CRUD pattern for **metadata-fields** (`create-metadata-fie ### Authentication errors (401 / token expired) **Cause:** the OAuth session behind the grant ended (e.g., the user signed out of Cloudinary). -**Solution:** re-authenticate the server. Tokens are short-lived by design and normally refresh automatically. +**Solution:** re-authenticate the server; completing the OAuth sign-in again re-establishes the grant. ### Assets the user expects are missing @@ -181,19 +181,19 @@ Tools follow the same CRUD pattern for **metadata-fields** (`create-metadata-fie ### Need direct REST API access -**Cause:** MCP tokens work only against the MCP servers, not `https://api.cloudinary.com/...`. +**Cause:** the MCP servers cover agent-driven operations on the account, not your application's runtime traffic. **Solution:** for application code, use a Cloudinary SDK with credentials the user copies from their Console. Don't route app runtime traffic through MCP. -### Provisioning returns 400 "email has already been taken" +### Provisioning returns 400 (account already exists) -**Cause:** the user already has a Cloudinary account. +**Cause:** a Cloudinary account already exists for that email. **Solution:** skip provisioning; authenticate via OAuth (Onboarding Step 2). ## License and Support **License:** MIT (SPDX-License-Identifier: `MIT`) — see the `LICENSE` file at the repository root. -This power integrates with [Cloudinary](https://cloudinary.com); the four MCP servers are operated by Cloudinary, so the links below cover both the power and the servers. +This power integrates with [Cloudinary](https://cloudinary.com); these MCP servers are operated by Cloudinary, so the links below cover both the power and the servers. - [Documentation](https://cloudinary.com/documentation) - [Support](https://support.cloudinary.com) diff --git a/cloudinary/steering/account-provisioning.md b/cloudinary/steering/account-provisioning.md index b970c39..331a399 100644 --- a/cloudinary/steering/account-provisioning.md +++ b/cloudinary/steering/account-provisioning.md @@ -19,7 +19,7 @@ Content-Type: application/json } ``` -Field constraints: `email` required; `agent_framework` and `agent_llm_model` required, 2–100 chars; `agent_goal` required, 2–300 chars; `sdk_framework` optional, 2–100 chars. +Field constraints: `email` required (must be a valid address and **not** a disposable-email provider); `agent_framework` and `agent_llm_model` required, 2–100 chars; `agent_goal` required, 2–300 chars; `sdk_framework` optional, 2–100 chars. A `200` response includes the new account's product environment with root `api_key` / `api_secret` and a `CLOUDINARY_URL` string, plus a `guidance` block. @@ -49,18 +49,14 @@ There is **no pollable completion signal** for the claim. The user completing th Once the user has claimed the account, return to the power's onboarding Step 2: connect the `cloudinary-*` MCP servers. The user signs in with the password they just set and selects the product environment. You receive scoped, short-lived tokens and never handle the root secret. -**Fallback — root credentials.** Only if the user explicitly needs direct REST API access beyond what the MCP servers expose (MCP tokens do not work against `https://api.cloudinary.com/...`), the root `api_key`/`api_secret` from Step 1 can be used with HTTP Basic auth or via `CLOUDINARY_URL` in an SDK. Treat the secret as full-access and long-lived: never log it, never put it in client-side code, and have the user store it in their own secrets manager. Prefer OAuth for everything else. +**Fallback — root credentials.** Only if the user explicitly needs direct REST API access beyond what the MCP servers expose, the root `api_key`/`api_secret` from Step 1 can be used with HTTP Basic auth or via `CLOUDINARY_URL` in an SDK. Treat the secret as full-access and long-lived: never log it, never put it in client-side code, and have the user store it in their own secrets manager. Prefer OAuth for everything else. ## Errors -Cloudinary's standard error envelope: `{ "error": { "category", "message", "code?", "details?" } }`. `code` is optional — the validation and duplicate-email 400s carry only `category` + `message`. +Cloudinary returns an error envelope of the form `{ "error": { "category", "message" } }` (observed `category`: `user_error`). The documented failure conditions are: | Status | Meaning | What to do | | --- | --- | --- | -| 400 (validation) | Missing/oversized field or invalid UTF-8 | Fix the request body. | -| 400 (email taken) | Message contains `{"email":["has already been taken"]}` | The user already has an account — don't retry; connect via OAuth instead. | -| 403 `agent_registration_disabled` | Agent signup temporarily off | Don't retry tightly; ask the user to sign up at cloudinary.com themselves. | -| 403 `geo_location_not_permitted` | Region not allowed | Inform the user; do not retry. | -| 403 (generic "Invalid request") | Request blocked (e.g., IP gating) | Do not probe further. | -| 429 `ip_rate_limit_exceeded` | Per-IP signup cap (default 10/day) | Back off; retry later. | -| 5xx | Transient | Retry with exponential backoff. | +| 400 | A required parameter is missing, the email format is invalid, the email domain is a disposable-email provider, or an account already exists for the email. | Read the message. If an account already exists, don't retry — connect via OAuth instead (Onboarding Step 2). Otherwise fix the request body. | +| 403 | Cloudinary's abuse controls blocked the request. The message is **intentionally generic** (e.g., IP or region gating), so don't infer a specific cause. | Don't probe or retry tightly; if it persists, ask the user to sign up at cloudinary.com themselves. | +| 429 | Too many account-creation requests from the same IP address. | Back off and retry later. | diff --git a/cloudinary/steering/transformations.md b/cloudinary/steering/transformations.md index 4e2b309..a5ec5d8 100644 --- a/cloudinary/steering/transformations.md +++ b/cloudinary/steering/transformations.md @@ -38,8 +38,7 @@ Append `q_auto/f_auto` (or `q_auto,f_auto` as the final component) to **every** | `c_fill` | Scale + crop to exactly fill w×h | Fixed-size slots (cards, heroes); pair with `g_auto` | | `c_lfill` | Like fill, but never upscales | Fill without quality loss on small originals | | `c_fit` | Scale to fit within w×h, no crop | Whole image must be visible; container tolerates letterbox | -| `c_lfit` | Like fit, but never upscales | Responsive max-size delivery | -| `c_limit` | Alias behavior of lfit: shrink only if larger | Capping dimensions safely — good default for user content | +| `c_limit` | Like fit, but only scales down — never upscales | Responsive max-size delivery; capping dimensions safely — good default for user content | | `c_scale` | Force to w×h (or one dim, other auto) | Simple resizing; may distort if both dims given | | `c_crop` | Extract region, no scaling | Cutting a region at original resolution | | `c_thumb` | Crop toward detected face(s), then scale | Avatars/profile photos; use with `g_face` and optional `z_` zoom | @@ -109,7 +108,7 @@ These count against quota at a **higher special rate** than standard transformat 4. **Fixed quality with `f_auto`.** `q_80,f_auto` locks quality tuned for one format; use `q_auto,f_auto`. 5. **Changing public_id casing or extension.** IDs are case-sensitive; `Sample.jpg` ≠ `sample.jpg`. Don't append an extension you haven't verified — with `f_auto` omit it. 6. **Forgetting URL encoding** in text overlays and gen-AI prompts (spaces, commas, slashes). -7. **Upscaling small originals** with `c_fill`/`c_scale` — use `c_limit`/`c_lfill`/`c_lfit` when the source size is unknown. +7. **Upscaling small originals** with `c_fill`/`c_scale` — use `c_limit`/`c_lfill` when the source size is unknown. 8. **CSS-only resizing** — shipping a 4000px original into a 300px slot; always resize in the URL. 9. **`f_auto` inside a named transformation** — it won't negotiate formats there; keep it in the delivery URL. 10. **Unbounded gen-AI usage** — generative transforms are billed at a premium; cache/eagerly generate rather than composing unique prompts per request. diff --git a/cloudinary/steering/upload.md b/cloudinary/steering/upload.md index 36c1cae..b173fc0 100644 --- a/cloudinary/steering/upload.md +++ b/cloudinary/steering/upload.md @@ -30,7 +30,7 @@ For Python: `cloudinary.uploader.upload(file, **options)` with the same option n ## public_id: explicit vs. auto-generated - If you omit `public_id`, Cloudinary assigns a random ID (e.g. `8jsb1xofxdqamu2rzwt9q`). Fine for user-generated content; bad for assets referenced by predictable URLs. -- Set an explicit `public_id` when the app or user needs stable, readable delivery URLs. Rules: max 255 chars, no leading/trailing space or `/`, no `? & # \ % < >`. Omit the file extension for image/video; **include** it for `raw`. +- Set an explicit `public_id` when the app or user needs stable, readable delivery URLs. Rules: max 255 chars, no leading/trailing space or `/`, no `? & # \ % < > +`. Omit the file extension for image/video; **include** it for `raw`. - `use_filename: true` derives the public_id from the uploaded filename. Combined with: - `unique_filename: true` (default) → filename + random suffix (collision-safe). - `unique_filename: false` → exact normalized filename (predictable, but can collide). @@ -38,7 +38,8 @@ For Python: `cloudinary.uploader.upload(file, **options)` with the same option n ## Overwrite behavior -- Default is effectively "don't clobber": uploading with an existing public_id only replaces the asset if `overwrite: true`. +- For **signed** (server-side) uploads, `overwrite` defaults to `true` — uploading with an existing public_id **replaces** the asset unless you pass `overwrite: false`. Set it explicitly when you want collision protection. (Note: `unique_filename: true` — the default with `use_filename` — sidesteps this by generating a unique ID, which is why uploads often don't appear to clobber.) +- For **unsigned** uploads, `overwrite` defaults to `false` and cannot be set to `true`. - Overwriting may clear existing tags, context, and structured metadata — re-send them in the same call if they must survive. - After overwriting, add `invalidate: true` to purge cached CDN copies (propagation takes seconds to minutes). From 02477a88a8631fb04234fe6707713e597dea587c Mon Sep 17 00:00:00 2001 From: Eitan Peer Date: Tue, 7 Jul 2026 15:32:00 +0300 Subject: [PATCH 5/6] Fix mcp_auth and environment-config tool references in POWER.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace the fabricated `mcp_auth` agent action (onboarding + troubleshooting) with Kiro's real OAuth flow: browser auth opened automatically on connect (DCR), with the MCP-panel connect/Re-authenticate button or `/mcp auth` command as the user-driven fallback. There is no agent-invocable mcp_auth tool in Kiro. - Correct the environment-config tool description: the create/list/update/delete pattern is uniform, but read tools are not — get-transformation-details, get-upload-preset-details, get-streaming-profile, and no get for upload-mappings or triggers. Co-Authored-By: Claude Opus 4.8 --- cloudinary/POWER.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cloudinary/POWER.md b/cloudinary/POWER.md index 1d8bdbc..d3823a0 100644 --- a/cloudinary/POWER.md +++ b/cloudinary/POWER.md @@ -32,7 +32,7 @@ Check which Cloudinary tools are available: ### Step 2: Authenticate (existing account) -Attempt `mcp_auth` on the `cloudinary-asset-management` server, or ask the user to approve the connection when Kiro prompts. The user's browser opens to sign in to Cloudinary, consent, and **select the product environment (cloud)** the tokens will act on. +When Kiro connects to the `cloudinary-asset-management` server it opens the browser OAuth flow automatically — ask the user to complete it. (If it doesn't trigger, have the user connect/re-authenticate the server from Kiro's MCP panel, or run `/mcp auth`.) In the browser the user signs in to Cloudinary, consents, and **selects the product environment (cloud)** the tokens will act on. Notes: @@ -100,7 +100,7 @@ Load the steering file that matches the user's task. Load more than one when the **Connection:** `https://environment-config.mcp.cloudinary.com/mcp` — OAuth (browser sign-in) **Covers:** product-environment configuration -Tools follow a list/get/create/update/delete pattern for: **transformations** (named), **upload-presets**, **upload-mappings**, **triggers** (plus `test-trigger`), and **streaming-profiles** (e.g., `list-upload-presets`, `create-transformation`, `update-streaming-profile`). +Tools follow a create/list/update/delete pattern for **transformations** (named), **upload-presets**, **upload-mappings**, **triggers**, and **streaming-profiles** (e.g., `create-transformation`, `list-upload-presets`, `update-streaming-profile`, `delete-upload-mapping`). The read/detail tools are not uniform: `get-transformation-details`, `get-upload-preset-details`, and `get-streaming-profile` — while upload-mappings and triggers have no `get`. Triggers also add `test-trigger`. ### cloudinary-structured-metadata @@ -162,7 +162,7 @@ Tools follow the same CRUD pattern for **metadata-fields** (`create-metadata-fie ### No Cloudinary tools available **Cause:** the MCP server isn't connected or authorized. -**Solution:** attempt `mcp_auth` on the server, or have the user check Kiro's MCP panel and approve the connection. The browser sign-in must complete, including product-environment selection. +**Solution:** have the user connect the server so Kiro opens the browser OAuth flow — from Kiro's MCP panel (connect / Re-authenticate) or via `/mcp auth`. The browser sign-in must complete, including product-environment selection. ### Authentication errors (401 / token expired) From bff20a22f014b65c00bfbdb68af3e68780076fd8 Mon Sep 17 00:00:00 2001 From: Eitan Peer Date: Thu, 9 Jul 2026 13:21:55 +0300 Subject: [PATCH 6/6] Surface docs/llms.txt references in POWER.md and steering files Promote the Cloudinary docs index (llms.txt) and the get-tx-reference tool to a first-class "Staying current" section in the POWER.md Overview, instead of a single buried line under Best Practices. Add a "Further reference" footer to each steering file pointing at the most relevant doc section plus llms.txt, framed around the version-sensitive specifics (rate limits, size/duration limits, add-on catalogs, transformation params) that drift over time. Curated guidance left intact. Co-Authored-By: Claude Opus 4.8 --- cloudinary/POWER.md | 9 +++++++++ cloudinary/steering/account-provisioning.md | 4 ++++ cloudinary/steering/analysis.md | 8 ++++++++ cloudinary/steering/asset-management.md | 8 ++++++++ cloudinary/steering/transformations.md | 8 ++++++++ cloudinary/steering/upload.md | 7 +++++++ 6 files changed, 44 insertions(+) diff --git a/cloudinary/POWER.md b/cloudinary/POWER.md index d3823a0..1a3d4a1 100644 --- a/cloudinary/POWER.md +++ b/cloudinary/POWER.md @@ -21,6 +21,15 @@ Connect Kiro to Cloudinary — the media platform for uploading, storing, transf - **OAuth authentication**: no API keys pasted into config — the user signs in to Cloudinary in a browser and selects a product environment - **Agentic account provisioning**: users without a Cloudinary account can have one created from their email, claimed via email verification +## Staying current + +The steering files carry curated defaults and judgment, but version-sensitive specifics (rate limits, add-on catalogs, exact size/duration limits, new transformation parameters) drift. Treat Cloudinary's docs as the source of truth: + +- **[`https://cloudinary.com/documentation/llms.txt`](https://cloudinary.com/documentation/llms.txt)** — the machine-readable index of Cloudinary's documentation. Fetch it (and the specific pages it links) whenever you need to confirm current behavior or go beyond the steering content. +- **`get-tx-reference`** (asset-management server) — loads the official transformation syntax reference; pull it once per session before composing non-trivial transformation URLs. + +Each steering file ends with a **Further reference** pointer to the most relevant doc section. + ## Onboarding ### Step 1: Check connection state diff --git a/cloudinary/steering/account-provisioning.md b/cloudinary/steering/account-provisioning.md index 331a399..00efd62 100644 --- a/cloudinary/steering/account-provisioning.md +++ b/cloudinary/steering/account-provisioning.md @@ -60,3 +60,7 @@ Cloudinary returns an error envelope of the form `{ "error": { "category", "mess | 400 | A required parameter is missing, the email format is invalid, the email domain is a disposable-email provider, or an account already exists for the email. | Read the message. If an account already exists, don't retry — connect via OAuth instead (Onboarding Step 2). Otherwise fix the request body. | | 403 | Cloudinary's abuse controls blocked the request. The message is **intentionally generic** (e.g., IP or region gating), so don't infer a specific cause. | Don't probe or retry tightly; if it persists, ask the user to sign up at cloudinary.com themselves. | | 429 | Too many account-creation requests from the same IP address. | Back off and retry later. | + +## Further reference + +The endpoint contract above is the authority for this flow. For the surrounding account/OAuth concepts, see [`llms.txt`](https://cloudinary.com/documentation/llms.txt); if the provisioning endpoint's field constraints or error envelope appear to have changed, trust the live API response over this file. diff --git a/cloudinary/steering/analysis.md b/cloudinary/steering/analysis.md index b261878..13ccb0f 100644 --- a/cloudinary/steering/analysis.md +++ b/cloudinary/steering/analysis.md @@ -65,3 +65,11 @@ Analysis results are returned to you — they are not automatically stored on th | Moderate all future uploads | Upload-time `moderation` parameter (add-on) — not this server | | Auto-tag at upload | Upload-time `detection` + `auto_tagging` threshold | | Check a running job | `tasks-get-status` with the `task_id` | + +## Further reference + +The model catalog, add-on requirements, and quota specifics change as Cloudinary adds capabilities — verify before large jobs: + +- [Analyze API](https://cloudinary.com/documentation/analyze_api) — current model list, parameters, and response shapes. +- [AI Content Analysis add-on](https://cloudinary.com/documentation/cloudinary_ai_content_analysis_addon) and the [add-ons catalog](https://cloudinary.com/documentation/cloudinary_add_ons) — which subscription each model requires. +- [`llms.txt`](https://cloudinary.com/documentation/llms.txt) — index to the rest of the docs when you need to go deeper. diff --git a/cloudinary/steering/asset-management.md b/cloudinary/steering/asset-management.md index 26abf16..ca5774f 100644 --- a/cloudinary/steering/asset-management.md +++ b/cloudinary/steering/asset-management.md @@ -73,3 +73,11 @@ Determine the environment's folder mode before moving assets — behavior differ - Check `X-FeatureRateLimit-Remaining` / `X-FeatureRateLimit-Reset` headers when doing bulk work; batch operations (delete by tag/prefix, bulk tag updates) instead of looping per-asset calls. - Never poll listings in a loop to "watch" for changes — recommend webhooks instead. - For destructive or URL-breaking operations (bulk delete, rename in fixed folder mode, editing a shared named transformation or preset), summarize the blast radius and get user confirmation first. + +## Further reference + +Search field names, rate-limit numbers, and metadata/config options evolve — confirm against the docs before relying on specifics: + +- [Search API](https://cloudinary.com/documentation/search_api) — full expression syntax and queryable fields. +- [Admin API reference](https://cloudinary.com/documentation/admin_api) and [Structured metadata](https://cloudinary.com/documentation/structured_metadata) — current limits, field types, and config options. +- [`llms.txt`](https://cloudinary.com/documentation/llms.txt) — index to the rest of the docs when you need to go deeper. diff --git a/cloudinary/steering/transformations.md b/cloudinary/steering/transformations.md index a5ec5d8..e7dafae 100644 --- a/cloudinary/steering/transformations.md +++ b/cloudinary/steering/transformations.md @@ -112,3 +112,11 @@ These count against quota at a **higher special rate** than standard transformat 8. **CSS-only resizing** — shipping a 4000px original into a 300px slot; always resize in the URL. 9. **`f_auto` inside a named transformation** — it won't negotiate formats there; keep it in the delivery URL. 10. **Unbounded gen-AI usage** — generative transforms are billed at a premium; cache/eagerly generate rather than composing unique prompts per request. + +## Further reference + +The syntax and gotchas above are stable, but the full parameter catalog and premium/credit specifics change. For authoritative, current detail: + +- **`get-tx-reference`** (asset-management MCP server) — official transformation syntax reference; pull it once per session before composing non-trivial URLs. +- [Transformation reference](https://cloudinary.com/documentation/transformation_reference) and [Generative AI transformations](https://cloudinary.com/documentation/generative_ai_transformations) — full parameter lists, current limits, and credit rates. +- [`llms.txt`](https://cloudinary.com/documentation/llms.txt) — index to the rest of the docs when you need to go deeper. diff --git a/cloudinary/steering/upload.md b/cloudinary/steering/upload.md index b173fc0..e46faac 100644 --- a/cloudinary/steering/upload.md +++ b/cloudinary/steering/upload.md @@ -96,3 +96,10 @@ Set these in the upload call rather than with follow-up API calls — it's atomi 3. Fixed vs dynamic folder mode → `public_id` path vs `asset_folder`. 4. Attach tags/context/metadata in the same call. 5. Video or >100 MB → chunked upload, explicit `resource_type`, consider eager async + notification_url. + +## Further reference + +Size limits, default preset behavior, and the full option list are version- and plan-sensitive — verify current values rather than trusting the numbers above: + +- [Upload API reference](https://cloudinary.com/documentation/image_upload_api_reference) and [Upload guide](https://cloudinary.com/documentation/upload_images) — complete parameter list, limits, and preset behavior. +- [`llms.txt`](https://cloudinary.com/documentation/llms.txt) — index to the rest of the docs when you need to go deeper.