Skip to content

Commit fc2ee65

Browse files
authored
Update Registry Server docs for v1.2 (#741)
* Update Registry Server docs for v1.2 Reflect upstream changes through toolhive-registry-server v1.2.0: - Drop references to the ToolHive source format; the Registry Server now only accepts the upstream MCP registry format (#724). - Document the PUT /v1/entries/{type}/{name}/claims endpoint and the requirement that publish requests include claims when authentication is enabled (#720, #727). - Document the single managed source limit and the 409 response on duplicate creation (#719). - Document database password config fields, THV_REGISTRY_DATABASE_* environment variables, and the precedence order over pgpass (#716). - Document the THV_REGISTRY_COMPRESS_RESPONSE, WATCH_NAMESPACE, LEADER_ELECTION_ID, and INSECURE_URL environment variables. - Update managed source endpoint references to /v1/entries paths and refine auth-only mode description (role pass-through, /v1/me). * Address review feedback on registry v1.2 docs - Drop 'static' from password precedence lead-in so dynamic auth appearing as item 1 no longer contradicts the framing. - Reword the consumer-reads bullet in managed-source operations to match the METHOD/path shape of the other bullets. - Pull the claims requirement out of the publish bullet parenthetical into a dedicated follow-on sentence. - Clarify rule 3 on claim consistency for first-version-had-no-claims. - Tighten the auth-only mode wording on /v1/me. - Trim the skills publish note and fix "deploy guides" singular link. * Apply second-pass review polish - Replace "Pick one" in database password section with a framing that matches the precedence chain that follows. - Broaden the "separate database users" guidance so it applies to every credential-delivery method, not just pgpass. - Drop the redundant "(consumer reads)" trailer from the GET bullet. - Smooth the claim-consistency sentence in authorization rule 3. * Indent list items in deploy-manual config.yaml Match the indented list style used elsewhere in the same Deployment block and across the doc set. The flush style was valid YAML but inconsistent with surrounding examples.
1 parent 248ee24 commit fc2ee65

9 files changed

Lines changed: 202 additions & 114 deletions

File tree

docs/toolhive/guides-registry/authorization.mdx

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ on each CRD. Managed source entries get claims from the publish request payload.
140140
```yaml title="config-source-claims.yaml"
141141
sources:
142142
- name: platform-tools
143-
format: upstream
144143
git:
145144
repository: https://github.com/acme/platform-tools.git
146145
branch: main
@@ -154,7 +153,6 @@ sources:
154153
# highlight-end
155154
156155
- name: data-tools
157-
format: upstream
158156
git:
159157
repository: https://github.com/acme/data-tools.git
160158
branch: main
@@ -230,37 +228,75 @@ for Kubernetes sources).
230228
## Claims on published entries
231229

232230
When you publish an MCP server version or skill to a managed source, you can
233-
attach claims to the entry. The server enforces two rules:
231+
attach claims to the entry. The server enforces three rules:
234232

235-
1. **Publish claims must be a subset of the publisher's JWT claims.** Every
233+
1. **Claims are required when authentication is enabled.** Publishing without
234+
claims, or with an empty `claims` object, against an authenticated endpoint
235+
returns `400 Bad Request`. Without claims, the entry would be invisible to
236+
every non-super-admin caller, so the server rejects the request up front.
237+
2. **Publish claims must be a subset of the publisher's JWT claims.** Every
236238
claim key in the publish request must exist in the publisher's JWT with a
237239
matching value. For example, if your JWT has
238240
`{org: "acme", team: "platform"}`, you can publish entries with
239-
`{org: "acme"}`, `{org: "acme", team: "platform"}`, or no claims at all, but
240-
not with `{org: "contoso"}` (a value your JWT doesn't have).
241-
242-
2. **Subsequent versions must have the same claims as the first.** Once you
241+
`{org: "acme"}` or `{org: "acme", team: "platform"}`, but not
242+
`{org: "contoso"}` (a value your JWT doesn't have).
243+
3. **Subsequent versions must have the same claims as the first.** Once you
243244
publish the first version of an entry with specific claims, all future
244-
versions must carry the exact same claims. This prevents accidentally
245-
narrowing or broadening visibility across versions.
245+
versions must carry the exact same claims. If the first version had none,
246+
subsequent versions must also have none. This prevents accidentally narrowing
247+
or broadening visibility across versions.
246248

247249
```bash title="Publish a server with claims"
248250
curl -X POST \
249-
https://registry.example.com/default/v0.1/publish \
251+
https://registry.example.com/v1/entries \
252+
-H "Authorization: Bearer $TOKEN" \
253+
-H "Content-Type: application/json" \
254+
-d '{
255+
"server": {
256+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
257+
"name": "io.github.acme/my-server",
258+
"description": "Team-scoped MCP server",
259+
"version": "1.0.0"
260+
},
261+
"claims": {
262+
"org": "acme",
263+
"team": "platform"
264+
}
265+
}'
266+
```
267+
268+
See the [Registry API reference](../reference/registry-api.mdx) for the full
269+
server payload schema, including `packages`, `remotes`, and `_meta` fields.
270+
271+
### Update claims on an existing entry
272+
273+
Use `PUT /v1/entries/{type}/{name}/claims` to change the claims on every version
274+
of a previously published entry. The `type` path parameter is either `server` or
275+
`skill`.
276+
277+
```bash title="Update claims on a published server"
278+
curl -X PUT \
279+
"https://registry.example.com/v1/entries/server/io.github.acme%2Fmy-server/claims" \
250280
-H "Authorization: Bearer $TOKEN" \
251281
-H "Content-Type: application/json" \
252282
-d '{
253-
"name": "my-server",
254-
"version": "1.0.0",
255-
"url": "https://mcp.example.com/my-server",
256-
"description": "Team-scoped MCP server",
257283
"claims": {
258284
"org": "acme",
259285
"team": "platform"
260286
}
261287
}'
262288
```
263289

290+
Because entry names contain a slash separating namespace from server name,
291+
URL-encode the slash as `%2F` so the path is treated as a single `{name}` path
292+
parameter.
293+
294+
Pass an empty `claims` object (`{"claims": {}}`) to clear claims entirely. The
295+
same authorization rules apply: your JWT claims must satisfy both the existing
296+
claims on the entry (so you can modify it) and the new claims you're setting (so
297+
you can't escalate visibility beyond what you're entitled to). Successful
298+
updates return `204 No Content`.
299+
264300
## Admin API claim scoping
265301

266302
When authorization is enabled, the admin API endpoints for managing sources and
@@ -289,7 +325,10 @@ your configuration, the server runs in **auth-only mode**. In this mode:
289325
- Callers must still authenticate with a valid JWT token
290326
- All claims-based filtering is disabled. Every authenticated caller sees all
291327
entries regardless of source or registry claims
292-
- Role checks are not enforced (no roles are resolved)
328+
- Role checks are pass-throughs, so every authenticated caller can reach every
329+
endpoint
330+
- `GET /v1/me` reports every defined role for authenticated callers, reflecting
331+
that authorization isn't being enforced
293332
- The server logs a warning at startup:
294333
`Authorization not configured, per-entry claims filtering disabled (auth-only mode)`
295334

@@ -336,7 +375,6 @@ This example shows a multi-team setup with full RBAC and claims-based scoping:
336375
```yaml title="config-multi-tenant.yaml"
337376
sources:
338377
- name: platform-tools
339-
format: upstream
340378
git:
341379
repository: https://github.com/acme/platform-tools.git
342380
branch: main
@@ -348,7 +386,6 @@ sources:
348386
team: 'platform'
349387
350388
- name: data-tools
351-
format: upstream
352389
git:
353390
repository: https://github.com/acme/data-tools.git
354391
branch: main

