Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/upstream-projects.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ projects:

- id: toolhive
repo: stacklok/toolhive
version: v0.42.1
version: v0.43.0
# toolhive is a monorepo covering the CLI, the Kubernetes
# operator, and the vMCP gateway. It also introduces cross-
# cutting features that land in concepts/, integrations/,
Expand Down
75 changes: 70 additions & 5 deletions docs/toolhive/guides-cli/skills-management.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -223,24 +223,71 @@ thv skill info <SKILL_NAME>
This shows the skill's name, version, description, scope, status, source
reference, installation date, and associated clients.

## Upgrade skills
## Reproduce and upgrade project-scoped skills

Skill upgrades are experimental and require `TOOLHIVE_SKILLS_LOCK_ENABLED=true`
on the ToolHive API server. Upgrade project-scoped skills from the project
directory:
Project-scoped installs record their source, resolved digest, and signer
identity in a `toolhive.lock.yaml` file at the project root. This lets you
reinstall the same skill content on another machine, detect drift in CI, and
upgrade to newer content when the catalog moves forward. User-scoped installs do
not participate in the lock file.

### Restore skills from the lock file

To reinstall the skills recorded in the lock file (for example, after cloning
the project on a new machine), run `thv skill sync` from the project directory:

```bash
thv skill sync --project-root .
```

Missing or drifted skills are reinstalled at their pinned digest. Sync prompts
for confirmation before installing; pass `--yes` in non-interactive environments
such as CI.

Use `--check` to report drift without installing or writing anything. This is
useful as a CI gate:

```bash
thv skill sync --project-root . --check
```

If you have project-scoped skills that were installed before the lock file
existed, `--adopt` records lock entries for them so subsequent syncs include
them:

```bash
thv skill sync --project-root . --adopt
```

Use `--prune` to remove installs that are no longer in the lock file.

See the [`thv skill sync` command reference](../reference/cli/thv_skill_sync.md)
for all options.

### Upgrade project-scoped skills

Upgrade the project's skills to newer pinned content:

```bash
thv skill upgrade --project-root .
```

A version or tag change within the same OCI repository proceeds without
`--allow-ref-change`, including a move to an older tag. ToolHive still pins the
`--allow-ref-change`, including a move to an older tag. ToolHive pins the
resolved digest and blocks signer changes. If the catalog moves the skill to a
different repository, organization, or registry, the upgrade is blocked. Review
the new source, then repeat the command with `--allow-ref-change` if you intend
to permit that repository move. ToolHive prompts before installing the planned
changes; pass `--yes` in non-interactive environments.

Skills pinned to an immutable reference (an OCI digest or a full Git commit
hash) are reported as not upgradable, because there is nothing newer to resolve
to.

Use `--preview` to see what would change without persisting anything, or
`--fail-on-changes` as a CI freshness gate that reports pending upgrades without
installing them.

See the
[`thv skill upgrade` command reference](../reference/cli/thv_skill_upgrade.md)
for all options.
Expand Down Expand Up @@ -335,6 +382,24 @@ After building, push the artifact to a remote OCI registry:
thv skill push ghcr.io/my-org/skills/my-skill:v1.0.0
```

`thv skill push` signs the pushed artifact by default. Pass `--key` to use a
cosign private key on disk, or `--no-sign` to push without signing:

```bash
# Sign with a cosign key on disk. Set COSIGN_PASSWORD in the thv serve
# environment to decrypt an encrypted key.
thv skill push ghcr.io/my-org/skills/my-skill:v1.0.0 \
--key cosign.key

