@stabgan/openrouter-mcp-multimodal 4.5.1 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +367 -283
  2. package/dist/index.js +1 -1
  3. package/dist/model-cache.d.ts +22 -12
  4. package/dist/model-cache.js +58 -21
  5. package/dist/openrouter-api.d.ts +45 -0
  6. package/dist/openrouter-api.js +50 -0
  7. package/dist/tool-descriptions.d.ts +19 -0
  8. package/dist/tool-descriptions.js +573 -0
  9. package/dist/tool-handlers/analyze-audio.js +5 -1
  10. package/dist/tool-handlers/analyze-image.js +6 -5
  11. package/dist/tool-handlers/analyze-video.js +6 -5
  12. package/dist/tool-handlers/async-chat.d.ts +51 -0
  13. package/dist/tool-handlers/async-chat.js +216 -0
  14. package/dist/tool-handlers/audio-utils.js +4 -2
  15. package/dist/tool-handlers/chat-completion.js +1 -1
  16. package/dist/tool-handlers/fetch-utils.js +16 -2
  17. package/dist/tool-handlers/generate-audio.js +2 -4
  18. package/dist/tool-handlers/generate-image-dedicated.d.ts +32 -0
  19. package/dist/tool-handlers/generate-image-dedicated.js +176 -0
  20. package/dist/tool-handlers/generate-image-input.d.ts +3 -0
  21. package/dist/tool-handlers/generate-image-input.js +38 -0
  22. package/dist/tool-handlers/generate-image.d.ts +13 -51
  23. package/dist/tool-handlers/generate-image.js +32 -119
  24. package/dist/tool-handlers/generate-video.d.ts +2 -2
  25. package/dist/tool-handlers/generate-video.js +78 -30
  26. package/dist/tool-handlers/image-utils.d.ts +1 -0
  27. package/dist/tool-handlers/image-utils.js +26 -16
  28. package/dist/tool-handlers/openrouter-errors.js +6 -2
  29. package/dist/tool-handlers/path-safety.js +32 -5
  30. package/dist/tool-handlers/provider-routing.js +7 -2
  31. package/dist/tool-handlers/rerank.js +2 -5
  32. package/dist/tool-handlers/search-models.d.ts +2 -2
  33. package/dist/tool-handlers/search-models.js +2 -6
  34. package/dist/tool-handlers/speech-to-text.d.ts +20 -0
  35. package/dist/tool-handlers/speech-to-text.js +140 -0
  36. package/dist/tool-handlers/structured-output.d.ts +8 -0
  37. package/dist/tool-handlers/structured-output.js +11 -0
  38. package/dist/tool-handlers/text-to-speech.d.ts +29 -0
  39. package/dist/tool-handlers/text-to-speech.js +105 -0
  40. package/dist/tool-handlers/video-utils.js +6 -9
  41. package/dist/tool-handlers.js +253 -125
  42. package/dist/version.d.ts +1 -1
  43. package/dist/version.js +1 -1
  44. package/package.json +27 -15
package/README.md CHANGED
@@ -1,148 +1,155 @@
1
1
  <p align="center">
2
- <img src="assets/logo.png" alt="OpenRouter MCP Multimodal" width="200" height="200" />
2
+ <img src="assets/logo.svg" alt="OpenRouter MCP Multimodal — MCP server for chat, vision, audio, and video AI tools" width="128" height="128" />
3
3
  </p>
4
4
 
5
- <h1 align="center">OpenRouter MCP Multimodal Server</h1>
5
+ <h1 align="center">OpenRouter MCP Multimodal</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>The all-in-one MCP server for 300+ LLMs — text, vision, audio, and video in a single package.</strong>
8
+ <strong>The MCP server for multimodal AI agents.</strong><br/>
9
+ One install · 14 tools · 300+ OpenRouter models · text, vision, audio &amp; video — analysis and generation.
9
10
  </p>
10
11
 
11
12
  <p align="center">
12
13
  <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>
14
+ <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>
15
+ <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>
13
16
  <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>
14
- <a href="https://github.com/stabgan/openrouter-mcp-multimodal/actions/workflows/publish.yml"><img src="https://github.com/stabgan/openrouter-mcp-multimodal/actions/workflows/publish.yml/badge.svg" alt="CI" /></a>
15
- <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" /></a>
16
- <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%E2%89%A518-43853d?logo=node.js&logoColor=white" alt="Node.js" /></a>
17
+ <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>
18
+ <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>
19
+ <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%E2%89%A520-43853d?logo=node.js&logoColor=white" alt="Node.js 20+" /></a>
17
20
  </p>
18
21
 
19
22
  <p align="center">
20
23
  <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>
21
- <a href="https://www.npmjs.com/package/@stabgan/openrouter-mcp-multimodal"><img src="https://img.shields.io/npm/dm/@stabgan/openrouter-mcp-multimodal.svg?label=monthly&color=cb3837&logo=npm" alt="npm monthly" /></a>
22
24
  <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>
23
- <a href="https://smithery.ai/servers/stabgan/openrouter-mcp-multimodal"><img src="https://smithery.ai/badge/stabgan/openrouter-mcp-multimodal" alt="Smithery" /></a>
24
- <a href="https://github.com/stabgan/openrouter-mcp-multimodal/stargazers"><img src="https://img.shields.io/github/stars/stabgan/openrouter-mcp-multimodal.svg?style=social" alt="GitHub stars" /></a>
25
- <a href="https://github.com/stabgan/openrouter-mcp-multimodal/network/members"><img src="https://img.shields.io/github/forks/stabgan/openrouter-mcp-multimodal.svg?style=social" alt="GitHub forks" /></a>
26
- </p>
27
- <p align="center">
28
- <sub>4,700+ installs across npm + Docker Hub &middot; ~950 npm installs/month and accelerating</sub>
25
+ <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>
26
+ <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>
29
27
  </p>
30
28
 
31
29
  <p align="center">
32
- <a href="#one-click-install">Install</a> &middot;
33
- <a href="#tools">Tools</a> &middot;
34
- <a href="#quick-start">Quick Start</a> &middot;
35
- <a href="#configuration">Config</a> &middot;
36
- <a href="#usage-examples">Examples</a> &middot;
37
- <a href="#architecture">Architecture</a> &middot;
38
- <a href="./CHANGELOG.md">Changelog</a>
30
+ <a href="#quick-start">Quick start</a> ·
31
+ <a href="#tools">Tools</a> ·
32
+ <a href="#examples">Examples</a> ·
33
+ <a href="#security">Security</a> ·
34
+ <a href="#development">Development</a> ·
35
+ <a href="#faq">FAQ</a>
39
36
  </p>
40
37
 
41
38
  ---
