{
  "markdown": "# pexafy-mcp\n\n[![CI](https://github.com/Pexafy/pexafy-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Pexafy/pexafy-mcp/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\n**Stock photo search for AI assistants.** An [MCP](https://modelcontextprotocol.io)\nserver that lets Claude, ChatGPT or any MCP client search a library of royalty-free\nimages — by describing a scene in plain language, from an example image, or \"more\nlike this\" — and render the results as a thumbnail grid **inside the conversation**.\n\n> Remote MCP, OAuth, no API key to paste, 3 tools, images rendered inline.\n\nThe product page, with the same steps in twenty-three languages, is at\n[pexafy.com/mcp](https://pexafy.com/mcp/).\n\n![The Pexafy result grid, rendered inline in a Claude conversation](docs/screenshot-grid.jpg)\n\n---\n\n## Use it (nothing to install)\n\nA hosted server runs at:\n\n```\nhttps://mcp.pexafy.com/mcp\n```\n\nIt speaks Streamable HTTP and authenticates with **OAuth 2.1** — you sign in to\nPexafy in a browser window and the connector receives its own credentials. There is\nno API key to generate, paste into a JSON file, or rotate later.\n\n### Claude (web and desktop)\n\n1. Open **Settings → Connectors** (on Team/Enterprise, an owner adds it once under\n   **Organization settings → Connectors**).\n2. Click **Add custom connector**.\n3. Paste `https://mcp.pexafy.com/mcp` and confirm.\n4. Sign in to Pexafy in the window that opens. Done — ask Claude for a photo.\n\n### Claude Code\n\n```bash\nclaude mcp add --transport http pexafy https://mcp.pexafy.com/mcp\n```\n\n### Any other MCP client\n\nPoint it at the same URL with the `streamable-http` transport. Clients that don't\nimplement OAuth can authenticate instead with a Pexafy API key sent as\n`Authorization: Bearer <key>` or `x-api-key: <key>` — get one from the\n[dashboard](https://pexafy.com/dashboard/api-keys/).\n\nLiveness: [`GET /health`](https://mcp.pexafy.com/health) (public, no auth).\n\nAlso listed in the [official MCP registry](https://registry.modelcontextprotocol.io)\nas `com.pexafy/pexafy-mcp`, and on\n[Smithery](https://smithery.ai/servers/pexafy/pexafy-mcp) — where a hosted\ngateway URL is available for clients that prefer it.\n\n### What it costs\n\nThe Free plan covers 5,000 searches a month with one connector — enough for regular\nuse, no card required. Higher tiers are on the [pricing page](https://pexafy.com/pricing/).\nWhen you hit a limit, the assistant tells you in-chat instead of failing with an\nopaque error.\n\n---\n\n## Tools\n\nThree read-only tools. No write scope, no account mutation.\n\n### `search_photos` — semantic text search\n\nDescribe the scene in a full sentence; Pexafy is semantic, so sentences beat\nkeywords. All parameters are optional, but pass either `q` or at least one filter.\n\n| Parameter | Type | Notes |\n|---|---|---|\n| `q` | string | The scene, in natural language. Max 500 characters. |\n| `color_name` | string | One of: red, orange, yellow, green, blue, purple, pink, brown, black, white, gray, teal, beige, gold, navy. Excludes `color_hex`. |\n| `color_hex` | string | e.g. `#1E90FF`. Excludes `color_name`. |\n| `color_tolerance` | integer | 0 (exact) to 255 (loose). Default 20. Only with `color_hex`. |\n| `orientation` | string[] | `landscape`, `portrait`, `square`. |\n| `source` | string[] | Unsplash, Pexels, Pixabay, Kaboompics, Burst, StockSnap, Picjumbo, Skitterphoto, NegativeSpace. |\n| `license_type` | string[] | `free`, `cc0`. |\n| `photographer` | string | Exact username. |\n| `after_date` | string | `YYYY-MM-DD`. Published on or after. |\n| `cursor` | string | `pagination.next_cursor` from a previous response. |\n\n### `search_photos_by_image` — visual search from an example\n\nFinds photos that look like a reference image, optionally tweaked in words\n(\"like this, but at night\").\n\n| Parameter | Type | Notes |\n|---|---|---|\n| `image_url` | string | Public http(s) URL of the reference image. |\n| `image_file` | object | Auto-filled by hosts that support uploads (e.g. ChatGPT). |\n| `image_base64` | string | Raw base64 bytes, for programmatic clients. |\n| `q` | string | Text to combine with the image (\"but with hands raised\"). |\n| `text_alpha` | number | Weight of `q` against the image. |\n| `orientation`, `source`, `color_name`, `license_type`, `photographer`, `after_date` | string | Same filters as above. |\n| `cursor` | string | Pagination token. |\n\nOne of `image_url`, `image_file` or `image_base64` is required. Images are fetched\nserver-side; max 20 MB.\n\n### `get_similar_photos` — more like this\n\n| Parameter | Type | Notes |\n|---|---|---|\n| `photo_id` | string | **Required.** A photo's UUID, taken from a previous result. |\n| `cursor` | string | Pagination token. |\n\n### What comes back\n\nEvery photo carries its id, URLs at several sizes, dimensions, dominant colour,\norientation, source, licence, photographer, and an `attribution` string to display\nas credit — enough for the assistant to reason about the results rather than just\nlist them.\n\nResults are numbered `#1, #2, …`, so you refer to a photo the way you would in\nconversation. No ids to copy around:\n\n![Asking for more photos like #1, and the assistant reasoning over the new set](docs/screenshot-similar.jpg)\n\nIn clients that support [MCP Apps](https://modelcontextprotocol.io), clicking a\nthumbnail opens a detail panel with the full metadata — no extra call, it is all in\nthe tool result already:\n\n![The detail panel: photographer, source, resolution, licence, dominant colour, orientation and description](docs/screenshot-detail.jpg)\n\n---\n\n## Self-host\n\nYou don't need to — the hosted server above is the intended way in. But the server\nis a thin, plain client of the [Pexafy API](https://api.pexafy.com/schema.json), so\nyou can run your own against your own key.\n\nRequires Python 3.12+.\n\n```bash\ngit clone https://github.com/Pexafy/pexafy-mcp.git && cd pexafy-mcp\n./run.sh setup        # venv + editable install + seed .env\n# edit .env — set PEXAFY_API_KEY\n./run.sh dev          # stdio, for Claude Desktop / Claude Code\n```\n\nWith the installed console script (`pip install .`):\n\n```bash\npexafy-mcp                             # stdio (default)\nPEXAFY_MCP_TRANSPORT=http pexafy-mcp   # remote Streamable HTTP\n```\n\nClaude Desktop / Claude Code, over stdio:\n\n```json\n{\n  \"mcpServers\": {\n    \"pexafy\": {\n      \"command\": \"pexafy-mcp\",\n      \"env\": { \"PEXAFY_API_KEY\": \"pexafy_api_…\" }\n    }\n  }\n}\n```\n\nDocker, over HTTP — see [`docker-compose.example.yml`](docker-compose.example.yml):\n\n```bash\ndocker compose -f docker-compose.example.yml up -d\ncurl localhost:8765/health\n```\n\nThe image itself defaults to **stdio**, the transport an MCP client uses to drive a\ncontainer, so it also works directly:\n\n```bash\ndocker run -i --rm pexafy-mcp\n```\n\nThat answers `initialize` and `tools/list` with no API key and no network — the\ntools come from the vendored OpenAPI snapshot. A key is only needed to run a search.\nServing over HTTP is a matter of setting the transport, which both compose files do.\n\n### Configuration\n\nEvery setting is an environment variable, and every one of them is optional: with\nnone set, `pexafy-mcp` starts on stdio and answers `initialize` and `tools/list`\noffline. Two are worth knowing about.\n\n| Variable | Default | Purpose |\n|---|---|---|\n| `PEXAFY_MCP_TRANSPORT` | `stdio` | `stdio` for a local client, `http` to serve remotely |\n| `PEXAFY_API_BASE_URL` | `http://localhost:8000` | Pexafy API root — point it at `https://api.pexafy.com`, or at your own deployment |\n\nThe rest belongs to a deployment rather than to someone running the container, and\nlives in [`.env.example`](.env.example): a fallback `PEXAFY_API_KEY` for stdio use\nwhen the client sends no key of its own, `PEXAFY_THUMB_BASE_URL` and\n`PEXAFY_THUMB_HMAC_SECRET` to sign the thumbnails behind the inline grid, and\n`PEXAFY_OAUTH_*` with `MCP_RESOLVE_SECRET` to run the HTTP transport as an OAuth\nresource server. None of them is needed to start the server.\n\n---\n\n## How it works\n\n```\nsrc/pexafy_mcp/\n├── server.py     # entry point: builds the server, wires hooks, custom tools, /health\n├── tooling.py    # tunes the OpenAPI-derived tools for an LLM (descriptions, value sets)\n├── widget.py     # MCP Apps UI resource — the inline result grid (self-contained HTML)\n├── previews.py   # signs the thumbnail URLs injected into each result\n├── limits.py     # turns plan-limit (429) responses into in-chat upgrade nudges\n├── auth.py       # per-user auth: OAuth Resource Server or forwarded API key\n└── assets/       # vendored, shipped with the package:\n    ├── openapi.json          # OpenAPI snapshot the tools are generated from\n    ├── facets.json           # evolving source/license value sets\n    └── ext_apps_bundle.js    # @modelcontextprotocol/ext-apps SDK (inlined in the widget)\n```\n\n- The tools are **generated** from the Pexafy OpenAPI spec via `FastMCP.from_openapi()`,\n  so the API stays the single source of truth; `tooling.py` then reshapes them for an\n  LLM — narrowing the surface to the search core, dropping parameters that mislead a\n  model, and inlining the closed value sets so no facet lookup is ever needed.\n- `build_server()` assembles everything. **Importing the package has no side effects and\n  does no network I/O**: it reads the vendored `assets/openapi.json` and `assets/facets.json`.\n  `prepare.sh` regenerates those.\n- `search_photos_by_image` is hand-written: a chat assistant cannot upload a binary file\n  to an MCP tool, so the tool takes an image URL and fetches it server-side.\n- The inline grid is an **MCP Apps** UI resource. The ext-apps client is bundled and\n  inlined, because the host's sandboxed iframe cannot fetch external scripts at runtime.\n\n## Development\n\n```bash\n./run.sh test         # offline test suite (pytest)\n./run.sh inspect      # MCP Inspector\n./prepare.sh          # maintainers: regenerate the vendored assets/\n```\n\nContributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n\nThe package also redistributes third-party assets (the Inter typeface, the\n`@modelcontextprotocol/ext-apps` browser bundle and the libraries bundled into it),\neach under its own licence — see [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).\n",
  "bytes": 10243,
  "sha": "64ba35034e5f626f784dd8751986726da3a2c5d7f1e07734469e13862ff53e74",
  "repo_slug": "pexafy/pexafy-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_com_pexafy_pexafy_mcp_8f9423d8/readme"
}