{
  "markdown": "<p align=\"center\">\n  <img src=\"assets/logo.png\" alt=\"OpenRouter MCP Multimodal — MCP server for chat, vision, audio, and video AI tools\" width=\"128\" height=\"128\" />\n</p>\n\n<h1 align=\"center\">OpenRouter MCP Multimodal</h1>\n\n<p align=\"center\">\n  <strong>The MCP server for multimodal AI agents.</strong><br/>\n  One install · 19 tools · 300+ OpenRouter models · text, vision, audio &amp; video — analysis and generation.\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/@stabgan/openrouter-mcp-multimodal\"><img src=\"https://img.shields.io/npm/v/@stabgan/openrouter-mcp-multimodal.svg?label=npm&color=cb3837&logo=npm\" alt=\"npm version\" /></a>\n  <a href=\"https://pypi.org/project/mcp-server-openrouter-multimodal/\"><img src=\"https://img.shields.io/pypi/v/mcp-server-openrouter-multimodal.svg?label=pypi&color=3775A9&logo=pypi&logoColor=white\" alt=\"PyPI version\" /></a>\n  <a href=\"https://github.com/stabgan/openrouter-mcp-multimodal/releases\"><img src=\"https://img.shields.io/github/v/release/stabgan/openrouter-mcp-multimodal?label=release&color=6366f1\" alt=\"GitHub release\" /></a>\n  <a href=\"https://hub.docker.com/r/stabgan/openrouter-mcp-multimodal\"><img src=\"https://img.shields.io/docker/v/stabgan/openrouter-mcp-multimodal/latest?label=docker&color=2496ed&logo=docker&logoColor=white\" alt=\"Docker version\" /></a>\n  <a href=\"https://github.com/stabgan/openrouter-mcp-multimodal/actions/workflows/ci.yml\"><img src=\"https://github.com/stabgan/openrouter-mcp-multimodal/actions/workflows/ci.yml/badge.svg\" alt=\"CI status\" /></a>\n  <a href=\"https://www.apache.org/licenses/LICENSE-2.0\"><img src=\"https://img.shields.io/badge/License-Apache_2.0-blue.svg\" alt=\"Apache 2.0 license\" /></a>\n  <a href=\"https://nodejs.org\"><img src=\"https://img.shields.io/badge/node-%E2%89%A522-43853d?logo=node.js&logoColor=white\" alt=\"Node.js 22+\" /></a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/@stabgan/openrouter-mcp-multimodal\"><img src=\"https://img.shields.io/npm/dt/@stabgan/openrouter-mcp-multimodal.svg?label=npm%20downloads&color=cb3837&logo=npm\" alt=\"npm downloads\" /></a>\n  <a href=\"https://hub.docker.com/r/stabgan/openrouter-mcp-multimodal\"><img src=\"https://img.shields.io/docker/pulls/stabgan/openrouter-mcp-multimodal.svg?label=docker%20pulls&color=2496ed&logo=docker&logoColor=white\" alt=\"Docker pulls\" /></a>\n  <a href=\"https://registry.modelcontextprotocol.io/servers/io.github.stabgan/openrouter-multimodal\"><img src=\"https://img.shields.io/badge/MCP_Registry-listed-6366f1\" alt=\"MCP Registry\" /></a>\n  <a href=\"https://smithery.ai/server/@stabgan/openrouter-mcp-multimodal\"><img src=\"https://img.shields.io/badge/Smithery-Install-6366f1\" alt=\"Smithery MCP registry\" /></a>\n</p>\n\n<p align=\"center\">\n  <a href=\"#quick-start\">Quick start</a> ·\n  <a href=\"#tools\">Tools</a> ·\n  <a href=\"#examples\">Examples</a> ·\n  <a href=\"#security\">Security</a> ·\n  <a href=\"#troubleshooting\">Troubleshooting</a> ·\n  <a href=\"#development\">Development</a> ·\n  <a href=\"#releasing\">Releasing</a> ·\n  <a href=\"#faq\">FAQ</a>\n</p>\n\n---\n\n## What is this?\n\n**OpenRouter MCP Multimodal** is a production-grade [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server — listed on the [official MCP Registry](https://registry.modelcontextprotocol.io/servers/io.github.stabgan/openrouter-multimodal) as `io.github.stabgan/openrouter-multimodal`. It connects AI coding agents ([Cursor](https://cursor.com), [Claude Desktop](https://claude.ai/download), [VS Code](https://code.visualstudio.com), [Windsurf](https://codeium.com/windsurf), [Cline](https://github.com/cline/cline), and others) to [OpenRouter](https://openrouter.ai)'s unified LLM API over stdio.\n\nUnlike text-only MCP servers, one install covers the **full multimodal surface**:\n\n| Capability  | Tools                                                                                   | Highlights                                                                                                                                                                        |\n| :---------- | :-------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Chat**    | `chat_completion`, `start_chat_completion`, `get_chat_completion_status`                | 300+ models, `:nitro` / `:floor` / `:free` / `:online` / `:exacto` suffixes, provider routing, web search, response caching, reasoning tokens, async jobs for long-running models |\n| **Vision**  | `analyze_image`, `generate_image`, `generate_image_dedicated`                           | OCR, captioning, VQA, image generation with reference inputs, dedicated Image API with resolution/quality/format control                                                          |\n| **Audio**   | `analyze_audio`, `generate_audio`, `text_to_speech`, `speech_to_text`                   | Transcription, speech/music generation, dedicated TTS (free Deepgram default; model-specific voices, mp3/pcm), dedicated STT (Whisper/GPT-4o Transcribe)                                                   |\n| **Video**   | `analyze_video`, `generate_video`, `generate_video_from_image`, `get_video_status`      | Clip understanding, Veo 3.1 / Seedance 2.0 / Wan 2.7 generation with progress notifications                                                                                       |\n| **Catalog** | `search_models`, `get_model_info`, `validate_model`, `rerank_documents`, `health_check` | Model discovery, validation, reranking, ops health                                                                                                                                |\n\n**Production hardening:** input/output path sandboxes (including analyze\\_\\* local files as of v4.5.2), SSRF guards, structured errors with `_meta.code`, MCP 2025-06-18 structured outputs, tool icons (2025-11-25), async video progress notifications, and **1000+** automated tests (unit, mock, regression, and live integration).\n\n## Quick start\n\n**1. Get an API key** (free tier works) → [openrouter.ai/keys](https://openrouter.ai/keys)\n\n**2. Run the server**\n\n```bash\nexport OPENROUTER_API_KEY=sk-or-v1-...\nnpx -y @stabgan/openrouter-mcp-multimodal\n```\n\n**3. Add to your MCP client** — copy one JSON block from [Install](#install) into your client config:\n\n| Client             | Config location                                                                                                                   |\n| :----------------- | :-------------------------------------------------------------------------------------------------------------------------------- |\n| **Cursor**         | Project: `.cursor/mcp.json` · User: Cursor Settings → MCP                                                                         |\n| **Claude Desktop** | macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` · Windows: `%APPDATA%\\Claude\\claude_desktop_config.json` |\n| **VS Code**        | `.vscode/mcp.json` (workspace) or User Settings → MCP                                                                             |\n| **Windsurf**       | Windsurf Settings → MCP (same `mcpServers` JSON shape as Cursor)                                                                  |\n\nUse the `mcpServers` object from [Manual config](#manual-config) below.\n\n> **No credits required to start.** Free models such as `google/gemma-4-26b-a4b-it:free` work for chat and vision. Video/audio generation typically needs credits.\n\n## Install\n\nMCP servers are distributed through several packaging models. **This server is implemented in Node.js/TypeScript**; the table below maps each ecosystem method to how you run it here.\n\n| Method                                  | Runtime                          | Best for                                                       | This server                                                                                            |\n| :-------------------------------------- | :------------------------------- | :------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------- |\n| **[npx](#manual-config)**               | Node.js 22+                      | Most MCP clients (default)                                     | ✅ `@stabgan/openrouter-mcp-multimodal`                                                                |\n| **[uvx / pipx](#manual-config)**        | Python 3.10+ **and** Node.js 22+ | Python-first workflows, same pattern as PyPI MCP servers       | ✅ [`mcp-server-openrouter-multimodal`](https://pypi.org/project/mcp-server-openrouter-multimodal/)    |\n| **[npm global](#manual-config)**        | Node.js 22+                      | Pin a version without re-downloading                           | ✅                                                                                                     |\n| **[node (local)](#manual-config)**      | Node.js 22+                      | Contributors / air-gapped builds                               | ✅                                                                                                     |\n| **[Docker Hub](#manual-config)**        | Docker                           | Isolation, no Node on host                                     | ✅ `stabgan/openrouter-mcp-multimodal`                                                                 |\n| **[GHCR](#manual-config)**              | Docker                           | GitHub-native OCI pulls                                        | ✅ `ghcr.io/stabgan/openrouter-mcp-multimodal`                                                         |\n| **[Smithery CLI](#smithery)**           | Node.js (via installer)          | Interactive install into Claude/Cursor/etc.                    | ✅                                                                                                     |\n| **[MCP Registry](#mcp-registry)**       | npm or OCI                       | Official discovery (`io.github.stabgan/openrouter-multimodal`) | ✅ [listing](https://registry.modelcontextprotocol.io/servers/io.github.stabgan/openrouter-multimodal) |\n| **[One-click deeplinks](#one-click)**   | Node.js                          | Cursor, VS Code, Kiro                                          | ✅                                                                                                     |\n| **[Claude Code CLI](#claude-code-cli)** | Node.js                          | Terminal-first Claude Code users                               | ✅                                                                                                     |\n| **[MCP Inspector](#mcp-inspector)**     | Node.js                          | Debug / list tools locally                                     | ✅                                                                                                     |\n| **Windows `cmd /c npx`**                | Node.js                          | Claude Desktop / Cursor when `npx` not on GUI PATH             | ✅ [see below](#windows-npx)                                                                           |\n| pip / uv (direct)                       | —                                | Native Python MCP servers only                                 | — use **uvx** row above                                                                                |\n| DXT desktop extensions                  | —                                | Bundled Claude Desktop `.dxt`                                  | not yet                                                                                                |\n| Remote HTTP / SSE                       | —                                | Hosted Smithery / Cloudflare endpoints                         | via [Smithery](https://smithery.ai/server/@stabgan/openrouter-mcp-multimodal)                          |\n\n> **uvx vs npx:** In the MCP ecosystem, **`npx` runs npm (Node) packages** and **`uvx` runs PyPI (Python) packages**. Because this server is Node-based, `uvx` uses a thin [Python launcher](./python/) that execs `npx -y @stabgan/openrouter-mcp-multimodal` — you still need Node installed.\n\n### One-click\n\n<table>\n<tr><td><strong>Cursor</strong></td><td><a href=\"https://cursor.com/en/install-mcp?name=openrouter&config=eyJ0eXBlIjoic3RkaW8iLCJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBzdGFiZ2FuL29wZW5yb3V0ZXItbWNwLW11bHRpbW9kYWwiXSwiZW52Ijp7Ik9QRU5ST1VURVJfQVBJX0tFWSI6InNrLW9yLXYxLS4uLiJ9fQ%3D%3D\"><img src=\"https://cursor.com/deeplink/mcp-install-dark.svg\" alt=\"Add OpenRouter MCP to Cursor\" /></a></td></tr>\n<tr><td><strong>VS Code</strong></td><td><a href=\"https://insiders.vscode.dev/redirect/mcp/install?name=openrouter&config=%7B%22type%22%3A%22stdio%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40stabgan%2Fopenrouter-mcp-multimodal%22%5D%2C%22env%22%3A%7B%22OPENROUTER_API_KEY%22%3A%22sk-or-v1-...%22%7D%7D\"><img src=\"https://img.shields.io/badge/Add_to-VS_Code-007ACC?style=for-the-badge&logo=visualstudiocode&logoColor=white\" alt=\"Add to VS Code\" /></a></td></tr>\n<tr><td><strong>Kiro</strong></td><td><a href=\"https://kiro.dev/launch/mcp/add?name=openrouter&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40stabgan%2Fopenrouter-mcp-multimodal%22%5D%2C%22env%22%3A%7B%22OPENROUTER_API_KEY%22%3A%22sk-or-v1-...%22%7D%2C%22disabled%22%3Afalse%2C%22autoApprove%22%3A%5B%5D%7D\"><img src=\"https://img.shields.io/badge/Add_to-Kiro-232F3E?style=for-the-badge&logo=amazonaws&logoColor=white\" alt=\"Add to Kiro\" /></a></td></tr>\n<tr><td><strong>Claude Desktop / Windsurf / Cline</strong></td><td><a href=\"#manual-config\">Manual JSON config</a> (pick any method below)</td></tr>\n<tr><td><strong>Smithery</strong></td><td><a href=\"https://smithery.ai/server/@stabgan/openrouter-mcp-multimodal\"><code>npx -y @smithery/cli install @stabgan/openrouter-mcp-multimodal --client claude</code></a></td></tr>\n<tr><td><strong>MCP Registry</strong></td><td><a href=\"https://registry.modelcontextprotocol.io/servers/io.github.stabgan/openrouter-multimodal\">Official registry page</a> — npm + OCI packages</td></tr>\n</table>\n\nPaste your `OPENROUTER_API_KEY` when prompted — deeplinks use placeholders so secrets never appear in URLs.\n\n### Manual config\n\n<details open>\n<summary><strong>npx (recommended)</strong></summary>\n\n```bash\nexport OPENROUTER_API_KEY=sk-or-v1-...\nnpx -y @stabgan/openrouter-mcp-multimodal\n```\n\n```json\n{\n  \"mcpServers\": {\n    \"openrouter\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@stabgan/openrouter-mcp-multimodal\"],\n      \"env\": {\n        \"OPENROUTER_API_KEY\": \"sk-or-v1-...\"\n      }\n    }\n  }\n}\n```\n\nPin a release: `\"args\": [\"-y\", \"@stabgan/openrouter-mcp-multimodal@5.0.1\"]`\n\n</details>\n\n<details>\n<summary><strong>uvx / pipx (Python launcher)</strong></summary>\n\nInstall [uv](https://docs.astral.sh/uv/getting-started/installation/) (includes `uvx`), ensure **Node.js 22+** is also on your `PATH`, then:\n\n```bash\nexport OPENROUTER_API_KEY=sk-or-v1-...\nuvx mcp-server-openrouter-multimodal\n# pin npm version: OPENROUTER_MCP_NPM_VERSION=5.0.1 uvx mcp-server-openrouter-multimodal\n```\n\n```json\n{\n  \"mcpServers\": {\n    \"openrouter\": {\n      \"command\": \"uvx\",\n      \"args\": [\"mcp-server-openrouter-multimodal\"],\n      \"env\": {\n        \"OPENROUTER_API_KEY\": \"sk-or-v1-...\"\n      }\n    }\n  }\n}\n```\n\n**pipx equivalent:** `pipx run mcp-server-openrouter-multimodal`\n\nOptional: `OPENROUTER_MCP_NPM_VERSION=5.0.1` pins the underlying npm package.\n\n</details>\n\n<details>\n<summary><strong>npm global</strong></summary>\n\n```bash\nnpm install -g @stabgan/openrouter-mcp-multimodal\n```\n\n```json\n{\n  \"mcpServers\": {\n    \"openrouter\": {\n      \"command\": \"openrouter-multimodal\",\n      \"env\": { \"OPENROUTER_API_KEY\": \"sk-or-v1-...\" }\n    }\n  }\n}\n```\n\n</details>\n\n<details>\n<summary><strong>node (local clone)</strong></summary>\n\n```bash\ngit clone https://github.com/stabgan/openrouter-mcp-multimodal.git\ncd openrouter-mcp-multimodal\nnpm ci && npm run build\n```\n\n```json\n{\n  \"mcpServers\": {\n    \"openrouter\": {\n      \"command\": \"node\",\n      \"args\": [\"/absolute/path/to/openrouter-mcp-multimodal/dist/index.js\"],\n      \"env\": { \"OPENROUTER_API_KEY\": \"sk-or-v1-...\" }\n    }\n  }\n}\n```\n\n</details>\n\n<details>\n<summary><strong>Docker</strong></summary>\n\n```bash\ndocker run --rm -i -e OPENROUTER_API_KEY=sk-or-v1-... stabgan/openrouter-mcp-multimodal:latest\n```\n\n```json\n{\n  \"mcpServers\": {\n    \"openrouter\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"--rm\",\n        \"-i\",\n        \"-e\",\n        \"OPENROUTER_API_KEY=sk-or-v1-...\",\n        \"stabgan/openrouter-mcp-multimodal:latest\"\n      ]\n    }\n  }\n}\n```\n\nUse `-i` (interactive stdio). Avoid `-t` (TTY corrupts MCP framing on some hosts).\n\n</details>\n\n<details>\n<summary><strong>GHCR (GitHub Container Registry)</strong></summary>\n\n```bash\ndocker run --rm -i -e OPENROUTER_API_KEY=sk-or-v1-... \\\n  ghcr.io/stabgan/openrouter-mcp-multimodal:5.0.1\n```\n\n```json\n{\n  \"mcpServers\": {\n    \"openrouter\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"--rm\",\n        \"-i\",\n        \"-e\",\n        \"OPENROUTER_API_KEY=sk-or-v1-...\",\n        \"ghcr.io/stabgan/openrouter-mcp-multimodal:5.0.1\"\n      ]\n    }\n  }\n}\n```\n\n</details>\n\n<details>\n<summary><strong>Smithery</strong></summary>\n\nInteractive install (writes config for your client):\n\n```bash\nnpx -y @smithery/cli install @stabgan/openrouter-mcp-multimodal --client claude\n# or: --client cursor | vscode | windsurf | ...\n```\n\nListing: [smithery.ai/server/@stabgan/openrouter-mcp-multimodal](https://smithery.ai/server/@stabgan/openrouter-mcp-multimodal)\n\n</details>\n\n<details>\n<summary><strong>MCP Registry</strong></summary>\n\nOfficial name: `io.github.stabgan/openrouter-multimodal`\n\n- Registry: [registry.modelcontextprotocol.io](https://registry.modelcontextprotocol.io/servers/io.github.stabgan/openrouter-multimodal)\n- npm package: `@stabgan/openrouter-mcp-multimodal`\n- OCI image: `docker.io/stabgan/openrouter-mcp-multimodal`\n\nClients that support registry-driven install will offer npm or Docker; otherwise use the JSON blocks above.\n\n</details>\n\n<details>\n<summary><strong>Claude Code CLI</strong></summary>\n\n```bash\nclaude mcp add openrouter -- npx -y @stabgan/openrouter-mcp-multimodal\n# project scope:\nclaude mcp add --scope project openrouter -- npx -y @stabgan/openrouter-mcp-multimodal\n```\n\nSet `OPENROUTER_API_KEY` in your shell or client env before starting Claude Code.\n\n</details>\n\n<details>\n<summary><strong>MCP Inspector</strong></summary>\n\nDebug tools/list and tool calls against a live OpenRouter key:\n\n```bash\nexport OPENROUTER_API_KEY=sk-or-v1-...\nnpx -y @modelcontextprotocol/inspector npx -y @stabgan/openrouter-mcp-multimodal\n```\n\n</details>\n\n<details>\n<summary><strong>Windows npx</strong></summary>\n\nWhen Claude Desktop or Cursor cannot find `npx` (GUI apps often miss shell `PATH`), wrap with `cmd`:\n\n```json\n{\n  \"mcpServers\": {\n    \"openrouter\": {\n      \"command\": \"cmd\",\n      \"args\": [\"/c\", \"npx\", \"-y\", \"@stabgan/openrouter-mcp-multimodal\"],\n      \"env\": { \"OPENROUTER_API_KEY\": \"sk-or-v1-...\" }\n    }\n  }\n}\n```\n\nIf still failing, use the full path from `where npx` as the command.\n\n</details>\n\n## Why this server?\n\n| Capability                           | This server | Typical MCP LLM servers |\n| :----------------------------------- | :---------: | :---------------------: |\n| Text chat (300+ models)              |     ✅      |           ✅            |\n| Image analysis + generation          |     ✅      |         partial         |\n| Audio analysis + TTS                 |     ✅      |           ❌            |\n| Video analysis + generation          |     ✅      |           ❌            |\n| Model search / validate / rerank     |     ✅      |           ❌            |\n| Path sandbox + SSRF protection       |     ✅      |          rare           |\n| MCP 2025 structured outputs          |     ✅      |          rare           |\n| Async video + progress notifications |     ✅      |           ❌            |\n\n## Tools\n\n19 MCP tools. Each description includes **Use when**, **Good/Bad examples**, **Fails when**, and **Works with** so agents pick the right tool and recover from errors.\n\n| Tool                         | Purpose                                                                    |\n| :--------------------------- | :------------------------------------------------------------------------- |\n| `chat_completion`            | Text chat, web search, provider routing, caching, reasoning                |\n| `start_chat_completion`      | Async background job for long-running reasoning models                     |\n| `get_chat_completion_status` | Poll / retrieve async completion results                                   |\n| `analyze_image`              | Vision — local path, URL, or data URL + `question`                         |\n| `analyze_audio`              | Transcribe / analyze audio files                                           |\n| `analyze_video`              | Describe / Q&A over video files                                            |\n| `generate_image`             | Text-to-image via chat completions with reference images                   |\n| `generate_image_dedicated`   | Text-to-image via dedicated `/api/v1/images` (resolution, quality, format) |\n| `generate_audio`             | Text-to-speech / music via chat completions                                |\n| `text_to_speech`             | Dedicated TTS (`/api/v1/audio/speech`) — free Deepgram default, voices, speed, mp3/pcm |\n| `speech_to_text`             | Dedicated STT (`/api/v1/audio/transcriptions`) — Whisper, GPT-4o           |\n| `generate_video`             | Text-to-video (async, resumable)                                           |\n| `generate_video_from_image`  | Image-to-video (narrower schema)                                           |\n| `get_video_status`           | Poll / resume video jobs                                                   |\n| `search_models`              | Paginated model catalog search                                             |\n| `get_model_info`             | Pricing, context, modalities                                               |\n| `validate_model`             | Cheap model ID existence check                                             |\n| `rerank_documents`           | Relevance ranking for RAG                                                  |\n| `health_check`               | API key + reachability probe                                               |\n\nErrors use a closed `_meta.code` taxonomy: `INVALID_INPUT` · `UNSAFE_PATH` · `UPSTREAM_*` · `MODEL_NOT_FOUND` · `JOB_STILL_RUNNING` · and more.\n\n### Binary tool results (v4.7.0+)\n\nGenerate tools (`generate_image`, `generate_image_dedicated`, `generate_audio`, `text_to_speech`, `generate_video`, `generate_video_from_image`, `get_video_status`) return image, audio, or video bytes. As of **4.7.0** the behavior is explicit:\n\n| `save_path`                   | Tool result                                                                                                                                                                      |\n| :---------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Set**                       | **Text pointer only** — e.g. `Image saved to: out.png (… bytes, image/png)` plus `_meta.save_path`. **No inline base64** (avoids duplicating large payloads in the MCP channel). |\n| **Unset, under byte ceiling** | Inline media block **and** summary text (images/audio use MCP `image` / `audio` types; video uses MCP `resource` blocks).                                                        |\n| **Unset, over ceiling**       | Text only with a hint to pass `save_path`.                                                                                                                                       |\n\nDefault inline ceilings (override per kind or globally):\n\n| Kind  | Default | Env vars (precedence: per-kind → global)                            |\n| :---- | :------ | :------------------------------------------------------------------ |\n| Image | 1 MiB   | `OPENROUTER_IMAGE_INLINE_MAX_BYTES` → `OPENROUTER_INLINE_MAX_BYTES` |\n| Audio | 1 MiB   | `OPENROUTER_AUDIO_INLINE_MAX_BYTES` → `OPENROUTER_INLINE_MAX_BYTES` |\n| Video | 10 MiB  | `OPENROUTER_VIDEO_INLINE_MAX_BYTES` → `OPENROUTER_INLINE_MAX_BYTES` |\n\nIf you previously relied on **both** a saved file **and** inline media in the same tool result, read the file from `_meta.save_path` (or omit `save_path` to get inline media when under the ceiling).\n\n## Examples\n\n### Chat (free model)\n\n```json\n{\n  \"tool\": \"chat_completion\",\n  \"arguments\": {\n    \"model\": \"google/gemma-4-26b-a4b-it:free\",\n    \"messages\": [{ \"role\": \"user\", \"content\": \"Summarize MCP in one sentence.\" }]\n  }\n}\n```\n\n### Analyze an image\n\n```json\n{\n  \"tool\": \"analyze_image\",\n  \"arguments\": {\n    \"image_path\": \"diagram.png\",\n    \"question\": \"List every label in this diagram.\"\n  }\n}\n```\n\n> Use `image_path` and `question` — not `image` / `prompt`.\n\n### Search models (vision + free)\n\n```json\n{\n  \"tool\": \"search_models\",\n  \"arguments\": {\n    \"query\": \"gemma\",\n    \"capabilities\": { \"vision\": true },\n    \"limit\": 10,\n    \"offset\": 0\n  }\n}\n```\n\n### Generate video (async)\n\n```json\n{\n  \"tool\": \"generate_video\",\n  \"arguments\": {\n    \"model\": \"google/veo-3.1\",\n    \"prompt\": \"Ocean waves at sunrise, cinematic drone shot\",\n    \"duration\": 4,\n    \"save_path\": \"river.mp4\"\n  }\n}\n```\n\nIf the job is still running when `max_wait_ms` elapses, the response succeeds with `_meta.code: JOB_STILL_RUNNING` and a `video_id` — call `get_video_status` to resume. **This is not an error.**\n\nWith `save_path` set (as above), the result is a **text pointer** to the saved file once complete — not inline video. See [Binary tool results](#binary-tool-results-v470).\n\nMore examples: [docs/plans/tool-description-improvement.md](./docs/plans/tool-description-improvement.md)\n\n## Security\n\n- **Input path sandbox** — local paths on `analyze_*` and reference images must stay inside `OPENROUTER_INPUT_DIR` (falls back to `OPENROUTER_OUTPUT_DIR`, then `cwd`)\n- **Output path sandbox** — `save_path` must stay inside `OPENROUTER_OUTPUT_DIR`\n- **Async job reads** — `get_chat_completion_status` resolves disk paths only under `OPENROUTER_OUTPUT_DIR/openrouter-jobs/` (4.7.0+)\n- **SSRF protection** — private/reserved IPs blocked on URL fetches\n- **Untrusted content** — analyze outputs tagged `_meta.content_is_untrusted: true`\n\nOverride sandboxes only with `OPENROUTER_ALLOW_UNSAFE_PATHS=1` (discouraged).\n\nReport vulnerabilities: **[SECURITY.md](./SECURITY.md)** (private disclosure — do not file public issues for exploits).\n\n## Configuration\n\n<details>\n<summary><strong>Environment variables</strong></summary>\n\n| Variable                            | Required | Default                               | Description                         |\n| :---------------------------------- | :------: | :------------------------------------ | :---------------------------------- |\n| `OPENROUTER_API_KEY`                | **Yes**  | —                                     | OpenRouter API key                  |\n| `OPENROUTER_DEFAULT_MODEL`          |    No    | `google/gemma-4-26b-a4b-it:free` | Default when tools omit `model`     |\n| `OPENROUTER_OUTPUT_DIR`             |    No    | `cwd`                                 | Sandbox root for `save_path`        |\n| `OPENROUTER_INPUT_DIR`              |    No    | `OUTPUT_DIR` or `cwd`                 | Sandbox root for local input files  |\n| `OPENROUTER_INLINE_MAX_BYTES`       |    No    | `1048576` (image/audio)               | Global inline media ceiling         |\n| `OPENROUTER_IMAGE_INLINE_MAX_BYTES` |    No    | falls back to global                  | Per-kind inline ceiling             |\n| `OPENROUTER_AUDIO_INLINE_MAX_BYTES` |    No    | falls back to global                  | Per-kind inline ceiling             |\n| `OPENROUTER_VIDEO_INLINE_MAX_BYTES` |    No    | `10485760`                            | Video inline ceiling                |\n| `OPENROUTER_LOG_LEVEL`              |    No    | `info`                                | `error` / `warn` / `info` / `debug` |\n\nSee [`.env.example`](./.env.example) for the full list (provider routing, fetch limits, caching, video polling, async jobs, integration-test overrides).\n\n</details>\n\n## Development\n\n```bash\ngit clone https://github.com/stabgan/openrouter-mcp-multimodal.git\ncd openrouter-mcp-multimodal\nnpm install\ncp .env.example .env   # add OPENROUTER_API_KEY\nnpm run build\n```\n\n### Testing\n\n| Command                    | What it runs                                               |\n| :------------------------- | :--------------------------------------------------------- |\n| `npm test`                 | **1018** unit + mock tests (no API key, &lt;20s)           |\n| `npm run test:regression`  | Security + schema regression guards                        |\n| `npm run test:integration` | **16** live OpenRouter scenarios (**requires** `.env` key) |\n| `npm run test:e2e`         | Full MCP stdio smoke (`scripts/live-e2e.mjs`)              |\n| `npm run ci`               | lint + format + build + **all** of the above except e2e    |\n\n**Free models for CI / zero-credit accounts:** integration tests default to `google/gemma-4-26b-a4b-it:free` (override with `OPENROUTER_INTEGRATION_MODEL`). GitHub Actions requires the `OPENROUTER_API_KEY` repository secret.\n\nMock tests live under `src/__tests__/mock/` and cover handlers, path sandboxes, SSRF blocks, model-cache pagination, tool descriptions, and structured outputs — **330+** additional cases beyond the core suite.\n\n```bash\nnpm run lint\nnpm run format:check\nnpm run version:check   # package.json vs src/version.ts, server.json, pyproject.toml\n```\n\n## Releasing\n\nPublished artifacts (**npm**, **PyPI/uvx**, **Docker**, **GHCR**) all ship from the **same semver** on a git tag (`vX.Y.Z`). Pushing to `main` runs tests but does **not** publish to npm or PyPI.\n\n**Normal flow:** merge conventional commits to `main` → [Release Please](https://github.com/googleapis/release-please) opens a Release PR → merge it → tag is created → CI publishes everywhere.\n\n**Manual flow:** bump all version files → `npm run version:check` → `npm run ci` + smoke tests → commit → `git tag vX.Y.Z` → `git push origin vX.Y.Z`.\n\nFull checklist, file list, CI secrets, and agent instructions:\n\n- **[`docs/RELEASING.md`](docs/RELEASING.md)** — maintainer release guide\n- **[`AGENTS.md`](AGENTS.md)** — quick reference for AI agents\n\n## Troubleshooting\n\n| Symptom                                                     | Likely cause                             | Fix                                                                                                             |\n| :---------------------------------------------------------- | :--------------------------------------- | :-------------------------------------------------------------------------------------------------------------- |\n| Server exits immediately / `OPENROUTER_API_KEY is required` | Missing or empty API key                 | Set `OPENROUTER_API_KEY` in client `env` or shell — get one at [openrouter.ai/keys](https://openrouter.ai/keys) |\n| `_meta.code: INVALID_CREDENTIALS` or HTTP 401               | Bad or revoked key                       | Regenerate at [openrouter.ai/keys](https://openrouter.ai/keys); restart the MCP client                          |\n| `_meta.code: MODEL_NOT_FOUND`                               | Typo or retired model ID                 | Run `search_models` or `validate_model`; check [openrouter.ai/models](https://openrouter.ai/models)             |\n| HTTP 402 / insufficient credits                             | Paid model or generation on zero balance | Add credits at [openrouter.ai/credits](https://openrouter.ai/credits) or use a `:free` model                    |\n| `_meta.code: UPSTREAM_HTTP` with 429                        | Rate limit                               | Wait for `_meta.retry_after_seconds` if present; reduce concurrency                                             |\n| `_meta.code: UNSAFE_PATH`                                   | Local path outside sandbox               | Put files under `OPENROUTER_INPUT_DIR` or set `OPENROUTER_OUTPUT_DIR` wider; see [Security](#security)          |\n| `npx` not found (Windows GUI apps)                          | GUI `PATH` differs from terminal         | Use the [Windows npx](#windows-npx) `cmd /c` wrapper                                                            |\n| No inline image/audio after upgrade                         | **v4.7.0** with `save_path` set          | Expected — result is text + `_meta.save_path` only; omit `save_path` or read the saved file                     |\n| MCP client shows stale tool list                            | Client cache                             | Restart MCP / reload window after upgrading the package pin                                                     |\n\nStructured errors include `_meta.suggestions` with agent-oriented next steps when available.\n\n## FAQ\n\n### Do I need paid OpenRouter credits?\n\nNo, to get started. Free models work for chat and vision. Audio/video **generation** usually requires credits; analysis may return `402` on some models — the server surfaces that as a structured error.\n\n### Which MCP clients are supported?\n\nAny MCP-compatible client over stdio: Cursor, Claude Desktop, VS Code Copilot, Windsurf, Cline, Kiro, and custom agents.\n\n### How is this different from calling OpenRouter directly?\n\nThis server adds MCP tool schemas, security sandboxes, error taxonomy, model caching, async video polling with progress notifications, and agent-oriented tool descriptions — so LLMs invoke the right capability without custom HTTP glue.\n\n### Where is the security advisory for path traversal?\n\nFixed in 4.5.2+ — see [GHSA-3q7p-736f-x44v](https://github.com/stabgan/openrouter-mcp-multimodal/security/advisories/GHSA-3q7p-736f-x44v), [`SECURITY.md`](./SECURITY.md), and `docs/solutions/security-issues/`.\n\n## Compatibility\n\nWorks with any MCP client. Protocol: **MCP 2025-06-18**. Node **≥ 22** (Docker image uses Node 24).\n\n## License\n\nApache 2.0 — see [LICENSE](./LICENSE).\n\n## Contributing\n\nIssues and PRs welcome. For large changes, open an issue first.\n\nBefore submitting: run **`npm run ci`**. Use [Conventional Commits](https://www.conventionalcommits.org/) (`fix:`, `feat:`, etc.) so [Release Please](docs/RELEASING.md) can cut the next release. See **[`docs/RELEASING.md`](docs/RELEASING.md)** if you need to ship a version.\n",
  "bytes": 34886,
  "sha": "156d299966259b7b980cd7a8f003bb8b90cf6747a6d9885523b3e2fd8b555b2c",
  "repo_slug": "stabgan/openrouter-mcp-multimodal",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_stabgan_openrouter_multimodal_3581e181/readme"
}