Skill: joeru/claw2immich (version 1.0.0)

--- name: claw2immich description: Work with Immich photo library via MCP (claw2immich) - search photos by people, dates, locations, albums. Download assets via shared links. Handles multi-person search, CLIP smart search, and metadata queries. homepage: https://github.com/JoeRu/claw2immich metadata: openclaw: emoji: 📸 requires: mcp: [immich] --- # Immich Photo Library (via claw2immich) Work with your Immich photo library via the **claw2immich** MCP server. Search by people, dates, locations, and albums. Download photos via shared links or inline base64. 249 tools available from the full Immich OpenAPI spec. ## Prerequisites - **Immich instance** running (https://immich.app) - **claw2immich MCP server** installed and running - Repository: https://github.com/JoeRu/claw2immich - Follow setup instructions in the repo README - MCP server configured in `config/mcporter.json`: ```json { "mcpServers": { "immich": { "baseUrl": "http://your-claw2immich-host:port/sse" } } } ``` ## Key Tools | Tool | Description | |------|-------------| | `immich_searchassets` | Metadata search (date, people, location, etc.) | | `immich_searchsmart` | CLIP-based natural language search | | `immich_searchperson` | Find person by name | | `immich_getassetinfo` | Get full asset details including `web_url` | | `immich_viewasset` | Get thumbnail/preview as base64 (WebP) | | `downloadAsset` | Download asset via shared link (default) or inline base64 | | `immich_getallpeople` | List all people | | `immich_getallalbums` | List all albums | | `immich_createsharedlink` | Create shared link for album/assets | | `tool_access_report` | Check which tools are available | ## Quick Start ### Find people by name ``` mcporter call immich immich_searchperson query_name="Maria" ``` ### Search photos with multiple people (AND logic) ``` mcporter call immich immich_searchassets \ 'body_personIds=["person-uuid-1","person-uuid-2"]' \ body_order=desc body_size=10 ``` ### CLIP smart search (natural language) ``` mcporter call immich immich_searchsmart \ body_query="sunset at the beach" body_size=5 ``` ### Get asset info (includes web_url) ``` mcporter call immich immich_getassetinfo path_id=<asset-uuid> ``` ### Download a photo (shared link) ``` mcporter call immich downloadAsset asset_id=<asset-uuid> ``` Returns a short-lived shared link (30 min, no auth needed). ### Get thumbnail for display ``` mcporter call immich immich_viewasset path_id=<asset-uuid> query_size=thumbnail ``` Returns `{encoding: "base64", content_type: "image/webp", size_bytes: ..., data: "..."}`. ## Web URLs Tool responses for assets, albums, people, and places include a `web_url` field: - Assets: `https://<domain>/photos/<asset-id>` - Albums: `https://<domain>/albums/<album-id>` - People: `https://<domain>/people/<person-id>` This requires `IMMICH_EXTERNAL_DOMAIN` to be configured on the server. ## Common Workflows ### "Show me recent photos of X and Y together" 1. **Find person IDs:** ``` mcporter call immich immich_searchperson query_name="Alice" mcporter call immich immich_searchperson query_name="Bob" ``` 2. **Search photos (AND logic):** ``` mcporter call immich immich_searchassets \ 'body_personIds=["alice-id","bob-id"]' \ body_order=desc body_size=10 ``` 3. **Display a photo:** ``` mcporter call immich immich_viewasset path_id=<asset-id> query_size=thumbnail ``` Decode base64 data, save as .webp, send to user. ### "Find vacation photos from summer 2024" ``` mcporter call immich immich_searchassets \ body_createdAfter="2024-06-01T00:00:00Z" \ body_createdBefore="2024-08-31T23:59:59Z" \ body_city="Barcelona" body_order=desc ``` ### "Download a photo" ``` mcporter call immich downloadAsset asset_id=<asset-uuid> ``` Response: ```json { "delivery_mode": "shared_link", "download_url": "https://immich.example.com/share/<token>", "expires_in_minutes": 30, "requires_auth": false } ``` The shared link can be sent directly to users — no auth required. ### Displaying photos in chat 1. Get thumbnail via `immich_viewasset` (query_size=thumbnail, typically < 30KB) 2. Decode the base64 `data` field 3. Save as `.webp` file 4. Send via messaging tool **Note:** `preview` size may exceed the 64KB MCP transport limit. Use `thumbnail` for reliable delivery. ## Key Parameters ### immich_searchassets (POST /api/search/assets) **Filtering:** - `body_personIds: ["uuid1", "uuid2"]` — Photos with these people (AND) - `body_city: "string"` — Filter by city - `body_country: "string"` — Filter by country - `body_createdAfter: "ISO8601"` — Minimum date - `body_createdBefore: "ISO8601"` — Maximum date - `body_isFavorite: boolean` — Only favorites - `body_albumIds: ["uuid"]` — Filter by albums **Sorting & Pagination:** - `body_order: "desc"` — Newest first - `body_order: "asc"` — Oldest first - `body_size: number` — Limit results - `body_page: number` — Page number ### immich_searchsmart (POST /api/search/smart) - `body_query: "string"` — Natural language query (CLIP-based) - `body_size: number` — Limit results - Same filter parameters as searchassets ### downloadAsset - `asset_id: "uuid"` — Asset to download Delivery mode is controlled server-side via `IMMICH_DOWNLOAD_ASSET_DELIVERY`: - `shared_link` (default): Returns a tokenized shared link (30 min TTL, no auth) - `inline_base64`: Returns base64-encoded file data (limited by 64KB transport) - `immich_link`: Returns direct Immich URL (requires auth) ### immich_viewasset (GET /api/assets/{id}/thumbnail) - `path_id: "uuid"` — Asset ID - `query_size: "thumbnail"|"preview"` — Image size Returns structured base64 response. Use `thumbnail` to stay under transport limits. ## Important Patterns ### Multi-Person Search (AND) ✅ **Correct:** Array in `body_personIds` ```json {"body_personIds": ["person-1", "person-2"]} ``` ❌ **Wrong:** Separate calls (that's OR, not AND) ### Parameter Prefixes All OpenAPI tool parameters use prefixes: - `path_<name>` — Path parameters - `query_<name>` — Query parameters - `body_<name>` — Body parameters (for POST endpoints) ### Date Filtering Always use ISO 8601: `"2024-01-15T00:00:00Z"` ### 64KB Transport Limit MCP responses are truncated at 64KB. This affects: - `downloadAsset` with `inline_base64` mode (use `shared_link` instead) - `immich_viewasset` with `query_size=preview` (use `thumbnail` instead) - Large search results (reduce `body_size`) ## Access Profiles Set `IMMICH_PROFILE` on the server to restrict tools: - `read_only` — Only GET endpoints (search, browse) - `read_write` — Read + write (upload, update, delete) - `full_scope` — Everything including admin Use `tool_access_report` to check available tools. ## Troubleshooting **No results with multiple people:** - Verify person IDs (search each person first) - Add `body_isArchived: false` if photos might be archived **downloadAsset returns error:** - Check `tool_access_report` for permissions - Shared link creation requires write access to shared-links API **Thumbnail too large:** - Use `query_size=thumbnail` instead of `preview` - Thumbnails are typically 5-25 KB (WebP) **MCP call fails:** - Verify server is running: `mcporter call immich ping_server` - Check config: `mcporter list immich` ## Reference - **Immich:** https://immich.app - **claw2immich:** https://github.com/JoeRu/claw2immich - **Immich API docs:** https://immich.app/docs/api/

path: README.md
content: # Immich Skill for OpenClaw

OpenClaw agent skill for working with Immich photo libraries via the **claw2immich** MCP server.

## What is this?

This skill teaches OpenClaw agents how to search and interact with your Immich photo library using natural language queries like:
- "Show me photos of Alice and Bob together"
- "Find vacation photos from last summer"
- "Photos taken in Paris"

## Prerequisites

1. **Immich instance** running (https://immich.app)
2. **claw2immich MCP server** installed and running
   - Repository: https://github.com/JoeRu/claw2immich
   - Follow the setup instructions in the claw2immich README
3. **mcporter** configured in OpenClaw (included by default)

## Installation

### 1. Install claw2immich MCP Server

Follow the installation guide at: https://github.com/JoeRu/claw2immich

Make sure the MCP server is running and accessible from your OpenClaw host.

### 2. Install this skill

**Via ClawHub:**
```bash
clawhub install immich
```

**Manual:**
```bash
# Copy to your workspace skills directory
cp -r immich ~/.openclaw/workspace/skills/
```

### 3. Configure MCP Server

Add the claw2immich server to `~/.openclaw/workspace/config/mcporter.json`:

```json
{
  "mcpServers": {
    "immich": {
      "baseUrl": "http://your-claw2immich-host:port/sse"
    }
  }
}
```

**Replace:**
- `your-claw2immich-host` - Hostname/IP where claw2immich is running
- `port` - claw2immich server port (check claw2immich config)

### 4. Configure Photo URLs (Optional but Recommended)

For displaying/downloading photos, add your Immich server URL to `TOOLS.md`:

```markdown
### Immich
- Web-UI: http://your-immich-server:2283
- Photo URLs: http://your-immich-server:2283/api/assets/{id}/thumbnail
```

This helps the agent construct photo URLs when needed.

### 5. Verify

```bash
mcporter list
```

Should show `immich` server with ~248 tools available.

### 6. Restart Gateway

```bash
openclaw gateway restart
```

The skill will be loaded automatically on next agent interaction.

## Usage

Once installed, your agent can handle natural language photo queries:

**Examples:**
- "Show me the newest photo with Alice and Bob together"
- "Find all photos from our Paris vacation in summer 2024"
- "Search for pictures of my dog"
- "Show me favorite photos from last year"

The agent will:
1. Search for people by name to get their IDs
2. Use the correct MCP calls with proper parameters
3. Handle multi-person searches (AND logic)
4. Format results in a readable way

## Example Scripts

The skill includes two example shell scripts:

### Find people together
```bash
cd ~/.openclaw/workspace/skills/immich
./examples/find-people-together.sh "Alice" "Bob" 10
```

### Find by date range
```bash
./examples/find-by-date.sh "2024-06-01" "2024-08-31" "Paris" 20
```

### Get photo URLs
```bash
./examples/get-photo-urls.sh "Alice" 5 "http://your-immich-server:2283"
```

Generates thumbnail and original URLs for photos, with optional download.

## Documentation

- **[SKILL.md](./SKILL.md)** - Complete API reference for agents
  - Common workflows
  - Parameter documentation
  - Response structure
  - Tips and troubleshooting

## Requirements

- **Immich:** Photo management system (https://immich.app)
- **claw2immich:** MCP server for Immich (https://github.com/JoeRu/claw2immich)
- **mcporter:** MCP client (included in OpenClaw)
- **jq:** JSON processor (for example scripts, optional)

## Troubleshooting

### MCP server not found
```bash
# Check if server is running
mcporter list

# Should show:
# - immich (248 tools, ...)
```

If not listed, verify:
1. claw2immich is running
2. `mcporter.json` config is correct
3. Network connectivity to claw2immich host

### No photos found
- Verify Immich has indexed your photos
- Check person names (fuzzy search, try partial names)
- Photos might be archived: include `body_isArchived: false` in queries

## Contributing

This skill is designed to be generic and reusable.

**To contribute:**
1. Keep examples generic (no personal names/IDs)
2. Document new patterns in SKILL.md
3. Test with different Immich/claw2immich versions
4. Submit improvements via pull request

## License

MIT - Free to use, modify, and distribute

## Links

- **Immich:** https://immich.app
- **claw2immich MCP Server:** https://github.com/JoeRu/claw2immich
- **OpenClaw:** https://openclaw.ai
- **ClawHub:** https://clawhub.com


path: examples/find-by-date.sh
content: #!/usr/bin/env bash
# Example: Find photos by date range
# Usage: ./find-by-date.sh "2024-06-01" "2024-08-31" [city] [limit]

set -euo pipefail

START_DATE="${1:-}"
END_DATE="${2:-}"
CITY="${3:-}"
LIMIT="${4:-20}"

if [[ -z "$START_DATE" ]] || [[ -z "$END_DATE" ]]; then
  echo "Usage: $0 <start-date> <end-date> [city] [limit]"
  echo "Example: $0 2024-06-01 2024-08-31 Paris 10"
  echo ""
  echo "Dates in YYYY-MM-DD format"
  exit 1
fi

# Convert to ISO 8601 with timezone
START_ISO="${START_DATE}T00:00:00Z"
END_ISO="${END_DATE}T23:59:59Z"

echo "📅 Searching photos from $START_DATE to $END_DATE"
[[ -n "$CITY" ]] && echo "📍 Location: $CITY"
echo ""

# Build query
QUERY="{
  \"body_createdAfter\": \"$START_ISO\",
  \"body_createdBefore\": \"$END_ISO\",
  \"query_order\": \"desc\",
  \"query_size\": $LIMIT"

if [[ -n "$CITY" ]]; then
  QUERY="$QUERY,
  \"body_city\": \"$CITY\""
fi

QUERY="$QUERY
}"

# Search
RESULTS=$(mcporter call immich.immich_searchassets \
  --args "$QUERY" \
  --output json 2>/dev/null)

TOTAL=$(echo "$RESULTS" | jq -r '.assets.total // 0')
COUNT=$(echo "$RESULTS" | jq -r '.assets.count // 0')

echo "Results: $COUNT of $TOTAL total matches"
echo ""

if [[ "$COUNT" -eq 0 ]]; then
  echo "No photos found in this date range."
  exit 0
fi

# Display grouped by date
echo "📷 Photos by date:"
echo "$RESULTS" | jq -r '.assets.items[] | 
  "\(.fileCreatedAt[:10]) - \(.originalFileName)"' | 
  awk '{
    if ($1 != prev) {
      if (prev != "") print ""
      prev = $1
      print "  " $1 ":"
    }
    sub(/^[^ ]+ - /, "    • ")
    print
  }'

echo ""
echo "Summary:"
echo "$RESULTS" | jq -r '.assets.items | group_by(.fileCreatedAt[:10]) | 
  map({date: .[0].fileCreatedAt[:10], count: length}) | 
  .[] | "  \(.date): \(.count) photo(s)"'


path: examples/find-people-together.sh
content: #!/usr/bin/env bash
# Example: Find photos with multiple people together
# Usage: ./find-people-together.sh "Alice" "Bob" [limit]

set -euo pipefail

PERSON1="${1:-}"
PERSON2="${2:-}"
LIMIT="${3:-10}"

if [[ -z "$PERSON1" ]] || [[ -z "$PERSON2" ]]; then
  echo "Usage: $0 <person1> <person2> [limit]"
  echo "Example: $0 Alice Bob 5"
  exit 1
fi

echo "🔍 Searching for people..."

# Find first person
echo "Finding: $PERSON1"
PERSON1_DATA=$(mcporter call immich.immich_searchperson \
  query_name="$PERSON1" \
  --output json 2>/dev/null)

PERSON1_ID=$(echo "$PERSON1_DATA" | jq -r '.[0].id // empty')

if [[ -z "$PERSON1_ID" ]]; then
  echo "❌ Person not found: $PERSON1"
  exit 1
fi

echo "✓ Found: $PERSON1 (ID: $PERSON1_ID)"

# Find second person
echo "Finding: $PERSON2"
PERSON2_DATA=$(mcporter call immich.immich_searchperson \
  query_name="$PERSON2" \
  --output json 2>/dev/null)

PERSON2_ID=$(echo "$PERSON2_DATA" | jq -r '.[0].id // empty')

if [[ -z "$PERSON2_ID" ]]; then
  echo "❌ Person not found: $PERSON2"
  exit 1
fi

echo "✓ Found: $PERSON2 (ID: $PERSON2_ID)"

# Search photos with both people
echo ""
echo "📸 Searching photos with both people together..."

RESULTS=$(mcporter call immich.immich_searchassets \
  --args "{
    \"body_personIds\": [\"$PERSON1_ID\", \"$PERSON2_ID\"],
    \"query_order\": \"desc\",
    \"query_size\": $LIMIT
  }" \
  --output json 2>/dev/null)

TOTAL=$(echo "$RESULTS" | jq -r '.assets.total // 0')
COUNT=$(echo "$RESULTS" | jq -r '.assets.count // 0')

echo ""
echo "Results: $COUNT of $TOTAL total matches"
echo ""

if [[ "$COUNT" -eq 0 ]]; then
  echo "No photos found with both $PERSON1 and $PERSON2 together."
  exit 0
fi

# Display results
echo "📷 Photos:"
echo "$RESULTS" | jq -r '.assets.items[] | "  - \(.originalFileName) (\(.fileCreatedAt[:10]))"'

# Optionally show most recent photo details
echo ""
echo "Most recent photo:"
echo "$RESULTS" | jq -r '.assets.items[0] | "  File: \(.originalFileName)\n  Date: \(.fileCreatedAt)\n  Path: \(.originalPath)\n  ID: \(.id)"'


path: examples/get-photo-urls.sh
content: #!/usr/bin/env bash
# Example: Get photo URLs for assets
# Usage: ./get-photo-urls.sh <person-name> [limit] [immich-server-url]

set -euo pipefail

PERSON="${1:-}"
LIMIT="${2:-5}"
IMMICH_SERVER="${3:-http://joesnuc:2283}"

if [[ -z "$PERSON" ]]; then
  echo "Usage: $0 <person-name> [limit] [immich-server-url]"
  echo "Example: $0 Alice 5 http://joesnuc:2283"
  exit 1
fi

echo "🔍 Searching for: $PERSON"

# Find person
PERSON_DATA=$(mcporter call immich.immich_searchperson \
  query_name="$PERSON" \
  --output json 2>/dev/null)

PERSON_ID=$(echo "$PERSON_DATA" | jq -r '.[0].id // empty')

if [[ -z "$PERSON_ID" ]]; then
  echo "❌ Person not found: $PERSON"
  exit 1
fi

PERSON_NAME=$(echo "$PERSON_DATA" | jq -r '.[0].name')
echo "✓ Found: $PERSON_NAME (ID: $PERSON_ID)"

# Search photos
echo ""
echo "📸 Fetching latest $LIMIT photos..."

RESULTS=$(mcporter call immich.immich_searchassets \
  --args "{
    \"body_personIds\": [\"$PERSON_ID\"],
    \"query_order\": \"desc\",
    \"query_size\": $LIMIT
  }" \
  --output json 2>/dev/null)

COUNT=$(echo "$RESULTS" | jq -r '.assets.count // 0')

if [[ "$COUNT" -eq 0 ]]; then
  echo "No photos found for $PERSON_NAME"
  exit 0
fi

echo "Found $COUNT photo(s)"
echo ""
echo "📷 Photo URLs:"
echo ""

# Generate URLs for each photo
echo "$RESULTS" | jq -r --arg server "$IMMICH_SERVER" '.assets.items[] | 
  "File: \(.originalFileName) (\(.fileCreatedAt[:10]))\n" +
  "  Thumbnail: \($server)/api/assets/\(.id)/thumbnail\n" +
  "  Original:  \($server)/api/assets/\(.id)/original\n" +
  "  ID:        \(.id)\n"'

# Optional: Download first photo
echo ""
read -p "Download first photo? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
  FIRST_ID=$(echo "$RESULTS" | jq -r '.assets.items[0].id')
  FIRST_NAME=$(echo "$RESULTS" | jq -r '.assets.items[0].originalFileName')
  
  echo "Downloading: $FIRST_NAME"
  curl -o "$FIRST_NAME" "${IMMICH_SERVER}/api/assets/${FIRST_ID}/original"
  echo "✓ Saved to: $FIRST_NAME"
fi


path: skill.json
content: {
  "name": "immich",
  "version": "1.0.0",
  "description": "Work with Immich photo library via claw2immich MCP server - search photos by people, dates, locations, albums",
  "author": "OpenClaw Community",
  "license": "MIT",
  "homepage": "https://github.com/JoeRu/claw2immich",
  "repository": "https://github.com/openclaw/skills-immich",
  "keywords": [
    "photos",
    "immich",
    "claw2immich",
    "mcp",
    "search",
    "media",
    "gallery"
  ],
  "requirements": {
    "mcp": {
      "servers": ["immich"]
    },
    "external": {
      "claw2immich": "https://github.com/JoeRu/claw2immich",
      "immich": "https://immich.app"
    },
    "tools": ["jq"]
  },
  "files": [
    "SKILL.md",
    "README.md",
    "skill.json",
    "examples/find-people-together.sh",
    "examples/find-by-date.sh",
    "examples/get-photo-urls.sh"
  ],
  "examples": [
    {
      "title": "Find photos with multiple people",
      "description": "Search for photos where specific people appear together",
      "command": "./examples/find-people-together.sh Alice Bob 10"
    },
    {
      "title": "Find photos by date range",
      "description": "Search photos within a specific date range, optionally filtered by location",
      "command": "./examples/find-by-date.sh 2024-06-01 2024-08-31 Paris 20"
    },
    {
      "title": "Get photo URLs and download",
      "description": "Generate thumbnail and original URLs for photos, optionally download",
      "command": "./examples/get-photo-urls.sh Alice 5 http://joesnuc:2283"
    }
  ],
  "tags": {
    "category": "media",
    "difficulty": "intermediate",
    "mcp-integration": true
  }
}


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (CRITICAL)