From 87cba670bc25873412917f8c0702922e065c7bc1 Mon Sep 17 00:00:00 2001 From: Radoslav Dimitrov Date: Tue, 21 Apr 2026 01:20:15 +0300 Subject: [PATCH 1/4] 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). --- .../guides-registry/authorization.mdx | 73 ++++++++++---- .../guides-registry/configuration.mdx | 67 ++++++++----- docs/toolhive/guides-registry/database.mdx | 99 +++++++++++++------ .../guides-registry/deploy-manual.mdx | 1 - .../guides-registry/deploy-operator.mdx | 17 +--- docs/toolhive/guides-registry/intro.mdx | 2 +- docs/toolhive/guides-registry/skills.mdx | 29 ++++-- .../reference/registry-schema-toolhive.mdx | 9 +- .../reference/registry-schema-upstream.mdx | 8 +- 9 files changed, 196 insertions(+), 109 deletions(-) diff --git a/docs/toolhive/guides-registry/authorization.mdx b/docs/toolhive/guides-registry/authorization.mdx index e55f6dd9..a39aecbc 100644 --- a/docs/toolhive/guides-registry/authorization.mdx +++ b/docs/toolhive/guides-registry/authorization.mdx @@ -140,7 +140,6 @@ on each CRD. Managed source entries get claims from the publish request payload. ```yaml title="config-source-claims.yaml" sources: - name: platform-tools - format: upstream git: repository: https://github.com/acme/platform-tools.git branch: main @@ -154,7 +153,6 @@ sources: # highlight-end - name: data-tools - format: upstream git: repository: https://github.com/acme/data-tools.git branch: main @@ -230,30 +228,58 @@ for Kubernetes sources). ## Claims on published entries When you publish an MCP server version or skill to a managed source, you can -attach claims to the entry. The server enforces two rules: +attach claims to the entry. The server enforces three rules: -1. **Publish claims must be a subset of the publisher's JWT claims.** Every +1. **Claims are required when authentication is enabled.** Publishing without + claims, or with an empty `claims` object, against an authenticated endpoint + returns `400 Bad Request`. Without claims, the entry would be invisible to + every non-super-admin caller, so the server rejects the request up front. +2. **Publish claims must be a subset of the publisher's JWT claims.** Every claim key in the publish request must exist in the publisher's JWT with a matching value. For example, if your JWT has `{org: "acme", team: "platform"}`, you can publish entries with - `{org: "acme"}`, `{org: "acme", team: "platform"}`, or no claims at all, but - not with `{org: "contoso"}` (a value your JWT doesn't have). - -2. **Subsequent versions must have the same claims as the first.** Once you + `{org: "acme"}` or `{org: "acme", team: "platform"}`, but not + `{org: "contoso"}` (a value your JWT doesn't have). +3. **Subsequent versions must have the same claims as the first.** Once you publish the first version of an entry with specific claims, all future - versions must carry the exact same claims. This prevents accidentally - narrowing or broadening visibility across versions. + versions must carry the exact same claims (including the case where the first + version had no claims). This prevents accidentally narrowing or broadening + visibility across versions. ```bash title="Publish a server with claims" curl -X POST \ - https://registry.example.com/default/v0.1/publish \ + https://registry.example.com/v1/entries \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "server": { + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.acme/my-server", + "description": "Team-scoped MCP server", + "version": "1.0.0" + }, + "claims": { + "org": "acme", + "team": "platform" + } + }' +``` + +See the [Registry API reference](../reference/registry-api.mdx) for the full +server payload schema, including `packages`, `remotes`, and `_meta` fields. + +### Update claims on an existing entry + +Use `PUT /v1/entries/{type}/{name}/claims` to change the claims on every version +of a previously published entry. The `type` path parameter is either `server` or +`skill`. + +```bash title="Update claims on a published server" +curl -X PUT \ + "https://registry.example.com/v1/entries/server/io.github.acme%2Fmy-server/claims" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ - "name": "my-server", - "version": "1.0.0", - "url": "https://mcp.example.com/my-server", - "description": "Team-scoped MCP server", "claims": { "org": "acme", "team": "platform" @@ -261,6 +287,16 @@ curl -X POST \ }' ``` +Because entry names contain a slash separating namespace from server name, +URL-encode the slash as `%2F` so the path is treated as a single `{name}` path +parameter. + +Pass an empty `claims` object (`{"claims": {}}`) to clear claims entirely. The +same authorization rules apply: your JWT claims must satisfy both the existing +claims on the entry (so you can modify it) and the new claims you're setting (so +you can't escalate visibility beyond what you're entitled to). Successful +updates return `204 No Content`. + ## Admin API claim scoping 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: - Callers must still authenticate with a valid JWT token - All claims-based filtering is disabled. Every authenticated caller sees all entries regardless of source or registry claims -- Role checks are not enforced (no roles are resolved) +- Role checks are pass-throughs, so every authenticated caller can reach every + endpoint +- `GET /v1/me` reports every defined role for authenticated callers, reflecting + the effective access they have when authorization is not configured - The server logs a warning at startup: `Authorization not configured, per-entry claims filtering disabled (auth-only mode)` @@ -336,7 +375,6 @@ This example shows a multi-team setup with full RBAC and claims-based scoping: ```yaml title="config-multi-tenant.yaml" sources: - name: platform-tools - format: upstream git: repository: https://github.com/acme/platform-tools.git branch: main @@ -348,7 +386,6 @@ sources: team: 'platform' - name: data-tools - format: upstream git: repository: https://github.com/acme/data-tools.git branch: main diff --git a/docs/toolhive/guides-registry/configuration.mdx b/docs/toolhive/guides-registry/configuration.mdx index 001ecf7b..c7ecd247 100644 --- a/docs/toolhive/guides-registry/configuration.mdx +++ b/docs/toolhive/guides-registry/configuration.mdx @@ -18,7 +18,6 @@ sources). # Sources define where registry data comes from sources: - name: toolhive - format: upstream git: repository: https://github.com/stacklok/toolhive-catalog.git branch: main @@ -96,17 +95,11 @@ Kubernetes lease name suffixes for leader election. | `--internal-address` | Listen address for internal endpoints (health, readiness, version) | No | `:8081` | | `--auth-mode` | Override auth mode (`anonymous` or `oauth`) | No | - | -## Registry data formats +## Registry data format -The `format` field on a source specifies the JSON schema format for the registry -data: - -- **`upstream`**: The official - [upstream MCP registry format](../reference/registry-schema-upstream.mdx), - used by the MCP Registry API and recommended for new registries. -- **`toolhive`**: The - [ToolHive-native format](../reference/registry-schema-toolhive.mdx), used by - the built-in ToolHive registry. +The Registry Server reads registry data in the official +[upstream MCP registry format](../reference/registry-schema-upstream.mdx). All +synced sources (Git, API, and File) must provide data in this format. ## Registry sources @@ -129,7 +122,6 @@ on every container restart, adding startup latency and increasing network usage. ```yaml title="config-git.yaml" sources: - name: toolhive - format: upstream git: repository: https://github.com/stacklok/toolhive-catalog.git branch: main @@ -169,7 +161,6 @@ credentials: ```yaml title="config-git-private.yaml" sources: - name: private-registry - format: upstream git: repository: https://github.com/my-org/private-registry.git branch: main @@ -253,7 +244,6 @@ scenarios. ```yaml title="config-api.yaml" sources: - name: mcp-upstream - format: upstream api: endpoint: https://registry.modelcontextprotocol.io syncPolicy: @@ -268,8 +258,6 @@ registries: - `endpoint` (required): Base URL of the upstream MCP Registry API (without path) -- `format` (optional): Must be `upstream` if specified (API sources only support - the upstream format) :::note @@ -285,7 +273,6 @@ Read from filesystem. Ideal for local development and testing. ```yaml title="config-file.yaml" sources: - name: local - format: upstream file: path: /data/registry.json syncPolicy: @@ -301,7 +288,6 @@ Alternatively, the source can be a custom URL. ```yaml title="config-file-url.yaml" sources: - name: remote - format: upstream file: url: https://www.example.com/registry.json timeout: 5s @@ -344,12 +330,27 @@ registries: - `managed` (required): Empty object to indicate managed source type - No sync policy required (managed sources are updated via API, not synced) +:::warning[Single managed source limit] + +A server deployment can have at most one managed source. Attempts to create a +second managed source through the admin API return `409 Conflict`. Startup also +fails validation if the configuration file declares multiple managed sources, or +if it declares a managed source while an API-created managed source already +exists in the database. + +::: + **Supported operations:** -- `POST /{registryName}/v0.1/publish` - Publish new server versions -- `DELETE /{registryName}/v0.1/servers/{name}/versions/{version}` - Delete - versions -- `GET` operations for listing and retrieving servers +- `POST /v1/entries` - Publish new server or skill versions (requires a `claims` + object when authentication is enabled; see + [Claims on published entries](./authorization.mdx#claims-on-published-entries)) +- `DELETE /v1/entries/{type}/{name}/versions/{version}` - Delete a specific + version +- `PUT /v1/entries/{type}/{name}/claims` - Update authorization claims on a + published entry +- Consumer reads at `/registry/{registryName}/v0.1/...` for listing and + retrieving servers and skills - [Skills management](./skills.mdx) via the extensions API See the [Registry API reference](../reference/registry-api.mdx) for complete @@ -378,7 +379,6 @@ RBAC requirements. ```yaml title="config-kubernetes.yaml" sources: - name: default - format: upstream kubernetes: {} registries: @@ -523,7 +523,6 @@ Kubernetes sources. ```yaml sources: - name: toolhive - format: upstream git: repository: https://github.com/stacklok/toolhive-catalog.git branch: main @@ -557,7 +556,6 @@ name patterns. ```yaml sources: - name: toolhive - format: upstream git: repository: https://github.com/stacklok/toolhive-catalog.git branch: main @@ -596,7 +594,6 @@ controls. ```yaml title="config-multi-source.yaml" sources: - name: toolhive-catalog - format: upstream git: repository: https://github.com/stacklok/toolhive-catalog.git branch: main @@ -605,7 +602,6 @@ sources: interval: '30m' - name: upstream-mcp - format: upstream api: endpoint: https://registry.modelcontextprotocol.io syncPolicy: @@ -666,6 +662,23 @@ The server supports OpenTelemetry for comprehensive observability, including distributed tracing and metrics collection. See the [Telemetry and metrics](./telemetry-metrics.mdx) guide for detailed information. +## Environment variables + +Most configuration values can be overridden with environment variables prefixed +with `THV_REGISTRY_`. Nested keys use underscores; for example `database.host` +maps to `THV_REGISTRY_DATABASE_HOST`. A few settings are environment-only: + +| Variable | Description | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------- | +| `THV_REGISTRY_COMPRESS_RESPONSE` | Set to `true` to enable gzip compression on HTTP responses | +| `THV_REGISTRY_WATCH_NAMESPACE` | Comma-separated list of namespaces to watch for Kubernetes sources (see [Kubernetes source](#kubernetes-source)) | +| `THV_REGISTRY_LEADER_ELECTION_ID` | Unique identifier for the leader election lease when running multiple instances | +| `THV_REGISTRY_INSECURE_URL` | Set to `true` to allow HTTP OAuth issuer URLs (development only) | + +Database passwords can also be provided via environment variables. See +[Database configuration](./database.mdx#password-configuration) for the full +precedence order. + ## Next steps - [Configure authentication](./authentication.mdx) to secure access to your diff --git a/docs/toolhive/guides-registry/database.mdx b/docs/toolhive/guides-registry/database.mdx index a8cfa5ab..0d6af26a 100644 --- a/docs/toolhive/guides-registry/database.mdx +++ b/docs/toolhive/guides-registry/database.mdx @@ -28,34 +28,73 @@ database: ### Configuration fields -| Field | Type | Required | Default | Description | -| ----------------- | ------ | -------- | --------- | -------------------------------------------------------------------------------------------------- | -| `host` | string | Yes | - | Database server hostname or IP address | -| `port` | int | Yes | - | Database server port | -| `user` | string | Yes | - | Database username for normal operations | -| `migrationUser` | string | No | `user` | Database username for running migrations (should have elevated privileges) | -| `database` | string | Yes | - | Database name | -| `sslMode` | string | No | `require` | SSL mode (`disable`, `require`, `verify-ca`, `verify-full`) | -| `maxOpenConns` | int | No | `25` | Maximum number of open connections to the database | -| `maxIdleConns` | int | No | `5` | Maximum number of idle connections in the pool | -| `connMaxLifetime` | string | No | `5m` | Maximum lifetime of a connection (e.g., "1h", "30m") | -| `maxMetaSize` | int | No | `262144` | Maximum allowed size in bytes for publisher-provided metadata extensions (256 KB) | -| `dynamicAuth` | object | No | - | Dynamic authentication configuration (see [Dynamic authentication](#dynamic-authentication) below) | - -\* Password configuration is required but has multiple sources (see Password -Security and Dynamic authentication below) - -## Password security - -The server supports secure password management with separate credentials for -migrations and normal operations. This follows the principle of least privilege -by using elevated privileges only when necessary. - -Password configuration is done using a -[Postgres Password File](https://www.postgresql.org/docs/current/libpq-pgpass.html) -and exporting the `PGPASSFILE` environment variable. - -### Recommended setup +| Field | Type | Required | Default | Description | +| ------------------- | ------ | -------- | --------- | -------------------------------------------------------------------------------------------------- | +| `host` | string | Yes | - | Database server hostname or IP address | +| `port` | int | Yes | - | Database server port | +| `user` | string | Yes | - | Database username for normal operations | +| `migrationUser` | string | No | `user` | Database username for running migrations (should have elevated privileges) | +| `password` | string | No | - | Password for the application user. Mutually exclusive with `dynamicAuth` | +| `migrationPassword` | string | No | - | Password for the migration user. Defaults to `password` when `migrationUser` equals `user` | +| `database` | string | Yes | - | Database name | +| `sslMode` | string | No | `require` | SSL mode (`disable`, `require`, `verify-ca`, `verify-full`) | +| `maxOpenConns` | int | No | `25` | Maximum number of open connections to the database | +| `maxIdleConns` | int | No | `5` | Maximum number of idle connections in the pool | +| `connMaxLifetime` | string | No | `5m` | Maximum lifetime of a connection (e.g., "1h", "30m") | +| `maxMetaSize` | int | No | `262144` | Maximum allowed size in bytes for publisher-provided metadata extensions (256 KB) | +| `dynamicAuth` | object | No | - | Dynamic authentication configuration (see [Dynamic authentication](#dynamic-authentication) below) | + +Passwords are optional in the config file; see +[Password configuration](#password-configuration) for the full precedence order. + +## Password configuration + +Pick one of the following mechanisms to provide database credentials. +`dynamicAuth` is mutually exclusive with the static password options: setting +both a `password` (or `migrationPassword`) field and a `dynamicAuth` block fails +startup validation. + +When multiple static sources are configured, the server resolves them in this +order of precedence: + +1. **Dynamic authentication** (e.g., AWS RDS IAM) - short-lived tokens generated + automatically via the `dynamicAuth` config block +2. **Config field or environment variable** - the `password` and + `migrationPassword` fields, or the `THV_REGISTRY_DATABASE_PASSWORD` and + `THV_REGISTRY_DATABASE_MIGRATIONPASSWORD` environment variables (environment + values override values set in YAML) +3. **pgpass file** - PostgreSQL's standard `~/.pgpass` or `$PGPASSFILE` + mechanism (used when no password is configured via the methods above) + +### Configure passwords via environment variables + +Environment variables are convenient for containerized deployments because they +let you inject secrets from Kubernetes Secrets, Docker secrets, or your CI/CD +system without baking passwords into config files: + +```bash +export THV_REGISTRY_DATABASE_PASSWORD=app_password +export THV_REGISTRY_DATABASE_MIGRATIONPASSWORD=migrator_password +thv-registry-api serve --config config.yaml +``` + +The environment variable takes precedence over a value set in the YAML config +file. When `migrationUser` equals `user` and `migrationPassword` is not set, the +server reuses `password` for migrations. + +:::tip[Kubernetes Secrets] + +In Kubernetes, reference a Secret via `envFrom` or `secretKeyRef` rather than +storing the password in a ConfigMap. See the +[deploy guides](./deploy-operator.mdx) for complete examples. + +::: + +### Configure passwords via a pgpass file + +As an alternative, the server supports PostgreSQL's standard pgpass file. This +approach works well for traditional VM deployments or when you want the server +to pick up credentials without any environment configuration. For production deployments, use separate database users: @@ -67,7 +106,7 @@ For production deployments, use separate database users: - CREATE, ALTER, DROP on schemas and tables - Used only during migration operations -### Example configuration with separate users +Example configuration: ```yaml title="config-production.yaml" database: @@ -90,8 +129,6 @@ echo "db.example.com:5432:registry:db_migrator:migrator_password" >> /etc/secret chmod 600 /etc/secrets/pgpassfile ``` -**Using the pgpass file:** - Set the `PGPASSFILE` environment variable when running the server: ```bash diff --git a/docs/toolhive/guides-registry/deploy-manual.mdx b/docs/toolhive/guides-registry/deploy-manual.mdx index a07f9093..3fd9b13d 100644 --- a/docs/toolhive/guides-registry/deploy-manual.mdx +++ b/docs/toolhive/guides-registry/deploy-manual.mdx @@ -116,7 +116,6 @@ data: config.yaml: | sources: - name: toolhive - format: upstream git: repository: https://github.com/stacklok/toolhive-catalog.git branch: main diff --git a/docs/toolhive/guides-registry/deploy-operator.mdx b/docs/toolhive/guides-registry/deploy-operator.mdx index 67791098..356a9d1f 100644 --- a/docs/toolhive/guides-registry/deploy-operator.mdx +++ b/docs/toolhive/guides-registry/deploy-operator.mdx @@ -80,7 +80,6 @@ spec: configYAML: | sources: - name: toolhive - format: upstream git: repository: https://github.com/stacklok/toolhive-catalog.git branch: main @@ -125,7 +124,6 @@ and each registry references sources by name. configYAML: | sources: - name: my-source - format: upstream git: { ... } registries: - name: default @@ -147,7 +145,6 @@ spec: configYAML: | sources: - name: toolhive - format: upstream git: repository: https://github.com/stacklok/toolhive-catalog.git branch: main @@ -201,7 +198,6 @@ spec: configYAML: | sources: - name: local - format: upstream file: path: /data/registry/registry.json syncPolicy: @@ -229,7 +225,6 @@ spec: configYAML: | sources: - name: upstream - format: upstream api: endpoint: https://registry.example.com syncPolicy: @@ -257,7 +252,6 @@ spec: configYAML: | sources: - name: k8s - format: upstream kubernetes: {} registries: - name: default @@ -279,7 +273,6 @@ spec: configYAML: | sources: - name: remote - format: upstream file: url: https://example.com/registry.json syncPolicy: @@ -315,7 +308,6 @@ spec: configYAML: | sources: - name: toolhive - format: upstream git: repository: https://github.com/stacklok/toolhive-catalog.git branch: main @@ -368,7 +360,6 @@ spec: configYAML: | sources: - name: toolhive - format: upstream git: repository: https://github.com/stacklok/toolhive-catalog.git branch: main @@ -450,7 +441,6 @@ spec: configYAML: | sources: - name: toolhive - format: upstream git: repository: https://github.com/stacklok/toolhive-catalog.git branch: main @@ -511,7 +501,6 @@ spec: configYAML: | sources: - name: k8s - format: upstream kubernetes: {} registries: - name: default @@ -554,7 +543,6 @@ spec: configYAML: | sources: - name: k8s - format: upstream kubernetes: {} registries: - name: default @@ -592,7 +580,6 @@ spec: configYAML: | sources: - name: toolhive - format: upstream git: repository: https://github.com/stacklok/toolhive-catalog.git branch: main @@ -643,7 +630,6 @@ spec: configYAML: | sources: - name: toolhive - format: upstream git: repository: https://github.com/stacklok/toolhive-catalog.git branch: main @@ -819,7 +805,8 @@ Common causes include: - **Source unavailable**: Git repository, API endpoint, or file is inaccessible - **Invalid JSON format**: Registry file contains invalid JSON -- **Format mismatch**: The `format` field doesn't match the actual data format +- **Schema mismatch**: The data does not conform to the + [upstream MCP registry schema](../reference/registry-schema-upstream.mdx) - **Filter too restrictive**: Filters may be excluding all servers diff --git a/docs/toolhive/guides-registry/intro.mdx b/docs/toolhive/guides-registry/intro.mdx index 0c9d8fd4..1d85489d 100644 --- a/docs/toolhive/guides-registry/intro.mdx +++ b/docs/toolhive/guides-registry/intro.mdx @@ -103,7 +103,7 @@ The server supports five source types: 1. **Managed** - A fully-managed MCP source - Ideal for hosting private MCP server catalogs - Automatically exposes entries following upstream MCP Registry format - - Supports adding new MCP servers via `/publish` endpoint + - Supports publishing MCP servers and skills via the `/v1/entries` admin API 2. **Upstream API** - Sync from upstream MCP Registry APIs - Supports federation and aggregation scenarios diff --git a/docs/toolhive/guides-registry/skills.mdx b/docs/toolhive/guides-registry/skills.mdx index 2770ca79..626f7ce6 100644 --- a/docs/toolhive/guides-registry/skills.mdx +++ b/docs/toolhive/guides-registry/skills.mdx @@ -38,6 +38,14 @@ config has `registries: [{name: my-registry, ...}]`). To publish a new skill version, send a `POST` request to the `/v1/entries` endpoint with the skill metadata wrapped in a `skill` object: +:::note + +The example below shows the request shape without claims. When authentication is +enabled, the body must also include a `claims` object at the top level or the +server returns `400 Bad Request`. See [Claims](#claims) below. + +::: + ```bash title="Publish a skill" curl -X POST \ https://registry.example.com/v1/entries \ @@ -68,7 +76,7 @@ curl -X POST \ }' ``` -**Required fields:** `namespace`, `name`, `version` +**Required fields in the `skill` object:** `namespace`, `name`, `version` A successful response returns `201 Created` with the published skill. If the version already exists, the server returns `409 Conflict`. @@ -78,15 +86,17 @@ insensitive). The server normalizes status values to uppercase internally. ### Claims -When [authorization](./authorization.mdx) is enabled, you can attach claims to -published skills to control visibility. Include a `claims` object in the request -body: +When [authentication](./authentication.mdx) is enabled, publish requests must +include a `claims` object. Skipping it returns `400 Bad Request`. Attach claims +at the top level of the request body, alongside the `skill` object: ```json { - "namespace": "io.github.acme", - "name": "code-review", - "version": "1.0.0", + "skill": { + "namespace": "io.github.acme", + "name": "code-review", + "version": "1.0.0" + }, "claims": { "org": "acme", "team": "platform" } } ``` @@ -189,10 +199,13 @@ To delete a specific version, use the `/v1/entries` endpoint: ```bash title="Delete a skill version" curl -X DELETE \ - https://registry.example.com/v1/entries/skill/code-review/versions/1.0.0 \ + "https://registry.example.com/v1/entries/skill/io.github.acme%2Fcode-review/versions/1.0.0" \ -H "Authorization: Bearer " ``` +The skill name includes the namespace prefix. URL-encode the slash as `%2F` so +the full `namespace/name` is captured as a single `{name}` path parameter. + A successful delete returns `204 No Content`. Deleting a non-existent version returns `404 Not Found`. diff --git a/docs/toolhive/reference/registry-schema-toolhive.mdx b/docs/toolhive/reference/registry-schema-toolhive.mdx index e8630c5b..8270c899 100644 --- a/docs/toolhive/reference/registry-schema-toolhive.mdx +++ b/docs/toolhive/reference/registry-schema-toolhive.mdx @@ -16,10 +16,11 @@ conform to a consistent format. :::info This format is considered legacy now that an -[official upstream registry format](registry-schema-upstream.mdx) exists. -ToolHive supports both formats, and the ToolHive-native format continues to work -for the built-in registry, custom file-based registries, and the ToolHive -Registry Server. +[official upstream registry format](registry-schema-upstream.mdx) exists. The +ToolHive-native format continues to work for the built-in registry and for +custom file-based registries used by the CLI and UI. The +[Registry Server](/toolhive/guides-registry/intro) only accepts the upstream +format. ::: diff --git a/docs/toolhive/reference/registry-schema-upstream.mdx b/docs/toolhive/reference/registry-schema-upstream.mdx index c8fa7cef..8526b11c 100644 --- a/docs/toolhive/reference/registry-schema-upstream.mdx +++ b/docs/toolhive/reference/registry-schema-upstream.mdx @@ -18,10 +18,10 @@ server schema, which is documented separately below. :::info -ToolHive also supports the -[ToolHive-native registry format](./registry-schema-toolhive.mdx), which is used -by the built-in registry. Both formats work with custom file-based registries -and the ToolHive Registry Server. +The [Registry Server](/toolhive/guides-registry/intro) only accepts this +upstream format. The CLI and UI additionally support the legacy +[ToolHive-native registry format](./registry-schema-toolhive.mdx) for their +built-in and custom file-based registries. ::: From 83a67c05b4c6ac382ba4bdfcfc1f5f2706f71aba Mon Sep 17 00:00:00 2001 From: Radoslav Dimitrov Date: Tue, 21 Apr 2026 01:23:48 +0300 Subject: [PATCH 2/4] 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. --- docs/toolhive/guides-registry/authorization.mdx | 8 ++++---- docs/toolhive/guides-registry/configuration.mdx | 13 ++++++++----- docs/toolhive/guides-registry/database.mdx | 9 ++++----- docs/toolhive/guides-registry/skills.mdx | 5 ++--- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/docs/toolhive/guides-registry/authorization.mdx b/docs/toolhive/guides-registry/authorization.mdx index a39aecbc..2f94acc8 100644 --- a/docs/toolhive/guides-registry/authorization.mdx +++ b/docs/toolhive/guides-registry/authorization.mdx @@ -242,9 +242,9 @@ attach claims to the entry. The server enforces three rules: `{org: "contoso"}` (a value your JWT doesn't have). 3. **Subsequent versions must have the same claims as the first.** Once you publish the first version of an entry with specific claims, all future - versions must carry the exact same claims (including the case where the first - version had no claims). This prevents accidentally narrowing or broadening - visibility across versions. + versions must carry the exact same claims. If the first version had none, + subsequent versions must have none either. This prevents accidentally + narrowing or broadening visibility across versions. ```bash title="Publish a server with claims" curl -X POST \ @@ -328,7 +328,7 @@ your configuration, the server runs in **auth-only mode**. In this mode: - Role checks are pass-throughs, so every authenticated caller can reach every endpoint - `GET /v1/me` reports every defined role for authenticated callers, reflecting - the effective access they have when authorization is not configured + that authorization isn't being enforced - The server logs a warning at startup: `Authorization not configured, per-entry claims filtering disabled (auth-only mode)` diff --git a/docs/toolhive/guides-registry/configuration.mdx b/docs/toolhive/guides-registry/configuration.mdx index c7ecd247..ebb2ab50 100644 --- a/docs/toolhive/guides-registry/configuration.mdx +++ b/docs/toolhive/guides-registry/configuration.mdx @@ -342,17 +342,20 @@ exists in the database. **Supported operations:** -- `POST /v1/entries` - Publish new server or skill versions (requires a `claims` - object when authentication is enabled; see - [Claims on published entries](./authorization.mdx#claims-on-published-entries)) +- `POST /v1/entries` - Publish new server or skill versions - `DELETE /v1/entries/{type}/{name}/versions/{version}` - Delete a specific version - `PUT /v1/entries/{type}/{name}/claims` - Update authorization claims on a published entry -- Consumer reads at `/registry/{registryName}/v0.1/...` for listing and - retrieving servers and skills +- `GET /registry/{registryName}/v0.1/...` - List and retrieve servers and skills + (consumer reads) - [Skills management](./skills.mdx) via the extensions API +When authentication is enabled, publish requests must include a `claims` object. +See +[Claims on published entries](./authorization.mdx#claims-on-published-entries) +for the full rules. + See the [Registry API reference](../reference/registry-api.mdx) for complete endpoint documentation and request/response examples. diff --git a/docs/toolhive/guides-registry/database.mdx b/docs/toolhive/guides-registry/database.mdx index 0d6af26a..d91472d2 100644 --- a/docs/toolhive/guides-registry/database.mdx +++ b/docs/toolhive/guides-registry/database.mdx @@ -54,15 +54,14 @@ Pick one of the following mechanisms to provide database credentials. both a `password` (or `migrationPassword`) field and a `dynamicAuth` block fails startup validation. -When multiple static sources are configured, the server resolves them in this -order of precedence: +When more than one source is configured, the server resolves them in this order +of precedence: 1. **Dynamic authentication** (e.g., AWS RDS IAM) - short-lived tokens generated automatically via the `dynamicAuth` config block 2. **Config field or environment variable** - the `password` and `migrationPassword` fields, or the `THV_REGISTRY_DATABASE_PASSWORD` and - `THV_REGISTRY_DATABASE_MIGRATIONPASSWORD` environment variables (environment - values override values set in YAML) + `THV_REGISTRY_DATABASE_MIGRATIONPASSWORD` environment variables 3. **pgpass file** - PostgreSQL's standard `~/.pgpass` or `$PGPASSFILE` mechanism (used when no password is configured via the methods above) @@ -86,7 +85,7 @@ server reuses `password` for migrations. In Kubernetes, reference a Secret via `envFrom` or `secretKeyRef` rather than storing the password in a ConfigMap. See the -[deploy guides](./deploy-operator.mdx) for complete examples. +[operator deploy guide](./deploy-operator.mdx) for complete examples. ::: diff --git a/docs/toolhive/guides-registry/skills.mdx b/docs/toolhive/guides-registry/skills.mdx index 626f7ce6..05a3bd51 100644 --- a/docs/toolhive/guides-registry/skills.mdx +++ b/docs/toolhive/guides-registry/skills.mdx @@ -40,9 +40,8 @@ endpoint with the skill metadata wrapped in a `skill` object: :::note -The example below shows the request shape without claims. When authentication is -enabled, the body must also include a `claims` object at the top level or the -server returns `400 Bad Request`. See [Claims](#claims) below. +The example below omits claims. When authentication is enabled, requests must +also include a top-level `claims` object - see [Claims](#claims) below. ::: From 3d09d5e90fb1ffc385d14445756de6e013c7e012 Mon Sep 17 00:00:00 2001 From: Radoslav Dimitrov Date: Tue, 21 Apr 2026 01:29:35 +0300 Subject: [PATCH 3/4] 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. --- docs/toolhive/guides-registry/authorization.mdx | 4 ++-- docs/toolhive/guides-registry/configuration.mdx | 1 - docs/toolhive/guides-registry/database.mdx | 5 +++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/toolhive/guides-registry/authorization.mdx b/docs/toolhive/guides-registry/authorization.mdx index 2f94acc8..e56f7bb3 100644 --- a/docs/toolhive/guides-registry/authorization.mdx +++ b/docs/toolhive/guides-registry/authorization.mdx @@ -243,8 +243,8 @@ attach claims to the entry. The server enforces three rules: 3. **Subsequent versions must have the same claims as the first.** Once you publish the first version of an entry with specific claims, all future versions must carry the exact same claims. If the first version had none, - subsequent versions must have none either. This prevents accidentally - narrowing or broadening visibility across versions. + subsequent versions must also have none. This prevents accidentally narrowing + or broadening visibility across versions. ```bash title="Publish a server with claims" curl -X POST \ diff --git a/docs/toolhive/guides-registry/configuration.mdx b/docs/toolhive/guides-registry/configuration.mdx index ebb2ab50..03906fd8 100644 --- a/docs/toolhive/guides-registry/configuration.mdx +++ b/docs/toolhive/guides-registry/configuration.mdx @@ -348,7 +348,6 @@ exists in the database. - `PUT /v1/entries/{type}/{name}/claims` - Update authorization claims on a published entry - `GET /registry/{registryName}/v0.1/...` - List and retrieve servers and skills - (consumer reads) - [Skills management](./skills.mdx) via the extensions API When authentication is enabled, publish requests must include a `claims` object. diff --git a/docs/toolhive/guides-registry/database.mdx b/docs/toolhive/guides-registry/database.mdx index d91472d2..3c4c8dad 100644 --- a/docs/toolhive/guides-registry/database.mdx +++ b/docs/toolhive/guides-registry/database.mdx @@ -49,7 +49,7 @@ Passwords are optional in the config file; see ## Password configuration -Pick one of the following mechanisms to provide database credentials. +The server supports several mechanisms for providing database credentials. `dynamicAuth` is mutually exclusive with the static password options: setting both a `password` (or `migrationPassword`) field and a `dynamicAuth` block fails startup validation. @@ -95,7 +95,8 @@ As an alternative, the server supports PostgreSQL's standard pgpass file. This approach works well for traditional VM deployments or when you want the server to pick up credentials without any environment configuration. -For production deployments, use separate database users: +For production, use separate database users regardless of how you provide +passwords: 1. **Application user** (`user`): Limited privileges for normal operations - SELECT, INSERT, UPDATE, DELETE on application tables From fe4289f436a97505025bd55e73ed286c46316b11 Mon Sep 17 00:00:00 2001 From: Radoslav Dimitrov Date: Tue, 21 Apr 2026 01:36:51 +0300 Subject: [PATCH 4/4] 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. --- .../toolhive/guides-registry/deploy-manual.mdx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/toolhive/guides-registry/deploy-manual.mdx b/docs/toolhive/guides-registry/deploy-manual.mdx index 3fd9b13d..4a21a74f 100644 --- a/docs/toolhive/guides-registry/deploy-manual.mdx +++ b/docs/toolhive/guides-registry/deploy-manual.mdx @@ -115,16 +115,16 @@ metadata: data: config.yaml: | sources: - - name: toolhive - git: - repository: https://github.com/stacklok/toolhive-catalog.git - branch: main - path: pkg/catalog/toolhive/data/registry-upstream.json - syncPolicy: - interval: "15m" + - name: toolhive + git: + repository: https://github.com/stacklok/toolhive-catalog.git + branch: main + path: pkg/catalog/toolhive/data/registry-upstream.json + syncPolicy: + interval: "15m" registries: - - name: default - sources: ["toolhive"] + - name: default + sources: ["toolhive"] auth: mode: oauth oauth: