API Registry
Local registry of authoritative API and library docs so agents verify integrations against current sources.
Install
claude plugin marketplace add tyroneross/RossLabs-AI-Toolkit
claude plugin install api-registry@rosslabs-ai-toolkit
The Problem
Integration bugs often start before code is written. An agent remembers an old SDK shape, a renamed model, a deprecated environment variable, or a config path from another framework. The result is plausible code that fails late.
API Registry moves source verification to the front of the workflow. The registry stores approved documentation URLs, refresh logs, and lookup commands so build tools can ask “what docs should I trust for this service?” before writing the integration.
What I Built
API Registry gives coding agents a local source-of-truth map for API, library, and tool documentation. Before an agent configures Better Auth, Groq, Neon, Vercel, or another integration, it can look up the authoritative docs instead of relying on stale training data.
Impact
The registry is deliberately local and explicit. It does not try to be a universal search engine. It captures the docs this development environment trusts, keeps them refreshable, and makes source verification cheap enough to use every time an integration appears.
Architecture
API Registry is a deterministic TypeScript CLI plugin, not a research agent — every script in scripts/ and src/ is synchronous logic over SQLite plus a handful of plain HTTP calls (npm, PyPI, GitHub). The only place an LLM enters the loop is documentation curation, and that step is deliberately delegated to the calling Claude session rather than run inside the plugin (see Models below).
Two pipelines matter: registry population (turning a project’s dependencies into registered services) and doc lookup (routing a question to the right source and caching the answer). A third, smaller pipeline — refresh + cooldown — keeps version data current and gates freshly-published packages.
flowchart LR
subgraph Populate["registry population — scan.ts, deterministic"]
S1["project dirs: package.json,<br/>requirements.txt, .mcp.json, .env.example"] --> S2[scan.ts walkers]
S2 --> S3[(scan_candidates table)]
S3 -->|/api-registry:add, approve| S4[(services table<br/>SQLite WAL — registry.db)]
S4 --> S5[export-yaml.ts]
S5 --> S6[registry.yaml mirror]
end
subgraph Lookup["doc lookup — one question, traced"]
Q["/api-registry:docs service query"] --> R[docs.ts route resolver]
S4 -.getServiceByName.-> R
R --> D{route}
D -->|not_registered| STOP["stop — no training-data answer"]
D -->|"answer_from_cache<br/>(last_checked < 7d)"| CACHE[read cached .md, no network]
D -->|"verify_then_answer<br/>(last_checked > 7d)"| HC["hashCompare:<br/>GET + SHA-256 vs stored hash"]
HC -->|unchanged| CACHE
HC -->|changed| CUR["in-session Claude:<br/>WebFetch + curate section"]
D -->|"fetch_and_cache<br/>(no cache yet)"| CUR
D -->|"context7_fallback<br/>(no docs_url registered)"| C7[Context7 MCP query-docs]
CUR --> WRITE[writeCachedDoc]
WRITE --> DOCFILE[("~/.api-registry/docs/<service>/<slug>.md")]
CACHE --> ANS["answer + Source: footer"]
DOCFILE --> ANS
C7 --> ANS
end
subgraph Refresh["refresh + cooldown — refresh.ts / cooldown.ts"]
NPM[npm registry] --> RF[refresh.ts]
PYPI[PyPI JSON API] --> RF
GH[GitHub Releases API] --> RF
RF --> S4
S4 --> CD["cooldown.ts verdict<br/>7d supply-chain dwell"]
end
HOOK["SessionStart hook<br/>staleness.ts --marker"] -.detect only, never fetches.-> DOCFILE
Models — which, where, why:
Models: none. The plugin contains no model call anywhere in src/ or scripts/ — routing, hashing, cooldown math, and YAML export are all pure TypeScript. The one point where model reasoning is required — fetching a doc page and extracting the section relevant to a query — is explicitly assigned to the in-session Claude that invoked the command, per the repo’s own invariant: “Run an LLM call or background daemon to curate docs” is listed under Do not in the plugin’s CLAUDE.md. Context7 (mcp__plugin_context7_context7__query-docs) is called only as a fallback, when a service has no registered docs_url — it’s an external MCP tool the calling session reaches out to, not a model the plugin hosts or invokes internally.
Tools & infra — which, why:
- better-sqlite3 (synchronous SQLite driver, WAL mode,
~/.api-registry/registry.db) — source of truth forservices,scan_candidates,refresh_log, anddoc_cache; chosen for a zero-daemon local store with a synchronous API, appropriate for a CLI that runs one command and exits. - YAML mirror (
registry.yaml, written byexport-yaml.ts) — human-readable, read-only reflection of the same rows; SQLite stays canonical per the repo’s invariant. - Markdown doc cache (
~/.api-registry/docs/<service>/<slug>.md, YAML frontmatter withsource_url/fetched_at/last_checked/content_hash) — payload store for curated doc content; git-ignored, greppable without a database client. - npm registry / PyPI JSON API / GitHub Releases API — plain HTTP metadata lookups (
src/http.ts) thatrefresh.tsandscan.tsuse for latest-version and publish-date data; no SDKs, justfetch. - Context7 MCP — fallback documentation source, reached only when a service has no
docs_urlregistered. - tsx — runs the TypeScript scripts directly; commands are markdown files that shell out to
tsx, no build step. - Claude Code plugin runtime — 8 slash commands, 3 skills (
api-registry,docs-search,build-loop-bridge), and oneSessionStarthook (session-start-staleness.sh) that are the plugin’s actual surface area. node --test— the test runner (npm test), no separate test framework dependency.
How it works
/api-registry:scan [--path <dir>]walks project directories (depth ≤ 3, skippingnode_modules/.git) looking forpackage.json,requirements.txt,.mcp.json, and.env.example. Each dependency, MCP server, or matched env-var prefix (ENV_PREFIX_MAP—GROQ,ANTHROPIC,STRIPE,NEON, etc.) becomes a row inscan_candidateswith a proposed category and a confidence score (50 without network metadata, 80 with it).- Approving a candidate — or running
/api-registry:add <name>directly — writes aservicesrow:docs_url,context7_id,package_ids,category(one of 12: auth, db, llm, infra, ui, obs, payments, email, storage, search, protocol, other),author_owned,maintenance_status.export-yaml.tsmirrors the table toregistry.yaml. - When an agent needs docs,
/api-registry:docs <service> <query>runsscripts/docs.ts. It looks up the service, slugifies the query, and checksdoc_cachefor a matching(service_id, slug)entry — then emits one of five routes:not_registered,answer_from_cache,verify_then_answer,fetch_and_cache, orcontext7_fallback. not_registeredstops the agent from answering off training data.answer_from_cache(fresh,last_checked< 7 days) reads the local.mdfile with no network call at all.verify_then_answer(cache present but stale) runshashCompare: aGETon the registereddocs_urlplus a SHA-256 comparison against the storedcontent_hash. Unchanged content just bumpslast_checked; changed content triggers re-curation.fetch_and_cache(no cache yet) and a changed-contentverify_then_answerboth hand off to the in-session Claude, whichWebFetches the doc and curates the section relevant to the query — no external LLM call, per the repo’s own rule.- The curated result is written via
writeCachedDoc(frontmatter + body to~/.api-registry/docs/<service>/<slug>.md) and indexed viainsertDocCacheEntryindoc_cache. The answer returns with aSource: <url> (cached/verified <date>)footer. context7_fallbackfires only when a service has nodocs_urlregistered; it routes to Context7 MCP’squery-docsinstead./api-registry:refresh [<name> | --all | --stale]hits npm, PyPI, or GitHub Releases for the latest version and its publish date, logs the transition torefresh_log, and updateslatest_version_released_at.cooldown.tsturns that date into an install verdict: a package released under 7 days ago isinstall_blocked: true(supply-chain dwell time) unless it’sauthor_owned(matched against the user’s own npm scopes/projects in~/.api-registry/owned.json); an unknown release date fails open rather than blocking.- A
SessionStarthook runsstaleness.ts --markeron every session start — detect-only, it writes~/.api-registry/staleness.jsonand, only if any cached doc exceeds the 7-day threshold, emits one line nudging/api-registry:refresh --stale. It never fetches or curates.
Tech stack
TypeScript on Node ≥ 20, run directly via tsx (no build step). better-sqlite3 (WAL mode) is the sole datastore — registry.db for services/candidates/refresh log/doc-cache index, mirrored read-only to registry.yaml. Curated doc bodies live as frontmatter’d markdown on disk, not in SQLite. No web framework, no ORM, no vector store: src/http.ts is a thin fetch wrapper over the npm registry, PyPI JSON API, and GitHub Releases API. Ships as a Claude Code plugin (commands + skills + one SessionStart hook); Context7 MCP is the only external service dependency, used solely as a fallback when a service has no registered docs_url. Tests run on Node’s built-in node --test.
Worked Example
Before wiring up Better Auth in a project, an agent routes the question through the registry instead of answering from training data:
/api-registry:docs better-auth "what should I trust for session config?"
Illustrative shape of the routed response (exact fields vary; the real script also returns stale_threshold_days and, on a cache hit, content_hash):
{
"route": "fetch_and_cache",
"service": "better-auth",
"display_name": "Better Auth",
"docs_url": "<registered docs_url>",
"context7_id": null,
"file_path": "~/.api-registry/docs/better-auth/what-should-i-trust-for-session-config.md"
}
The match comes from a service the registry already knows about — Better Auth, Groq, Neon, and Vercel are among the 26 services seeded in this environment’s registry. Lookup latency and refresh-cycle timing aren’t measured anywhere in this write-up, so both stay flagged rather than estimated.