Skip to main content
Glama
lumishoang

OpenRouter MCP Server

by lumishoang

search_models

Search and filter AI models by provider, input price, context window, and required features like tool calling or vision.

Instructions

Search and filter OpenRouter models.

Args: query: Free-text search in model name/id/description provider: Filter by provider (anthropic, google, openai, etc.) max_input_price: Max input price per 1M tokens, 0 = no limit min_context: Minimum context window size requires_tools: Only models supporting tool calling requires_vision: Only models with vision/image input free_only: Only free models

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryNo
providerNo
max_input_priceNo
min_contextNo
requires_toolsNo
requires_visionNo
free_onlyNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The `search_models` function (lines 139-195) is the core handler for the 'search_models' tool. It is decorated with @mcp.tool() on line 139, registering it as an MCP tool. It accepts parameters: query, provider, max_input_price, min_context, requires_tools, requires_vision, free_only. It fetches models via fetch_models(), applies all filters in a loop, and returns formatted results (up to 50 models).
    @mcp.tool()
    def search_models(
        query: str = "",
        provider: str = "",
        max_input_price: float = 0,
        min_context: int = 0,
        requires_tools: bool = False,
        requires_vision: bool = False,
        free_only: bool = False,
    ) -> str:
        """Search and filter OpenRouter models.
    
        Args:
            query: Free-text search in model name/id/description
            provider: Filter by provider (anthropic, google, openai, etc.)
            max_input_price: Max input price per 1M tokens, 0 = no limit
            min_context: Minimum context window size
            requires_tools: Only models supporting tool calling
            requires_vision: Only models with vision/image input
            free_only: Only free models
        """
        models = fetch_models()
        results = []
    
        for m in models:
            if query:
                searchable = f"{m.get('id','')} {m.get('name','')} {m.get('description','')}".lower()
                if not all(w in searchable for w in query.lower().split()):
                    continue
            if provider and provider.lower() not in m.get("id", "").lower():
                continue
            if max_input_price > 0:
                if float(m.get("pricing", {}).get("prompt", 0)) * 1_000_000 > max_input_price:
                    continue
            if min_context > 0 and m.get("context_length", 0) < min_context:
                continue
            if requires_tools and "tools" not in m.get("supported_parameters", []):
                continue
            if requires_vision:
                mods = m.get("arch_modality", [])
                if "image" not in mods and "image-to-text" not in mods:
                    continue
            if free_only:
                p = m.get("pricing", {})
                if float(p.get("prompt", 0)) > 0 or float(p.get("completion", 0)) > 0:
                    continue
            results.append(m)
    
        if not results:
            return "No models match your criteria."
    
        lines = [f"# Search Results — {len(results)} models\n"]
        for m in results[:50]:
            lines.append(_format_model(m))
        if len(results) > 50:
            lines.append(f"\n... and {len(results) - 50} more")
        return "\n\n".join(lines)
  • The @mcp.tool() decorator on line 139 registers `search_models` as an MCP tool with the FastMCP server instance. The registration occurs at import time when the decorator is applied.
    @mcp.tool()
    def search_models(
        query: str = "",
        provider: str = "",
        max_input_price: float = 0,
        min_context: int = 0,
        requires_tools: bool = False,
        requires_vision: bool = False,
        free_only: bool = False,
    ) -> str:
  • The `fetch_models` helper function fetches the model list from the OpenRouter API (https://openrouter.ai/api/v1/models) with in-memory caching (TTL: 300s). It is called by `search_models` to retrieve the full model catalog.
    def fetch_models(force=False) -> list[dict]:
        """Fetch model list from OpenRouter with caching."""
        now = time.time()
        if _cache["data"] is not None and (now - _cache["ts"]) < CACHE_TTL and not force:
            return _cache["data"]
    
        headers = {"Accept": "application/json"}
        if OR_API_KEY:
            headers["Authorization"] = f"Bearer {OR_API_KEY}"
    
        req = Request(OR_MODELS_URL, headers=headers)
        try:
            with urlopen(req, timeout=30) as resp:
                body = json.loads(resp.read())
                _cache["data"] = body.get("data", [])
                _cache["ts"] = now
                return _cache["data"]
  • The `_format_model` helper function formats a model dict into a human-readable Markdown string. It is used by `search_models` to render each result entry with provider, context length, pricing, and features.
    def _format_model(m: dict, detail=False) -> str:
        pricing = m.get("pricing", {})
        input_p = float(pricing.get("prompt", 0))
        output_p = float(pricing.get("completion", 0))
        ctx = m.get("context_length", "?")
        name = m.get("name", m["id"])
        provider = m["id"].split("/")[0]
        supported = m.get("supported_parameters", [])
    
        lines = [
            f"**{m['id']}**",
            f"  Provider: {provider} | Context: {ctx:,}" if isinstance(ctx, (int, float)) else f"  Provider: {provider} | Context: {ctx}",
            f"  Pricing — Input: {_price_str(input_p)} | Output: {_price_str(output_p)}",
        ]
        if supported:
            lines.append(f"  Features: {', '.join(supported)}")
        if detail and m.get("description"):
            lines.append(f"  {m['description'][:150]}")
        if detail and m.get("architecture", {}).get("modality"):
            lines.append(f"  Modality: {m['architecture']['modality']}")
        if detail and m.get("top_provider"):
            lines.append(f"  Top provider: {m['top_provider']}")
        return "\n".join(lines)
  • The `search_models` function is re-exported in the package's __init__.py, making it publicly accessible as part of the package's API.
    from .server import main, fetch_models, list_models, get_model, search_models, compare_models, refresh_cache
    
    __all__ = ["main", "fetch_models", "list_models", "get_model", "search_models", "compare_models", "refresh_cache"]
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must carry the full burden, but it only explains parameter semantics. It does not disclose side effects, authentication requirements, rate limits, or how filters combine. The tool's effect on the system is opaque.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the main purpose and then lists parameters in a clear, compact format. Every line provides essential information without redundancy or filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the 7 parameters and the existence of an output schema, the description covers parameter purposes but omits context on output format, default behavior, and limitations. It is adequate but not thorough.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 0% description coverage, so the description adds crucial meaning for each parameter, e.g., 'Free-text search in model name/id/description'. It compensates well for the schema's lack of descriptions, though individual parameter explanations are brief.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states 'Search and filter OpenRouter models,' identifying both the action (search and filter) and the resource. It is distinct from sibling tools like list_models and get_model, which have different purposes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description lists filters but does not provide explicit guidance on when to use this tool versus siblings. No alternatives or exclusion criteria are mentioned, only implied through the list of parameters.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/lumishoang/openrouter-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server