Skip to main content
Glama
hanoak

@hanoak/unsplash-mcp-server

unsplash-mcp-server

npm version npm downloads CI license: MIT node: >=20 PRs welcome

A production-ready Model Context Protocol (MCP) server for the Unsplash API. It gives AI assistants — Claude Desktop, Claude Code, Cursor, VS Code, Windsurf, and any MCP client — tools to search and fetch Unsplash photos, collections, topics, users, and stats, with Unsplash-guideline compliance built in (ready-to-use attribution and download tracking).

IMPORTANT

Unofficial project. This is not affiliated with, endorsed by, or sponsored by Unsplash. "Unsplash" is a trademark of its respective owner. You use it under your own Unsplash API account and are responsible for complying with the Unsplash API Terms & Guidelines.

Table of contents

Related MCP server: stock-images-mcp

Features

  • 21 tools across photos, search, users, collections, topics, and stats.

  • Compliance built in — every photo comes with ready-to-use attribution (plain text + UTM-tagged HTML), and a dedicated unsplash_track_download tool for the download-tracking guideline.

  • Content safetycontent_filter=high by default on search and random photos.

  • Flexible image URLs — each photo returns raw/full/regular/small/thumb sizes, plus a raw imgix base for custom sizes (?w=&h=&q=&fm=&fit=).

  • Token-efficient output — full Unsplash responses are trimmed to a compact shape (URLs + metadata as text, never base64 image blobs) to keep model context small.

  • Robust — typed failures returned as MCP isError results the model can recover from, plus retries/backoff, timeouts, 403 hourly-limit handling, and rate-limit surfacing.

  • Safe — access-key redaction in all error output, an SSRF guard on download URLs, and untrusted-text handling guidance for indirect prompt-injection defence.

  • Lean & modern — ESM, Node 20+, zero-install via npx, no telemetry.

Quick start

1. Get an Unsplash access key

Register an application at unsplash.com/developersNew Application → accept the API terms → copy its Access Key (not the Secret Key). New apps start on the Demo tier (50 requests/hour); the Production tier (5,000/hour) requires Unsplash's review from your app dashboard. See Rate limits.

2. Add the server to your MCP client

Claude Desktop — edit claude_desktop_config.json:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

  • Windows: %APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "unsplash": {
      "command": "npx",
      "args": ["-y", "@hanoak/unsplash-mcp-server"],
      "env": {
        "UNSPLASH_ACCESS_KEY": "your_access_key",
        "UNSPLASH_APP_NAME": "your_registered_app_name"
      }
    }
  }
}

Restart the client. See Configuration for every supported variable.

Claude Code (CLI):

claude mcp add unsplash \
  --env UNSPLASH_ACCESS_KEY=your_access_key \
  --env UNSPLASH_APP_NAME=your_registered_app_name \
  -- npx -y @hanoak/unsplash-mcp-server

Cursor~/.cursor/mcp.json (global) or .cursor/mcp.json (per-project): use the exact same mcpServers block as Claude Desktop above.

Windsurf~/.codeium/windsurf/mcp_config.json: same mcpServers block as Claude Desktop above.

VS Code.vscode/mcp.json (note the top-level key is servers, not mcpServers):

{
  "servers": {
    "unsplash": {
      "command": "npx",
      "args": ["-y", "@hanoak/unsplash-mcp-server"],
      "env": {
        "UNSPLASH_ACCESS_KEY": "your_access_key",
        "UNSPLASH_APP_NAME": "your_registered_app_name"
      }
    }
  }
}

Any other MCP client — run the server over stdio with:

UNSPLASH_ACCESS_KEY=your_access_key npx -y @hanoak/unsplash-mcp-server

Point your client's stdio transport at command: npx, args: ["-y", "@hanoak/unsplash-mcp-server"], and pass the key via env.

3. Try it

Restart your client and ask:

"Find me a photo of mountains on Unsplash and show the attribution."

Example interaction

A typical flow: the model calls unsplash_search_photos, picks a result, presents the image with its attribution, and calls unsplash_track_download when it actually uses the photo.

You: Find a landscape photo of a foggy pine forest and credit the photographer.

