This guide shows how to build an OpenRouter AI agent with MCP tools: an agent that reaches models from Anthropic, Google, OpenAI, DeepSeek, Qwen and others through one OpenRouter key, and calls tools from a Model Context Protocol server. It is for developers who want to try or switch models without opening an account with every provider. You will set up the key, pick a model that really supports tools, make OpenRouter route only to providers that can run them, and fix the errors you are likely to see.
How do you use OpenRouter with Promptise?
Create a key at openrouter.ai, put it in OPENROUTER_API_KEY, and pass "openrouter:anthropic/claude-sonnet-5.5" or Model("anthropic/claude-sonnet-5.5", provider="openrouter") to build_agent(). The model part is OpenRouter's provider/model id. Promptise calls OpenRouter's OpenAI-compatible API at https://openrouter.ai/api/v1 with langchain-openai, which is part of the core install. Add your MCP servers in servers=, and the agent sends their tools to the model and runs the calls it makes.
One key, many models. Changing the model is a one-line change, and billing goes through your OpenRouter credits.
Pick a model that lists `tools`. Not every model on OpenRouter supports tool calling, and an agent needs it.
Require tool support in routing. By default, if no provider of a model supports tools, OpenRouter still sends the request and ignores the tools. Set require_parameters so that can't happen silently.
Control where data goes with OpenRouter's provider settings, such as data_collection and zdr.
Check before you call.
promptise models check openrouter:<model>shows the endpoint and what is missing.
[02]
Set up OpenRouter
You need Python 3.10 or later, Promptise Foundry 1.4.2 or later, an OpenRouter account with credits, and a key from openrouter.ai, Keys.
pip install "promptise>=1.4.2"Put the key in a .env file next to your script. Promptise loads the nearest .env file automatically, and a variable already exported in your shell wins:
# OpenRouter — model string example: openrouter:anthropic/claude-sonnet-4.5
export OPENROUTER_API_KEY=...
# ↳ openrouter.ai → KeysThen check the configuration. With the key set, the check shows the endpoint and stops before calling anything:
$ promptise models check openrouter:anthropic/claude-sonnet-5.5
openrouter:anthropic/claude-sonnet-5.5 → openrouter:anthropic/claude-sonnet-5.5 (OpenRouter)
model part: anthropic/claude-sonnet-5.5 — the provider/model path as listed by OpenRouter
route: OpenAI-compatible endpoint https://openrouter.ai/api/v1 (core, nothing to install)
OPENROUTER_API_KEY: set — openrouter.ai → Keys
Configuration OK — every required setting is in place; nothing was called. Add --ping to make a real one-token call to the model."Configuration OK" means the key is set. --ping makes one real call, which proves the key, your credits and the model id.
Find a model that supports tools
OpenRouter lists hundreds of models, and each one says which request parameters it supports. An agent needs tools. The public models API needs no key, so you can filter it with a few lines of Python:
"""List OpenRouter models that accept tools. Usage: python list_tool_models.py anthropic/"""
import sys
import httpx
prefix = sys.argv[1] if len(sys.argv) > 1 else ""
models = httpx.get("https://openrouter.ai/api/v1/models", timeout=30).json()["data"]
for m in models:
if m["id"].startswith(prefix) and "tools" in (m.get("supported_parameters") or []):
print(f'{m["id"]:<40} context {m["context_length"]:>9,}')This is the real output for Claude models at the time of writing:
$ python list_tool_models.py anthropic/claude | head -6
anthropic/claude-haiku-5.5 context 1,000,000
anthropic/claude-haiku-5.5:batch context 1,000,000
anthropic/claude-sonnet-5.5 context 1,000,000
anthropic/claude-sonnet-5.5:batch context 1,000,000
anthropic/claude-opus-5.5 context 1,000,000
anthropic/claude-opus-5.5:batch context 1,000,000The :batch ids are separate variants that OpenRouter lists next to each model. For an interactive agent, use the plain id. You can browse the same filter on the models page. To decide between models, test them on your own tool calls as shown in the best LLM for AI agents.
[03]
Build a minimal OpenRouter AI agent
The agent answers questions about a shop's products. The model is set once at the top of the file:
import asyncio
import sys
from promptise import Model, build_agent
from promptise.config import StdioServerSpec
# The key comes from OPENROUTER_API_KEY (environment or .env).
ROUTED = Model("anthropic/claude-sonnet-5.5", provider="openrouter")
async def make_agent(model=ROUTED):
return await build_agent(
model=model,
servers={"shop": StdioServerSpec(command=sys.executable, args=["shop_server.py"])},
instructions="You answer questions about the shop's products. "
"Use the tools for stock and prices. Never guess them.",
max_agent_iterations=10,
)
async def main():
agent = await make_agent()
try:
result = await agent.ainvoke(
{"messages": [{"role": "user", "content": "Is KB-101 in stock, and what does it cost?"}]}
)
calls = [c["name"] for m in result["messages"] for c in getattr(m, "tool_calls", None) or []]
print("tool calls:", ", ".join(calls) or "none")
print(result["messages"][-1].text)
finally:
await agent.shutdown()
if __name__ == "__main__":
asyncio.run(main())What each part does:
`Model("anthropic/claude-sonnet-5.5", provider="openrouter")` names the model by its OpenRouter id. The part before the slash is the model's maker, not the service that runs it. OpenRouter picks the serving provider for you.
`max_agent_iterations=10` stops a run that keeps calling tools without finishing.
`result["messages"][-1].text` reads the answer text.
Run it with python openrouter_agent.py. With a valid key and credits, the script prints the tools the model called and then its answer. To try another model, change only the id, for example to google/gemini-3.8-flash or deepseek/deepseek-v4-flash.
[04]
Use MCP tools through OpenRouter
The tools come from a small MCP server that the agent starts over stdio:
"""A small MCP tool server: product stock and prices for a shop."""
from promptise.mcp.server import MCPServer
server = MCPServer("shop")
STOCK = {"KB-101": 14, "MS-220": 0, "HD-350": 3}
PRICES = {"KB-101": 89.00, "MS-220": 39.50, "HD-350": 129.00}
@server.tool()
async def check_stock(sku: str) -> dict:
"""How many units of a product (by SKU) are in stock."""
if sku not in STOCK:
return {"sku": sku, "error": "unknown SKU"}
return {"sku": sku, "in_stock": STOCK[sku]}
@server.tool()
async def get_price(sku: str) -> dict:
"""The current price of a product (by SKU), in USD."""
if sku not in PRICES:
return {"sku": sku, "error": "unknown SKU"}
return {"sku": sku, "price_usd": PRICES[sku]}
if __name__ == "__main__":
server.run() # stdio by defaultyour code ──▶ Promptise agent ──HTTPS──▶ openrouter.ai/api/v1 ──▶ a provider that serves the model
│ ▲ │ (Anthropic, Google, a host, ...)
│ └──── tool_calls ◀───────┘
▼
MCP client ──stdio──▶ shop_server.py ──▶ tool result ──▶ next model callPromptise is the MCP client. It connects to each server in servers=, lists the tools and sends them as OpenAI-style tool definitions on every call. OpenRouter passes them to providers that use the same format and translates them for the others. When the model returns tool calls, Promptise runs them against your MCP server and calls the model again with the results. Neither OpenRouter nor the model provider connects to your MCP server, so it can stay local or on a private network. The MCP connection guide covers remote servers and authentication.
[05]
OpenRouter-specific notes
Route only to providers that support your request
Many models on OpenRouter are served by several providers, and they don't all support the same parameters. OpenRouter's provider routing docs say that with the default settings, tools is a soft preference: if no provider for the model supports tools, the request still goes through and the tools are ignored. For an agent, that means a run that never calls a tool and gives no error.
Turn on require_parameters to rule that out. OpenRouter's provider settings go in the request body, which you pass through extra_body:
ROUTED = Model(
"anthropic/claude-sonnet-5.5",
provider="openrouter",
extra={
"extra_body": {"provider": {"require_parameters": True, "data_collection": "deny"}},
"default_headers": {"HTTP-Referer": "https://example.com", "X-OpenRouter-Title": "Shop agent"},
},
)`require_parameters: True` routes only to providers that support every parameter in the request, including tools. If none does, you get an error instead of a silent run without tools.
`data_collection: "deny"` skips providers that may store your data. zdr: True goes further and uses only zero data retention endpoints.
`order`, `only`, `ignore` and `sort` pin or exclude providers, or sort them by price, throughput or latency.
`HTTP-Referer` and `X-OpenRouter-Title` are optional headers that identify your app on OpenRouter. They change nothing about routing.
Fallbacks: OpenRouter's and Promptise's
OpenRouter already falls back to other providers of the same model when one returns a 5xx error or is rate limited. That covers an outage at one host. To fall back to a different model or to another service entirely, use a Promptise FallbackChain:
from promptise import FallbackChain
model = FallbackChain([ROUTED, "openai:gpt-5-mini"], timeout_per_model=60)
agent = await make_agent(model=model)Test the agent on every model in the chain, because different models make different tool choices with the same instructions.
Model-specific settings
OpenRouter forwards common parameters such as temperature and max_tokens to the provider. The model's own rules still apply: current Claude models, for example, reject non-default temperature, so leave it out for them. Check each model's supported parameters in the models API before you set them.
Pricing and limits
Each model has its own price per million input and output tokens, shown on the models page, and you pay from prepaid credits. OpenRouter's API overview and errors page describe limits and the status codes in the table below. An agent makes several calls per question, so watch the cost per run and set a budget on the agent.
[06]
Common problems and fixes
Symptom | Cause | Fix |
|---|---|---|
---|---|--- | ||
| No key in the environment, the .env file or the code | Set it, or pass api_key= |
HTTP 401 | Invalid, disabled or malformed key | Create a new key on openrouter.ai and replace it |
HTTP 402 | Not enough credits on the account or key | Add credits to the account, and check any limit set on the key |
HTTP 403 | Blocked by a guardrail or moderation | Check your guardrail settings on OpenRouter and the content of the request |
The agent never calls a tool and returns no error | No provider of the model supports tools, so the tools were ignored | Pick a model that lists tools, and set require_parameters: True |
HTTP 503 | No provider meets your routing requirements, for example require_parameters or zdr with a model whose providers don't qualify | Relax the requirement or pick another model |
HTTP 502 | The chosen model is down or returned an invalid response | Retry, or add a FallbackChain to another model |
HTTP 429 | You are being rate limited | Back off and retry, or lower concurrency |
The status codes and their meanings come from OpenRouter's errors page. This is the real error the example printed with a malformed key:
$ python openrouter_agent.py
...
promptise.engine.execution.GraphExecutionError: Graph 'react' failed at node 'reason': OpenAIAuthenticationError: Error code: 401 - {'error': {'message': 'Missing Authentication header', 'code': 401}}[07]
Frequently asked questions
Can OpenRouter models call MCP tools?
Yes, if the model supports tool calling. Promptise is the MCP client: it reads the tools from your MCP servers and sends them to OpenRouter as tool definitions, and OpenRouter passes them to the provider serving the model. Pick a model whose supported parameters include tools, and set require_parameters so OpenRouter never drops the tools silently.
Is OpenRouter slower or more expensive than calling providers directly?
OpenRouter adds a network hop, and its prices and fees are listed on its own site. Check them for the models you use. In exchange you get one key and one bill for many providers, automatic fallback between hosts of the same model, and a one-line model switch. For a single provider at high volume, a direct key can be simpler.
How do I keep my data from being stored by providers on OpenRouter?
Use the provider settings in the request: data_collection: "deny" skips providers that may store data, and zdr: True routes only to zero data retention endpoints. In Promptise, pass them in extra={"extra_body": {"provider": {...}}}. Check OpenRouter's privacy documentation for what each setting guarantees.
Which OpenRouter model should I use for an AI agent?
Pick from the models that list tools, then test two or three on your own questions. A strong hosted model such as anthropic/claude-sonnet-5.5 is a good baseline, and cheaper models such as deepseek/deepseek-v4-flash or google/gemini-3.8-flash are worth comparing. Choose by correct tool calls and cost per task, not by leaderboard rank.
Can I switch from OpenRouter to a direct provider later?
Yes. Change the Model(...) line to the provider's own prefix, for example "anthropic:claude-sonnet-5-5", and set that provider's key. The agent, its instructions and its MCP servers don't change. Note that model ids differ: OpenRouter uses anthropic/claude-sonnet-5.5, while Anthropic's API uses claude-sonnet-5-5.
[08]
Next steps
Compare models on your own tasks: best LLM for AI agents.
Build the full agent step by step: how to build an AI agent.
Track spend across models: AI agent cost tracking.
Add fallbacks and caching: semantic cache and model fallback.
Write the tool side: build a production MCP server.
Every provider setting: Models and providers reference.