docs/toolhive/guides-registry/configuration.mdx

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ sources).
1818
# Sources define where registry data comes from
1919
sources:
2020
- name: toolhive
21-
format: upstream
2221
git:
2322
repository: https://github.com/stacklok/toolhive-catalog.git
2423
branch: main
@@ -96,17 +95,11 @@ Kubernetes lease name suffixes for leader election.
9695
| `--internal-address` | Listen address for internal endpoints (health, readiness, version) | No | `:8081` |
9796
| `--auth-mode` | Override auth mode (`anonymous` or `oauth`) | No | - |
9897

99-
## Registry data formats
98+
## Registry data format
10099

101-
The `format` field on a source specifies the JSON schema format for the registry
102-
data:
103-
104-
- **`upstream`**: The official
105-
[upstream MCP registry format](../reference/registry-schema-upstream.mdx),
106-
used by the MCP Registry API and recommended for new registries.
107-
- **`toolhive`**: The
108-
[ToolHive-native format](../reference/registry-schema-toolhive.mdx), used by
109-
the built-in ToolHive registry.
100+
The Registry Server reads registry data in the official
101+
[upstream MCP registry format](../reference/registry-schema-upstream.mdx). All
102+
synced sources (Git, API, and File) must provide data in this format.
110103

111104
## Registry sources
112105