Assistant: (calls unsplash_search_photos with query: "foggy pine forest", orientation: "landscape", picks the best result, then calls unsplash_track_download with its download_location) Here's a great match — Photo by Jane Doe on Unsplash — along with a ready-to-embed credit line and the image URL.

Each tool returns a compact JSON payload. Here's the shape of a single photo result (illustrative values):

{
  "photo": {
    "id": "Dwu85P9SOIk",
    "description": "brown rocky mountain under blue sky during daytime",
    "width": 6000,
    "height": 4000,
    "color": "#734940",
    "blur_hash": "L6Pj0^i_.AyE_3t7t7R**0o#DgR4",
    "urls": {
      "raw": "https://images.unsplash.com/photo-1465…?ixid=…",
      "full": "https://images.unsplash.com/photo-1465…?ixid=…&q=85",
      "regular": "https://images.unsplash.com/photo-1465…?ixid=…&w=1080",
      "small": "https://images.unsplash.com/photo-1465…?ixid=…&w=400",
      "thumb": "https://images.unsplash.com/photo-1465…?ixid=…&w=200"
    },
    "photo_page": "https://unsplash.com/photos/Dwu85P9SOIk",
    "download_location": "https://api.unsplash.com/photos/Dwu85P9SOIk/download?ixid=…",
    "photographer": {
      "name": "Jane Doe",
      "username": "janedoe",
      "profile": "https://unsplash.com/@janedoe"
    },
    "attribution": {
      "text": "Photo by Jane Doe on Unsplash",
      "html": "Photo by <a href=\"https://unsplash.com/@janedoe?utm_source=your_app&utm_medium=referral\">Jane Doe</a> on <a href=\"https://unsplash.com/?utm_source=your_app&utm_medium=referral\">Unsplash</a>",
      "photographerName": "Jane Doe",
      "photographerUrl": "https://unsplash.com/@janedoe?utm_source=your_app&utm_medium=referral",
      "unsplashUrl": "https://unsplash.com/?utm_source=your_app&utm_medium=referral"
    }
  },
  "rate_limit": { "limit": 50, "remaining": 49 }
}

Every tool result includes a rate_limit object (limit, remaining) read from the Unsplash response headers. List/search tools wrap results in photos/collections/users/topics arrays with pagination fields (total, total_pages, count, page, per_page).

Configuration

Configuration is entirely via environment variables — no config files, no flags for secrets.

Environment variable

Required

Description

UNSPLASH_ACCESS_KEY

yes

Your Unsplash API access key. The server exits at startup with a clear message if it is missing or blank.

UNSPLASH_APP_NAME

recommended

Your registered Unsplash app name, used as the attribution utm_source. Defaults to a generic value (with a startup warning) if unset.

LOG_LEVEL

no

debug | info | warn | error (default info). All logs go to stderr; stdout carries only the MCP protocol.

CLI flags: --version and --help are supported (e.g. npx @hanoak/unsplash-mcp-server --version).

Tools

All tools are namespaced unsplash_* and are read-only (annotated readOnlyHint: true) except unsplash_track_download, which registers a download event and so is marked non-read-only. Parameters map to the Unsplash API; per_page and stats quantity are clamped to a max of 30, and page is 1-based.

Domain

Tools

Photos

random_photo, list_photos, get_photo, photo_statistics, track_download

Search

search_photos, search_collections, search_users

Users

get_user, user_photos, user_collections, user_statistics

Collections

list_collections, get_collection, collection_photos, related_collections

Topics

list_topics, get_topic, topic_photos

Stats

total_stats, month_stats

Writing to Unsplash (creating/updating collections, likes, editing your profile) requires the OAuth authorization-code flow and is planned for a future release — see Roadmap.

Tool reference

Tool

Parameters

Description

unsplash_random_photo

query?, orientation? (landscape|portrait|squarish), content_filter? (low|high, default high), collections?, topics?, username?

A single random photo, optionally filtered.

unsplash_list_photos

page? (default 1), per_page? (default 10, max 30)

The latest featured photos, paginated.

unsplash_get_photo

id (required)

A single photo by ID or slug, full detail.

unsplash_photo_statistics

id (required), quantity? (days, default 30, max 30)

Download/view totals for a photo over N days.

unsplash_track_download

