Skip to content

Commit d9e0016

Browse files
docs(registries): document official v0.1 protocol + standardized default set
- New docs/registries.md: default registry table, official v0.1 protocol, per-entry transport classification, opt-in API keys, offline reference set. - registry-add.md: switch examples from the now-opt-in pulse to the zero-config official registry. - specs/071-official-registry-protocol: spec / plan / tasks (speckit format). Related #566 Related #567 Co-Authored-By: Paperclip <noreply@paperclip.ing>
1 parent ecc41f9 commit d9e0016

5 files changed

Lines changed: 161 additions & 4 deletions

File tree

docs/features/registry-add.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Before adding, find the registry id and server id:
3535
```bash
3636
mcpproxy registry list # list configured registries + ids
3737
mcpproxy registry search <query> -r <id> # search one registry
38-
mcpproxy registry search sqlite -r pulse --tag database --limit 5
38+
mcpproxy registry search sqlite -r official --limit 5
3939
```
4040

4141
`registry search` flags: `--registry/-r <id>`, `--tag/-t <tag>`,
@@ -58,7 +58,7 @@ Flags:
5858
Example:
5959

6060
```bash
61-
mcpproxy registry add pulse github-mcp --env GITHUB_TOKEN=ghp_xxx
61+
mcpproxy registry add official io.github.example/github-mcp --env GITHUB_TOKEN=ghp_xxx
6262
# ✅ Added 'github-mcp' (quarantined — approve with: mcpproxy upstream approve github-mcp)
6363
```
6464

@@ -134,7 +134,7 @@ POST /api/v1/registries/{id}/refresh
134134
Response:
135135

136136
```json
137-
{ "registry_id": "pulse", "cleared": 3 }
137+
{ "registry_id": "official", "cleared": 3 }
138138
```
139139

140140
`cleared` is the number of cached entries dropped.
@@ -150,7 +150,7 @@ Use the built-in `upstream_servers` tool with `operation: "add_from_registry"`:
150150

151151
| Parameter | Required | Description |
152152
|-----------|----------|-------------|
153-
| `registry` | yes | Registry id (e.g. `pulse`). Discover via the `list_registries` / `search_servers` tools |
153+
| `registry` | yes | Registry id (e.g. `official`). Discover via the `list_registries` / `search_servers` tools |
154154
| `id` | yes | Server id within the registry |
155155
| `name` | no | Name override |
156156
| `env_json` | no | JSON object of env / required-input values, e.g. `{"GITHUB_TOKEN":"…"}` |

