OpenRouter MCP Multimodal Server
MCP server that connects clients to OpenRouter for multimodal AI chat, analysis, generation, and model discovery.
Chat with OpenRouter models via
chat_completion, with optional model, token limit, and temperature.Analyze images, audio, and video from local paths, URLs, or data URLs, with optional questions/model overrides.
Generate images from prompts with optional reference images, size, aspect ratio, and sandboxed save path.
Generate audio and video; video jobs are async and can be resumed via
get_video_status.Search, inspect, and validate OpenRouter model IDs and capabilities.
README-wide capabilities also include embeddings, reranking, TTS/STT, structured decisions, async chat jobs, health checks, and access to 300+ models.
Offers deployment through Docker containers, with support for environment variables and seamless integration with MCP configurations.
Enables use of Google's Gemini models through OpenRouter for text chat and multimodal conversations, with support for vision capabilities and model customization.
Provides Node.js-based installation and execution options with NPX support for easy integration into MCP environments.
Supports Python-based installation and execution using UV package manager for those preferring Python environments.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@OpenRouter MCP Multimodal Server analyze this product photo and suggest improvements for the lighting"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
What is this?
OpenRouter MCP Multimodal is a production-grade Model Context Protocol (MCP) server — listed on the official MCP Registry as io.github.stabgan/openrouter-multimodal. It connects AI coding agents (Cursor, Claude Desktop, VS Code, Windsurf, Cline, and others) to OpenRouter's unified LLM API over stdio.
Unlike text-only MCP servers, one install covers the full multimodal surface:
Capability | Tools | Highlights |
Chat |
| 300+ models, |
Vision |
| OCR, captioning, VQA, image generation with reference inputs, dedicated Image API with resolution/quality/format control |
Audio |
| Transcription, speech/music generation, dedicated TTS (Deepgram, ElevenLabs, OpenAI; free Deepgram default, voices, mp3/pcm), dedicated STT (Whisper, GPT-4o Transcribe, Voxtral, ElevenLabs Scribe) |
Video |
| Clip understanding, Veo 3.1 / Seedance 2.5 / Kling v3.0 / Wan 2.7 / Grok Imagine Video generation with progress notifications |
Embedding |
| 37+ embedding models (text-embedding-3-small/large, Nomic, Jina, Voyage, etc.) |
Decisions |
| Structured yes/no, choice, or score decisions with calibrated probabilities (System One models) |
Catalog |
| Model discovery, validation, reranking, ops health |
Production hardening: input/output path sandboxes (including analyze_* local files as of v4.5.2), SSRF guards, structured errors with _meta.code, MCP 2025-11-25 structured outputs + tool icons, async video progress notifications, and 3929 automated tests (3905 unit/mock + 7 regression + 17 live integration).
Related MCP server: OpenRouter MCP Multimodal Server
Quick start
1. Get an API key (free tier works) → openrouter.ai/keys
2. Run the server
export OPENROUTER_API_KEY=sk-or-v1-...
npx -y @stabgan/openrouter-mcp-multimodal3. Add to your MCP client — copy one JSON block from Install into your client config:
Client | Config location |
Cursor | Project: |
Claude Desktop | macOS: |
VS Code |
|
Windsurf | Windsurf Settings → MCP (same |
Use the mcpServers object from Manual config below.
No credits required to start. Free models such as
google/gemma-4-26b-a4b-it:freework for chat and vision. Video/audio generation typically needs credits.
Install
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.
Method | Runtime | Best for | This server |
Node.js 22+ | Most MCP clients (default) | ✅ | |
Python 3.10+ and Node.js 22+ | Python-first workflows, same pattern as PyPI MCP servers | ||
Node.js 22+ | Pin a version without re-downloading | ✅ | |
Node.js 22+ | Contributors / air-gapped builds | ✅ | |
Docker | Isolation, no Node on host | ✅ | |
Docker | GitHub-native OCI pulls | ✅ | |
Node.js (via installer) | Interactive install into Claude/Cursor/etc. | ✅ | |
npm or OCI | Official discovery ( | ✅ listing | |
Node.js | Cursor, VS Code, Kiro | ✅ | |
Node.js | Terminal-first Claude Code users | ✅ | |
Node.js | Debug / list tools locally | ✅ | |
Windows | Node.js | Claude Desktop / Cursor when | |
pip / uv (direct) | — | Native Python MCP servers only | — use uvx row above |
DXT desktop extensions | — | Bundled Claude Desktop | not yet |
Remote HTTP / SSE | — | Hosted Smithery / Cloudflare endpoints | via Smithery |
uvx vs npx: In the MCP ecosystem,
npxruns npm (Node) packages anduvxruns PyPI (Python) packages. Because this server is Node-based,uvxuses a thin Python launcher that execsnpx -y @stabgan/openrouter-mcp-multimodal— you still need Node installed.
One-click
Paste your OPENROUTER_API_KEY when prompted — deeplinks use placeholders so secrets never appear in URLs.
Manual config
export OPENROUTER_API_KEY=sk-or-v1-...
npx -y @stabgan/openrouter-mcp-multimodal{
"mcpServers": {
"openrouter": {
"command": "npx",
"args": ["-y", "@stabgan/openrouter-mcp-multimodal"],
"env": {
"OPENROUTER_API_KEY": "sk-or-v1-..."
}
}
}
}Pin a release: "args": ["-y", "@stabgan/openrouter-mcp-multimodal@5.1.3"]
Install uv (includes uvx), ensure Node.js 22+ is also on your PATH, then:
export OPENROUTER_API_KEY=sk-or-v1-...
uvx mcp-server-openrouter-multimodal
# pin npm version: OPENROUTER_MCP_NPM_VERSION=5.1.3 uvx mcp-server-openrouter-multimodal{
"mcpServers": {
"openrouter": {
"command": "uvx",
"args": ["mcp-server-openrouter-multimodal"],
"env": {
"OPENROUTER_API_KEY": "sk-or-v1-..."
}
}
}
}pipx equivalent: pipx run mcp-server-openrouter-multimodal
Optional: OPENROUTER_MCP_NPM_VERSION=5.1.3 pins the underlying npm package.
npm install -g @stabgan/openrouter-mcp-multimodal{
"mcpServers": {
"openrouter": {
"command": "openrouter-multimodal",
"env": { "OPENROUTER_API_KEY": "sk-or-v1-..." }
}
}
}git clone https://github.com/stabgan/openrouter-mcp-multimodal.git
cd openrouter-mcp-multimodal
npm ci && npm run build{
"mcpServers": {
"openrouter": {
"command": "node",
"args": ["/absolute/path/to/openrouter-mcp-multimodal/dist/index.js"],
"env": { "OPENROUTER_API_KEY": "sk-or-v1-..." }
}
}
}docker run --rm -i -e OPENROUTER_API_KEY=sk-or-v1-... stabgan/openrouter-mcp-multimodal:latest{
"mcpServers": {
"openrouter": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"-e",
"OPENROUTER_API_KEY=sk-or-v1-...",
"stabgan/openrouter-mcp-multimodal:latest"
]
}
}
}Use -i (interactive stdio). Avoid -t (TTY corrupts MCP framing on some hosts).
docker run --rm -i -e OPENROUTER_API_KEY=sk-or-v1-... \
ghcr.io/stabgan/openrouter-mcp-multimodal:5.1.3{
"mcpServers": {
"openrouter": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"-e",
"OPENROUTER_API_KEY=sk-or-v1-...",
"ghcr.io/stabgan/openrouter-mcp-multimodal:5.1.3"
]
}
}
}Interactive install (writes config for your client):
npx -y @smithery/cli install @stabgan/openrouter-mcp-multimodal --client claude
# or: --client cursor | vscode | windsurf | ...Listing: smithery.ai/server/@stabgan/openrouter-mcp-multimodal
Official name: io.github.stabgan/openrouter-multimodal
Registry: registry.modelcontextprotocol.io
npm package:
@stabgan/openrouter-mcp-multimodalOCI image:
docker.io/stabgan/openrouter-mcp-multimodal
Clients that support registry-driven install will offer npm or Docker; otherwise use the JSON blocks above.
claude mcp add openrouter -- npx -y @stabgan/openrouter-mcp-multimodal
# project scope:
claude mcp add --scope project openrouter -- npx -y @stabgan/openrouter-mcp-multimodalSet OPENROUTER_API_KEY in your shell or client env before starting Claude Code.
Debug tools/list and tool calls against a live OpenRouter key:
export OPENROUTER_API_KEY=sk-or-v1-...
npx -y @modelcontextprotocol/inspector npx -y @stabgan/openrouter-mcp-multimodalWhen Claude Desktop or Cursor cannot find npx (GUI apps often miss shell PATH), wrap with cmd:
{
"mcpServers": {
"openrouter": {
"command": "cmd",
"args": ["/c", "npx", "-y", "@stabgan/openrouter-mcp-multimodal"],
"env": { "OPENROUTER_API_KEY": "sk-or-v1-..." }
}
}
}If still failing, use the full path from where npx as the command.
Why this server?
Capability | This server | Typical MCP LLM servers |
Text chat (300+ models) | ✅ | ✅ |
Image analysis + generation | ✅ | partial |
Audio analysis + TTS + STT | ✅ | ❌ |
Video analysis + generation | ✅ | ❌ |
Vector embeddings (37+ models) | ✅ | ❌ |
Structured decisions (System One) | ✅ | ❌ |
Model search / validate / rerank | ✅ | ❌ |
Path sandbox + SSRF protection | ✅ | rare |
MCP 2025 structured outputs | ✅ | rare |
Async video + progress notifications | ✅ | ❌ |
Tools
21 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.
Tool | Purpose |
| Text chat, web search, provider routing, caching, reasoning, structured output |
| Async background job for long-running reasoning models |
| Poll / retrieve async completion results |
| Vision — local path, URL, or data URL + |
| Transcribe / analyze audio files |
| Describe / Q&A over video files |
| Text-to-image via chat completions with reference images |
| Text-to-image via dedicated |
| Text-to-speech / music via chat completions |
| Dedicated TTS ( |
| Dedicated STT ( |
| Text-to-video (async, resumable) |
| Image-to-video (narrower schema) |
| Poll / resume video jobs |
| Paginated model catalog search |
| Pricing, context, modalities |
| Cheap model ID existence check |
| Relevance ranking for RAG |
| Vector embeddings (37+ models: text-embedding-3-small/large, Nomic, etc.) |
| Structured decisions with calibrated probabilities (System One models) |
| API key + reachability probe |
Every error carries _meta.code from a closed taxonomy (13 codes). Switch on these for programmatic handling:
| Meaning | Recovery |
| Bad or missing arguments | Check tool description for correct schema |
| API key missing, invalid, or revoked | Regenerate at openrouter.ai/keys |
| Local path escaped the input/output sandbox | Move files under |
| Non-auth HTTP error from OpenRouter (4xx/5xx) | Check |
| Upstream did not respond in time | Retry, or use |
| Content policy, rate limit, or insufficient credits | Check |
| File is not a recognized media format | Verify file type matches tool expectations |
| File exceeds fetch/inline size cap | Trim file or use |
| Model/provider requires zero data retention | Use a ZDR-compatible provider or different model |
| Model slug not in OpenRouter catalog | Run |
| Async job (video/chat) failed on provider side | Check |
| Async job not yet complete (not an error) | Call |
| Unexpected server-side error | Report if reproducible |
Errors include _meta.suggestions with agent-oriented next steps when available.
Binary tool results (v4.7.0+)
Generate tools (generate_image, generate_image_dedicated, generate_audio, text_to_speech, generate_video, generate_video_from_image, get_video_status) return image, audio, or video bytes. As of 4.7.0 the behavior is explicit:
| Tool result |
Set | Text pointer only — e.g. |
Unset, under byte ceiling | Inline media block and summary text (images/audio use MCP |
Unset, over ceiling | Text only with a hint to pass |
Default inline ceilings (override per kind or globally):
Kind | Default | Env vars (precedence: per-kind → global) |
Image | 1 MiB |
|
Audio | 1 MiB |
|
Video | 10 MiB |
|
If you previously relied on both a saved file and inline media in the same tool result, read the file from _meta.save_path (or omit save_path to get inline media when under the ceiling).
Examples
Chat (free model)
{
"tool": "chat_completion",
"arguments": {
"model": "google/gemma-4-26b-a4b-it:free",
"messages": [{ "role": "user", "content": "Summarize MCP in one sentence." }]
}
}Chat with structured output (JSON Schema)
{
"tool": "chat_completion",
"arguments": {
"model": "openai/gpt-4o",
"messages": [{ "role": "user", "content": "Extract the name and age from: 'Alice is 30 years old.'" }],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "person",
"schema": {
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer" }
},
"required": ["name", "age"]
}
}
}
}
}For simpler JSON without a schema, use
"response_format": { "type": "json_object" }. Add"response_healing": trueto auto-fix malformed JSON.
Analyze an image
{
"tool": "analyze_image",
"arguments": {
"image_path": "diagram.png",
"question": "List every label in this diagram."
}
}Use
image_pathandquestion— notimage/prompt.
Search models (vision + free)
{
"tool": "search_models",
"arguments": {
"query": "gemma",
"capabilities": { "vision": true },
"limit": 10,
"offset": 0
}
}Generate video (async)
{
"tool": "generate_video",
"arguments": {
"model": "google/veo-3.1",
"prompt": "Ocean waves at sunrise, cinematic drone shot",
"duration": 4,
"save_path": "river.mp4"
}
}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.
With save_path set (as above), the result is a text pointer to the saved file once complete — not inline video. See Binary tool results.
Embed text (vector embeddings)
{
"tool": "embed_text",
"arguments": {
"texts": ["What is MCP?", "Model Context Protocol enables tool use for LLMs."],
"model": "openai/text-embedding-3-small",
"dimensions": 1024
}
}Returns embedding arrays for RAG, semantic search, or clustering. 37+ models supported — pass a single string or an array of up to 2048 texts.
Structured decision (System One)
{
"tool": "decide",
"arguments": {
"questions": {
"is_bug": {
"type": "noul",
"instructions": "Is this a bug report or a feature request?",
"criteria": {
"true": "Describes broken or incorrect behavior",
"false": "Requests new functionality"
}
}
},
"state": {
"ticket": "The login button does not respond when clicked on Safari."
}
}
}Returns calibrated probabilities instead of generated text. Three question types: noul (yes/no), choice (pick one category), score (ordinal rubric). Models: typesafe/jev-1.13 (default), upstage/solar-decide, microsoft/decision-1.
More examples: docs/plans/tool-description-improvement.md
Security
Input path sandbox — local paths on
analyze_*and reference images must stay insideOPENROUTER_INPUT_DIR(falls back toOPENROUTER_OUTPUT_DIR, thencwd)Output path sandbox —
save_pathmust stay insideOPENROUTER_OUTPUT_DIRAsync job reads —
get_chat_completion_statusresolves disk paths only underOPENROUTER_OUTPUT_DIR/openrouter-jobs/(4.7.0+)SSRF protection — private/reserved IPs blocked on URL fetches
Untrusted content — analyze outputs tagged
_meta.content_is_untrusted: truePrototype-pollution defense — all API output boundaries (model records, embeddings, decisions, rerank results, log context) recursively strip
__proto__,constructor, andprototypekeys before forwarding to MCP clientsSecret redaction — the stderr JSON logger redacts API keys and sensitive values; stdout is reserved for MCP protocol traffic
Override sandboxes only with OPENROUTER_ALLOW_UNSAFE_PATHS=1 (discouraged).
Report vulnerabilities: SECURITY.md (private disclosure — do not file public issues for exploits).
Configuration
Variable | Required | Default | Description |
| Yes | — | OpenRouter API key |
| No |
| Default when tools omit |
| No |
| Sandbox root for |
| No |
| Sandbox root for local input files |
| No |
| Global inline media ceiling |
| No | falls back to global | Per-kind inline ceiling |
| No | falls back to global | Per-kind inline ceiling |
| No |
| Video inline ceiling |
| No |
|
|
See .env.example for the full list (provider routing, fetch limits, caching, video polling, async jobs, integration-test overrides).
Development
git clone https://github.com/stabgan/openrouter-mcp-multimodal.git
cd openrouter-mcp-multimodal
npm install
cp .env.example .env # add OPENROUTER_API_KEY
npm run buildTesting
Command | What it runs |
| 3905 unit + mock tests (no API key, <20s) |
| 7 security + schema regression guards |
| 17 live OpenRouter scenarios (requires |
| Full MCP stdio smoke ( |
| lint + format + version sync + build + all tests |
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.
Mock tests live under src/__tests__/mock/ and cover handlers, path sandboxes, SSRF blocks, model-cache pagination, tool descriptions, and structured outputs — 396 additional cases beyond the core suite.
npm run lint
npm run format:check
npm run version:check # package.json vs src/version.ts, server.json, pyproject.tomlReleasing
Published artifacts (npm, PyPI/uvx, Docker, GHCR) all ship from the same semver on a git tag (vX.Y.Z). Pushing to main runs tests but does not publish to npm or PyPI.
Normal flow: merge conventional commits to main → Release Please opens a Release PR → merge it → tag is created → CI publishes everywhere.
Manual flow: bump all version files → npm run version:check → npm run ci + smoke tests → commit → git tag vX.Y.Z → git push origin vX.Y.Z.
Full checklist, file list, CI secrets, and agent instructions:
docs/RELEASING.md— maintainer release guideAGENTS.md— quick reference for AI agents
Troubleshooting
Symptom | Likely cause | Fix |
Server exits immediately / | Missing or empty API key | Set |
| Bad or revoked key | Regenerate at openrouter.ai/keys; restart the MCP client |
| Typo or retired model ID | Run |
HTTP 402 / insufficient credits | Paid model or generation on zero balance | Add credits at openrouter.ai/credits or use a |
| Rate limit | Wait for |
| Local path outside sandbox | Put files under |
| GUI | Use the Windows npx |
No inline image/audio after upgrade | v4.7.0 with | Expected — result is text + |
MCP client shows stale tool list | Client cache | Restart MCP / reload window after upgrading the package pin |
Structured errors include _meta.suggestions with agent-oriented next steps when available.
FAQ
Do I need paid OpenRouter credits?
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.
Which MCP clients are supported?
Any MCP-compatible client over stdio: Cursor, Claude Desktop, VS Code Copilot, Windsurf, Cline, Kiro, and custom agents.
How is this different from calling OpenRouter directly?
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.
Where is the security advisory for path traversal?
Fixed in 4.5.2+ — see GHSA-3q7p-736f-x44v, SECURITY.md, and docs/solutions/security-issues/.
Compatibility
Works with any MCP client. Protocol: MCP 2025-11-25 (backwards-compatible with 2025-06-18 and earlier). Node ≥ 22 (Docker image uses Node 24).
License
Apache 2.0 — see LICENSE.
Contributing
Issues and PRs welcome. For large changes, open an issue first.
Before submitting: run npm run ci. Use Conventional Commits (fix:, feat:, etc.) so Release Please can cut the next release. See docs/RELEASING.md if you need to ship a version.
Available Tools
11 toolsanalyze_audioBRead-only
Analyze or transcribe an audio file using a multimodal model
Inputs: audio_path (required), model, question.
analyze_imageBRead-only
Analyze an image using a vision model
Inputs: image_path (required), model, question.
analyze_videoARead-only
Analyze or transcribe a video file using a multimodal model. Accepts mp4, mpeg, mov, or webm from a local file path, HTTP(S) URL, or base64 data URL. Default model: google/gemini-2.5-flash.
Inputs: video_path (required), model, question.
chat_completionB
Send messages to an OpenRouter model and get a response
Inputs: messages (required), model, max_tokens, temperature.
generate_audioA
Generate audio from a text prompt. Conversational models (e.g. openai/gpt-audio) respond in spoken audio. Music models (e.g. google/lyria-3-clip-preview) need a structured prompt. Output format is auto-detected and file extension is corrected automatically.
Inputs: prompt (required), model, voice, format, +1 more.
generate_imageA
Generate an image from a text prompt. Optionally conditioned on one or more reference images (file paths, http(s) URLs, or data URLs) for character / style consistency. Sends modalities: ["image","text"] by default; override via the modalities field if needed.
Inputs: prompt (required), model, save_path, image_size, +4 more.
generate_videoA
Generate a video from a text prompt using an OpenRouter video-generation model (default: google/veo-3.1). Submits an async job, polls until completion or max_wait_ms, then downloads the result. Optionally conditioned on first/last-frame images or reference images. Large outputs are auto-saved when save_path is provided and path-sandboxed.
Inputs: prompt (required), seed, model, duration, +9 more.
get_model_infoCRead-onlyIdempotent
Get details about a specific model
Inputs: model (required).
get_video_statusARead-onlyIdempotent
Resume a previously submitted video generation job by id. Returns the latest status; if completed, downloads the video (and saves it when save_path is provided).
Inputs: video_id (required), save_path.
search_modelsCRead-onlyIdempotent
Search available OpenRouter models
Inputs: limit, query, provider, capabilities.
validate_modelARead-onlyIdempotent
Check if a model ID exists
Inputs: model (required).
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 11 tools
Each tool targets a distinct function: analyzing audio/image/video, generating content, chat completions, and model queries. No two tools have overlapping purposes, and even the video generation status tool is clearly a helper for async workflows.
Most tools follow a verb_noun pattern (analyze_, generate_, get_, search_, validate_). The exception is 'chat_completion', which combines two nouns rather than a verb_noun, causing a minor inconsistency in the naming style.
With 11 tools, the set covers all major modalities (audio, image, video, text) and supporting functions (model info, search, validation). The count is well-balanced—not excessive or too sparse for the server's multimodal purpose.
Core analysis, generation, and query tools are present. Some minor gaps exist (e.g., no explicit tool for listing all models, though search_models and get_model_info cover it). Overall, the surface is comprehensive for typical multimodal workflows.
Maintenance
Related MCP Connectors
Chat with any LLM, generate images, browse models and check your usage and costs.
LLM chat, text tools, image generation, editing, batch image jobs, and asynchronous video generation
Every major chat, image and video model on one prepaid key. OpenAI, Anthropic and Gemini formats.
Search UnoRouter models, free ones included, check prices and chat through one OpenAI-compatible key
Related MCP Servers
- AlicenseAqualityDmaintenanceProvides integration with OpenRouter.ai, allowing access to various AI models through a unified interface.4301 npm64Apache 2.0
- FlicenseBqualityDmaintenanceProvides access to OpenRouter.ai's diverse model ecosystem for text chat and image analysis capabilities, with support for multimodal conversations and automatic image optimization.712 npm-
- AlicenseAqualityBmaintenanceProvides access to 400+ AI models from OpenRouter, enabling users to chat with models like GPT-4, Claude, Gemini, and Llama, compare responses across multiple models, and retrieve model information with pricing details.487 npm14MIT
- AlicenseBqualityDmaintenanceEnables vision capabilities for any AI model by routing image analysis requests through OpenRouter's vision models. It provides tools to analyze images from URLs, local file paths, or base64 data.2630 npm19MIT