@@ -129,7 +122,6 @@ on every container restart, adding startup latency and increasing network usage.
129122
```yaml title="config-git.yaml"
130123
sources:
131124
- name: toolhive
132-
format: upstream
133125
git:
134126
repository: https://github.com/stacklok/toolhive-catalog.git
135127
branch: main
@@ -169,7 +161,6 @@ credentials:
169161
```yaml title="config-git-private.yaml"
170162
sources:
171163
- name: private-registry
172-
format: upstream
173164
git:
174165
repository: https://github.com/my-org/private-registry.git
175166
branch: main
@@ -253,7 +244,6 @@ scenarios.
253244
```yaml title="config-api.yaml"
254245
sources:
255246
- name: mcp-upstream
256-
format: upstream
257247
api:
258248
endpoint: https://registry.modelcontextprotocol.io
259249
syncPolicy:
@@ -268,8 +258,6 @@ registries:
268258

269259
- `endpoint` (required): Base URL of the upstream MCP Registry API (without
270260
path)
271-
- `format` (optional): Must be `upstream` if specified (API sources only support
272-
the upstream format)
273261

274262
:::note
275263

@@ -285,7 +273,6 @@ Read from filesystem. Ideal for local development and testing.
285273
```yaml title="config-file.yaml"
286274
sources:
287275
- name: local
288-
format: upstream
289276
file:
290277
path: /data/registry.json
291278
syncPolicy:
@@ -301,7 +288,6 @@ Alternatively, the source can be a custom URL.
301288
```yaml title="config-file-url.yaml"
302289
sources:
303290
- name: remote
304-
format: upstream
305291
file:
306292
url: https://www.example.com/registry.json
307293
timeout: 5s
@@ -344,14 +330,31 @@ registries:
344330
- `managed` (required): Empty object to indicate managed source type
345331
- No sync policy required (managed sources are updated via API, not synced)
346332

333+
:::warning[Single managed source limit]
334+
335+
A server deployment can have at most one managed source. Attempts to create a
336+
second managed source through the admin API return `409 Conflict`. Startup also
337+
fails validation if the configuration file declares multiple managed sources, or
338+
if it declares a managed source while an API-created managed source already
339+
exists in the database.
340+
341+
:::
342+
347343
**Supported operations:**
348344

349-
- `POST /{registryName}/v0.1/publish` - Publish new server versions
350-
- `DELETE /{registryName}/v0.1/servers/{name}/versions/{version}` - Delete
351-
versions
352-
- `GET` operations for listing and retrieving servers
345+
- `POST /v1/entries` - Publish new server or skill versions
346+
- `DELETE /v1/entries/{type}/{name}/versions/{version}` - Delete a specific
347+
version
348+
- `PUT /v1/entries/{type}/{name}/claims` - Update authorization claims on a
349+
published entry
350+
- `GET /registry/{registryName}/v0.1/...` - List and retrieve servers and skills
353351
- [Skills management](./skills.mdx) via the extensions API
354352

353+
When authentication is enabled, publish requests must include a `claims` object.
354+
See
355+
[Claims on published entries](./authorization.mdx#claims-on-published-entries)
356+
for the full rules.
357+
355358
See the [Registry API reference](../reference/registry-api.mdx) for complete
356359
endpoint documentation and request/response examples.
357360

@@ -378,7 +381,6 @@ RBAC requirements.
378381
```yaml title="config-kubernetes.yaml"
379382
sources:
380383
- name: default
381-
format: upstream
382384
kubernetes: {}
383385
384386
registries:
@@ -523,7 +525,6 @@ Kubernetes sources.
523525
```yaml
524526
sources:
525527
- name: toolhive
526-
format: upstream
527528
git:
528529
repository: https://github.com/stacklok/toolhive-catalog.git
529530
branch: main
@@ -557,7 +558,6 @@ name patterns.
557558
```yaml
558559
sources:
559560
- name: toolhive
560-
format: upstream
561561
git:
562562
repository: https://github.com/stacklok/toolhive-catalog.git
563563
branch: main
@@ -596,7 +596,6 @@ controls.
596596
```yaml title="config-multi-source.yaml"
597597
sources:
598598
- name: toolhive-catalog
599-
format: upstream
600599
git:
601600
repository: https://github.com/stacklok/toolhive-catalog.git
602601
branch: main
@@ -605,7 +604,6 @@ sources:
605604
interval: '30m'
606605
607606
- name: upstream-mcp
608-
format: upstream
609607
api:
610608
endpoint: https://registry.modelcontextprotocol.io
611609
syncPolicy:
@@ -666,6 +664,23 @@ The server supports OpenTelemetry for comprehensive observability, including
666664
distributed tracing and metrics collection. See the
667665
[Telemetry and metrics](./telemetry-metrics.mdx) guide for detailed information.
668666

667+
## Environment variables
668+
669+
Most configuration values can be overridden with environment variables prefixed
670+
with `THV_REGISTRY_`. Nested keys use underscores; for example `database.host`
671+
maps to `THV_REGISTRY_DATABASE_HOST`. A few settings are environment-only:
672+
673+
| Variable | Description |
674+
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
675+
| `THV_REGISTRY_COMPRESS_RESPONSE` | Set to `true` to enable gzip compression on HTTP responses |
676+
| `THV_REGISTRY_WATCH_NAMESPACE` | Comma-separated list of namespaces to watch for Kubernetes sources (see [Kubernetes source](#kubernetes-source)) |
677+
| `THV_REGISTRY_LEADER_ELECTION_ID` | Unique identifier for the leader election lease when running multiple instances |
678+
| `THV_REGISTRY_INSECURE_URL` | Set to `true` to allow HTTP OAuth issuer URLs (development only) |
679+
680+
Database passwords can also be provided via environment variables. See
681+
[Database configuration](./database.mdx#password-configuration) for the full
682+
precedence order.
683+
669684
## Next steps
670685

671686
- [Configure authentication](./authentication.mdx) to secure access to your

0 commit comments

Comments
 (0)