docs/registries.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# MCP Server Registries
2+
3+
mcpproxy discovers MCP servers through a built-in set of registries. Discovery is
4+
available via the `search_servers` / `list_registries` MCP tools, the
5+
`mcpproxy registry search|list|add` CLI, and the REST `/api/v1/registries` routes.
6+
7+
## Default registries
8+
9+
| ID | Name | Protocol | Key required | Notes |
10+
|----|------|----------|--------------|-------|
11+
| `official` | Official MCP Registry | `modelcontextprotocol/registry` | no | Primary, zero-config aggregator (`registry.modelcontextprotocol.io/v0.1/servers`). |
12+
| `reference` | Reference Servers | `builtin/reference` | no | Curated `@modelcontextprotocol` servers, **shipped in-binary** so the basics work offline. |
13+
| `docker-mcp-catalog` | Docker MCP Catalog | `custom/docker` | no | Signed-container MCP server inventory. |
14+
| `pulse` | Pulse MCP | `custom/pulse` | **yes** | Opt-in; set `MCPPROXY_REGISTRY_PULSE_API_KEY`. |
15+
| `smithery` | Smithery | `modelcontextprotocol/registry` | **yes** | Opt-in; set `MCPPROXY_REGISTRY_SMITHERY_API_KEY`. |
16+
17+
Key-requiring registries are **skipped** (not failed) when no key is configured, so
18+
a default search always succeeds. The API-key env var is
19+
`MCPPROXY_REGISTRY_<ID>_API_KEY` (ID upper-cased, non-alphanumerics → `_`).
20+
21+
User-configured registries in `mcp_config.json` (`registries: [...]`) are **merged**
22+
with these defaults (keyed by ID); a custom entry never drops the shipped set.
23+
24+
## Official v0.1 protocol
25+
26+
The official registry returns a cursor-paginated list of wrapped entries:
27+
28+
```json
29+
{ "servers": [ { "server": { /* server.json */ }, "_meta": { "io.modelcontextprotocol.registry/official": { "status": "active", "isLatest": true } } } ],
30+
"metadata": { "nextCursor": "..." } }
31+
```
32+
33+
mcpproxy:
34+
35+
- descends into each `.server`, and **skips** entries whose `_meta` status is
36+
`deleted`/`deprecated` or that are not `isLatest`;
37+
- follows `metadata.nextCursor` (bounded) and passes through `version=latest` and an
38+
optional `search` query.
39+
40+
## Transport classification (local vs remote)
41+
42+
Classification is **per transport entry** — never "has remotes ⇒ remote" (the fix
43+
for GH #567 / #483):
44+
45+
| server.json | Result |
46+
|---|---|
47+
| `packages[]` present | **stdio**: launch command derived from `runtimeHint` + `runtimeArguments` + `identifier`(`@version`) + `packageArguments`; `environmentVariables[]` become required inputs. No URL. |
48+
| `remotes[]` only | **remote/http**: `type` + `url` become the connection endpoint; `headers[]` become required inputs. |
49+
| both (hybrid) | the **package** is preferred (stdio); the remote endpoint is kept as a fallback connection URL. |
50+
51+
Because every add surface (MCP, REST, CLI) funnels through the same keystone, a
52+
packages-only server is added as stdio and a remotes-only server as http
53+
identically across all surfaces.
54+
55+
## Adding a discovered server
56+
57+
See [registry-add.md](features/registry-add.md). New servers are quarantined by
58+
default until you approve them.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Implementation Plan — 071 Official MCP registry v0.1 protocol
2+
3+
Authoritative design: [MCP-856 plan, BOARD-DECIDED section](/MCP/issues/MCP-856#document-plan).
4+
5+
## Architecture
6+
7+
All discovery surfaces funnel through `registries.SearchServers` / `registries.FindServerByID` → protocol parser → `[]ServerEntry`, and all add surfaces funnel through `server.AddServerFromRegistryRef``buildToServerConfig`. `buildToServerConfig` already derives transport from the entry: `InstallCmd != ""``stdio`, else `URL`/`ConnectURL``http`. Therefore the #567 fix lives entirely in the parser deciding which field to set — every surface inherits the correct classification for free.
8+
9+
## Components
10+
11+
- `internal/registries/official.go` (new) — official v0.1 protocol: `fetchOfficialServers` (cursor follow-loop + versioned User-Agent), `parseOfficialPage` (envelope + status filter + legacy-flat fallback), `officialServerToEntry` (per-entry classification), `buildPackageCommand` (runtime-aware command synthesis), map helpers tolerant of camelCase/snake_case.
12+
- `internal/registries/reference.go` (new) — `builtin/reference` source served in-binary (`referenceServers()`); 7 curated stdio servers.
13+
- `internal/registries/search.go``fetchServers` short-circuits official (pagination) and reference (in-binary); query threaded through for server-side `search`; dispatch trimmed; bespoke parsers (`parseMCPRun`, `parseMCPStore`, `parseFleur`, `parseAzureMCPDemoWithoutGuesser`, `parseRemoteMCPServers`, `buildFleurInstallCmd`) and `constructServerURL` synthesis removed.
14+
- `internal/config/config.go``DefaultRegistries()` rewired: official + reference + docker (kept) + pulse/smithery (opt-in `RequiresKey`).
15+
16+
## Classification rule (the #567 root fix)
17+
18+
| Input | InstallCmd | URL | ConnectURL | Transport |
19+
|---|---|---|---|---|
20+
| packages[] only | set | empty || stdio |
21+
| remotes[] only | empty | set || http |
22+
| hybrid | set (package) | empty | set (remote) | stdio |
23+
24+
## Pagination
25+
26+
`fetchOfficialServers` loops up to `officialMaxPages` (20 × `officialPageLimit` 100), following `metadata.nextCursor`, stopping on empty/repeated cursor.
27+
28+
## Test strategy (TDD)
29+
30+
- `official_test.go` — golden fixture (`testdata/official_v0.1_servers.json`): wrapped parse, status/isLatest filter, classification matrix, pagination (httptest two-page), reference offline.
31+
- `server/consistency_official_test.go` — end-to-end #567 guard: packages→stdio / remotes→http identical across REST + CLI keystone.
32+
- Updated `registry_data_test.go` / `integration_test.go` to the new default set.
33+
34+
## Risk
35+
36+
- Smithery (opt-in) uses the official protocol parser as a best-effort default; gated behind `RequiresKey`, so it is skipped without a key. Exact Smithery-shape parsing is a future refinement.
37+
- Legacy flat `modelcontextprotocol/registry` responses still parse via the `parseOpenAPIRegistry` fallback inside `parseOfficialPage`.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Feature Spec — Official MCP registry v0.1 protocol + standardized default registries
2+
3+
**Spec:** 071-official-registry-protocol
4+
**Issue:** [MCP-865](/MCP/issues/MCP-865) (keystone backend) under goal [MCP-856](/MCP/issues/MCP-856)
5+
**Status:** Implemented (design board-decided 2026-06-02 in [MCP-856 plan](/MCP/issues/MCP-856#document-plan); Glass-Cockpit Gate 2 satisfied at parent level)
6+
**Resolves by design:** GH #566 (Pulse 410), GH #567 (Fleur all-remote misclassification)
7+
8+
## Why
9+
10+
mcpproxy's registry discovery shipped a stale, bespoke set of registries with hand-written per-registry parsers and URL synthesis. Two production bugs trace directly to that design:
11+
12+
- **#566** — Pulse's `v0beta` endpoint is 410-dead.
13+
- **#567** — a server with a non-empty `remotes[]` was classified as *remote* even when it also had local `packages[]`, so the add path synthesized a bogus `docker://`/`npx://` HTTP transport (the #483 failure class).
14+
15+
The official Model Context Protocol registry (`registry.modelcontextprotocol.io/v0.1/servers`) is now the canonical aggregator. Adopting its protocol replaces most bespoke parsers and fixes the classification bug at the root.
16+
17+
## User scenarios
18+
19+
1. **Zero-config discovery.** A fresh install can `retrieve_tools`/`registry search` against the official registry with no API key.
20+
2. **Correct transport per server.** A server published as an npm/pypi/oci package is added as a **stdio** server (install command, no URL); a server published only as a remote endpoint is added as an **http** server. A hybrid server prefers its local package.
21+
3. **Offline basics.** The `@modelcontextprotocol` reference servers (filesystem, fetch, memory, time, git, sequentialthinking, everything) are discoverable even with no network, because they currently 404 on the official registry (modelcontextprotocol/servers#3047).
22+
4. **Opt-in proprietary registries.** Pulse and Smithery remain available but require an API key (`RequiresKey`), so they never break a default search.
23+
24+
## Functional requirements
25+
26+
- **FR-1 Official v0.1 parser.** Parse the wrapped `{ "server": <server.json>, "_meta": {...} }` envelope. Read `_meta["io.modelcontextprotocol.registry/official"].status`/`.isLatest`; **skip `deleted`/`deprecated`/non-latest** by default. Tolerate camelCase wire fields (`registryType`, `runtimeHint`, `nextCursor`, `isLatest`) and snake_case fallbacks.
27+
- **FR-2 Cursor pagination.** Follow `metadata.nextCursor` (opaque) with `cursor`+`limit`, bounded by a page cap (`officialMaxPages`). Pass through `version=latest` and an optional `search` query.
28+
- **FR-3 Per-entry classification (the #567 fix).** Classify per transport entry, never "remotes present ⇒ remote":
29+
- `server.packages[]`**LOCAL/stdio**: command = `runtimeHint` + `runtimeArguments[]` + `identifier`(`@version` for npm) + `packageArguments[]`; set `InstallCmd`, leave `URL` empty; `environmentVariables[]``RequiredInputs`.
30+
- `server.remotes[]`**REMOTE**: `type`+`url``URL`; `headers[]``RequiredInputs`.
31+
- **Hybrid** → prefer the package for stdio, keep the remote as `ConnectURL`.
32+
- **FR-4 Default-registry rewire.** Ship: Official (primary, no key), built-in Reference, Docker MCP catalog (kept). Demote Pulse + Smithery to opt-in (`RequiresKey`). Drop Azure-demo (`mcp/v0`), mcp.run, mcpstore, Fleur, remote-mcp-servers. Remove their bespoke parsers and all `constructServerURL` URL synthesis.
33+
- **FR-5 Curated reference source.** Built-in `builtin/reference` registry served in-binary (no network), all stdio.
34+
- **FR-6 Cross-surface consistency.** A packages-only server reads `stdio` and a remotes-only server reads `remote` identically across MCP, REST, and CLI add surfaces (mirrors spec 070).
35+
36+
## Out of scope (delegated)
37+
38+
- User-added registries + provenance/trust model + `registry add-source` CLI → [MCP-866](/MCP/issues/MCP-866) (blocked by this).
39+
40+
## Acceptance
41+
42+
- Official registry shipped + searchable zero-config; `registry search/list/add` work against it.
43+
- packages[]→stdio, remotes[]→remote across all surfaces (regression-tested).
44+
- Dropped/demoted registries handled per the board-decided §4 set.
45+
- Reference set discoverable offline.
46+
- #566 + #567 resolved by design.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Tasks — 071 Official MCP registry v0.1 protocol
2+
3+
Dependency-ordered; TDD (failing test first) per task.
4+
5+
- [x] **T01** Golden fixture `testdata/official_v0.1_servers.json` (packages-only, remotes-only, hybrid, deprecated, non-latest, deleted).
6+
- [x] **T02** `official_test.go` — failing tests: `parseOfficialPage` envelope + status/isLatest filter; classification matrix; `fetchOfficialServers` cursor pagination; `referenceServers` offline.
7+
- [x] **T03** `official.go``parseOfficialPage` / `parseOfficialItems` / `officialServerToEntry` / `buildPackageCommand` + camelCase/snake_case-tolerant map helpers.
8+
- [x] **T04** `official.go``fetchOfficialServers` cursor follow-loop, `buildOfficialURL` (version=latest, limit, search, cursor), versioned User-Agent (#566).
9+
- [x] **T05** `reference.go` — built-in curated reference set (7 stdio servers), `referenceServers()`.
10+
- [x] **T06** `search.go``fetchServers` short-circuits official + reference; thread `query`; route dispatch to `parseOfficialPage`; remove URL-synthesis loop.
11+
- [x] **T07** `search.go` — remove dropped parsers (`parseMCPRun`, `parseMCPStore`, `parseFleur`, `buildFleurInstallCmd`, `parseRemoteMCPServers`, `parseAzureMCPDemoWithoutGuesser`, `constructServerURL`) + dead constants + their tests; keep `parseOpenAPIRegistry` as legacy-flat fallback.
12+
- [x] **T08** `config.go``DefaultRegistries()` rewire (official + reference + docker; pulse/smithery opt-in).
13+
- [x] **T09** Update `registry_data_test.go` + `integration_test.go` to the new default set.
14+
- [x] **T10** `server/consistency_official_test.go` — cross-surface #567 regression (REST + CLI).
15+
- [x] **T11** Docs: `docs/registries.md` (new) + README registry list; CLAUDE.md unchanged (40k gate).
16+
- [x] **T12** Local green: `run-linter.sh`, `go test ./internal/... -race`, `test-api-e2e.sh`. Open PR (never merge); dual-AI review.

0 commit comments

Comments
 (0)