feat: add stock-image-search skill (Getty via media-management MCP)

Two-step skill: search via search_stock_images, render a clickable
result list, then license + download via download_stock_image when
the user selects a result. Bash download script mirrors the
generating-images/download-image.sh pattern.
This commit is contained in:
Shruthi Paramesh 2026-06-10 17:35:46 -07:00
parent 84ee08cd47
commit 8408f93a5e
2 changed files with 189 additions and 0 deletions

View File

@ -0,0 +1,112 @@
---
name: stock-image-search
description: "Searches Getty stock photography via the media-management MCP server and presents a clickable result list, then downloads the user's selected image. Use this skill when a user asks to \"find a stock image\", \"search for a photo of X\", \"get a royalty-free image\", \"find an ethical image\", \"look up a Getty image\", or when AI image generation fails and a stock fallback is appropriate. Triggers also for editorial/news photography requests. Does not apply to searching internal CMS / Data Cloud media (use searching-media), generating new images with AI (use generating-images), or applying brand guidelines (use applying-cms-brand). The skill ends after the result list is shown — Agentforce prompts the user for the selection. When the user picks one, the download flow at the bottom of this file is run to license and save the bytes locally."
compatibility: "Requires search_stock_images and download_stock_image MCP tools (media-management server)"
user-invocable: true
argument-hint: "<search query> [--orientation Horizontal|Vertical|Square|PanoramicHorizontal|PanoramicVertical] [--searchType Creative|Editorial] [--sortOrder BestMatch|MostPopular|Newest] [--pageSize N]"
metadata:
version: "1.0"
---
# Stock Image Search (Getty via `media-management` MCP)
## Goal
Search Getty Images, present a concise clickable result list, and — on user selection — license and download the chosen image to disk.
## Workflow
1. Call `search_stock_images` with the user's query and any explicit options.
2. Render a numbered list with each title hyperlinked to its `previewUrl`. **End the turn.** Do not auto-download, write files, or ask a follow-up question — Agentforce injects its own picker prompt and a question here causes a duplicate "double ask".
3. When the user selects a result, run the download flow at the bottom of this file.
## Step 1 — Search
**MCP tool:** `search_stock_images` · **Timeout:** 30s · Default `pageSize: 5`.
| Parameter | Required | Default | Options |
|--------------|----------|-------------|----------------------------------------------------------------------------------|
| `query` | ✅ | — | Natural language phrase |
| `searchType` | No | `Creative` | `Creative` (royalty-free stock), `Editorial` (rights-managed news/event imagery) |
| `orientation`| No | _(none)_ | `Horizontal`, `Vertical`, `Square`, `PanoramicHorizontal`, `PanoramicVertical` |
| `sortOrder` | No | `BestMatch` | `BestMatch`, `MostPopular`, `Newest` |
| `pageSize` | No | `5` | 1100 |
| `page` | No | `1` | 1-indexed |
**Smart keyword extraction:** if the query is >20 words or returns no results, the tool LLM-extracts short keyword phrases and retries. The `effectiveQuery` field in the response shows what was actually sent — `null` if the original query was used unchanged.
**Per-result fields used:** `assetId`, `title`, `previewUrl`, `thumbnailUrl`, `width`, `height`, `licenseModel`, `artist`, `collection`. The response also returns `searchRequestId` (used by the download step). Do not display `assetId` or `searchRequestId` to the user.
## Step 2 — Present Results, then STOP
```markdown
1. [<title>](<previewUrl>) — <artist>, <width>×<height>, <licenseModel>
```
Prefix the list with `Getty searched for: <effectiveQuery>` only if `effectiveQuery` is non-null and differs from the user's original query.
After the list: stop. No "Pick 1N", no "Reply with the number", no closing remark — Agentforce shows the selection UI on its own.
Do not download anything until the user replies with a selection.
---
# Download flow (separate invocation)
Run only when the user has selected a result from a prior search. Skip otherwise.
## Download (billed)
⚠️ **Each call spends a Getty download credit.** Only invoke after explicit user selection.
**MCP tool:** `download_stock_image` · **Timeout:** 60s
| Parameter | Required | Default | Notes |
|-------------------|----------|---------|-----------------------------------------------------------------------------------------|
| `assetId` | ✅ | — | From the prior `search_stock_images` result |
| `size` | No | `comp` | `comp` (web-quality composite, default), `medium_jpg`, `largest` (full res, can exceed 100 MB) |
| `searchRequestId` | No | — | The `searchRequestId` from the originating search response. Pass it through so the service can record the asset's keywords and link the download to the search. |
Use `largest` only when the user explicitly asks for full/original resolution.
The response yields `url`, `format`, `assetId`, `managedContentBodyId`, `byteCount`, and tracking IDs (`contentGenAiOutputId`, child IDs).
**Non-interactive mode** (scheduled/headless, no user present): pick `images[0]` from the search and note auto-selection in the report.
## Save locally
```bash
bash skills/stock-image-search/download-stock-image.sh \
--url "<url>" --id "<assetId>" --format "<format>" --preview
```
Pass `--output-dir <dir>` to override the default `stockimages/` directory. Use this script — not `curl`/`wget` — so the bearer-token retrieval and path-safety checks stay consistent across skills. Never resize or post-process; size at point of use via CSS (`width`, `height`, `object-fit`).
## Report
- Title and artist
- Local file path (printed by the script)
- `url` from the download response
- `managedContentBodyId`
- Attribution: `© Getty Images · {artist} · {collection}`
---
## Fallbacks & Errors
| Situation | Action |
|------------------------------------|-------------------------------------------------------------------------|
| `errorMessage` non-null | Show it; suggest a simpler query or different `searchType`/`orientation`|
| `images` empty | Suggest broader keywords; try `Editorial` vs `Creative` |
| MCP tools unavailable | Use the placeholder URL below — do not download or save |
| Download fails | Show error; confirm with user before retrying (each retry is billed) |
Placeholder (use directly as `src` / CSS `url()`):
```
https://cdn.scs.static.lightning.force.com/content/assets/d5222d4a11e6c2b735152d7eea824ce4/placeholder.svg
```
## Placeholder Policy
There is only one placeholder URL. Do not download it, modify it, or generate alternative placeholders using Python, ImageMagick, or any other tool. If the user asks for a placeholder of a specific size or format, tell them only this URL is available and direct them to use CSS to scale it at the point of use.

