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
9 changes: 9 additions & 0 deletions docs/toolhive/concepts/backend-auth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,15 @@ signing automatically, with claim-based IAM role selection. See the
[AWS STS integration tutorial](../integrations/aws-sts.mdx) for a step-by-step
setup guide.

You can also combine the embedded authorization server with AWS STS on the same
`MCPServer` or `MCPRemoteProxy` resource. In this pattern, the embedded auth
server handles incoming client authentication (using `authServerRef`), while AWS
STS handles outgoing backend credentials (using `externalAuthConfigRef`). This
is useful when your MCP clients don't have their own OIDC tokens and need
ToolHive to manage the full OAuth flow. See
[Combine embedded auth with AWS STS](../integrations/aws-sts.mdx#combine-embedded-auth-with-aws-sts)
for a complete example.

## Related information

- For client authentication concepts, see
Expand Down
50 changes: 45 additions & 5 deletions docs/toolhive/concepts/embedded-auth-server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,48 @@ for a quick setup, or the full
[Redis Sentinel session storage](../guides-k8s/redis-session-storage.mdx) guide
for an end-to-end walkthrough.

## Configuring the embedded auth server with `authServerRef`

On `MCPServer` and `MCPRemoteProxy` resources, use the `authServerRef` field to
reference an `MCPExternalAuthConfig` resource that defines the embedded auth
server. This is the preferred configuration method because it keeps the embedded
auth server (incoming client authentication) separate from
`externalAuthConfigRef` (outgoing backend authentication such as AWS STS or
token exchange).

```yaml
spec:
authServerRef:
kind: MCPExternalAuthConfig
name: my-embedded-auth-server
```

The `authServerRef` field uses a `TypedLocalObjectReference`, so you must
specify both `kind: MCPExternalAuthConfig` and the `name` of the resource.

The `authServerRef` field is the preferred way to configure the embedded auth
server. Using `externalAuthConfigRef` with `type: embeddedAuthServer` is
maintained for backward compatibility. When you need both incoming and outgoing
auth on the same resource, you must use `authServerRef` for the embedded auth
server so that `externalAuthConfigRef` remains available for the outgoing auth
configuration.

For setup instructions, see
[Set up embedded authorization server authentication](../guides-k8s/auth-k8s.mdx#set-up-embedded-authorization-server-authentication).
For the combined auth pattern with AWS STS, see
[Combine embedded auth with AWS STS](../integrations/aws-sts.mdx#combine-embedded-auth-with-aws-sts).

## MCPServer vs. VirtualMCPServer

The embedded auth server is available on both `MCPServer` and `VirtualMCPServer`
resources, with some differences:

| | MCPServer | VirtualMCPServer |
| ---------------------- | ------------------------------------------- | ------------------------------------------------------------------------------ |
| Configuration location | Separate `MCPExternalAuthConfig` resource | Inline `authServerConfig` block on the resource |
| Upstream providers | Single upstream provider | Multiple upstream providers with sequential authorization chaining |
| Token forwarding | Automatic (single provider, single backend) | Explicit `upstreamInject` or `tokenExchange` config maps providers to backends |
| | MCPServer | VirtualMCPServer |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| Configuration location | `authServerRef` (preferred) or `externalAuthConfigRef` — both reference a separate `MCPExternalAuthConfig` resource | Inline `authServerConfig` block on the resource |
| Upstream providers | Single upstream provider | Multiple upstream providers with sequential authorization chaining |
| Token forwarding | Automatic (single provider, single backend) | Explicit `upstreamInject` or `tokenExchange` config maps providers to backends |
| Combined auth | Supports `authServerRef` + `externalAuthConfigRef` on same resource | Not applicable (uses inline config) |

For single-backend deployments on MCPServer, the embedded auth server
automatically swaps the token for each request. For vMCP with multiple backends,
Expand All @@ -175,6 +207,14 @@ you configure which upstream provider's token goes to which backend using
or
[token exchange with upstream tokens](../guides-vmcp/authentication.mdx#token-exchange-with-upstream-tokens).

:::note

`VirtualMCPServer` uses an inline `authServerConfig` block, not `authServerRef`.
The `authServerRef` field is available only on `MCPServer` and `MCPRemoteProxy`
resources.

:::

## Next steps

- [Set up embedded authorization server authentication](../guides-k8s/auth-k8s.mdx#set-up-embedded-authorization-server-authentication)
Expand Down
51 changes: 31 additions & 20 deletions docs/toolhive/guides-k8s/auth-k8s.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -609,12 +609,12 @@ kubectl apply -f embedded-auth-config.yaml

**Step 5: Create the MCPServer resource**

The MCPServer needs two configuration references: `externalAuthConfigRef`
enables the embedded authorization server, and `oidcConfig` validates the JWTs
that the embedded authorization server issues. Unlike approaches 1-3 where
`oidcConfig` points to an external identity provider, here it points to the
embedded authorization server itself—the `oidcConfig` issuer must match the
`issuer` in your `MCPExternalAuthConfig`.
The MCPServer needs two configuration references: `authServerRef` enables the
embedded authorization server, and `oidcConfig` validates the JWTs that the
embedded authorization server issues. Unlike approaches 1-3 where `oidcConfig`
points to an external identity provider, here it points to the embedded
authorization server itself—the `oidcConfig` issuer must match the `issuer` in
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove em dash

Suggested change
authorization server itself—the `oidcConfig` issuer must match the `issuer` in
authorization server itself. The `oidcConfig` issuer must match the `issuer` in

your `MCPExternalAuthConfig`.

```yaml title="mcp-server-embedded-auth.yaml"
apiVersion: toolhive.stacklok.dev/v1alpha1
Expand All @@ -629,9 +629,12 @@ spec:
permissionProfile:
type: builtin
name: network
# highlight-start
# Reference the embedded authorization server configuration
externalAuthConfigRef:
authServerRef:
kind: MCPExternalAuthConfig
name: embedded-auth-server
# highlight-end
# Validate JWTs issued by the embedded authorization server
oidcConfig:
type: inline
Expand All @@ -652,33 +655,41 @@ spec:
kubectl apply -f mcp-server-embedded-auth.yaml
```

:::tip[Combining embedded auth with outgoing token exchange]
The `authServerRef` field is a `TypedLocalObjectReference` that requires both
`kind` and `name`. This field is also available on `MCPRemoteProxy` resources.

:::info[Backward compatibility]

If you need both an embedded auth server for incoming client authentication
**and** an outgoing token exchange (such as AWS STS) on the same MCPServer, use
the dedicated `authServerRef` field instead of `externalAuthConfigRef` for the
embedded auth server. This separates the two configurations so they don't
compete for the same field:
You can also use `externalAuthConfigRef` with `type: embeddedAuthServer` to
configure the embedded authorization server. This approach continues to work,
but `authServerRef` is preferred. For details on when and why to use each field,
see
[Configuring the embedded auth server with authServerRef](../concepts/embedded-auth-server.mdx#configuring-the-embedded-auth-server-with-authserverref).

:::

:::tip[Combining embedded auth with outgoing auth]

Use `authServerRef` for the embedded auth server and `externalAuthConfigRef` for
outgoing auth (such as AWS STS) on the same resource:

```yaml
spec:
# Dedicated field for the embedded auth server
# Embedded auth server for incoming client authentication
authServerRef:
kind: MCPExternalAuthConfig
name: embedded-auth-server
# Outgoing token exchange (e.g., AWS STS)
externalAuthConfigRef:
name: aws-sts-config
kind: MCPExternalAuthConfig
oidcConfig:
type: inline
inline:
issuer: 'https://mcp.example.com'
```

`authServerRef` and `externalAuthConfigRef` cannot both reference an
`embeddedAuthServer` type. The same `authServerRef` field is available on
MCPRemoteProxy resources.
For a complete example, see
[Combine embedded auth with AWS STS](../integrations/aws-sts.mdx#combine-embedded-auth-with-aws-sts).

:::

Expand Down Expand Up @@ -1014,8 +1025,8 @@ kubectl logs -n toolhive-system -l app.kubernetes.io/name=weather-server-k8s

- Verify the `MCPExternalAuthConfig` resource exists in the same namespace:
`kubectl get mcpexternalauthconfig -n toolhive-system`
- Check that the `externalAuthConfigRef.name` in your `MCPServer` matches the
`MCPExternalAuthConfig` resource name
- Check that the `authServerRef.name` (or `externalAuthConfigRef.name`) in your
`MCPServer` matches the `MCPExternalAuthConfig` resource name
- Verify the upstream provider's client ID and redirect URI are correctly
configured in the `MCPExternalAuthConfig`

Expand Down
77 changes: 74 additions & 3 deletions docs/toolhive/integrations/aws-sts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -391,10 +391,10 @@ spec:
resources:
limits:
cpu: '500m'
memory: 512Mi
memory: '512Mi'
requests:
cpu: 100m
memory: 128Mi
cpu: '100m'
memory: '128Mi'
Comment on lines 391 to +397
Copy link
Copy Markdown
Collaborator

@danbarr danbarr Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the resource settings actually relevant to the topic/example? Consider removing if not - or validate that these are in fact the right recommended resources for MCPRemoteProxy; I think these are the same as carried forward from other full MCPServer examples.

```

Replace the placeholders with your OIDC provider's configuration.
Expand All @@ -416,6 +416,77 @@ When you apply this resource, the ToolHive Operator:

:::

## Combine embedded auth with AWS STS

If you want ToolHive to handle the full OAuth flow for incoming client
authentication (instead of validating tokens from an external OIDC provider),
you can combine the
[embedded authorization server](../concepts/embedded-auth-server.mdx) with AWS
STS on the same `MCPRemoteProxy`. Use `authServerRef` for the embedded auth
server and `externalAuthConfigRef` for the AWS STS configuration.

This pattern is useful when your MCP clients don't have their own OIDC tokens.
The embedded auth server redirects users to an upstream identity provider (such
as Okta or Google), issues its own JWTs, and ToolHive then exchanges those JWTs
for temporary AWS credentials via STS.

First, create an `MCPExternalAuthConfig` for the embedded auth server following
the steps in
[Set up embedded authorization server authentication](../guides-k8s/auth-k8s.mdx#set-up-embedded-authorization-server-authentication)
(steps 1 through 4). Then deploy the `MCPRemoteProxy` with both references:

```yaml {10-12,14-15} title="aws-mcp-remote-proxy-combined.yaml"
Copy link
Copy Markdown
Collaborator

@danbarr danbarr Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Highlight is misaligned. Tip: using the # highlight-start and # highlight-end comments is usually easier than using the numbered indicators (especially for LLMs that can't count).

Suggested change
```yaml {10-12,14-15} title="aws-mcp-remote-proxy-combined.yaml"
```yaml {10-12,15-16} title="aws-mcp-remote-proxy-combined.yaml"

apiVersion: toolhive.stacklok.dev/v1alpha1
kind: MCPRemoteProxy
metadata:
name: aws-mcp-proxy
namespace: toolhive-system
spec:
remoteURL: https://aws-mcp.us-east-1.api.aws/mcp

# Embedded auth server for incoming client authentication
authServerRef:
kind: MCPExternalAuthConfig
name: embedded-auth-server

# AWS STS for outgoing backend authentication
externalAuthConfigRef:
name: aws-mcp-sts-auth

# Validate JWTs issued by the embedded authorization server
oidcConfig:
type: inline
resourceUrl: https://<YOUR_DOMAIN>/mcp
inline:
# This must match the issuer in your embedded auth server config
issuer: https://<YOUR_EMBEDDED_AUTH_ISSUER>

proxyPort: 8080
transport: streamable-http

audit:
enabled: true

resources:
limits:
cpu: '500m'
memory: '512Mi'
requests:
cpu: '100m'
memory: '128Mi'
Comment on lines +466 to +476
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the audit and resource settings actually relevant to the example? Consider removing if not.

```

In this configuration:

- `authServerRef` points to the `MCPExternalAuthConfig` with
`type: embeddedAuthServer`, which handles the OAuth flow for incoming clients.
- `externalAuthConfigRef` points to the `MCPExternalAuthConfig` with
`type: awsSts`, which exchanges OIDC tokens for AWS credentials on outgoing
requests.
- `oidcConfig` validates JWTs issued by the embedded auth server. The `issuer`
must match the `issuer` in your embedded auth server's
`MCPExternalAuthConfig`.

## Step 5: Expose the proxy

To make the proxy accessible to clients outside the cluster, create Gateway and
Expand Down