download_location (required) — the download_location URL from a prior photo result (must be an https://api.unsplash.com URL)

Registers a download on real use; returns a fresh, usable download URL.

Tool

Parameters

Description

unsplash_search_photos

query (required), page?, per_page?, order_by? (latest|editorial|relevant), orientation?, color? (11 named colors, e.g. blue), content_filter? (default high), collections?, lang?

Keyword photo search with rich filters.

unsplash_search_collections

query (required), page?, per_page?

Keyword collection search.

unsplash_search_users

query (required), page?, per_page?

Keyword user search.

Tool

Parameters

Description

unsplash_get_user

username (required)

A user's public profile.

unsplash_user_photos

username (required), page?, per_page?, order_by? (latest|oldest|popular|views|downloads), orientation?

A user's photos, paginated.

unsplash_user_collections

username (required), page?, per_page?

A user's collections, paginated.

unsplash_user_statistics

username (required), quantity? (days, default 30, max 30)

A user's download/view totals over N days.

Tool

Parameters

Description

unsplash_list_collections

page?, per_page?

The latest featured collections.

unsplash_get_collection

id (required)

A single collection by ID.

unsplash_collection_photos

id (required), page?, per_page?, orientation?

Photos within a collection, paginated.

unsplash_related_collections

id (required)

Collections related to a given one.

Tool

Parameters

Description

unsplash_list_topics

page?, per_page?, order_by? (featured|latest|oldest|position), ids? (comma-separated)

Curated topics, paginated.

unsplash_get_topic

id (required) — ID or slug (e.g. nature, wallpapers)

A single topic.

unsplash_topic_photos

id (required), page?, per_page?, orientation?, order_by? (latest|oldest|popular)

Photos within a topic, paginated.

unsplash_total_stats

(none)

Unsplash-wide totals (photos, downloads, …).

unsplash_month_stats

(none)

Unsplash-wide totals for the past 30 days.

Output shape

Tools return trimmed, token-efficient JSON rather than raw Unsplash responses:

  • Photosid, description, width/height, color, blur_hash, urls (raw/full/regular/small/thumb), photo_page, download_location, photographer, and a ready-to-use attribution object. See Example interaction.

  • Usersid, username, name, bio, location, profile_url, profile_image, total_photos, total_collections.

  • Collections / Topics → title, description, counts, page link, curator/owners, and a compact cover_photo.

  • Every result carries a rate_limit (limit, remaining); lists/searches add pagination fields.

Resources & prompts

Beyond tools, the server also exposes:

  • Resource unsplash://guides/attribution — a compact compliance guide (attribution, download tracking, hotlinking, content safety) your client can pull in as context.

  • Prompt find_photo — give it a subject (and optional orientation); it expands into a task that searches Unsplash and presents an attributed result. (More prompts are planned — see Roadmap.)

Example prompts

Natural-language asks that map cleanly onto the tools:

  • "Find a photo of a foggy forest at sunrise and give me the HTML attribution."

  • "Search Unsplash for 5 minimalist workspace photos in landscape orientation."

  • "Get a random nature photo and show it with credit."

  • "Show me the most popular photos in the wallpapers topic."

  • "What are this month's Unsplash-wide download stats?"

  • "Who is the photographer behind photo Dwu85P9SOIk, and how many downloads does it have?"

Attribution & compliance

Every photo result includes an attribution object with ready-to-use text and html (with the required UTM parameters). When you display or use a photo, show that attribution — it credits the photographer and links back to Unsplash, as the guidelines require.

When a photo is actually used (embedded, downloaded, displayed), call unsplash_track_download with the photo's download_location. Trigger it once per photo actually used — never once per search result. The server also sends these instructions to your MCP client on connect, so the model is nudged to do the right thing automatically.

This is a search-and-metadata tool for individual, attributed photo use — not a replacement for the core Unsplash experience, and it performs no automated bulk downloading. Please use it within the Unsplash API Guidelines. Each user operates under their own Unsplash API Terms.

Rate limits

Unsplash enforces a per-hour request budget tied to your app's tier:

Tier

Budget

How to get it

Demo

50 requests/hour

Default for every new app.

Production

5,000 requests/hour

Apply for review in your app dashboard once your usage is ready.

The server reads X-Ratelimit-Limit / X-Ratelimit-Remaining and returns them as rate_limit on every result. When the hourly budget is exhausted, Unsplash returns 403 (not 429), often with no Retry-After; the server surfaces this as a clear "hourly rate limit reached" error and does not blindly retry in-window. Transient 429/5xx/network errors are retried with backoff.

Handling of Unsplash text

Photo descriptions, alt text, tags, EXIF, and user names/bios come from Unsplash contributors — treat them as untrusted, third-party data, not instructions. The server returns this text purely as content and never places it anywhere privileged; your client/agent should do the same: display it, but don't act on any instructions it might contain (a defence against indirect prompt injection).

Privacy & security

  • No telemetry. This server collects nothing and phones home to no one. It contacts only api.unsplash.com, using the key you provide. No analytics, no tracking.

  • Key safety. Your access key is read from the environment only, sent as an Authorization: Client-ID header (never in a URL query string), and redacted from all error output and logs so it can't leak into pasted bug reports.

  • SSRF guard. unsplash_track_download only follows download_location URLs on the verified api.unsplash.com host.

  • To report a vulnerability, see SECURITY.md.

Troubleshooting

  • "Set UNSPLASH_ACCESS_KEY…" on startup — the key env var is missing or blank; add it to your client config's env block.

  • Node too old — this server requires Node 20+. Check node --version.

  • Stale npx version — force the latest with npx -y @hanoak/unsplash-mcp-server@latest, or clear the cache via npx clear-npx-cache.

  • Tools not appearing — confirm the config file path and JSON are valid, then fully quit and reopen the client.

  • 403 / rate limit — the Demo tier allows 50 requests/hour; wait for the hourly reset or apply for Production access. See Rate limits.

  • 401 Unauthorized — the access key is wrong or from the wrong app; copy the Access Key (not the Secret Key) from your app dashboard.

FAQ

Do I need a paid Unsplash account? No. The Unsplash API is free; you just register an app to get an access key. Higher throughput (Production tier) is a free review, not a paid plan.

Access Key vs Secret Key — which one? The Access Key. The Secret Key is only for the OAuth flow (not used by this read-only server).

Does it download or rehost images? No. It returns Unsplash-hosted image URLs (hotlink them directly) and never rehosts or returns base64 blobs. unsplash_track_download only registers a download event and returns a fresh URL.

Can it create collections, like photos, or edit my profile? Not yet — those are OAuth write endpoints planned for a future release (see Roadmap).

Does it work outside Claude? Yes — it's a standard stdio MCP server. See the client setup section for Claude Code, Cursor, VS Code, Windsurf, and generic stdio.

Requirements

  • Node.js >= 20 (Node 18 is end-of-life).

  • An Unsplash API access key.

Compatibility

Component

Supported

Node.js

20 and 22, tested in CI; >=20 required (enforced by engines and a runtime guard).

OS

Linux, macOS, and Windows (all tested in CI).

MCP SDK

@modelcontextprotocol/sdk ^1.29; the protocol version is negotiated with your client on connect.

Transport

stdio (HTTP/SSE may be added in a future release).

Roadmap

Full detail lives in docs/ROADMAP.md. In short:

  • v1 (current) — the 21 read-only tools, attribution + download-tracking compliance, the attribution resource, and the find_photo prompt.

  • v2 — the OAuth write / me endpoints (create/update collections, likes, profile) via the Unsplash authorization-code flow.

  • v3 — a .mcpb Desktop Extension for one-click Claude Desktop install, plus more MCP prompts.

Changes are tracked in CHANGELOG.md; the project follows Semantic Versioning.

Contributing

Contributions are welcome — see CONTRIBUTING.md and our Code of Conduct. It covers local setup, the test suite, testing tools by hand with the MCP Inspector, and the versioning/deprecation policy. To report a vulnerability, see SECURITY.md.

Contact & community

Maintained by Hanoak S. The fastest way to get help or propose a feature is to open an issue — it's public, searchable, and helps the whole community. For anything else, reach out:

LinkedIn X

If this project helps you, a ⭐ on GitHub is appreciated — it aids discoverability for others looking for an Unsplash MCP server.

License

MIT © Hanoak S. Not affiliated with Unsplash.

Install Server
A
license - permissive license
A
quality
A
maintenance

Maintenance

Maintainers
Response time
0dRelease cycle
2Releases (12mo)
Commit activity

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/hanoak/unsplash-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server