View File

@ -0,0 +1,77 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<EOF
Usage: $(basename "$0") --url <image_url> --id <asset_id> --format <output_format> [--output-dir <dir>] [--preview]
Required:
--url URL of the licensed image to download (from download_stock_image response)
--id Asset ID used as the output filename
--format Image format (jpg, jpeg, png, webp)
Optional:
--output-dir Directory to save the image (default: stockimages)
--preview Open the image in VS Code after download
EOF
exit 1
}
OUTPUT_DIR="stockimages"
PREVIEW=false
URL=""
ASSET_ID=""
FORMAT=""
while [[ $# -gt 0 ]]; do
case "$1" in
--url) URL="$2"; shift 2 ;;
--id) ASSET_ID="$2"; shift 2 ;;
--format) FORMAT="$2"; shift 2 ;;
--output-dir) OUTPUT_DIR="$2"; shift 2 ;;
--preview) PREVIEW=true; shift ;;
*) usage ;;
esac
done
if [[ -z "$URL" || -z "$ASSET_ID" || -z "$FORMAT" ]]; then
usage
fi
case "$FORMAT" in
jpg|jpeg|png|webp) ;;
*) echo "Error: invalid format '$FORMAT'. Must be jpg, jpeg, png, or webp." >&2; exit 1 ;;
esac
if [[ "$ASSET_ID" == */* || "$ASSET_ID" == *..* ]]; then
echo "Error: invalid asset ID '$ASSET_ID'. Must not contain '/' or '..'." >&2
exit 1
fi
if [[ ! "$URL" =~ ^https:// ]]; then
echo "Error: URL must start with https://" >&2
exit 1
fi
TARGET_ORG=$(sf config get target-org --json | jq -r '.result[0].value')
ACCESS_TOKEN=$(sf org display --target-org "$TARGET_ORG" --json | jq -r '.result.accessToken')
if [[ -z "$ACCESS_TOKEN" || "$ACCESS_TOKEN" == "null" ]]; then
echo "Error: failed to retrieve access token for org '$TARGET_ORG'" >&2
exit 1
fi
mkdir -p "$OUTPUT_DIR"
OUTPUT_FILE="${OUTPUT_DIR}/${ASSET_ID}.${FORMAT}"
if ! curl -f -H "Authorization: Bearer $ACCESS_TOKEN" -o "$OUTPUT_FILE" -- "$URL"; then
echo "Error: failed to download image from $URL" >&2
exit 1
fi
echo "$OUTPUT_FILE"
if [[ "$PREVIEW" == true ]]; then
code "$OUTPUT_FILE"
fi