{"slug": "show-hn-i-built-a-router-for-agent-tools", "title": "Show HN: I built a router for agent tools", "summary": "Monid.ai launched a router for agent tools that gives AI agents access to more than 2,000 tools across 72+ providers through one base URL and one API key, with usage metered per call. The connector layer is declarative and open source, so a coding agent can write a connector and add a provider's API via pull request, and the platform's discover endpoint ranks the full catalog per call by price, live health, and observed p50 and p95 latency. The project requires Deno 2.x.", "body_md": "[**monid.ai**](https://monid.ai)  · \n  [Docs](https://docs.monid.ai)  · \n  [Tool catalog](https://monid.ai/tools)  · \n  [Quickstart](#quickstart)  · \n  [Development guide](https://github.com/monid-ai/monid/blob/main/DEVELOPMENT.md)\n\n**OpenRouter, but for agent tools.** One base URL, one key, and an agent can\nreach **2,000+ tools across 72+ providers**: web search and scraping, people and\ncompany enrichment, social platforms, reviews and market data, and video, image\nand voice generation.\n\n**Are you an AI agent?** Fetch `https://monid.ai/SKILL.md` and save it to your\nskill directory. It does the rest.\n\n**This repo is the connector layer**, the one generic framework every endpoint\nabove is described in: how it is called, what it accepts, what it returns, how\nits usage is counted. A connector is declarative, so a coding agent can write\none. Point it at your API docs and at this repo, and adding your API to Monid\nbecomes a pull request.\n\n**Usage is metered per call.** Every connector declares its own usage model in\nthe definition: a flat charge per call, a charge per returned result, or a rate\nper unit such as a thousand characters or a second of video. The engine settles\nthat model on the raw response envelope, before any output mapping, so what is\nbilled is what came back over the wire. A vendor error, an unmatched company, an\nunresolved person: each of those completes as data and settles at zero.\n\n**The endpoint is chosen per call.** `discover` ranks the whole catalog by what\nthe job is, across every provider at once, and returns each candidate with its\nprice, its live health and its observed p50 and p95 latency, plus hints naming a\ncheaper or better-fitting endpoint. The API is picked at call time against\neverything available, not pinned in code months earlier to the one vendor that\nhappened to get integrated.\n\nThree verbs, and the first two are free.\n\nA connector describes one provider and its endpoints. Adding one is a pull\nrequest, and once it merges those endpoints are in `discover` for every agent on\nthe platform.\n\nA **provider** declares identity, auth, and how usage is counted:\n\n```\n// connectors/tinyfish/provider.ts\nexport default defineProvider({\n    name: \"tinyfish\",\n    meta: {\n        displayName: \"TinyFish\",\n        summary: \"Zero-cost live-web search and clean multi-URL fetch.\",\n        homepageUrl: \"https://tinyfish.ai\",\n        categories: [\"web-search\"],\n    },\n    auth: { inject: presets.auth.header(\"X-API-Key\") },\n    usage: { model: { kind: UsageModelKind.FREE } },\n});\n```\n\nAn **endpoint** declares the request and the input schema:\n\n```\n// connectors/tinyfish/endpoints/search/endpoint.ts\nexport default defineEndpoint({\n    meta: {\n        displayName: \"TinyFish Web Search\",\n        summary: \"Search the live web, news, or research papers.\",\n        description: \"Browser-rendered search over the live web. Results are \" +\n            \"never cached, so pricing pages and breaking news are current at \" +\n            \"query time. Snippets only: pipe result URLs into TinyFish /fetch \" +\n            \"when you need full text.\",\n        docsUrl: \"https://docs.tinyfish.ai/search-api/reference\",\n        categories: [\"web-search\", \"news-search\"],\n    },\n    endpoint: \"/search\",\n    request: {\n        method: \"GET\",\n        path: \"/\",\n        baseUrl: \"https://api.search.tinyfish.ai\",\n    },\n    input: { schema: { queryParams: zTinyfishSearchQueryParams } },\n    timeouts: { requestMs: 15_000, runMs: 20_000 },\n});\n```\n\nThat is the whole contract. No client, no adaptor, no per-provider execution path.\n\n**Write `meta.description` like it is the product, because to an agent it is.**\nIt is the text `discover` ranks and `inspect` returns. Say what the endpoint\nreally does, what it will not do, and which endpoint to reach for instead. The\nTinyFish description above ends by naming its own successor, and that sentence\nis worth more than any number of parameter docs.\n\nRequires [Deno](https://deno.com) 2.x.\n\n```\ngit clone https://github.com/monid-ai/monid.git\ncd monid\n\ndeno task check && deno task test    # types + 188 replay tests, zero network\n```\n\nRun a real endpoint with your own vendor key:\n\n```\nexport TINYFISH_API_KEY=...\ndeno task engine:run 'tinyfish#search' \\\n  --query-params '{\"query\":\"solid-state battery suppliers\",\"domain_type\":\"news\"}'\n```\n\nBrowse the compiled catalog:\n\n```\ndeno task catalog providers                  # what exists\ndeno task catalog endpoints --provider exa   # under one provider\ndeno task catalog endpoints --category web-search\ndeno task catalog inspect 'exa#search'       # one endpoint's full contract\nconnectors/<name>/\n├── provider.ts                    # defineProvider: name, meta, auth, defaults\n├── schema/                        # provider-shared zod: fragments used by 2+ endpoints\n└── endpoints/<endpoint>/\n    ├── endpoint.ts                # defineEndpoint (id \"<provider>#<endpoint>\" inferred)\n    ├── schema/inputs.ts           # request schemas, this endpoint only\n    ├── endpoint.test.ts           # replay + gated live tests\n    └── fixtures/*.json            # recorded responses, trimmed\n```\n\n1. Read [`connectors/exa/`](https://github.com/monid-ai/monid/blob/main/connectors/exa) , the reference implementation, and\nthe authoring guide in[DEVELOPMENT.md](https://github.com/monid-ai/monid/blob/main/DEVELOPMENT.md) .\n2. Write the provider and the endpoint.\n3. Record a fixture with `deno task record` , then keep it trimmed.\n4. `deno task check && deno task test` must pass with no network.\n5. Open a pull request.\n\nTests replay from fixtures, so CI needs no vendor keys. Live tests run only when\nthe matching `<PROVIDER>_API_KEY` is present, and skip otherwise.\n\nThe format above is declarative and the contract is written down, so step 2 is\nwork a coding agent can do. [AGENT.md](https://github.com/monid-ai/monid/blob/main/AGENT.md) is the brief: give it that\nfile, your own API docs, and `connectors/exa/` as the worked example, and it can\nproduce the provider, the endpoint schemas and the tests. Because CI is\ntypecheck plus replayed fixtures with no network, what comes back either\ncompiles against the contract or does not, and the review is about whether the\nconnector describes your API correctly rather than about whether it runs.\n\nApify actors have a head start: `deno task apify:scaffold <actorId>` reads the\nactor's published input schema from the Apify API and generates the endpoint's\n`schema/inputs.ts` as static zod for you to review and commit. It needs\n`APIFY_API_KEY`.\n\nWhat the compiler and the engine do with the files you just wrote. You do not need this to add a connector, but it is why the format looks the way it does.\n\nFunctions in a definition are replaced by content-hash references, and each\ndistinct source is interned once, git-blob style, so the hash doubles as a\ntamper check. An endpoint executes from a **sealed unit**: its document plus the\nfunctions it actually references, passed by value into the engine. Nothing else\nis in scope.\n\nThat is what makes one artifact run three ways without branching: **locally**\nwith your own vendor key, **in CI** replayed against fixtures with no network,\nand **in the hosted platform**, where credentials are injected inside the\ntransport and never enter the engine process.\n\nIdentical for every provider:\n\n1. validate input against the compiled JSON Schema\n2. `input.toRequest` builds the request, with auth still unexecuted\n3. the transport executes it and injects credentials inside the port\n4. `usage.consolidate` settles on the raw envelope, before any output mapping,\nso billing anchors to the wire\n5. `output.fromResponse` maps the result, and the final output is validated\nagainst the declared contract\n\nA vendor's non-2xx response is **data, not an exception**: the run completes and\nsettles at zero usage. Load gates fail closed in order, and a run-time breach of\na declared contract is its own error class rather than a corrupted result.\n\n```\nconnectors/        provider + endpoint definitions (the part you will write)\nengine/            load, link, execute; transports; host ABI\nshared/core        the contract: def, doc, hook, and bundle schemas\nshared/compiler    pure def to doc mapping, fn normalization and interning\nshared/testing     sealed-unit test harness, fixture record and replay\nscripts/           CLI entrypoints (compile, run, catalog, record)\nopenspec/          spec-driven changes; the decision record\nconfig.yml         schema.* and compiler.* are contract; engine and scripts are tooling\n```\n\n- [DEVELOPMENT.md](https://github.com/monid-ai/monid/blob/main/DEVELOPMENT.md) covers hooks, the compiler, usage and\nbilling, configuration, versioning, catalog publishing, and the full CLI\nreference.\n- `openspec/changes/*/design.md` is the decision record, with the rationale\nbehind every choice above.\n- [AGENT.md](https://github.com/monid-ai/monid/blob/main/AGENT.md) is the brief to hand a coding agent you point at this\nrepo.\n\nMIT. See [LICENSE](https://github.com/monid-ai/monid/blob/main/LICENSE).", "url": "https://wpnews.pro/news/show-hn-i-built-a-router-for-agent-tools", "canonical_source": "https://github.com/monid-ai/monid", "published_at": "2026-09-16 17:58:40+00:00", "updated_at": "2026-09-16 18:15:34.491189+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-infrastructure", "developer-tools", "ai-products"], "entities": ["Monid.ai", "TinyFish", "Deno", "OpenRouter"], "alternates": {"html": "https://wpnews.pro/news/show-hn-i-built-a-router-for-agent-tools", "markdown": "https://wpnews.pro/news/show-hn-i-built-a-router-for-agent-tools.md", "text": "https://wpnews.pro/news/show-hn-i-built-a-router-for-agent-tools.txt", "jsonld": "https://wpnews.pro/news/show-hn-i-built-a-router-for-agent-tools.jsonld"}}