42
- [![Verified on MseeP](https://mseep.ai/badge.svg)](https://mseep.ai/app/8f27d6d4-0877-4b86-b377-8a33f451e755)
43
39
 
44
- Access 300+ LLMs — Claude, Gemini, GPT, Llama, Qwen, Grok, and more — through [OpenRouter](https://openrouter.ai) via the [Model Context Protocol](https://modelcontextprotocol.io). Analyze images, audio, and video. Generate images, speech, music, and video (Veo 3.1, Sora 2 Pro, Seedance, Wan). Chat with any model. Works with **Claude Desktop**, **Cursor**, **Kiro**, **VS Code**, **Windsurf**, **Cline**, and any MCP-compatible client. Every tool returns structured `_meta.code` errors so MCP clients can switch on failure modes without parsing strings.
40
+ ## What is this?
41
+
42
+ **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.
43
+
44
+ Unlike text-only MCP servers, one install covers the **full multimodal surface**:
45
+
46
+ | Capability | Tools | Highlights |
47
+ | :---------- | :-------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------- |
48
+ | **Chat** | `chat_completion` | 300+ models, `:nitro` / `:exacto` suffixes, provider routing, web search, response caching, reasoning tokens |
49
+ | **Vision** | `analyze_image`, `generate_image` | OCR, captioning, VQA, image generation with reference inputs |
50
+ | **Audio** | `analyze_audio`, `generate_audio` | Transcription, speech/music generation |
51
+ | **Video** | `analyze_video`, `generate_video`, `generate_video_from_image`, `get_video_status` | Clip understanding, Veo / Sora / Seedance / Wan generation with progress notifications |
52
+ | **Catalog** | `search_models`, `get_model_info`, `validate_model`, `rerank_documents`, `health_check` | Model discovery, validation, reranking, ops health |
53
+
54
+ **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, async video progress notifications, and **650+** automated tests (unit, mock, regression, and live integration).
45
55
 
56
+ ## Quick start
57
+
58
+ **1. Get an API key** (free tier works) → [openrouter.ai/keys](https://openrouter.ai/keys)
59
+
60
+ **2. Run the server**
61
+
62
+ ```bash
63
+ export OPENROUTER_API_KEY=sk-or-v1-...
64
+ npx -y @stabgan/openrouter-mcp-multimodal
65
+ ```
46
66
 
67
+ **3. Add to your MCP client** (Cursor, Claude Desktop, VS Code, etc.) — see [Install](#install) below.
47
68
 
48
- ## One-Click Install
69
+ > **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.
70
+
71
+ ## Install
72
+
73
+ MCP 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.
74
+
75
+ | Method | Runtime | Best for | This server |
76
+ | :--- | :--- | :--- | :--- |
77
+ | **[npx](#npx-recommended)** | Node.js 20+ | Most MCP clients (default) | ✅ `@stabgan/openrouter-mcp-multimodal` |
78
+ | **[uvx / pipx](#uvx--pipx-python-launcher)** | Python 3.10+ **and** Node.js 20+ | Python-first workflows, same pattern as PyPI MCP servers | ✅ [`mcp-server-openrouter-multimodal`](https://pypi.org/project/mcp-server-openrouter-multimodal/) |
79
+ | **[npm global](#npm-global)** | Node.js 20+ | Pin a version without re-downloading | ✅ |
80
+ | **[node (local)](#node-local-clone)** | Node.js 20+ | Contributors / air-gapped builds | ✅ |
81
+ | **[Docker Hub](#docker)** | Docker | Isolation, no Node on host | ✅ `stabgan/openrouter-mcp-multimodal` |
82
+ | **[GHCR](#ghcr-github-container-registry)** | Docker | GitHub-native OCI pulls | ✅ `ghcr.io/stabgan/openrouter-mcp-multimodal` |
83
+ | **[Smithery CLI](#smithery)** | Node.js (via installer) | Interactive install into Claude/Cursor/etc. | ✅ |
84
+ | **[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) |
85
+ | **[One-click deeplinks](#one-click)** | Node.js | Cursor, VS Code, Kiro | ✅ |
86
+ | **[Claude Code CLI](#claude-code-cli)** | Node.js | Terminal-first Claude Code users | ✅ |
87
+ | **[MCP Inspector](#mcp-inspector)** | Node.js | Debug / list tools locally | ✅ |
88
+ | **Windows `cmd /c npx`** | Node.js | Claude Desktop / Cursor when `npx` not on GUI PATH | ✅ [see below](#windows-npx) |
89
+ | pip / uv (direct) | — | Native Python MCP servers only | — use **uvx** row above |
90
+ | DXT desktop extensions | — | Bundled Claude Desktop `.dxt` | not yet |
91
+ | Remote HTTP / SSE | — | Hosted Smithery / Cloudflare endpoints | via [Smithery](https://smithery.ai/server/@stabgan/openrouter-mcp-multimodal) |
92
+
93
+ > **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.
94
+
95
+ ### One-click
49
96
 
50
97
  <table>
51
- <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>
52
- <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 to Cursor" /></a></td></tr>
98
+ <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>
53
99
  <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>
54
- <tr><td><strong>VS Code Insiders</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&quality=insiders"><img src="https://img.shields.io/badge/Add_to-VS_Code_Insiders-24bfa5?style=for-the-badge&logo=visualstudiocode&logoColor=white" alt="Add to VS Code Insiders" /></a></td></tr>
55
- <tr><td><strong>Claude Desktop</strong></td><td><a href="#option-1-npx-no-install">Install Guide</a> Add to <code>claude_desktop_config.json</code></td></tr>
56
- <tr><td><strong>Windsurf</strong></td><td><a href="#option-1-npx-no-install">Install Guide</a> Add to <code>~/.codeium/windsurf/mcp_config.json</code></td></tr>
57
- <tr><td><strong>Cline</strong></td><td><a href="#option-1-npx-no-install">Install Guide</a> — Add via Cline MCP settings</td></tr>
58
- <tr><td><strong>Smithery</strong></td><td><code>npx -y @smithery/cli install @stabgan/openrouter-mcp-multimodal --client claude</code></td></tr>
100
+ <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>
101
+ <tr><td><strong>Claude Desktop / Windsurf / Cline</strong></td><td><a href="#manual-config">Manual JSON config</a> (pick any method below)</td></tr>
102
+ <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>
103
+ <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>
59
104
  </table>
60
105
 
61
- > After clicking, the target client opens a confirmation prompt. You'll need to paste your `OPENROUTER_API_KEY` the deeplink ships a placeholder so no secrets end up in shared links.
62
-
63
- <!--
64
- Install-link audit (2026-04-20, round 4 — HTTPS redirectors only):
65
- GitHub Markdown strips non-HTTPS schemes (cursor://, vscode:) from href attributes
66
- and rewrites them to cursor.sh / nothing. The only deeplinks that survive GitHub's
67
- sanitizer are HTTPS URLs. Fortunately both Cursor and VS Code publish official
68
- HTTPS redirector endpoints that hand off to the IDE's native protocol handler:
69
-
70
- - Kiro: https://kiro.dev/launch/mcp/add?name=<name>&config=<url-encoded JSON>
71
- - Cursor: https://cursor.com/en/install-mcp?name=<name>&config=<base64>
72
- (config excludes `name`; badge is cursor.com/deeplink/mcp-install-dark.svg)
73
- - VS Code: https://insiders.vscode.dev/redirect/mcp/install?name=<name>&config=<urlenc JSON>
74
- (config excludes `name`; append &quality=insiders for Insiders)
75
-
76
- Earlier rounds used cursor://anysphere.cursor-deeplink/... and vscode:mcp/install?...
77
- — both got stripped by GitHub and rendered as broken redirects to cursor.sh or a
78
- camo.githubusercontent.com image-proxy URL. Pattern confirmed against
79
- github/github-mcp-server and modelcontextprotocol/servers READMEs.
80
-
81
- Regenerate with: node scripts/make-install-links.mjs
82
- -->
83
-
84
- ## Why This One?
85
-
86
- | Feature | Status |
87
- | :--- | :--- |
88
- | Text chat with 300+ models | ✅ |
89
- | Image analysis (vision) | ✅ Native with sharp optimization |
90
- | Audio analysis | ✅ Transcription + analysis, base64 auto-encoded |
91
- | Audio generation | ✅ Conversational, speech, and music with format auto-detection |
92
- | Image generation | ✅ Path-sandboxed disk output |
93
- | **Video understanding** | ✅ **v3** — mp4, mpeg, mov, webm from files, URLs, or data URLs |
94
- | **Video generation** | ✅ **v3** — Veo 3.1 / Sora 2 Pro / Seedance / Wan via async API with progress notifications |
95
- | **Response caching** | ✅ **v4.5** — `X-OpenRouter-Cache` passthrough, zero tokens billed on hit, 80–300ms latency |
96
- | **Web search plugin** | ✅ **v4.5** — `online: true` on `chat_completion` injects OpenRouter's Exa-backed plugin |
97
- | **Rerank** | ✅ **v4.5** — `rerank_documents` tool against `/rerank` (Cohere, Fireworks) |
98
- | **Health check** | ✅ **v4.5** — `health_check` verifies API key + OpenRouter reachability |
99
- | **Reasoning tokens** | ✅ **v4.5** — passthrough of DeepSeek R1 / Gemini Thinking / Opus 4.7 traces on `_meta.reasoning` |
100
- | **MCP 2025-06-18 spec** | ✅ **v4.5** — structured outputs (`outputSchema`), progress notifications, `title` + `openWorldHint` |
101
- | Auto image resize + compress | ✅ Configurable (defaults 800px max, JPEG 80%) |
102
- | Model search + validation | ✅ Filter by vision / audio / video modality |
103
- | Free model support | ✅ Default: free Nemotron VL |
104
- | Docker support | ✅ Multi-arch (amd64 + arm64), ~345 MB Alpine |
105
- | Retry-After + jitter | ✅ Honors `Retry-After` header, avoids thundering herd |
106
- | IPv4 + IPv6 SSRF blocklist | ✅ Covers mapped, compat, multicast, 6to4, Teredo, ORCHID |
107
- | Structured error taxonomy | ✅ Closed `_meta.code` so clients can switch on failure modes |
108
- | Reasoning-model awareness | ✅ Detects `max_tokens` cutoff during CoT, guides the caller |
109
- | MCP 2025 tool annotations | ✅ `readOnlyHint` / `destructiveHint` / `idempotentHint` on every tool |
106
+ Paste your `OPENROUTER_API_KEY` when prompted deeplinks use placeholders so secrets never appear in URLs.
110
107
 
111
- ## Tools
108
+ ### Manual config
109
+
110
+ <details open>
111
+ <summary><strong>npx (recommended)</strong></summary>
112
+
113
+ ```bash
114
+ export OPENROUTER_API_KEY=sk-or-v1-...
115
+ npx -y @stabgan/openrouter-mcp-multimodal
116
+ ```
112
117
 
113
- | Tool | Description |
114
- | :--- | :--- |
115
- | `chat_completion` | Send messages to any OpenRouter model. Detects reasoning-model cutoffs. Supports **provider routing** (`quantizations`, `ignore`, `sort`, `order`, `require_parameters`, `data_collection`, `allow_fallbacks`), **model suffixes** (`:nitro` for fastest, `:floor` for cheapest, `:exacto` for Auto Exacto tool-calling), **response caching** (`cache`, `cache_ttl`, `cache_clear`), **reasoning passthrough** (`include_reasoning`), and **web search** (`online`, `web_max_results`). |
116
- | `analyze_image` | Analyze images from local files, URLs, or data URIs. Auto-optimized with sharp. Optional `cache_input: true` attaches `cache_control: ephemeral` for Anthropic / Gemini 2.5+ prompt caching. |
117
- | `analyze_audio` | Analyze/transcribe audio (WAV, MP3, FLAC, OGG, etc.) from files, URLs, or data URIs. Optional `cache_input: true` for prompt caching. |
118
- | `analyze_video` | Analyze/transcribe video (mp4, mpeg, mov, webm) from files, URLs, or data URIs. Optional `cache_input: true` for prompt caching. |
119
- | `generate_image` | Generate images from text prompts. Supports `aspect_ratio` (14 values), `image_size` (0.5K–4K), and `max_tokens`. Optional path-sandboxed disk save. |
120
- | `generate_audio` | Generate audio from text. Auto-detects format, wraps raw PCM in WAV. |
121
- | `generate_video` | Generate video via OpenRouter's async API (Veo 3.1 / Sora 2 Pro / Seedance / Wan). Submits, polls, downloads, saves. Emits MCP `notifications/progress` when the client sends a `progressToken`. |
122
- | `generate_video_from_image` | Image-to-video wrapper around `generate_video`. Narrower schema, higher tool-call hit rate. |
123
- | `get_video_status` | Resume polling a `generate_video` job by id. Download + save when complete. |
124
- | `rerank_documents` | Rerank candidate documents against a query via OpenRouter's `/rerank` endpoint. Supports Cohere and Fireworks rerankers. |
125
- | `search_models` | Search/filter models by name, provider, or capabilities (vision / audio / video). Paginated via `offset` / `next_offset` / `has_more` / `total`. |
126
- | `get_model_info` | Get pricing, context length, and capabilities for any model. |
127
- | `validate_model` | Check if a model ID exists on OpenRouter. |
128
- | `health_check` | Verify API-key validity, OpenRouter reachability, and return server + protocol versions. |
118
+ ```json
119
+ {
120
+ "mcpServers": {
121
+ "openrouter": {
122
+ "command": "npx",
123
+ "args": ["-y", "@stabgan/openrouter-mcp-multimodal"],
124
+ "env": {
125
+ "OPENROUTER_API_KEY": "sk-or-v1-..."
126
+ }
127
+ }
128
+ }
129
+ }
130
+ ```
129
131
 
130
- > All error responses carry `_meta.code` from a closed taxonomy: `INVALID_INPUT` · `UNSAFE_PATH` · `UPSTREAM_HTTP` · `UPSTREAM_TIMEOUT` · `UPSTREAM_REFUSED` · `UNSUPPORTED_FORMAT` · `RESOURCE_TOO_LARGE` · `ZDR_INCOMPATIBLE` · `MODEL_NOT_FOUND` · `JOB_FAILED` · `JOB_STILL_RUNNING` · `INTERNAL`
132
+ Pin a release: `"args": ["-y", "@stabgan/openrouter-mcp-multimodal@4.5.3"]`
131
133
 
132
- ## Quick Start
134
+ </details>
133
135
 
134
- ### Prerequisites
136
+ <details>
137
+ <summary><strong>uvx / pipx (Python launcher)</strong></summary>
135
138
 
136
- Get a free API key from [openrouter.ai/keys](https://openrouter.ai/keys).
139
+ Install [uv](https://docs.astral.sh/uv/getting-started/installation/) (includes `uvx`), ensure **Node.js 20+** is also on your `PATH`, then:
137
140
 
138
- ### Option 1: npx (no install)
141
+ ```bash
142
+ export OPENROUTER_API_KEY=sk-or-v1-...
143
+ uvx mcp-server-openrouter-multimodal
144
+ # pin npm version: OPENROUTER_MCP_NPM_VERSION=4.5.3 uvx mcp-server-openrouter-multimodal
145
+ ```
139
146
 
140
147
  ```json
141
148
  {
142
149
  "mcpServers": {
143
150
  "openrouter": {
144
- "command": "npx",
145
- "args": ["-y", "@stabgan/openrouter-mcp-multimodal"],
151
+ "command": "uvx",
152
+ "args": ["mcp-server-openrouter-multimodal"],
146
153
  "env": {
147
154
  "OPENROUTER_API_KEY": "sk-or-v1-..."
148
155
  }
@@ -151,7 +158,61 @@ Get a free API key from [openrouter.ai/keys](https://openrouter.ai/keys).
151
158
  }
152
159
  ```
153
160
 
154
- ### Option 2: Docker
161
+ **pipx equivalent:** `pipx run mcp-server-openrouter-multimodal`
162
+
163
+ Optional: `OPENROUTER_MCP_NPM_VERSION=4.5.3` pins the underlying npm package.
164
+
165
+ </details>
166
+
167
+ <details>
168
+ <summary><strong>npm global</strong></summary>
169
+
170
+ ```bash
171
+ npm install -g @stabgan/openrouter-mcp-multimodal
172
+ ```
173
+
174
+ ```json
175
+ {
176
+ "mcpServers": {
177
+ "openrouter": {
178
+ "command": "openrouter-multimodal",
179
+ "env": { "OPENROUTER_API_KEY": "sk-or-v1-..." }
180
+ }
181
+ }
182
+ }
183
+ ```
184
+
185
+ </details>
186
+
187
+ <details>
188
+ <summary><strong>node (local clone)</strong></summary>
189
+
190
+ ```bash
191
+ git clone https://github.com/stabgan/openrouter-mcp-multimodal.git
192
+ cd openrouter-mcp-multimodal
193
+ npm ci && npm run build
194
+ ```
195
+
196
+ ```json
197
+ {
198
+ "mcpServers": {
199
+ "openrouter": {
200
+ "command": "node",
201
+ "args": ["/absolute/path/to/openrouter-mcp-multimodal/dist/index.js"],
202
+ "env": { "OPENROUTER_API_KEY": "sk-or-v1-..." }
203
+ }
204
+ }
205
+ }
206
+ ```
207
+
208
+ </details>
209
+
210
+ <details>
211
+ <summary><strong>Docker</strong></summary>
212
+
213
+ ```bash
214
+ docker run --rm -i -e OPENROUTER_API_KEY=sk-or-v1-... stabgan/openrouter-mcp-multimodal:latest
215
+ ```
155
216
 
156
217
  ```json
157
218
  {
@@ -159,8 +220,11 @@ Get a free API key from [openrouter.ai/keys](https://openrouter.ai/keys).
159
220
  "openrouter": {
160
221
  "command": "docker",
161
222
  "args": [
162
- "run", "--rm", "-i",
163
- "-e", "OPENROUTER_API_KEY=sk-or-v1-...",
223
+ "run",
224
+ "--rm",
225
+ "-i",
226
+ "-e",
227
+ "OPENROUTER_API_KEY=sk-or-v1-...",
164
228
  "stabgan/openrouter-mcp-multimodal:latest"
165
229
  ]
166
230
  }
@@ -168,229 +232,230 @@ Get a free API key from [openrouter.ai/keys](https://openrouter.ai/keys).
168
232
  }
169
233
  ```
170
234
 
171
- ### Option 3: Global install
235
+ Use `-i` (interactive stdio). Avoid `-t` (TTY corrupts MCP framing on some hosts).
236
+
237
+ </details>
238
+
239
+ <details>
240
+ <summary><strong>GHCR (GitHub Container Registry)</strong></summary>
172
241
 
173
242
  ```bash
174
- npm install -g @stabgan/openrouter-mcp-multimodal
243
+ docker run --rm -i -e OPENROUTER_API_KEY=sk-or-v1-... \
244
+ ghcr.io/stabgan/openrouter-mcp-multimodal:4.5.3
175
245
  ```
176
246
 
177
247
  ```json
178
248
  {
179
249
  "mcpServers": {
180
250
  "openrouter": {
181
- "command": "openrouter-multimodal",
182
- "env": { "OPENROUTER_API_KEY": "sk-or-v1-..." }
251
+ "command": "docker",
252
+ "args": [
253
+ "run", "--rm", "-i",
254
+ "-e", "OPENROUTER_API_KEY=sk-or-v1-...",
255
+ "ghcr.io/stabgan/openrouter-mcp-multimodal:latest"
256
+ ]
183
257
  }
184
258
  }
185
259
  }
186
260
  ```
187
261
 
188
- ### Option 4: Smithery
262
+ </details>
263
+
264
+ <details>
265
+ <summary><strong>Smithery</strong></summary>
266
+
267
+ Interactive install (writes config for your client):
189
268
 
190
269
  ```bash
191
270
  npx -y @smithery/cli install @stabgan/openrouter-mcp-multimodal --client claude
271
+ # or: --client cursor | vscode | windsurf | ...
192
272
  ```
193
273
 
194
- ## Configuration
274
+ Listing: [smithery.ai/server/@stabgan/openrouter-mcp-multimodal](https://smithery.ai/server/@stabgan/openrouter-mcp-multimodal)
275
+
276
+ </details>
195
277
 
196
278
  <details>
197
- <summary><strong>Environment variables</strong> (click to expand)</summary>
198
-
199
- | Variable | Required | Default | Description |
200
- | :--- | :---: | :--- | :--- |
201
- | `OPENROUTER_API_KEY` | Yes | — | Your OpenRouter API key |
202
- | `OPENROUTER_DEFAULT_MODEL` | No | `nvidia/nemotron-nano-12b-v2-vl:free` | Default model for chat + analyze tools |
203
- | `DEFAULT_MODEL` | No | — | Alias for above |
204
- | `OPENROUTER_MAX_TOKENS` | No | — | Default `max_tokens` for `chat_completion` when not set in the request. Useful on low-credit / free-tier accounts to avoid the full-context-window reservation. |
205
- | `OPENROUTER_PROVIDER_QUANTIZATIONS` | No | — | CSV. Filter providers by quantization (e.g. `fp16,int8`). |
206
- | `OPENROUTER_PROVIDER_IGNORE` | No | — | CSV. Exclude these provider slugs (e.g. `openai,anthropic`). |
207
- | `OPENROUTER_PROVIDER_SORT` | No | — | `price` / `throughput` / `latency`. |
208
- | `OPENROUTER_PROVIDER_ORDER` | No | — | JSON array or CSV of provider IDs (e.g. `["meta-llama","google"]`). |
209
- | `OPENROUTER_PROVIDER_REQUIRE_PARAMETERS` | No | — | `true` / `false`. Only use providers supporting every request parameter. |
210
- | `OPENROUTER_PROVIDER_DATA_COLLECTION` | No | — | `allow` / `deny`. Opt out of providers that log request data. |
211
- | `OPENROUTER_PROVIDER_ALLOW_FALLBACKS` | No | — | `true` / `false`. |
212
- | `OPENROUTER_CACHE_RESPONSES` | No | — | `1` / `true`. Enable response caching server-wide. Sends `X-OpenRouter-Cache: true` on chat + analyze_* calls unless overridden per-request with `cache: false`. Zero tokens billed on hits. |
213
- | `OPENROUTER_INCLUDE_REASONING` | No | — | `1` / `true`. Enable reasoning tokens passthrough server-wide for DeepSeek R1 / Gemini Thinking / Opus 4.7. Adds `_meta.reasoning` to `chat_completion` responses. |
214
- | `OPENROUTER_MODEL_CACHE_TTL_MS` | No | `3600000` | Model cache TTL (ms) |
215
- | `OPENROUTER_IMAGE_MAX_DIMENSION` | No | `800` | Longest edge for resize (px) |
216
- | `OPENROUTER_IMAGE_JPEG_QUALITY` | No | `80` | JPEG quality (1–100) |
217
- | `OPENROUTER_IMAGE_FETCH_TIMEOUT_MS` | No | `30000` | Image URL timeout |
218
- | `OPENROUTER_IMAGE_MAX_DOWNLOAD_BYTES` | No | `26214400` | Image URL size cap (~25 MB) |
219
- | `OPENROUTER_IMAGE_MAX_REDIRECTS` | No | `8` | Image URL redirect cap |
220
- | `OPENROUTER_IMAGE_MAX_DATA_URL_BYTES` | No | `20971520` | Image data URL size cap (~20 MB) |
221
- | `OPENROUTER_AUDIO_FETCH_TIMEOUT_MS` | No | `30000` | Audio URL timeout |
222
- | `OPENROUTER_AUDIO_MAX_DOWNLOAD_BYTES` | No | `26214400` | Audio URL size cap (~25 MB) |
223
- | `OPENROUTER_AUDIO_MAX_REDIRECTS` | No | `8` | Audio URL redirect cap |
224
- | `OPENROUTER_AUDIO_MAX_DATA_URL_BYTES` | No | `20971520` | Audio data URL size cap |
225
- | `OPENROUTER_DEFAULT_VIDEO_MODEL` | No | `google/gemini-2.5-flash` | Default for `analyze_video` |
226
- | `OPENROUTER_DEFAULT_VIDEO_GEN_MODEL` | No | `google/veo-3.1` | Default for `generate_video` |
227
- | `OPENROUTER_VIDEO_FETCH_TIMEOUT_MS` | No | `60000` | Video URL timeout |
228
- | `OPENROUTER_VIDEO_MAX_DOWNLOAD_BYTES` | No | `104857600` | Video URL size cap (~100 MB) |
229
- | `OPENROUTER_VIDEO_MAX_REDIRECTS` | No | `8` | Video URL redirect cap |
230
- | `OPENROUTER_VIDEO_MAX_DATA_URL_BYTES` | No | `104857600` | Video data URL size cap |
231
- | `OPENROUTER_VIDEO_POLL_INTERVAL_MS` | No | `15000` | Async video poll cadence |
232
- | `OPENROUTER_VIDEO_MAX_WAIT_MS` | No | `600000` | Max wait before returning a resumable handle |
233
- | `OPENROUTER_VIDEO_GEN_MAX_BYTES` | No | `268435456` | Generated video download cap (~256 MB) |
234
- | `OPENROUTER_VIDEO_INLINE_MAX_BYTES` | No | `10485760` | Inline video ceiling (~10 MB) |
235
- | `OPENROUTER_OUTPUT_DIR` | No | `process.cwd()` | Sandbox root for `save_path` |
236
- | `OPENROUTER_ALLOW_UNSAFE_PATHS` | No | — | `1` disables the sandbox |
237
- | `OPENROUTER_LOG_LEVEL` | No | `info` | `error` / `warn` / `info` / `debug` |
279
+ <summary><strong>MCP Registry</strong></summary>
238
280
 
239
- </details>
281
+ Official name: `io.github.stabgan/openrouter-multimodal`
240
282
 
241
- ### Security notes
283
+ - Registry: [registry.modelcontextprotocol.io](https://registry.modelcontextprotocol.io/servers/io.github.stabgan/openrouter-multimodal)
284
+ - npm package: `@stabgan/openrouter-mcp-multimodal`
285
+ - OCI image: `docker.io/stabgan/openrouter-mcp-multimodal`
242
286
 
243
- - **Analyze tools** can read local files and fetch HTTP(S) URLs. URL fetches block private/link-local/reserved IPv4 and IPv6 targets (SSRF mitigation) and cap response size.
244
- - **Generate tools** write to disk through a path sandbox: `save_path` is resolved against `OPENROUTER_OUTPUT_DIR` and any traversal attempt is rejected. Override with `OPENROUTER_ALLOW_UNSAFE_PATHS=1`.
245
- - **IPv6 SSRF blocklist** covers loopback, unspecified, IPv4-mapped, IPv4-compatible, link-local, site-local, ULA, multicast, documentation, Teredo, ORCHID, and 6to4 of private IPv4.
287
+ Clients that support registry-driven install will offer npm or Docker; otherwise use the JSON blocks above.
246
288
 
247
- ## Usage Examples
289
+ </details>
248
290
 
291
+ <details>
292
+ <summary><strong>Claude Code CLI</strong></summary>
293
+
294
+ ```bash
295
+ claude mcp add openrouter -- npx -y @stabgan/openrouter-mcp-multimodal
296
+ # project scope:
297
+ claude mcp add --scope project openrouter -- npx -y @stabgan/openrouter-mcp-multimodal
249
298
  ```
250
- # Chat
251
- Use chat_completion to explain quantum computing in simple terms.
252
299
 
253
- # Chat with provider routing prefer cheapest provider, exclude OpenAI, opt out of data collection
254
- Use chat_completion with model "anthropic/claude-3.5-sonnet", prompt "Summarize this",
255
- provider { sort: "price", ignore: ["openai"], data_collection: "deny" }
300
+ Set `OPENROUTER_API_KEY` in your shell or client env before starting Claude Code.
256
301
 
257
- # Chat with :nitro variant for faster response
258
- Use chat_completion with model "openai/gpt-4o:nitro", prompt "Reason step-by-step about this problem"
302
+ </details>
259
303
 
260
- # Chat with :floor variant for cheapest provider of the requested model
261
- Use chat_completion with model "mistralai/mistral-7b-instruct:floor", prompt "Quick check"
304
+ <details>
305
+ <summary><strong>MCP Inspector</strong></summary>
262
306
 
263
- # Chat with response caching + reasoning passthrough (v4.5)
264
- Use chat_completion with model "deepseek/deepseek-r1", prompt "Prove sqrt(2) is irrational",
265
- cache: true, cache_ttl: 3600, include_reasoning: true
266
- # → response.meta.cache = { status: "hit" | "miss", age, ttl }
267
- # → response.meta.reasoning = "<upstream reasoning trace>"
307
+ Debug tools/list and tool calls against a live OpenRouter key:
268
308
 
269
- # Chat with web search plugin (v4.5)
270
- Use chat_completion with model "openai/gpt-4o", prompt "What shipped in OpenRouter last week?",
271
- online: true, web_max_results: 5
309
+ ```bash
310
+ export OPENROUTER_API_KEY=sk-or-v1-...
311
+ npx -y @modelcontextprotocol/inspector npx -y @stabgan/openrouter-mcp-multimodal
312
+ ```
313
+
314
+ </details>
315
+
316
+ <details>
317
+ <summary><strong>Windows npx</strong></summary>
318
+
319
+ When Claude Desktop or Cursor cannot find `npx` (GUI apps often miss shell `PATH`), wrap with `cmd`:
320
+
321
+ ```json
322
+ {
323
+ "mcpServers": {
324
+ "openrouter": {
325
+ "command": "cmd",
326
+ "args": ["/c", "npx", "-y", "@stabgan/openrouter-mcp-multimodal"],
327
+ "env": { "OPENROUTER_API_KEY": "sk-or-v1-..." }
328
+ }
329
+ }
330
+ }
331
+ ```
272
332
 
273
- # Rerank documents against a query (v4.5)
274
- Use rerank_documents with query "best practices for MCP server auth",
275
- documents: ["doc A text...", "doc B text...", "doc C text..."], top_n: 3
333
+ If still failing, use the full path from `where npx` as the command.
276
334
 
277
- # Generate video from an image (v4.5)
278
- Use generate_video_from_image with image "./frame.png", prompt "zoom out slowly",
279
- model "google/veo-3.1", save to ./clip.mp4
335
+ </details>
280
336
 
281
- # Health check (v4.5)
282
- Use health_check
283
- # → { ok: true, server_version: "4.5.0", protocol_version: "2025-06-18", api_key_valid: true, models_cached: 312 }
337
+ ## Why this server?
284
338
 
285
- # Vision
286
- Use analyze_image on /path/to/photo.jpg and tell me what you see.
339
+ | Capability | This server | Typical MCP LLM servers |
340
+ | :----------------------------------- | :---------: | :---------------------: |
341
+ | Text chat (300+ models) | ✅ | ✅ |
342
+ | Image analysis + generation | ✅ | partial |
343
+ | Audio analysis + TTS | ✅ | ❌ |
344
+ | Video analysis + generation | ✅ | ❌ |
345
+ | Model search / validate / rerank | ✅ | ❌ |
346
+ | Path sandbox + SSRF protection | ✅ | rare |
347
+ | MCP 2025 structured outputs | ✅ | rare |
348
+ | Async video + progress notifications | ✅ | ❌ |
287
349
 
288
- # Audio transcription
289
- Use analyze_audio on /path/to/recording.mp3 to transcribe it.
350
+ ## Tools
290
351
 
291
- # Video understanding
292
- Use analyze_video on /path/to/clip.mp4 — what happens at 00:15?
352
+ 14 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.
293
353
 
294
- # Generate audio
295
- Use generate_audio with prompt "Explain neural networks" and voice "alloy", save to ./response.wav
354
+ | Tool | Purpose |
355
+ | :-------------------------- | :---------------------------------------------------------- |
356
+ | `chat_completion` | Text chat, web search, provider routing, caching, reasoning |
357
+ | `analyze_image` | Vision — local path, URL, or data URL + `question` |
358
+ | `analyze_audio` | Transcribe / analyze audio files |
359
+ | `analyze_video` | Describe / Q&A over video files |
360
+ | `generate_image` | Text-to-image with optional reference images |
361
+ | `generate_audio` | Text-to-speech / music |
362
+ | `generate_video` | Text-to-video (async, resumable) |
363
+ | `generate_video_from_image` | Image-to-video (narrower schema) |
364
+ | `get_video_status` | Poll / resume video jobs |
365
+ | `search_models` | Paginated model catalog search |
366
+ | `get_model_info` | Pricing, context, modalities |
367
+ | `validate_model` | Cheap model ID existence check |
368
+ | `rerank_documents` | Relevance ranking for RAG |
369
+ | `health_check` | API key + reachability probe |
296
370
 
297
- # Generate music
298
- Use generate_audio with model "google/lyria-3-clip-preview" and prompt "upbeat jazz piano trio"
371
+ Errors use a closed `_meta.code` taxonomy: `INVALID_INPUT` · `UNSAFE_PATH` · `UPSTREAM_*` · `MODEL_NOT_FOUND` · `JOB_STILL_RUNNING` · and more.
299
372
 
300
- # Generate image
301
- Use generate_image with prompt "a cat astronaut on mars", aspect_ratio "16:9", image_size "1K", save to ./cat.png
373
+ ## Examples
302
374
 
303
- # Generate video
304
- Use generate_video with model "google/veo-3.1", prompt "a calm river at sunrise",
305
- resolution 720p, duration 4, save to ./river.mp4
375
+ ### Chat (free model)
306
376
 
307
- # Resume a video job
308
- Use get_video_status with video_id "vid_abc123" and save_path "./river.mp4"
377
+ ```json
378
+ {
379
+ "tool": "chat_completion",
380
+ "arguments": {
381
+ "model": "google/gemma-4-26b-a4b-it:free",
382
+ "messages": [{ "role": "user", "content": "Summarize MCP in one sentence." }]
383
+ }
384
+ }
309
385
  ```
310
386
 
311
- ## Architecture
387
+ ### Analyze an image
312
388
 
389
+ ```json
390
+ {
391
+ "tool": "analyze_image",
392
+ "arguments": {
393
+ "image_path": "diagram.png",
394
+ "question": "List every label in this diagram."
395
+ }
396
+ }
313
397
  ```
314
- src/
315
- ├── index.ts # Entry, env validation, graceful shutdown
316
- ├── tool-handlers.ts # 14 tools (annotated) + dispatch
317
- ├── model-cache.ts # TTL + in-flight coalescing
318
- ├── openrouter-api.ts # REST client (chat + /videos)
319
- ├── errors.ts # Closed ErrorCode enum
320
- ├── logger.ts # JSON-line structured logger
321
- └── tool-handlers/
322
- ├── fetch-utils.ts # SSRF, bounded fetch, data-URL parser
323
- ├── openrouter-errors.ts # SDK/HTTP → ErrorCode classifier
324
- ├── completion-utils.ts # Reasoning-model cutoff detection
325
- ├── path-safety.ts # save_path sandbox
326
- ├── chat-completion.ts # Text + multimodal chat
327
- ├── analyze-image.ts # Vision analysis
328
- ├── analyze-audio.ts # Audio transcription
329
- ├── analyze-video.ts # Video understanding
330
- ├── generate-image.ts # Image generation
331
- ├── generate-audio.ts # Audio generation + streaming
332
- ├── generate-video.ts # Video generation (async)
333
- ├── image-utils.ts # Sharp optimization, MIME sniffing
334
- ├── audio-utils.ts # Audio format detection
335
- ├── video-utils.ts # Video format detection
336
- ├── search-models.ts # Model search
337
- ├── get-model-info.ts # Model detail lookup
338
- └── validate-model.ts # Model existence check
339
- ```
340
-
341
- ## Design Principles & Research
342
398
 
343
- v4.5.0's design draws from three threads of research and industry guidance. Rather than building in isolation, every feature ties to a cited source so decisions can be re-examined later.
399
+ > Use `image_path` and `question` not `image` / `prompt`.
344
400
 
345
- ### MCP-first design principles
401
+ ### Search models (vision + free)
346
402
 
347
- We follow [Phil Schmid's production guide for MCP servers](https://www.philschmid.de/mcp-best-practices) (Jan 2026), which argues that an MCP server is "a user interface for AI agents, not a REST API wrapper":
403
+ ```json
404
+ {
405
+ "tool": "search_models",
406
+ "arguments": {
407
+ "query": "gemma",
408
+ "capabilities": { "vision": true },
409
+ "limit": 10,
410
+ "offset": 0
411
+ }
412
+ }
413
+ ```
348
414
 
349
- - **Outcomes, not operations.** Our tools like `analyze_image` and `generate_video` encapsulate a whole workflow (fetch, validate, invoke, save) rather than exposing raw OpenRouter primitives.
350
- - **Flattened arguments.** Top-level primitives with enums (`aspect_ratio`, `image_size`), no deeply nested configuration blobs. The one nested object (`provider`) is required by OpenRouter's routing schema.
351
- - **Descriptions are context.** Every tool description includes "Fails when:" and "Works with:" sections (see next section for the research backing).
352
- - **Curated surface.** 14 tools total. Each is a distinct outcome; no "helper" tools that exist only for internal composition.
415
+ ### Generate video (async)
353
416
 
354
- Apigene's ["12 Rules for Production MCP Deployment"](https://apigene.ai/blog/mcp-best-practices) (March 2026) guided the error-handling posture: structured errors with `suggestions` and `retry_after_seconds` on `_meta` beat raw error strings the agent has to interpret.
417
+ ```json
418
+ {
419
+ "tool": "generate_video",
420
+ "arguments": {
421
+ "model": "google/veo-3.1",
422
+ "prompt": "Ocean waves at sunrise, cinematic drone shot",
423
+ "duration": 4,
424
+ "save_path": "river.mp4"
425
+ }
426
+ }
427
+ ```
355
428
 
356
- ### MCP 2025-06-18 spec compliance
429
+ If 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.**
357
430
 
358
- - **Structured outputs.** `validate_model`, `get_model_info`, `search_models`, `rerank_documents`, and `health_check` emit [`structuredContent` with `outputSchema`](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#output-schema), per §5.2.6-7. Agents can validate responses typefully.
359
- - **Progress notifications.** `generate_video` emits [`notifications/progress`](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress) on every poll tick when the client passes a `progressToken` in `_meta`. Progress values are guaranteed strictly monotonic per spec.
360
- - **Tool annotations.** Every tool carries `title` + `readOnlyHint` + `destructiveHint` + `idempotentHint` + `openWorldHint` so clients can render appropriate UI affordances.
431
+ More examples: [docs/plans/tool-description-improvement.md](./docs/plans/tool-description-improvement.md)
361
432
 
362
- ### Research-backed tool-design decisions
433
+ ## Security
363
434
 
364
- These papers shaped specific v4.5.0 choices:
435
+ - **Input path sandbox** `analyze_*` and reference images must stay inside `OPENROUTER_INPUT_DIR`
436
+ - **Output path sandbox** — `save_path` must stay inside `OPENROUTER_OUTPUT_DIR`
437
+ - **SSRF protection** — private/reserved IPs blocked on URL fetches
438
+ - **Untrusted content** — analyze outputs tagged `_meta.content_is_untrusted: true`
365
439
 
366
- | Finding | Source | How it shaped v4.5.0 |
367
- | :--- | :--- | :--- |
368
- | Failure-mode docs and inter-tool relationships measurably improve tool-selection accuracy | [Schlapbach, *Convergence of SGD & MCP*](https://arxiv.org/abs/2602.18764) (Feb 2026) | Every tool description has explicit "Fails when:" (ErrorCode triggers) and "Works with:" (related tools). |
369
- | Tool-call success drops with parameter count and schema complexity | [Fu et al., *ROSBag MCP Server*](https://arxiv.org/abs/2511.03497) (Nov 2025) | `generate_video_from_image` is a narrower image-to-video wrapper around `generate_video` — fewer params, higher hit rate. |
370
- | Indirect prompt injection via tool-returned content is a real attack vector | [Zhao et al., *ClawGuard*](https://arxiv.org/abs/2604.11790) (Apr 2026) · [Yu et al., *Defense via Tool Result Parsing*](https://arxiv.org/abs/2601.04795) (Jan 2026) | `analyze_image` / `analyze_audio` / `analyze_video` tag their output `_meta.content_is_untrusted: true`. Downstream agents know to treat that text as data, not instructions. |
371
- | Provider-level tool-calling variance is large and persists across providers for the same model | [OpenRouter Auto Exacto announcement](https://openrouter.ai/announcements/auto-exacto) (Mar 2026) | `chat_completion` documents the `:exacto` model suffix alongside `:nitro` / `:floor`. 80-88% error reduction on top tool-calling models. |
372
- | LLM JSON defects compound at scale | [OpenRouter Response Healing](https://openrouter.ai/announcements/response-healing-reduce-json-defects-by-80percent) (Dec 2025) | Structured outputs + outputSchema declarations give clients a parseable contract. (Response-healing plugin itself is opt-in on OpenRouter's side.) |
373
- | MCP servers are vulnerable to preference-manipulation and tool-poisoning attacks | [Wang et al., *MPMA*](https://arxiv.org/abs/2505.11154) (May 2025) · [Turgut & Gümüş, *CASCADE*](https://arxiv.org/abs/2604.17125) (Apr 2026) | Tool descriptions audited for injection surface; audit logging (`logger.audit()`) captures every paid-op invocation with a prompt preview for forensics. |
440
+ Override sandboxes only with `OPENROUTER_ALLOW_UNSAFE_PATHS=1` (discouraged).
374
441
 
375
- ### OpenRouter platform parity
442
+ ## Configuration
376
443
 
377
- v4.5.0 surfaces platform features shipped between Q4 2025 and Q2 2026:
444
+ <details>
445
+ <summary><strong>Environment variables</strong></summary>
378
446
 
379
- - [Response caching via `X-OpenRouter-Cache`](https://openrouter.ai/announcements/response-caching) (Apr 2026): zero tokens billed on identical request cache hits, 80-300ms latency.
380
- - [Web search plugin](https://openrouter.ai/announcements/introducing-web-search-via-the-api) (Jan 2025): Exa-backed, enabled via `online: true`.
381
- - [Reasoning tokens](https://openrouter.ai/announcements/reasoning-tokens-for-thinking-models) (Jan 2025): DeepSeek R1 / Gemini Thinking / Opus 4.7 chain-of-thought via `include_reasoning: true`.
382
- - [Auto Exacto](https://openrouter.ai/announcements/auto-exacto) (Mar 2026): on-by-default for tool-calling; `:exacto` suffix for all other requests.
383
- - [Rerank endpoint](https://openrouter.ai/announcements/april-release-spotlight) (Apr 2026): Cohere + Fireworks via the new `rerank_documents` tool.
384
- - [Prompt caching with `cache_control`](https://openrouter.ai/docs/guides/best-practices/prompt-caching): Anthropic Claude 10x / Gemini 2.5+ 4x savings on repeated input media via `cache_input: true` on analyze_* tools.
385
- - [Zero completion token insurance](https://openrouter.ai/announcements/never-pay-for-empty-ai-responses-again) (Mar 2025): automatic, no opt-in needed.
447
+ | Variable | Required | Default | Description |
448
+ | :----------------------------- | :------: | :------------------------------------ | :----------------------------------- |
449
+ | `OPENROUTER_API_KEY` | **Yes** | — | OpenRouter API key |
450
+ | `OPENROUTER_DEFAULT_MODEL` | No | `nvidia/nemotron-nano-12b-v2-vl:free` | Default when tools omit `model` |
451
+ | `OPENROUTER_INTEGRATION_MODEL` | No | `google/gemma-4-26b-a4b-it:free` | Model used by live integration tests |
452
+ | `OPENROUTER_OUTPUT_DIR` | No | `cwd` | Sandbox root for `save_path` |
453
+ | `OPENROUTER_INPUT_DIR` | No | — | Sandbox root for local input files |
454
+ | `OPENROUTER_LOG_LEVEL` | No | `info` | `error` / `warn` / `info` / `debug` |
386
455
 
387
- ### Security posture
456
+ See [`.env.example`](./.env.example) for the full list (provider routing, image/audio/video limits, caching, video polling).
388
457
 
389
- - **Path sandbox.** All file writes (`save_path`) and reads (`input_images`, frame images) go through `resolveSafeOutputPath` / `resolveSafeInputPath`, which reject traversal escapes. Legacy bypass: `OPENROUTER_ALLOW_UNSAFE_PATHS=1`.
390
- - **SSRF blocklist.** Loopback, private, link-local, multicast, 6to4, Teredo, ORCHID, and IPv4-mapped IPv6 all rejected at the fetch layer.
391
- - **Audit logging.** `logger.audit()` emits a JSON line at level=audit for every `generate_video`, `generate_audio`, and `generate_image` call. Bypasses `OPENROUTER_LOG_LEVEL` so unintended spend is always traceable. 80-char prompt preview is the hard PII boundary.
392
- - **Structured errors.** Closed `_meta.code` taxonomy means agents switch on failure modes without regex-parsing free text. Rate-limit errors include `retry_after_seconds` derived from `Retry-After` headers.
393
- - **No credential leakage.** `OPENROUTER_API_KEY` is read once at startup, passed to the SDK, and never echoed in logs, tool responses, or error messages. Fatal-error logging whitelists fields explicitly (name / message / trimmed stack) — no raw error objects. Verified by an independent bug-hunter audit (Apr 2026).
458
+ </details>
394
459
 
395
460
  ## Development
396
461
 
@@ -398,36 +463,55 @@ v4.5.0 surfaces platform features shipped between Q4 2025 and Q2 2026:
398
463
  git clone https://github.com/stabgan/openrouter-mcp-multimodal.git
399
464
  cd openrouter-mcp-multimodal
400
465
  npm install
401
- cp .env.example .env # Add your API key
466
+ cp .env.example .env # add OPENROUTER_API_KEY
402
467
  npm run build
403
- npm start
404
468
  ```
405
469
 
470
+ ### Testing
471
+
472
+ | Command | What it runs |
473
+ | :------------------------- | :--------------------------------------------------------- |
474
+ | `npm test` | **652** unit + mock tests (no API key, &lt;2s) |
475
+ | `npm run test:regression` | Security + schema regression guards |
476
+ | `npm run test:integration` | **16** live OpenRouter scenarios (**requires** `.env` key) |
477
+ | `npm run test:e2e` | Full MCP stdio smoke (`scripts/live-e2e.mjs`) |
478
+ | `npm run ci` | lint + format + build + **all** of the above except e2e |
479
+
480
+ **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.
481
+
482
+ Mock 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.
483
+
406
484
  ```bash
407
- npm test # 163 unit tests, <1s
408
- npm run test:integration # Live API tests
409
485
  npm run lint
410
- node scripts/live-e2e.mjs # 16 live E2E scenarios
486
+ npm run format:check
411
487
  ```
412
488
 
413
- ## Upgrading from v2
489
+ ## FAQ
490
+
491
+ ### Do I need paid OpenRouter credits?
492
+
493
+ No, 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.
494
+
495
+ ### Which MCP clients are supported?
496
+
497
+ Any MCP-compatible client over stdio: Cursor, Claude Desktop, VS Code Copilot, Windsurf, Cline, Kiro, and custom agents.
498
+
499
+ ### How is this different from calling OpenRouter directly?
500
+
501
+ This 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.
414
502
 
415
- v3 is **additive** no tool schemas or env vars were removed.
503
+ ### Where is the security advisory for path traversal?
416
504
 
417
- - Three new tools: `analyze_video`, `generate_video`, `get_video_status`
418
- - Structured `_meta.code` on every error response (text messages preserved)
419
- - `save_path` sandboxed by default — set `OPENROUTER_OUTPUT_DIR` or `OPENROUTER_ALLOW_UNSAFE_PATHS=1`
420
- - Reasoning-model awareness: `content: null` + `finish_reason: length` now returns `INVALID_INPUT` with a preview instead of empty string
421
- - IPv6 SSRF coverage extended to mapped, compat, multicast, 6to4, Teredo, ORCHID
505
+ Fixed in 4.5.2+ see [GHSA-3q7p-736f-x44v](https://github.com/stabgan/openrouter-mcp-multimodal/security/advisories/GHSA-3q7p-736f-x44v) and `docs/solutions/security-issues/`.
422
506
 
423
507
  ## Compatibility
424
508
 
425
- Works with any MCP client: [Kiro](https://kiro.dev) · [Claude Desktop](https://claude.ai/download) · [Cursor](https://cursor.sh) · [Windsurf](https://codeium.com/windsurf) · [Cline](https://github.com/cline/cline) · any MCP-compatible client.
509
+ Works with any MCP client. Protocol: **MCP 2025-06-18**. Node **≥ 20** (Docker image uses Node 22).
426
510
 
427
511
  ## License
428
512
 
429
- Apache 2.0. See [LICENSE](./LICENSE). v1.0.0 through v3.2.0 were released under MIT; v4.0.0 relicensed to Apache 2.0 (Apache 2.0 is a permissive superset of MIT with explicit patent grant).
513
+ Apache 2.0 see [LICENSE](./LICENSE).
430
514
 
431
515
  ## Contributing
432
516
 
433
- Issues and PRs welcome. Please open an issue first for major changes.
517
+ Issues and PRs welcome. For large changes, open an issue first. Run `npm run ci` before submitting.