models-mcp
# Models MCP
[](https://github.com/QAInsights/models-mcp/actions/workflows/ci.yml)
[](https://github.com/QAInsights/models-mcp/actions/workflows/catalog-drift.yml)
Search, compare, and inspect AI models by pricing, context window, and capabilities. An MCP server over the [models.dev](https://models.dev) catalog (`models.dev/api.json`), so your agent always has current model data without you hand-maintaining a list.
models.dev itself doesn't ship an MCP server, just a JSON API and a TypeScript SDK for reading it. This fills that gap.
Runs two ways from the same tool code:
- **stdio** (`src/index.ts`) for local MCP clients
- **Cloudflare Worker** (`src/worker.ts`) as a remote Streamable HTTP endpoint at `/models-mcp`
## Tools
| Tool | What it does |
|---|---|
| `list_providers` | Lists every provider (anthropic, openai, google, ...) with model counts |
| `find_models` | Filters models by name, provider, min context window, max input cost, or capability flags (reasoning, tool_call, attachment) |
| `get_model` | Full metadata for one model, by `provider/model` id |
| `compare_models` | Side-by-side diff of 2-6 models on pricing, context, and capabilities |
| `top_models` | Ranks models by cheapest input/output price, largest context, context-per-dollar, or newest release; supports the same filters as `find_models` |
| `estimate_cost` | Computes the USD cost of a request from a model's published per-million-token rates, including cache read/write components |
| `get_provider` | Provider metadata: display name, AI SDK package, API base URL, docs link, and a compact list of its models |
| `refresh_catalog` | Forces a re-fetch, bypassing the 1-hour cache |
All search-style tools (`find_models`, `top_models`) share one filter schema, so filter semantics are identical everywhere. Ranking and estimation exclude models that lack the relevant data (e.g. unpriced local models) rather than guessing.
## Install
```bash
npm install
npm run build
```
## Run standalone over stdio (for testing)
```bash
npm start
```
It speaks MCP over stdio, so you won't see much directly; use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) to poke at it:
```bash
npx @modelcontextprotocol/inspector node dist/index.js
```
## Host on Cloudflare Workers
The Worker entry (`src/worker.ts`) serves the same tools over Streamable HTTP at `/models-mcp`, with:
- **Catalog caching** in the Workers Cache API (`caches.default`) with a 1-hour TTL, shared across requests and isolates.
- **Per-IP rate limiting** via a Workers rate limiting binding: 60 requests/minute per IP, enforced per Cloudflare location. Excess requests get `429` with `Retry-After: 60`.
```bash
# local dev at http://localhost:8787/models-mcp
npm run dev:worker
# deploy
npm run deploy
```
After deploy, the canonical endpoint is `https://mcp.dosa.dev/models-mcp`. The generated `https://models-mcp.<your-subdomain>.workers.dev/models-mcp` URL stays live as a fallback.
Point MCP clients at it:
Claude Code:
```bash
claude mcp add --transport http models-mcp https://mcp.dosa.dev/models-mcp
```
Generic client config (anything that speaks Streamable HTTP):
```json
{
"mcpServers": {
"models-mcp": {
"url": "https://mcp.dosa.dev/models-mcp"
}
}
}
```
For stdio-only clients (Claude Desktop), bridge with [mcp-remote](https://github.com/geelen/mcp-remote):
```json
{
"mcpServers": {
"models-mcp": {
"command": "npx",
"args": ["mcp-remote", "https://mcp.dosa.dev/models-mcp"]
}
}
}
```
No API keys required anywhere. All data comes from the public `models.dev/api.json` endpoint.
## Try it out
With the dev server running (`npm run dev:worker`), the endpoint is `http://localhost:8787/models-mcp`.
Quick curl (MCP initialize):
```bash
curl -X POST http://localhost:8787/models-mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"manual","version":"1.0"}}}'
```
Expect an SSE response with `serverInfo.name: "models-mcp"`.
MCP Inspector (best for poking at tools interactively):
```bash
npx @modelcontextprotocol/inspector
```
Set Transport Type to Streamable HTTP and URL to `http://localhost:8787/models-mcp`, then call tools from the UI.
Claude Code against the local server:
```bash
claude mcp add --transport http models-mcp-local http://localhost:8787/models-mcp
```
Then ask it something like "which anthropic models cost under $1 per million input tokens?" and watch it reach for `find_models`.
Rate limiting: fire 61 rapid requests at the endpoint and request 61 onwards returns `429` with `Retry-After: 60`.
Things worth trying in the Inspector:
- `find_models` with combined filters, e.g. `maxInputCost: 0.5` together with `minContext: 200000`
- `get_model` with a bare id like `gpt-5.2` (resolves) and with a nonsense id (clean tool error)
- `compare_models` with one invalid id mixed in (it lands under `notFound`)
- The first call fetches the live catalog (~200ms); repeat calls are cache hits
## Tests
```bash
npm test
```
Covers the catalog client (flattening, TTL caching, force refresh, stale-on-failure fallback, id resolution) and all eight tools end-to-end through a real MCP client session over an in-memory transport.
## Notes on the data
- The catalog is cached for 1 hour: in the Workers Cache API when hosted, in process memory over stdio. Call `refresh_catalog` to force an update. If a refetch fails, the last good catalog keeps being served and `refresh_catalog` reports `servedStale: true` so you can tell.
- A daily GitHub Actions workflow (`Catalog drift`) fetches the live `api.json` and sanity-checks it against the flattening logic, since models.dev publishes no versioned schema. It opens a `catalog-drift` issue if upstream changes shape. Run it locally with `npm run build && npm run test:live`.
- models.dev doesn't publish a versioned schema for consumers, so the types in `src/types.ts` are intentionally loose (index signatures preserve any fields not explicitly typed).
- Model ids follow the `provider/model` convention used by the AI SDK and OpenCode, e.g. `anthropic/claude-sonnet-4-5`. `get_model` and `compare_models` also accept a bare model id when it names exactly one model across all providers; if the bare id is ambiguous (common with aggregator providers mirroring first-party models), the tool errors with the list of candidate `provider/model` ids instead of silently picking one. `get_provider` emits full `provider/model` ids so its output round-trips through `get_model` unchanged.
## Possible extensions
- A `list_facets` tool (modalities, tokenizers) similar to what other model-catalog MCPs expose.
- A `test_model` tool that makes a live call through whichever provider key you have configured, for latency/cost sanity checks.
- OAuth or Cloudflare Access in front of the Worker, if you want it private.
TDQS
Scored across 8 tools
Each tool targets a distinct operation: listing providers, searching models, fetching details, comparing, refreshing, estimating cost, ranking, and provider metadata. Even where two tools support similar filters, their purposes are clearly differentiated by descriptions.
Tool names mostly follow a clear verb_noun pattern such as list_providers, find_models, get_model, and estimate_cost. The exception is top_models, which reads more like a noun phrase than an imperative verb action, a minor deviation from the otherwise consistent convention.
Eight tools is a well-scoped size for a model catalog server. Each tool covers a meaningful workflow without redundancy or bloat, from discovery and search to comparison and cost estimation.
The surface covers the full read-only lifecycle of interacting with the models.dev catalog: list, search, detail, compare, rank, and cost estimation, plus provider-level metadata and cache refresh. No obvious dead ends or missing core operations are present.