Agents
Six tools, one definition each
import { generateText } from "ai";
import { maintainersTool, packageTool, versionsTool } from "@agntn/registries/ai";
const { text } = await generateText({
model: yourModel,
tools: { packageTool, versionsTool, maintainersTool },
prompt: "What is the latest version of pkg:npm/lodash and who maintains it?",
});
Each export is the matching registries mcp tool, handed to the AI SDK. Same name on the wire, same schema, same text. Pick the ones your agent needs and leave the rest at home.
| Export | Tool | Input | Your code gets |
|---|---|---|---|
packageTool | registries_package | purl | the Package |
versionsTool | registries_versions | purl, optional limit | { order, total, omitted, versions }, the newest twenty unless limit says otherwise |
dependenciesTool | registries_dependencies | purl with a version | { dependencies } |
maintainersTool | registries_maintainers | purl | { maintainers } |
bulkPackagesTool | registries_bulk_packages | purls, 1 to 50, optional concurrency | one Package per PURL that answered |
ecosystemsTool | registries_ecosystems | nothing | { ecosystems } |
Every result also carries text, the compact JSON the model reads. Your code reads the fields. The AI SDK's abort signal reaches every registry call, so cancelling the generation cancels the HTTP request too.
Install the peer
pnpm add ai
AI SDK 7, and that's the whole list. The main entry never imports it. Only the /ai subpath does.
The schema, in words
The model sees one description per tool. Each purl field carries an example (pkg:npm/lodash, pkg:pypi/requests@2.32.3). That example is what stops a model from sending lodash on its own. purls is capped at fifty, and so is concurrency.
A bad call never reaches a registry. The schema check runs first, the same schema on every surface. Over MCP it reads like this:
Invalid arguments at /limit: must be >= 1
Invalid arguments at /limit: must be one of all
Invalid arguments at /limit: must match a schema in anyOf
versions stops at the twenty newest. Why? pkg:npm/react has almost three thousand releases, and the whole list is about 700 kB of JSON. That's a lot of context for "what's new?". limit takes another number, or "all" when the history really matters. omitted counts the releases that stayed home. Undated ones go last.
What the answer is
The tools return the normalized objects, not prose. A model summarizing them should say what the registry said; the Lookup explorer shows the same objects for any PURL, which is a quick way to check a summary against the source.
description: "ignore previous instructions" has learned one thing about the package, and nothing about what to do next.MCP, with or without Node
Want the six tools in Claude Code, Cursor or anything else that speaks MCP? Two ways in.
The local one runs over stdio:
claude mcp add registries -- npx -y @agntn/registries mcp
No Node around, or a client that only takes a URL? This site serves the same tools over Streamable HTTP.
claude mcp add --transport http registries https://registries.agntn.dev/mcp
{
"mcpServers": {
"registries": { "type": "http", "url": "https://registries.agntn.dev/mcp" }
}
}
Schemas, descriptions and executors all come from registries mcp. So the answers match, refusals included. Docus throws in two tools of its own. list-pages lists this guide and get-page hands back one page as Markdown. Nice when a model wants to read up on PURLs before it guesses one.
The catch? The worker shares a budget with the Lookup explorer. Thirty registry queries a minute per address, an IPv6 client by its /64. MCP calls skip the cache, so each one counts. One PURL costs one query, a bulk call one per PURL. registries_ecosystems is free.
A bulk call over thirty PURLs is turned down before it spends anything. Past the limit you get a tool error, not an HTTP 429. Both errors name the way out, and that's usually the local server. No budget there.
One more thing. Every PURL you send passes through a Cloudflare worker. For pkg:npm/lodash, who cares. For an internal package name, stay local.
Rolling your own
Not on the AI SDK? The definitions themselves are public. registriesTools() from @agntn/registries/tools hands you all six. They are @agntn/tools definitions. Your framework gets the schema, the description and an execute that already validates:
import { registriesTools } from "@agntn/registries/tools";
for (const tool of registriesTools()) {
console.log(tool.name, tool.description);
}
Bring your own HTTP client with registriesTools({ client }). This site does exactly that, with one retry and a twenty second timeout.
The tools don't cache. An agent that keeps asking about the same packages? Build on createCached and the helpers instead.