@stabgan/openrouter-mcp-multimodal 4.5.1 → 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.
- package/README.md +367 -283
- package/dist/index.js +1 -1
- package/dist/model-cache.d.ts +22 -12
- package/dist/model-cache.js +58 -21
- package/dist/tool-descriptions.d.ts +19 -0
- package/dist/tool-descriptions.js +423 -0
- package/dist/tool-handlers/analyze-audio.js +5 -1
- package/dist/tool-handlers/analyze-image.js +6 -5
- package/dist/tool-handlers/analyze-video.js +6 -5
- package/dist/tool-handlers/audio-utils.js +4 -2
- package/dist/tool-handlers/chat-completion.js +1 -1
- package/dist/tool-handlers/fetch-utils.js +16 -2
- package/dist/tool-handlers/generate-audio.js +2 -4
- package/dist/tool-handlers/generate-image-input.d.ts +3 -0
- package/dist/tool-handlers/generate-image-input.js +38 -0
- package/dist/tool-handlers/generate-image.d.ts +13 -51
- package/dist/tool-handlers/generate-image.js +32 -119
- package/dist/tool-handlers/generate-video.js +28 -24
- package/dist/tool-handlers/image-utils.d.ts +1 -0
- package/dist/tool-handlers/image-utils.js +26 -16
- package/dist/tool-handlers/openrouter-errors.js +6 -2
- package/dist/tool-handlers/provider-routing.js +7 -2
- package/dist/tool-handlers/rerank.js +2 -5
- package/dist/tool-handlers/search-models.d.ts +2 -2
- package/dist/tool-handlers/search-models.js +2 -6
- package/dist/tool-handlers/structured-output.d.ts +8 -0
- package/dist/tool-handlers/structured-output.js +11 -0
- package/dist/tool-handlers/video-utils.js +6 -9
- package/dist/tool-handlers.js +25 -123
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +26 -14
package/README.md
CHANGED
|
@@ -1,148 +1,155 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="assets/logo.
|
|
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
|
|
5
|
+
<h1 align="center">OpenRouter MCP Multimodal</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<strong>The
|
|
8
|
+
<strong>The MCP server for multimodal AI agents.</strong><br/>
|
|
9
|
+
One install · 14 tools · 300+ OpenRouter models · text, vision, audio & 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/
|
|
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%
|
|
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://
|
|
24
|
-
<a href="https://
|
|
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 · ~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="#
|
|
33
|
-
<a href="#tools">Tools</a>
|
|
34
|
-
<a href="#
|
|
35
|
-
<a href="#
|
|
36
|
-
<a href="#
|
|
37
|
-
<a href="#
|
|
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
|
-
[](https://mseep.ai/app/8f27d6d4-0877-4b86-b377-8a33f451e755)
|
|
43
39
|
|
|
44
|
-
|
|
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
|
-
|
|
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>
|
|
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>
|
|
55
|
-
<tr><td><strong>Claude Desktop</strong></td><td><a href="#
|
|
56
|
-
<tr><td><strong>
|
|
57
|
-
<tr><td><strong>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
132
|
+
Pin a release: `"args": ["-y", "@stabgan/openrouter-mcp-multimodal@4.5.3"]`
|
|
131
133
|
|
|
132
|
-
|
|
134
|
+
</details>
|
|
133
135
|
|
|
134
|
-
|
|
136
|
+
<details>
|
|
137
|
+
<summary><strong>uvx / pipx (Python launcher)</strong></summary>
|
|
135
138
|
|
|
136
|
-
|
|
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
|
-
|
|
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": "
|
|
145
|
-
"args": ["-
|
|
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
|
-
|
|
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",
|
|
163
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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": "
|
|
182
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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>
|
|
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
|
-
|
|
281
|
+
Official name: `io.github.stabgan/openrouter-multimodal`
|
|
240
282
|
|
|
241
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
258
|
-
Use chat_completion with model "openai/gpt-4o:nitro", prompt "Reason step-by-step about this problem"
|
|
302
|
+
</details>
|
|
259
303
|
|
|
260
|
-
|
|
261
|
-
|
|
304
|
+
<details>
|
|
305
|
+
<summary><strong>MCP Inspector</strong></summary>
|
|
262
306
|
|
|
263
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
286
|
-
|
|
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
|
-
|
|
289
|
-
Use analyze_audio on /path/to/recording.mp3 to transcribe it.
|
|
350
|
+
## Tools
|
|
290
351
|
|
|
291
|
-
|
|
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
|
-
|
|
295
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
308
|
-
|
|
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
|
-
|
|
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
|
-
|
|
399
|
+
> Use `image_path` and `question` — not `image` / `prompt`.
|
|
344
400
|
|
|
345
|
-
###
|
|
401
|
+
### Search models (vision + free)
|
|
346
402
|
|
|
347
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
433
|
+
## Security
|
|
363
434
|
|
|
364
|
-
|
|
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
|
-
|
|
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
|
-
|
|
442
|
+
## Configuration
|
|
376
443
|
|
|
377
|
-
|
|
444
|
+
<details>
|
|
445
|
+
<summary><strong>Environment variables</strong></summary>
|
|
378
446
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
456
|
+
See [`.env.example`](./.env.example) for the full list (provider routing, image/audio/video limits, caching, video polling).
|
|
388
457
|
|
|
389
|
-
|
|
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
|
|
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, <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
|
-
|
|
486
|
+
npm run format:check
|
|
411
487
|
```
|
|
412
488
|
|
|
413
|
-
##
|
|
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
|
-
|
|
503
|
+
### Where is the security advisory for path traversal?
|
|
416
504
|
|
|
417
|
-
|
|
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:
|
|
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
|
|
513
|
+
Apache 2.0 — see [LICENSE](./LICENSE).
|
|
430
514
|
|
|
431
515
|
## Contributing
|
|
432
516
|
|
|
433
|
-
Issues and PRs welcome.
|
|
517
|
+
Issues and PRs welcome. For large changes, open an issue first. Run `npm run ci` before submitting.
|