# Push without a signature
thv skill push ghcr.io/my-org/skills/my-skill:v1.0.0 --no-sign
```

Signatures let consumers verify who published a skill before installing it. When
a consumer installs an unsigned skill project-scoped, they must pass
`--allow-unsigned` to `thv skill install`. User-scoped installs do not enforce
signatures.

:::note

Pushing to a remote registry uses your existing container registry credentials
Expand Down
82 changes: 82 additions & 0 deletions docs/toolhive/guides-k8s/embedded-auth-server-k8s.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,88 @@ For the two-layer trust model behind CIMD (why the upstream IdP never sees the
client's CIMD URL), see
[Client ID Metadata Document (CIMD)](../concepts/embedded-auth-server.mdx#client-id-metadata-document-cimd).

### Allow confidential DCR clients

By default, DCR only issues public clients: registrations must use
`token_endpoint_auth_method: "none"`, and the response carries no
`client_secret`. Set `allowConfidentialClientRegistration: true` to also accept
`client_secret_basic` and `client_secret_post` registrations and mint a real
`client_secret` (returned exactly once in the registration response):

```yaml title="MCPExternalAuthConfig: confidential DCR"
spec:
embeddedAuthServer:
allowConfidentialClientRegistration: true
```

`/oauth/register` is unauthenticated, so enabling this lets any caller who can
reach the endpoint mint a client credential. Confidential registrations are
restricted to `https` non-loopback redirect URIs, and (with the Redis storage
backend) any DCR registration idle for more than 30 days is evicted and must
re-register.

The following combinations are rejected at admission:

- `allowConfidentialClientRegistration: true` alongside
`insecureAllowHTTP: true` - client secrets would be issued in cleartext.
- `allowConfidentialClientRegistration: true` with a plain-HTTP loopback issuer
(for example, `http://localhost:8080`). Set
`insecureAllowConfidentialOverLoopbackHTTP: true` to opt in to this
combination for local development, where the traffic never leaves the machine.

Some MCP clients declare themselves public
(`token_endpoint_auth_method: "none"`) at registration but then refuse to
proceed because the response carries no `client_secret`. RFC 7591 permits the
server to substitute client metadata; `forceConfidentialRedirectUris` takes such
a client at its word and issues a real `client_secret` when its `redirect_uris`
contains an exact match for a listed URI:

```yaml title="MCPExternalAuthConfig: force confidential for specific redirects"
spec:
embeddedAuthServer:
allowConfidentialClientRegistration: true
forceConfidentialRedirectUris:
- https://client.example.com/oauth/callback
```

Every entry must be an `https` non-loopback URI. Matching is exact. Remove an
entry once the client is fixed to handle public (`"none"`) registrations
correctly.

### Pre-provision confidential clients for token exchange

Confidential DCR is registration-driven and unauthenticated. For a client that
you control and know in advance (typically a backend service that performs RFC
8693 token exchange against the embedded auth server), pre-provision it with
`delegateClients` instead. Each entry references a Kubernetes Secret for the
client secret and narrows the audiences and scopes the client may request:

```yaml title="MCPExternalAuthConfig: delegate clients"
spec:
embeddedAuthServer:
delegateClients:
- clientId: backend-exchange
clientSecretRef:
name: backend-exchange-client-secret
key: client-secret
audiences:
- https://backend.example.com
scopes:
- backend-api:read
```

`clientId`, `clientSecretRef`, `audiences`, and `scopes` are all required.
`audiences` and `scopes` narrow what the client can request at the token
endpoint; leaving them empty is not permitted because a declared client must not
receive every allowed audience or scope by default.

Delegate clients are independent of `allowConfidentialClientRegistration`:
declaring one here does not enable self-service confidential DCR, and enabling
DCR does not seed any delegate client. The two features govern different
endpoints. The delegate-clients validation blocks a plaintext `http://` issuer
categorically at admission. The CEL admission rule cannot express the loopback
exception, so use an `https://` issuer whenever you configure delegate clients.

### Enable baseline scopes for DCR clients

Some MCP clients (for example, Claude Code) register via DCR with a narrowed
Expand Down
50 changes: 50 additions & 0 deletions docs/toolhive/guides-vmcp/embedded-auth-server-vmcp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,56 @@ For an explanation of how ToolHive resolves CIMD documents and why the OAuth
provider never sees the MCP client's CIMD URL, see
[Client ID Metadata Document (CIMD)](../concepts/embedded-auth-server.mdx#client-id-metadata-document-cimd).

### Allow confidential DCR clients

To configure a VirtualMCPServer for confidential DCR, set
`allowConfidentialClientRegistration: true`:

```yaml title="VirtualMCPServer: confidential DCR"
spec:
authServerConfig:
allowConfidentialClientRegistration: true
```

To force confidential registration for clients that use listed redirect URIs,
add `forceConfidentialRedirectUris`:

```yaml title="VirtualMCPServer: force confidential for specific redirects"
spec:
authServerConfig:
allowConfidentialClientRegistration: true
forceConfidentialRedirectUris:
- https://client.example.com/oauth/callback
```

For the configuration's validation rules, security implications, and behavior,
see
[Allow confidential DCR clients](../guides-k8s/embedded-auth-server-k8s.mdx#allow-confidential-dcr-clients).

### Pre-provision confidential clients for token exchange

To pre-provision a confidential client that performs RFC 8693 token exchange
against the vMCP embedded auth server (typically a backend service you control),
add a `delegateClients` entry:

```yaml title="VirtualMCPServer: delegate clients"
spec:
authServerConfig:
delegateClients:
- clientId: backend-exchange
clientSecretRef:
name: backend-exchange-client-secret
key: client-secret
audiences:
- https://backend.example.com
scopes:
- backend-api:read
```

All four fields are required. Delegate clients are independent of
`allowConfidentialClientRegistration`. The issuer must use `https://`; delegate
clients are rejected at admission when the issuer is plaintext HTTP.

### Enable baseline scopes for DCR clients

If your MCP clients register via DCR with a narrowed `scope` value and then
Expand Down
4 changes: 2 additions & 2 deletions docs/toolhive/reference/cli/thv_skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ The skill command provides subcommands to manage skills.
* [thv skill install](thv_skill_install.md) - Install a skill
* [thv skill list](thv_skill_list.md) - List installed skills
* [thv skill push](thv_skill_push.md) - Push a built skill
* [thv skill sync](thv_skill_sync.md) - Restore project skills to match the lock file (experimental)
* [thv skill sync](thv_skill_sync.md) - Restore project skills to match the lock file
* [thv skill uninstall](thv_skill_uninstall.md) - Uninstall a skill
* [thv skill upgrade](thv_skill_upgrade.md) - Upgrade project skills to newer pinned content (experimental)
* [thv skill upgrade](thv_skill_upgrade.md) - Upgrade project skills to newer pinned content
* [thv skill validate](thv_skill_validate.md) - Validate a skill definition

4 changes: 3 additions & 1 deletion docs/toolhive/reference/cli/thv_skill_push.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ thv skill push [reference] [flags]
### Options

```
-h, --help help for push
-h, --help help for push
--key string Path to a cosign private key to sign the pushed artifact. Encrypted keys are decrypted with COSIGN_PASSWORD read from the 'thv serve' process, which performs the signing
--no-sign Push without signing (consumers will need an explicit unsigned exception to install project-scoped)
```

### Options inherited from parent commands
Expand Down
5 changes: 1 addition & 4 deletions docs/toolhive/reference/cli/thv_skill_sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,12 @@ mdx:

## thv skill sync

Restore project skills to match the lock file (experimental)
Restore project skills to match the lock file

### Synopsis

Restore a project's installed skills to match toolhive.lock.yaml.

Experimental: requires TOOLHIVE_SKILLS_LOCK_ENABLED=true on the ToolHive
server while the lock file feature rolls out.

Missing or drifted skills are reinstalled at their pinned digest. Use
--check to report drift without installing anything (suitable for CI).
Use --adopt to record lock entries for existing unmanaged installs, and
Expand Down
5 changes: 1 addition & 4 deletions docs/toolhive/reference/cli/thv_skill_upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,12 @@ mdx:

## thv skill upgrade

Upgrade project skills to newer pinned content (experimental)
Upgrade project skills to newer pinned content

### Synopsis

Re-resolve a project's lock entries and install newer content where available.

Experimental: requires TOOLHIVE_SKILLS_LOCK_ENABLED=true on the ToolHive
server while the lock file feature rolls out.

Skills pinned to an immutable reference (an OCI digest or a full git commit
hash) are reported not-upgradable — there is nothing newer to resolve to.
Use --preview to see what would change without persisting anything (OCI
Expand Down
Loading