@stabgan/openrouter-mcp-multimodal 4.5.0 → 4.5.3

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 (34) hide show
  1. package/README.md +380 -242
  2. package/dist/index.js +22 -6
  3. package/dist/model-cache.d.ts +35 -12
  4. package/dist/model-cache.js +79 -22
  5. package/dist/tool-descriptions.d.ts +19 -0
  6. package/dist/tool-descriptions.js +423 -0
  7. package/dist/tool-handlers/analyze-audio.js +5 -1
  8. package/dist/tool-handlers/analyze-image.js +6 -5
  9. package/dist/tool-handlers/analyze-video.js +6 -5
  10. package/dist/tool-handlers/audio-utils.js +4 -2
  11. package/dist/tool-handlers/chat-completion.js +1 -1
  12. package/dist/tool-handlers/fetch-utils.js +16 -2
  13. package/dist/tool-handlers/generate-audio.js +2 -4
  14. package/dist/tool-handlers/generate-image-input.d.ts +3 -0
  15. package/dist/tool-handlers/generate-image-input.js +38 -0
  16. package/dist/tool-handlers/generate-image.d.ts +13 -51
  17. package/dist/tool-handlers/generate-image.js +32 -119
  18. package/dist/tool-handlers/generate-video.js +28 -24
  19. package/dist/tool-handlers/health-check.js +4 -1
  20. package/dist/tool-handlers/image-utils.d.ts +1 -0
  21. package/dist/tool-handlers/image-utils.js +26 -16
  22. package/dist/tool-handlers/openrouter-errors.d.ts +5 -1
  23. package/dist/tool-handlers/openrouter-errors.js +78 -13
  24. package/dist/tool-handlers/provider-routing.js +7 -2
  25. package/dist/tool-handlers/rerank.js +2 -5
  26. package/dist/tool-handlers/search-models.d.ts +2 -2
  27. package/dist/tool-handlers/search-models.js +2 -6
  28. package/dist/tool-handlers/structured-output.d.ts +8 -0
  29. package/dist/tool-handlers/structured-output.js +11 -0
  30. package/dist/tool-handlers/video-utils.js +6 -9
  31. package/dist/tool-handlers.js +43 -123
  32. package/dist/version.d.ts +1 -1
  33. package/dist/version.js +1 -1
  34. package/package.json +26 -14
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).
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
+ ```
45
66
 
67
+ **3. Add to your MCP client** (Cursor, Claude Desktop, VS Code, etc.) — see [Install](#install) below.
46
68
 
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.
47
70
 
48
- ## One-Click Install
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
112
109
 
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. |
110
+ <details open>
111
+ <summary><strong>npx (recommended)</strong></summary>
129
112
 
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`
113
+ ```bash
114
+ export OPENROUTER_API_KEY=sk-or-v1-...
115
+ npx -y @stabgan/openrouter-mcp-multimodal
116
+ ```
131
117
 
132
- ## Quick Start
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
+ ```
133
131
 
134
- ### Prerequisites
132
+ Pin a release: `"args": ["-y", "@stabgan/openrouter-mcp-multimodal@4.5.3"]`
135
133
 
136
- Get a free API key from [openrouter.ai/keys](https://openrouter.ai/keys).
134
+ </details>
135
+
136
+ <details>
137
+ <summary><strong>uvx / pipx (Python launcher)</strong></summary>
137
138
 
138
- ### Option 1: npx (no install)
139
+ Install [uv](https://docs.astral.sh/uv/getting-started/installation/) (includes `uvx`), ensure **Node.js 20+** is also on your `PATH`, then:
140
+
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,212 +232,286 @@ 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>
280
+
281
+ Official name: `io.github.stabgan/openrouter-multimodal`
282
+
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`
286
+
287
+ Clients that support registry-driven install will offer npm or Docker; otherwise use the JSON blocks above.
238
288
 
239
289
  </details>
240
290
 
241
- ### Security notes
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
298
+ ```
242
299
 
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.
300
+ Set `OPENROUTER_API_KEY` in your shell or client env before starting Claude Code.
246
301
 
247
- ## Usage Examples
302
+ </details>
248
303
 
304
+ <details>
305
+ <summary><strong>MCP Inspector</strong></summary>
306
+
307
+ Debug tools/list and tool calls against a live OpenRouter key:
308
+
309
+ ```bash
310
+ export OPENROUTER_API_KEY=sk-or-v1-...
311
+ npx -y @modelcontextprotocol/inspector npx -y @stabgan/openrouter-mcp-multimodal
249
312
  ```
250
- # Chat
251
- Use chat_completion to explain quantum computing in simple terms.
252
313
 
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" }
314
+ </details>
256
315
 
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"
316
+ <details>
317
+ <summary><strong>Windows npx</strong></summary>
259
318
 
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"
319
+ When Claude Desktop or Cursor cannot find `npx` (GUI apps often miss shell `PATH`), wrap with `cmd`:
262
320
 
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>"
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
+ ```
268
332
 
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
333
+ If still failing, use the full path from `where npx` as the command.
272
334
 
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
335
+ </details>
276
336
 
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
337
+ ## Why this server?
280
338
 
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 }
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 | ✅ | ❌ |
284
349
 
