Skip to content

feat(registry): serve the MCP store from a static JSON catalog over REST#4169

Open
viktormarinho wants to merge 3 commits into
mainfrom
viktormarinho/registry-to-json-plan
Open

feat(registry): serve the MCP store from a static JSON catalog over REST#4169
viktormarinho wants to merge 3 commits into
mainfrom
viktormarinho/registry-to-json-plan

Conversation

@viktormarinho

@viktormarinho viktormarinho commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

What is this contribution about?

Moves the MCP store/registry off per-org Postgres rows + the MCP-binding fan-out and onto a single static JSON catalog (decocms/mcps registry.json), fetched and cached server-side and served via a plain REST route. Adds a new apps/mesh/src/registry-catalog/ module (single-flight + stale-while-revalidate + fail-soft caching, normalize → CatalogItem, in-memory merge/filter/paginate, 33 unit tests), a GET /api/registry/items[/:id] route, and a REGISTRY_CATALOG_URL setting (defaults to the decocms catalog). The frontend store hooks (use-merged-store-discovery, use-install-from-registry) are rewired to the REST catalog with their signatures unchanged, so call sites are untouched. This is additive and merge-dark: the legacy Postgres registry, its tools, the public MCP shim, and the seeded registry connections remain in place (just unused by the store), with their removal to follow in a separate PR. Cross-repo: the catalog lives in decocms/mcps (PR #476 merged, #477 adds GitHub's official MCP, issue #478 tracks retiring the old Supabase server).

Screenshots/Demonstration

No visual change — the store renders the same items, now sourced from the catalog instead of the per-registry MCP fan-out.

How to Test

  1. With REGISTRY_CATALOG_URL unset (uses the default decocms raw catalog) or set to a registry.json URL, run the app.
  2. curl 'localhost:3000/api/registry/items?search=stripe' → returns catalog items; open the "Add connection"/store dialog → items load + search works.
  3. Install an MCP (e.g. Stripe/Notion) → a connection is created with the correct server.remotes[].url.

Migration Notes

Config only: REGISTRY_CATALOG_URL (HTTPS, optional — defaults to https://raw.githubusercontent.com/decocms/mcps/main/registry.json). No database migration in this PR; the table/connection drops come in the follow-up deletion PR.

Review Checklist

  • PR title is clear and descriptive
  • Changes are tested and working (33 unit tests + a live-catalog smoke test; full app-run verification pending)
  • Documentation is updated (if needed)
  • No breaking changes (additive; legacy registry untouched)

Summary by cubic

Serve the MCP store from a static JSON catalog over REST to simplify and speed up discovery and installs. The frontend now reads the catalog via REST (no hook API changes) and stamps items with the Deco Store source id to keep existing filters/badges working.

  • New Features

    • Added a server-side catalog that fetches REGISTRY_CATALOG_URL (defaults to https://raw.githubusercontent.com/decocms/mcps/main/registry.json), with single-flight + stale-while-revalidate caching (1h TTL), normalization, merge/dedupe, and 33 tests.
    • Exposed GET /api/registry/items and GET /api/registry/items/:id (global, read-only) and warm the cache on boot.
    • Rewired use-merged-store-discovery and use-install-from-registry to call the REST catalog and stamp items with the Deco Store _registryId; signatures unchanged and no UI changes.
    • Cleanups: removed unused registry-utils (fan-out helpers) and dropped unused barrel re-exports flagged by knip.
  • Migration

    • Optional config: set REGISTRY_CATALOG_URL (HTTPS; localhost may use http). Defaults to the first-party decocms catalog. No database changes or call-site updates required.

Written for commit 8550a8d. Summary will update on new commits.

Review in cubic

viktormarinho and others added 3 commits June 26, 2026 16:16
Move the store/registry off the per-org Postgres rows + MCP-binding fan-out
and onto a single static JSON catalog (decocms/mcps `registry.json`), fetched
and cached server-side and served via a plain REST route.

- New `apps/mesh/src/registry-catalog/` module: fetches the first-party
  `registry.json` with single-flight + stale-while-revalidate + timeout +
  fail-soft caching (reuses `@decocms/std`), normalizes each entry into a
  canonical `CatalogItem`, merges N sources (first-party first, deduped) and
  filters/paginates in-memory. 33 unit tests.
- New `GET /api/registry/items[/:id]` REST route (global, top-level mount).
- `REGISTRY_CATALOG_URL` setting (HTTPS-only; defaults to the decocms raw
  catalog, overridable).
- Frontend: `use-merged-store-discovery` and `use-install-from-registry`
  rewired from the multi-registry MCP fan-out to the REST catalog (hook
  signatures unchanged → call sites untouched).

Additive and merge-dark by design: the legacy Postgres registry, its tools,
the public MCP shim, and the seeded registry connections remain in place
(no longer used by the store frontend); their removal lands in a follow-up PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- #1: stamp catalog items with the Deco Store well-known source id so
  connections.tsx's `_registryId` registry filter and source badges keep
  working under the single catalog (proper filter removal lands in a later
  phase, with the rest of the per-registry machinery).
- #2: the catalog is live by default (REGISTRY_CATALOG_URL has a default), so
  fix the app.ts / settings comments that claimed it "ships dark when unset".
- #3: drop the now-unused registry-utils helpers (inferRegistryListToolName,
  isWellKnownRegistry, flattenPaginatedItems, extractItemsFromResponse,
  callRegistryTool) left dangling by the REST migration — keeps knip green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI build job's knip gate flagged 6 unused exports: the index.ts barrel
re-exported createCatalog/firstPartyJsonSource/normalizeCatalog/toCatalogItem
(consumers + tests import them from their own modules) and a
__resetCatalogForTests seam no test used. Trim the barrel to what app.ts
consumes and remove the dead function.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant