A centralized, spec-compatible registry for AI ecosystem artifacts — MCP servers and A2A agents — with a clean public browse UI, an admin CRUD console, and a first-class HTTP API.
AI Registry gives teams a single place to publish, discover, and evaluate the building blocks of the AI ecosystem. Every entry is:
- Versioned — immutable published versions, draft/deprecated lifecycle.
- Spec-compatible — MCP endpoints conform to the Model Context Protocol registry shape; every agent emits a Google A2A Agent Card at
/.well-known/agent-card.json. - API-first — the UIs are thin clients. Nothing lives in the UI that isn't in the API.
- Observable — every handler is traced, every DB call is a child span, every business metric is an OTel counter or histogram.
- Browse, search, filter, and inspect MCP servers by namespace, runtime (stdio / http / sse), ecosystem (npm / pypi / oci / …), verification status, and tags.
- First-class
tools[]field on each version — the publisher-declared tool list, distinct from the MCP spec'scapabilities.toolscapability-negotiation flag. Tool cards render name, description, input schema, and annotations on the detail page. - Strict
/v0/endpoints pinned to the MCP registry wire format (top-levelservers,metadata.count/nextCursor, RFC 7807 errors, RFC 3339 timestamps) and validated by a 40-test conformance suite. - View/copy counters, freshness indicators, report-entry workflow.
- Browse agents by namespace, skills, and tags, each with a structured card and detail page.
- Auto-generated A2A Agent Cards at
/agents/{namespace}/{slug}/.well-known/agent-card.json, plus a global/.well-known/agent-card.jsonthat makes the registry itself a first-class A2A citizen. - A2A schema-conformant:
skills[]validated at write time (id,name,description,tags),securitySchemesrestricted to an explicit allowlist (Bearer, ApiKey, OAuth2, OpenIdConnect).
- Public UI — read-only. Browse, search, detail pages, JSON inspect, copy endpoints. No auth required.
- Admin UI (
/admin) — full CRUD, guarded by OIDC login. Publishers, workspaces (with Keycloak group bindings), MCP servers + versions, agents + versions, audit log, reports triage, feature-flag management, and a review queue for the change-approval workflow. - Both UIs consume the same versioned HTTP API. Zero client-only features.
- OAuth 2.1 / OIDC with PKCE (public client via
oidc-client-ts— no client secret, no NextAuth/Auth.js). - Keycloak in local dev via docker-compose with a pre-seeded realm.
realm_access.roles[]contains"admin"→ unlocks the admin scope. Write endpoints 403 without it, independent of the UI. Middleware-enforced, never UI-enforced.- Workspace authorship via Keycloak groups — each workspace can bind to a group; members can author MCPs and agents under that workspace without admin role (see ADR 0001 / 0002). The JWT claim path is configurable via
AUTH_GROUPS_CLAIM(defaultgroups). - Change-approval workflow — workspace authors submit version edits or deletion requests that a reviewer group approves before they go live. The reviewer group is configurable via
AUTH_REVIEWER_GROUP(defaultregistry-reviewers). Discriminated 409 errors prevent stale-edit clobbering. See ADR 0003. - MCP-authorization-spec compatible (resource indicators, protected resource metadata).
- OpenTelemetry SDK for traces, metrics, and logs; OTLP export (gRPC or HTTP).
- Every HTTP handler is wrapped by
otelhttp.NewHandler. DB calls produce child spans. Structured logs carrytrace_idandspan_id. - Business metrics (request counts, latency histograms, registry entry counts) exposed as OTel counters/histograms — contract-tested so regressions fail CI.
- OTel Collector config checked into
deploy/otel-collector-config.yaml.
Server — Go 1.25 · chi v5 · pgx/v5 · PostgreSQL 16 · golang-migrate · jwt/v5 · oklog/ulid · testcontainers-go · OpenTelemetry SDK + OTLP exporter
Frontend — Vite · React 19 · React Router v7 · TanStack Query v5 · TypeScript · shadcn/ui + Radix · Tailwind v4 · oidc-client-ts · Vitest + React Testing Library · Playwright (e2e)
Infra — docker-compose (dev / ci / prod) · Helm chart with optional CNPG-managed PostgreSQL cluster, HTTPRoute, and Ingress · Keycloak for local OIDC · OTel Collector
API spec — Hand-written OpenAPI 3.1 at server/api/openapi.yaml (81 operations), embedded into the binary and served live at /openapi.yaml. Server types and the TypeScript client are generated from the spec. A bijection test ensures the router and spec never drift.
┌─────────────────┐ ┌─────────────────┐
│ Public SPA │ │ Admin SPA │
│ (read-only) │ │ (/admin, auth) │
└────────┬────────┘ └────────┬────────┘
│ │
│ HTTP/JSON (v1/v0) │
└──────────┬──────────┘
▼
┌─────────────────┐
│ Go server (chi) │
│ OpenAPI 3.1 │
│ OIDC · OTel │
└────────┬────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Postgres │ │ Keycloak │ │ OTel │
│ + JSONB │ │ (OIDC) │ │ Collector │
└───────────┘ └───────────┘ └───────────┘
Directory layout:
server/ Go service
├── api/ OpenAPI 3.1 spec + A2A agent-card JSON schema (embedded)
├── cmd/server/ Entrypoint
├── internal/
│ ├── http/ chi router, handlers, middleware (auth, logging, rate limit)
│ ├── mcp/ MCP registry endpoints + /v0/ wire-format layer
│ ├── agents/ Agent registry + A2A card generation
│ ├── auth/ OIDC/JWT validation, scopes, admin guard
│ ├── store/ Postgres repositories (pgx)
│ ├── domain/ Entities, validation
│ ├── bootstrap/ Seed-from-YAML with idempotent upsert + narrow tools backfill
│ └── observability/ OTel tracer, meter, logger providers
└── migrations/ SQL migrations (forward-only)
web/ Vite + React SPA (public + admin)
├── src/components/ shadcn/ui + feature components
├── src/pages/ React Router v7 routes
├── src/lib/ API client (generated from OpenAPI), utils
└── src/auth/ oidc-client-ts PKCE flow
deploy/ docker-compose profiles, Keycloak realm, OTel config
└── helm/ai-registry/ Kubernetes chart (optional CNPG cluster)
docs/ Architecture notes, ADRs
PLAN.md Phased implementation roadmap
design.md System architecture, observability, data & API, UI/UX
CLAUDE.md Project non-negotiables (API-first, spec compat, OTel, etc.)
Prerequisites: Docker + Docker Compose.
git clone git@github.com:Haibread/ai-registry.git
cd ai-registry
# Brings up: Postgres, Keycloak (pre-seeded realm), OTel Collector, server, web
docker compose -f deploy/docker-compose.yml -f deploy/docker-compose.dev.yml up -d --buildThen open:
| URL | What |
|---|---|
| http://localhost:8080 | Public SPA (browse MCP servers + agents) |
| http://localhost:8080/admin | Admin SPA (sign in via Keycloak) |
| http://localhost:8081/openapi.yaml | Live OpenAPI 3.1 spec |
| http://localhost:8081/api/v1/mcp/servers | JSON API (versioned) |
| http://localhost:8081/v0/servers | MCP-registry-spec wire format |
| http://localhost:8081/.well-known/agent-card.json | Global A2A Agent Card |
| http://localhost:8180/ | Keycloak (realm ai-registry) |
A sample admin user is provisioned by the dev realm. See deploy/keycloak-realm-dev.json.
Point the server at a bootstrap file and it will upsert publishers, workspaces, MCP servers, and agents on every boot:
# deploy/bootstrap.example.yaml
publishers:
- slug: acme
name: Acme Corp
verified: true
# Optional — workspaces group MCPs and agents under a publisher and bind
# each set to a Keycloak group whose members can author content. Entries
# without a `workspace` field land in the publisher's lazy-created
# `default` workspace.
workspaces:
- publisher: acme
slug: core
name: Core
group_name: acme-core # empty / omitted = admin-only writes
mcp_servers:
- publisher: acme
workspace: core # optional; defaults to `default`
slug: files
name: Files Server
# …The full reference lives in deploy/bootstrap.example.yaml. Bootstrap is idempotent — existing rows are skipped on re-runs, with two narrow documented exceptions: it backfills tools[] when it has just been declared in the YAML, and group_name is only applied on first workspace creation so re-runs never silently overwrite operator-tweaked bindings.
Every setting is available in all three of these, with the listed precedence (highest wins):
- Environment variable —
UPPER_SNAKE_CASE(e.g.DATABASE_URL) - YAML config file —
lower_snake_casekey, path viaCONFIG_FILEenv or--configflag - Built-in default —
server/internal/config/config.go
See deploy/config.example.yaml and deploy/.env.example for the full list. Sensitive values (DSNs, client secrets) should come from env or a secrets manager, not a committed file.
81 operations across these tags:
| Tag | Purpose |
|---|---|
system |
/healthz, /readyz, OpenAPI spec, global .well-known/* |
publishers |
Namespace/publisher CRUD |
workspaces |
Workspaces under publishers; group binding; per-workspace listings |
mcp |
MCP server + version CRUD, search, detail, view/copy, reports, change-approval (submit / withdraw / approve / reject / deletion-request) |
agents |
Agent + version CRUD, per-agent A2A card, change-approval (same shape as MCP) |
review |
Reviewer-only GET /review-queue listing pending versions and deletions |
audit |
Admin-only audit log |
v0 |
Strict MCP-registry-spec-compatible read layer |
Versioned private API lives under /api/v1/; the spec-compatible wire-format layer lives under /v0/. Both are generated from the same OpenAPI document.
The CI pipeline enforces a set of contracts that mechanically prevent drift between spec, code, and the MCP / A2A specifications:
- OpenAPI ↔ router bijection — every route in the chi router has an operation in
openapi.yamland vice versa. Extra or missing either side = build failure. /v0/MCP wire-format conformance — 40 tests pinning response shapes, cursor semantics, error envelopes, and RFC 3339 timestamps to the MCP registry spec.- A2A Agent Card JSON Schema —
server/api/a2a-agent-card.schema.jsonpins the a2a-project/a2a June 2025 shape; every emission is validated against it. - Admin-guard router contract — every write endpoint requires
registry:admin, independent of the UI. - OTel span emission contract — every handler produces a span; drift fails CI.
- Migration forward-apply + idempotency — all 10 forward migrations apply cleanly on a fresh Postgres via testcontainers.
- Public rate-limit wiring — unauthenticated read endpoints are rate-limited by middleware, not handler code.
- Web test suite — 580+ Vitest + React Testing Library tests; Playwright e2e on admin flows including the change-approval workflow.
Run the suites locally:
# Go unit + integration (testcontainers Postgres)
cd server && go test ./...
# Web unit + component
cd web && npm test
# Web e2e (Playwright)
cd web && npm run test:e2e- Branching — never push directly to
main. Feature branches per task. - Commits — Conventional Commits (
feat:,fix:,docs:,chore:,test:). - Spec-first — when touching the API, update
server/api/openapi.yamlfirst, then regenerate types, then implement the handler. - Tests required — every new function, handler, or repository method needs unit coverage. Handlers and repositories also need integration coverage.
- OTel on every handler — new handlers get a span via the existing tracer from context, never an ad-hoc provider.
- Forward-only migrations — down migrations exist for local dev convenience only; never rely on them in production.
See CLAUDE.md for the full set of non-negotiables.
The phased roadmap lives in PLAN.md. High-level status:
- v0.1.x — Foundation: Postgres schema, chi router, OIDC, MCP + agent CRUD, public browse UI, admin UI, bootstrap seeding. ✅
- v0.2.x — Observability + coverage depth. OTel traces/metrics/logs wired everywhere; contract tests for every CLAUDE.md non-negotiable;
/v0/wire-format conformance; A2A schema conformance. ✅ - v0.3.x — Browse polish (real MCP
tools[]field end-to-end, card redesign, namespace landing pages, per-entry activity feed) and access control: workspaces under publishers, Keycloak group bindings, change-approval workflow with revision-tracked PR-style edits. ✅ - v0.4.x and beyond — Skills/prompts registry, federation, API-key auth (M2M), webhooks.
- Model Context Protocol
- MCP registry reference implementation
- Google A2A Protocol / Agent Card
- OAuth 2.1 draft
- RFC 7807 — Problem Details for HTTP APIs
Pre-1.0. The API is versioned (/api/v1/, /v0/) and the contract tests keep it honest, but breaking changes may still land on minor bumps before v1.0.0.
License TBD — this repository does not yet ship a LICENSE file. Please open an issue if you'd like to use the code before one lands.