Skip to content

Commit 05ba6a6

Browse files
committed
Address PR comments
1 parent 60dd40f commit 05ba6a6

File tree

2 files changed

+233
-27
lines changed

2 files changed

+233
-27
lines changed

docs/toolhive/guides-k8s/deploy-registry.mdx

Lines changed: 231 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ description:
1818
## Overview
1919

2020
The ToolHive operator deploys the Registry server in Kubernetes by creating
21-
`MCPRegistry` resources. This is the recommended method for deploying the
22-
ToolHive Registry Server in Kubernetes environments.
21+
`MCPRegistry` resources. Alternatively, you can deploy the Registry Server
22+
manually by following the
23+
[manual deployment instructions](../guides-registry/deployment.mdx#manual-deployment).
2324

2425
### High-level architecture
2526

@@ -53,11 +54,8 @@ flowchart LR
5354

5455
## Create a registry
5556

56-
You can create `MCPRegistry` resources in namespaces based on how the operator
57-
was deployed.
58-
59-
- **Cluster mode (default)**: Create MCPRegistry resources in any namespace
60-
- **Namespace mode**: Create MCPRegistry resources only in allowed namespaces
57+
You can create `MCPRegistry` resources in the namespaces where the ToolHive
58+
Operator is deployed.
6159

6260
See [Deploy the operator](./deploy-operator-helm.mdx#operator-deployment-modes)
6361
to learn about the different deployment modes.
@@ -74,6 +72,8 @@ metadata:
7472
namespace: my-namespace # Update with your namespace
7573
spec:
7674
displayName: My MCP Registry
75+
auth:
76+
mode: anonymous
7777
registries:
7878
- name: toolhive
7979
format: toolhive
@@ -115,12 +115,14 @@ registry configuration.
115115

116116
Clone and sync from Git repositories. Ideal for version-controlled registries.
117117

118-
```yaml {7-12} title="registry-git.yaml"
118+
```yaml {9-14} title="registry-git.yaml"
119119
apiVersion: toolhive.stacklok.dev/v1alpha1
120120
kind: MCPRegistry
121121
metadata:
122122
name: git-registry
123123
spec:
124+
auth:
125+
mode: anonymous
124126
registries:
125127
- name: toolhive
126128
format: toolhive
@@ -161,6 +163,8 @@ kind: MCPRegistry
161163
metadata:
162164
name: configmap-registry
163165
spec:
166+
auth:
167+
mode: anonymous
164168
registries:
165169
- name: local
166170
format: upstream
@@ -184,6 +188,8 @@ kind: MCPRegistry
184188
metadata:
185189
name: pvc-registry
186190
spec:
191+
auth:
192+
mode: anonymous
187193
registries:
188194
- name: shared
189195
format: upstream
@@ -214,6 +220,8 @@ kind: MCPRegistry
214220
metadata:
215221
name: api-registry
216222
spec:
223+
auth:
224+
mode: anonymous
217225
registries:
218226
- name: upstream
219227
format: upstream
@@ -247,6 +255,8 @@ kind: MCPRegistry
247255
metadata:
248256
name: filtered-registry
249257
spec:
258+
auth:
259+
mode: anonymous
250260
registries:
251261
- name: toolhive
252262
format: toolhive
@@ -274,17 +284,34 @@ spec:
274284
For production deployments, configure PostgreSQL database storage for
275285
persistence across restarts.
276286

277-
```yaml {6-15} title="registry-with-database.yaml"
287+
```yaml {17-32} title="registry-with-database.yaml"
288+
apiVersion: v1
289+
kind: Secret
290+
metadata:
291+
name: registry-api-db-passwords
292+
type: Opaque
293+
stringData:
294+
db-password: app_password
295+
migration-password: migrator_password
296+
---
278297
apiVersion: toolhive.stacklok.dev/v1alpha1
279298
kind: MCPRegistry
280299
metadata:
281300
name: production-registry
282301
spec:
302+
auth:
303+
mode: anonymous
283304
databaseConfig:
284305
host: postgres.database.svc.cluster.local
285306
port: 5432
286307
user: db_app
287308
migrationUser: db_migrator
309+
dbAppUserPasswordSecretRef:
310+
name: registry-api-db-passwords
311+
key: db-password
312+
dbMigrationUserPasswordSecretRef:
313+
name: registry-api-db-passwords
314+
key: migration-password
288315
database: registry
289316
sslMode: verify-full
290317
maxOpenConns: 25
@@ -303,26 +330,201 @@ spec:
303330

304331
**Database configuration fields:**
305332

306-
| Field | Default | Description |
307-
| ----------------- | ------------- | ------------------------------------------------- |
308-
| `host` | `postgres` | Database server hostname |
309-
| `port` | `5432` | Database server port |
310-
| `user` | `db_app` | Application user (SELECT, INSERT, UPDATE, DELETE) |
311-
| `migrationUser` | `db_migrator` | Migration user (CREATE, ALTER, DROP) |
312-
| `database` | `registry` | Database name |
313-
| `sslMode` | `prefer` | SSL mode (disable, prefer, require, verify-full) |
314-
| `maxOpenConns` | `10` | Maximum open connections |
315-
| `maxIdleConns` | `2` | Maximum idle connections |
316-
| `connMaxLifetime` | `30m` | Maximum connection lifetime |
333+
| Field | Default | Description |
334+
| ---------------------------------- | ------------- | ------------------------------------------------- |
335+
| `host` | `postgres` | Database server hostname |
336+
| `port` | `5432` | Database server port |
337+
| `user` | `db_app` | Application user (SELECT, INSERT, UPDATE, DELETE) |
338+
| `migrationUser` | `db_migrator` | Migration user (CREATE, ALTER, DROP) |
339+
| `dbAppUserPasswordSecretRef` | `` | Password of application user |
340+
| `dbMigrationUserPasswordSecretRef` | `` | Password of migration user |
341+
| `database` | `registry` | Database name |
342+
| `sslMode` | `prefer` | SSL mode (disable, prefer, require, verify-full) |
343+
| `maxOpenConns` | `10` | Maximum open connections |
344+
| `maxIdleConns` | `2` | Maximum idle connections |
345+
| `connMaxLifetime` | `30m` | Maximum connection lifetime |
317346

318347
:::tip
319348

320-
Provide database passwords using a pgpass file mounted as a secret. See the
321-
[Database configuration](../guides-registry/database.mdx) guide for details on
322-
setting up password security.
349+
Credentials are internally configured using a pgpass file mounted as a secret.
350+
351+
:::
352+
353+
## Configure authentication
354+
355+
You can configure authentication using the `authConfig` field in your
356+
`MCPRegistry` resource.
357+
358+
### Authentication modes
359+
360+
| Mode | Description | Use case |
361+
| ----------- | ----------------------------------------------- | ---------------------------- |
362+
| `oauth` | Validates access tokens from identity providers | Production deployments |
363+
| `anonymous` | No authentication required | Development and testing only |
364+
365+
:::info[Secure by default]
366+
367+
Configuring an authentication mode is mandatory, if you're not interested you
368+
can set it to `anonymous`.
369+
370+
:::
371+
372+
### OAuth authentication
373+
374+
OAuth mode validates JWT tokens from one or more identity providers. Configure
375+
providers in the `authConfig.oauth.providers` array.
376+
377+
```yaml title="registry-oauth.yaml"
378+
apiVersion: toolhive.stacklok.dev/v1alpha1
379+
kind: MCPRegistry
380+
metadata:
381+
name: registry
382+
namespace: toolhive-system
383+
spec:
384+
displayName: 'Authenticated MCP Server Registry'
385+
authConfig:
386+
mode: oauth
387+
oauth:
388+
providers:
389+
- name: kubernetes
390+
issuerUrl: https://kubernetes.default.svc.cluster.local
391+
jwksUrl: https://kubernetes.default.svc/openid/v1/jwks
392+
audience: registry-server
393+
caCertPath: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
394+
authTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
395+
allowPrivateIP: true
396+
registries:
397+
- name: toolhive
398+
format: toolhive
399+
git:
400+
repository: https://github.com/stacklok/toolhive.git
401+
branch: main
402+
path: pkg/registry/data/registry.json
403+
```
404+
405+
### OAuth configuration fields
406+
407+
| Field | Required | Default | Description |
408+
| ----------------- | -------- | ----------------------------------------- | -------------------------------------------------- |
409+
| `mode` | No | `oauth` | Authentication mode (`oauth` or `anonymous`) |
410+
| `resourceUrl` | No | - | URL identifying this protected resource (RFC 9728) |
411+
| `realm` | No | `mcp-registry` | Protection space identifier for WWW-Authenticate |
412+
| `scopesSupported` | No | `[mcp-registry:read, mcp-registry:write]` | OAuth scopes supported by this resource |
413+
414+
### Provider configuration fields
415+
416+
| Field | Required | Description |
417+
| ------------------ | -------- | ----------------------------------------------------------------------- |
418+
| `name` | Yes | Unique identifier for this provider (for logging and monitoring) |
419+
| `issuerUrl` | Yes | OIDC issuer URL (e.g., `https://accounts.google.com`) |
420+
| `audience` | Yes | Expected audience claim in the access token |
421+
| `jwksUrl` | No | JWKS endpoint URL (skips OIDC discovery if specified) |
422+
| `clientId` | No | OAuth client ID for token introspection |
423+
| `clientSecretFile` | No | Path to file containing the client secret |
424+
| `caCertPath` | No | Path to CA certificate for TLS verification |
425+
| `authTokenFile` | No | Path to token file for authenticating to OIDC/JWKS endpoints |
426+
| `introspectionUrl` | No | Token introspection endpoint URL for opaque token validation (RFC 7662) |
427+
| `allowPrivateIP` | No | Allow connections to private IP addresses (required for in-cluster) |
428+
429+
### Kubernetes service account authentication
430+
431+
For in-cluster deployments, you can configure OAuth to validate Kubernetes
432+
service account tokens:
433+
434+
```yaml title="registry-k8s-auth.yaml"
435+
apiVersion: toolhive.stacklok.dev/v1alpha1
436+
kind: MCPRegistry
437+
metadata:
438+
name: registry
439+
spec:
440+
authConfig:
441+
mode: oauth
442+
oauth:
443+
providers:
444+
- name: kubernetes
445+
issuerUrl: https://kubernetes.default.svc.cluster.local
446+
jwksUrl: https://kubernetes.default.svc/openid/v1/jwks
447+
audience: registry-server
448+
caCertPath: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
449+
authTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
450+
allowPrivateIP: true
451+
```
452+
453+
:::tip[Kubernetes provider settings]
454+
455+
- **issuerUrl**: for most Kubernetes distributions,
456+
`https://kubernetes.default.svc.cluster.local` is the correct value to match
457+
the `iss` claim in Kubernetes service account tokens.
458+
- **jwksUrl**: Specify directly to skip OIDC discovery (the Kubernetes API
459+
server doesn't support standard discovery).
460+
- **allowPrivateIP**: Required for in-cluster communication with the API server.
461+
462+
:::
463+
464+
### Multiple providers
465+
466+
You can configure multiple OAuth providers to accept tokens from different
467+
identity sources:
468+
469+
```yaml title="registry-multi-provider.yaml"
470+
apiVersion: toolhive.stacklok.dev/v1alpha1
471+
kind: MCPRegistry
472+
metadata:
473+
name: registry
474+
spec:
475+
authConfig:
476+
mode: oauth
477+
oauth:
478+
providers:
479+
# Kubernetes service accounts (in-cluster workloads)
480+
- name: kubernetes
481+
issuerUrl: https://kubernetes.default.svc.cluster.local
482+
jwksUrl: https://kubernetes.default.svc/openid/v1/jwks
483+
audience: registry-server
484+
caCertPath: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
485+
authTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
486+
allowPrivateIP: true
487+
# External identity provider
488+
- name: okta
489+
issuerUrl: https://YOUR_DOMAIN.okta.com/oauth2/default
490+
audience: registry
491+
```
492+
493+
The server validates tokens against each provider in order until one succeeds.
494+
495+
### Anonymous authentication
496+
497+
For development and testing, you can disable authentication entirely:
498+
499+
```yaml title="registry-anonymous.yaml"
500+
apiVersion: toolhive.stacklok.dev/v1alpha1
501+
kind: MCPRegistry
502+
metadata:
503+
name: registry
504+
spec:
505+
authConfig:
506+
mode: anonymous
507+
registries:
508+
- name: toolhive
509+
format: toolhive
510+
git:
511+
repository: https://github.com/stacklok/toolhive.git
512+
branch: main
513+
path: pkg/registry/data/registry.json
514+
```
515+
516+
:::danger[No access control]
517+
518+
Anonymous mode provides **no access control**. Only use it in trusted
519+
environments or when other security measures are in place. **Do not use
520+
anonymous mode in production.**
323521

324522
:::
325523

524+
For detailed information about authentication configuration, including
525+
provider-specific examples for Keycloak, Auth0, Azure AD, and Okta, see the
526+
[Authentication configuration](../guides-registry/authentication.mdx) guide.
527+
326528
## Customize the Registry server pod
327529

328530
You can customize the Registry server pod using the `podTemplateSpec` field.
@@ -345,6 +547,8 @@ spec:
345547
requests:
346548
cpu: '100m'
347549
memory: '128Mi'
550+
auth:
551+
mode: anonymous
348552
registries:
349553
- name: toolhive
350554
format: toolhive
@@ -360,7 +564,10 @@ spec:
360564

361565
When customizing containers in `podTemplateSpec`, you must use
362566
`name: registry-api` for the main container to ensure the operator can properly
363-
manage the Registry server.
567+
manage the Registry server. The container name is hardcoded to avoid conflict
568+
issues with user provided containers. Mandating a container name on the Operator
569+
side explicitly tells the Operator it is the main registry server container and
570+
any other containers provided by the use are sidecars/init containers.
364571

365572
:::
366573

docs/toolhive/guides-registry/deployment.mdx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,9 @@ Although it is possible to run ToolHive Registry to use an in-memory store, it
1515
is unreliable to run multiple replicas as they would not share state, and you
1616
should run it with a proper Postgres database.
1717

18-
### Deployment using the Operator
18+
### ToolHive Operator
1919

20-
The recommended way of deploying the Registry server is by using the ToolHive
21-
Operator. See
20+
ToolHive Operator supports deploying the Registry server. See
2221
[Deploy the Registry server in Kubernetes](../guides-k8s/deploy-registry.mdx)
2322
for a complete guide.
2423

0 commit comments

Comments
 (0)