285
- # Vision
286
- Use analyze_image on /path/to/photo.jpg and tell me what you see.
350
+ ## Tools
287
351
 
288
- # Audio transcription
289
- Use analyze_audio on /path/to/recording.mp3 to transcribe it.
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.
290
353
 
291
- # Video understanding
292
- Use analyze_video on /path/to/clip.mp4 — what happens at 00:15?
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 |
293
370
 
294
- # Generate audio
295
- Use generate_audio with prompt "Explain neural networks" and voice "alloy", save to ./response.wav
371
+ Errors use a closed `_meta.code` taxonomy: `INVALID_INPUT` · `UNSAFE_PATH` · `UPSTREAM_*` · `MODEL_NOT_FOUND` · `JOB_STILL_RUNNING` · and more.
296
372
 
297
- # Generate music
298
- Use generate_audio with model "google/lyria-3-clip-preview" and prompt "upbeat jazz piano trio"
373
+ ## Examples
299
374
 
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
375
+ ### Chat (free model)
302
376
 
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
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
+ }
385
+ ```
386
+
387
+ ### Analyze an image
306
388
 
307
- # Resume a video job
308
- Use get_video_status with video_id "vid_abc123" and save_path "./river.mp4"
389
+ ```json
390
+ {
391
+ "tool": "analyze_image",
392
+ "arguments": {
393
+ "image_path": "diagram.png",
394
+ "question": "List every label in this diagram."
395
+ }
396
+ }
309
397
  ```
310
398
 
311
- ## Architecture
399
+ > Use `image_path` and `question` — not `image` / `prompt`.
400
+
401
+ ### Search models (vision + free)
312
402
 
403
+ ```json
404
+ {
405
+ "tool": "search_models",
406
+ "arguments": {
407
+ "query": "gemma",
408
+ "capabilities": { "vision": true },
409
+ "limit": 10,
410
+ "offset": 0
411
+ }
412
+ }
313
413
  ```
314
- src/
315
- ├── index.ts # Entry, env validation, graceful shutdown
316
- ├── tool-handlers.ts # 11 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
414
+
415
+ ### Generate video (async)
416
+
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
+ }
339
427
  ```
340
428
 
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.**
430
+
431
+ More examples: [docs/plans/tool-description-improvement.md](./docs/plans/tool-description-improvement.md)
432
+
433
+ ## Security
434
+
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`
439
+
440
+ Override sandboxes only with `OPENROUTER_ALLOW_UNSAFE_PATHS=1` (discouraged).
441
+
442
+ ## Configuration
443
+
444
+ <details>
445
+ <summary><strong>Environment variables</strong></summary>
446
+
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` |
455
+
456
+ See [`.env.example`](./.env.example) for the full list (provider routing, image/audio/video limits, caching, video polling).
457
+
458
+ </details>
459
+
341
460
  ## Development
342
461
 
343
462
  ```bash
344
463
  git clone https://github.com/stabgan/openrouter-mcp-multimodal.git
345
464
  cd openrouter-mcp-multimodal
346
465
  npm install
347
- cp .env.example .env # Add your API key
466
+ cp .env.example .env # add OPENROUTER_API_KEY
348
467
  npm run build
349
- npm start
350
468
  ```
351
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
+
352
484
  ```bash
353
- npm test # 163 unit tests, <1s
354
- npm run test:integration # Live API tests
355
485
  npm run lint
356
- node scripts/live-e2e.mjs # 16 live E2E scenarios
486
+ npm run format:check
357
487
  ```
358
488
 
359
- ## 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.
360
502
 
361
- v3 is **additive** no tool schemas or env vars were removed.
503
+ ### Where is the security advisory for path traversal?
362
504
 
363
- - Three new tools: `analyze_video`, `generate_video`, `get_video_status`
364
- - Structured `_meta.code` on every error response (text messages preserved)
365
- - `save_path` sandboxed by default — set `OPENROUTER_OUTPUT_DIR` or `OPENROUTER_ALLOW_UNSAFE_PATHS=1`
366
- - Reasoning-model awareness: `content: null` + `finish_reason: length` now returns `INVALID_INPUT` with a preview instead of empty string
367
- - 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/`.
368
506
 
369
507
  ## Compatibility
370
508
 
371
- 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).
372
510
 
373
511
  ## License
374
512
 
375
- 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).
376
514
 
377
515
  ## Contributing
378
516
 
379
- 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.