{
  "markdown": "# FetchMux\n\n[![npm](https://img.shields.io/npm/v/@fetchmux/mcp?label=%40fetchmux%2Fmcp&color=cb0000)](https://www.npmjs.com/package/@fetchmux/mcp)\n[![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)\n[![Node](https://img.shields.io/badge/node-%E2%89%A524-3c873a)](https://nodejs.org)\n[![MCP](https://img.shields.io/badge/MCP-registry-6d5efc)](https://registry.modelcontextprotocol.io)\n\n**One search endpoint for AI agents.** Put a router in front of Brave, Tavily, Exa, Firecrawl, and\nCrossref. Every request carries a hard cost ceiling and deadline; every response comes back with a\nreceipt that says which provider ran, why, and what it cost.\n\nYour agents stop hard-coding a provider into prompts and app code. They send one request shape; the\ngateway picks an eligible provider under a policy you control, enforces the budget and deadline\nbefore the call, retries safely on failure, and returns normalized results plus a full trace. You\nkeep your provider keys — they never leave your gateway.\n\n## The receipt\n\nNothing is a black box. Every `/v1/search` response carries the routing decision:\n\n```jsonc\n\"route\": {\n  \"selectedProvider\": \"brave\",\n  \"attemptedProviders\": [\"brave\"],\n  \"reasonCodes\": [\"TASK_MATCH\", \"WITHIN_BUDGET\", \"RELIABILITY_WEIGHT\"],\n  \"attempts\": [\n    { \"provider\": \"brave\", \"outcome\": \"success\", \"latencyMs\": 640, \"estimatedCostUsd\": 0.005 }\n  ],\n  \"estimatedCostUsd\": 0.005,\n  \"latencyMs\": 640,\n  \"fallbackUsed\": false,\n  \"traceId\": \"rt_b400e7c8\"\n}\n```\n\n## How it routes\n\n```\n  agent  ──▶  { query · task · maxCostUsd · maxLatencyMs }\n                             │\n                             ▼\n                      ┌──────────────┐        your keys\n                      │   FetchMux    │ ─────▶ Brave · Tavily · Exa\n                      │    policy     │        Firecrawl · Crossref\n                      └──────────────┘ ◀─────  (bring your own)\n                             │\n                             ▼\n  agent  ◀──  evidence[]  +  route receipt\n```\n\nA provider is eligible only when its credentials, task fit, circuit state, spend, and deadline all\npass. Budgets and deadlines are eligibility rules, not best-effort hints. Fallback happens only on\nretryable failures.\n\n## Quick start\n\nNo provider account needed — the public Crossref route runs out of the box:\n\n```bash\ngit clone https://github.com/krutftw/fetchmux\ncd fetchmux\nnpm install\nnpm run build\n\nexport FETCHMUX_API_KEY=\"a-long-random-key\"\nexport CROSSREF_ENABLED=true\nexport CROSSREF_CONTACT_EMAIL=\"you@example.com\"\nnpm run dev:gateway\n```\n\nFrom another shell:\n\n```bash\ncurl http://127.0.0.1:8787/v1/search \\\n  -H \"Authorization: Bearer a-long-random-key\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{ \"query\": \"retrieval augmented generation\", \"task\": \"scholarly\", \"maxLatencyMs\": 8000 }'\n```\n\nTo route real web search, set a provider key and use a web task instead:\n\n```bash\nexport FETCHMUX_API_KEY=\"a-long-random-key\"\nexport BRAVE_API_KEY=\"your-brave-key\"\nexport BRAVE_COST_PER_REQUEST_USD=\"0.005\"   # from your provider plan\nnpm run dev:gateway\n```\n\nNew to Firecrawl? New accounts get 10% off the first month through\n[this link](https://firecrawl.link/kurt-robert-landman) (referral — FetchMux earns a small\ncommission, no extra cost to you).\n\n## Use it from an agent\n\nPoint any MCP client (Claude, Cursor, and friends) at the published server:\n\n```json\n{\n  \"mcpServers\": {\n    \"fetchmux\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@fetchmux/mcp\"],\n      \"env\": {\n        \"FETCHMUX_BASE_URL\": \"http://127.0.0.1:8787/\",\n        \"FETCHMUX_API_KEY\": \"your-gateway-key\"\n      }\n    }\n  }\n}\n```\n\nTwo read-only tools: `search_web` and `preview_search_route`.\n\nOr use the typed SDK, [`@fetchmux/sdk`](https://www.npmjs.com/package/@fetchmux/sdk):\n\n```typescript\nimport { FetchMux } from \"@fetchmux/sdk\";\n\nconst client = new FetchMux({\n  baseUrl: \"http://127.0.0.1:8787/\",\n  apiKey: process.env.FETCHMUX_API_KEY,\n  fetch: globalThis.fetch.bind(globalThis),\n});\n\nconst res = await client.search({\n  query: \"latest stable Node.js release\",\n  task: \"fresh_facts\",\n  maxCostUsd: 0.02,\n});\n```\n\n## Providers\n\nBring your own key for each. Set the matching `*_API_KEY`, plus an optional\n`*_COST_PER_REQUEST_USD` if you want dollar budgets enforced.\n\n| Provider | Use | Key |\n| --- | --- | --- |\n| Brave | web search | `BRAVE_API_KEY` |\n| Tavily | web search, research | `TAVILY_API_KEY` |\n| Exa | web search, docs | `EXA_API_KEY` |\n| Firecrawl | page content | `FIRECRAWL_API_KEY` |\n| Crossref | scholarly metadata | none (`CROSSREF_ENABLED=true`) |\n\n## REST endpoints\n\n| Method | Path | Auth | Behavior |\n| --- | --- | --- | --- |\n| `GET` | `/health` | public | Process health and version |\n| `GET` | `/ready` | public | Provider readiness |\n| `GET` | `/v1/providers` | bearer | Provider configuration status |\n| `POST` | `/v1/route/preview` | bearer | Ranked candidates, no provider call |\n| `POST` | `/v1/search` | bearer | Routed retrieval and route receipt |\n\nFull contract: [docs/openapi.yaml](docs/openapi.yaml).\n\n<details>\n<summary><b>All configuration variables</b></summary>\n\nThe process does not auto-load `.env` in local Node development; set variables in the shell or a\nprocess manager. Docker Compose reads the ignored `.env` file.\n\n| Variable | Default | Purpose |\n| --- | --- | --- |\n| `FETCHMUX_API_KEY` | none | Protected-route bearer key |\n| `FETCHMUX_API_KEYS` | none | Comma-separated keys for rotation |\n| `FETCHMUX_AUTH_DISABLED` | `false` | Exact `true` bypasses auth (trusted local use only) |\n| `FETCHMUX_ALLOWED_ORIGINS` | none | Comma-separated browser origins; no CORS when empty |\n| `FETCHMUX_HOST` | `127.0.0.1` | Bind address |\n| `FETCHMUX_PORT` | `8787` | TCP port |\n| `BRAVE_API_KEY` / `TAVILY_API_KEY` / `EXA_API_KEY` / `FIRECRAWL_API_KEY` | none | Provider credentials |\n| `CROSSREF_ENABLED` | `false` | Exact `true` enables the credential-free scholarly route |\n| `CROSSREF_CONTACT_EMAIL` | none | Monitored contact for Crossref's polite pool |\n| `*_COST_PER_REQUEST_USD` | none | Per-provider cost estimates used by dollar budgets |\n\nSee [provider configuration](docs/runbooks/provider-configuration.md) before enabling `maxCostUsd`.\n\n</details>\n\n## Run in Docker\n\n```bash\ncp .env.example .env    # add your keys, never commit it\ndocker compose up --build -d\ncurl http://127.0.0.1:8787/health\n```\n\nNon-root Distroless image: Linux capabilities dropped, read-only root filesystem, provider\ncredentials passed only at container start.\n\n## Benchmark\n\nValidate every case and provider pairing with no network calls or credits:\n\n```bash\nnpm run benchmark -- --workload benchmarks/workloads/founding-v1.json --mode dry-run\n```\n\nLive mode needs provider keys and an explicit `--confirm-live`. Check each provider's terms before\npublishing results — see the [benchmark methodology](docs/research/benchmark-methodology.md).\n\n## What it is (and isn't)\n\nOpen source, self-hosted, single-tenant, BYOK. Route events go to stdout as JSON and exclude your\nquery text, keys, and result content by default. No database, no telemetry.\n\nIt is not a hosted service, a pooled-credit reseller, or a claim that these providers are\ninterchangeable. Provider names are the adapters it ships with, not partnerships. A hosted version\nis on the roadmap — [star the repo](https://github.com/krutftw/fetchmux) to follow.\n\n## Development\n\n```bash\nnpm test          # 232 tests\nnpm run typecheck\nnpm run lint\nnpm run build\nnpm run dev:gateway\nnpm run dev:site\n```\n\nMore docs: [product design](docs/product-design.md) ·\n[local development](docs/runbooks/local-development.md) ·\n[deployment](docs/runbooks/deployment.md) ·\n[provider configuration](docs/runbooks/provider-configuration.md) ·\n[data handling](docs/runbooks/data-handling.md) ·\n[incident response](docs/runbooks/incident-response.md)\n\nSecurity issues: [security@fetchmux.com](mailto:security@fetchmux.com).\n\n## License\n\n[Apache-2.0](LICENSE). Free to self-host, modify, and redistribute.\n",
  "bytes": 7976,
  "sha": "0518838bf0561ff7cb2e7b695d0911629a8b515b5d734c180aedd17085a3a4ef",
  "repo_slug": "krutftw/fetchmux",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_krutftw_fetchmux_934e7c8c/readme"
}