docs: document Sandboxes MCP headers, secrets, and OAuth (#26094)

The Sandboxes guides omitted custom MCP request headers, didn't explain
the MCP secrets shown by `sbx secret ls`, and described older OAuth
discovery and scope-selection behavior.

Document API-key setup, host-only MCP credentials, managed endpoint
records, rotation, restart requirements, and removal. Correct OAuth
client-secret naming and explain migration from the older spelling.
Cover the advertised common OIDC scope fallback, non-blocking scope
warnings, and `--skip-ssrf-check` for private OAuth metadata discovery
and redirects, as backported in docker/sandboxes#6079.

Markdown and Vale checks pass for the changed pages.

@netlify /ai/sandboxes/mcp-gateway/

Generated by Codex
This commit is contained in:
David Karlsson authored and GitHub committed 2026-09-15 16:17:58 +02:00
1 parent 778ddbc66b
commit ef1f62541e
2 files changed
+110 -11

No files matched your search

@@ -51,6 +51,10 @@ keychain, keyed on a service identifier. Built-in agents declare a fixed set of
services. Custom kits can declare their own. The same `sbx secret set` flow
works for both.
Secrets whose names start with `mcp:` are reserved for the host's
[MCP gateway](../mcp-gateway.md). See [MCP secrets](#mcp-secrets) for how
these differ from agent and provider credentials.
### Where secrets are stored
The store backing `sbx secret set` depends on your operating system:
@@ -104,6 +108,20 @@ $ sbx secret set openai --sandbox my-sandbox
> you set or change a global secret while a sandbox is running, recreate the
> sandbox for the new value to take effect.
### MCP secrets
The MCP gateway uses the same host credential store for OAuth client secrets
and custom request header secrets. These records have names starting with
`mcp:` and appear in `sbx secret ls`. They stay on the host and aren't injected
into sandboxes. The gateway uses them to authenticate connections to MCP
servers on behalf of sandboxed agents.
For setup instructions, see
[OAuth client secrets](../mcp-gateway.md#use-a-pre-registered-oauth-client) and
[custom request headers](../mcp-gateway.md#custom-request-headers). Header
secrets use the global scope and have their own
[restart requirements](../mcp-gateway.md#manage-header-secrets).
### Use a dynamic secret source
Dynamic secret sources let `sbx` retrieve a credential from an authenticated
+92 -11
View File
@@ -87,8 +87,12 @@ If a `--url` hostname resolves to a private, loopback, link-local, or cloud
metadata address, `sbx` registers the server but warns you about the resolved
address. Register only URLs you trust. Fetching a manifest from an untrusted
URL can expose internal services or cloud metadata, and DNS rebinding can
redirect a hostname after it has been checked. For a trusted internal server,
pass `--skip-ssrf-check` to suppress the check and warning.
redirect a hostname after it has been checked. OAuth metadata discovery also
blocks private and other disallowed addresses, including redirect destinations.
For a trusted internal server, pass `--skip-ssrf-check` to skip both the MCP
URL check and the OAuth metadata discovery checks. This permits private OAuth
metadata endpoints and redirects. Use the flag only when you trust the MCP
host, OAuth provider, and all metadata redirect destinations.
### Remote endpoint URL
@@ -99,6 +103,72 @@ $ sbx mcp add notion --url https://mcp.notion.com/mcp
$ sbx mcp add linear --url https://mcp.linear.app/mcp
```
#### Custom request headers
Use `--header 'Name: value'` to send custom HTTP headers to a remote MCP
endpoint, for example to authenticate with an API key. Repeat the flag for
each header, using each header name once:
```console
$ sbx mcp add acme --url https://mcp.acme.com/mcp \
--header 'Authorization: Bearer ${api-key}' \
--header 'Accept: application/json, text/event-stream'
$ sbx secret set mcp:acme:api-key
```
Replace the example URL with your MCP endpoint. The `sbx secret set` command
prompts for the API key and stores it in the
[host credential store](configuration/credentials.md#where-secrets-are-stored).
The `${api-key}` placeholder stays in the registration. When a sandbox
connects, the gateway reads the secret and substitutes its value in the header.
Use single quotes around header values so your shell preserves placeholders.
Placeholders name stored secrets, not environment variables: `${api-key}`
reads `mcp:acme:api-key`, regardless of your shell environment.
Store each placeholder with `sbx secret set mcp:<server>:<placeholder>`.
Credential headers such as `Authorization` must use a secret placeholder.
An explicit `Authorization` header takes precedence over an OAuth access token.
After storing the secret, expose the server to a sandbox:
```console
$ sbx run claude --name acme-demo --static-mcp acme
```
Custom headers require a remote HTTP endpoint and can't be used with
`--command` or `--local`. They also require the host to connect to the server.
The hosted gateway rejects these registrations unless you supply
`--oauth-authorization-server`, which routes the connection through the host.
#### Manage header secrets
Header secrets use the global scope on the host. Set them with
`sbx secret set mcp:<server>:<placeholder>` without `--sandbox`.
Header secrets require a stored value and don't support `--ref` or `--command`
dynamic sources.
To check the header templates and whether their secrets are set, run
`sbx mcp inspect acme`. The command doesn't display resolved secret values.
The secret store also contains an automatically managed `:endpoint` record,
such as `mcp:acme:api-key:endpoint`. This metadata binds the secret to the
registered server's URLs so the gateway can detect an endpoint change before
sending the secret. You don't need to set this record yourself. If you change
the server's endpoint, follow the CLI guidance to set the secret again for
that endpoint.
To rotate a header secret, run `sbx secret set` with the same name. After
setting, changing, or removing a header secret, stop and restart the sandbox
or restart `sandboxd` to apply the change to an existing gateway. An existing
connection keeps its previous value, and a server skipped because its secret
was missing isn't retried automatically.
Removing a registration with `sbx mcp rm` keeps its header secrets and prints
commands to remove them. To remove the example secret:
```console
$ sbx secret rm mcp:acme:api-key
```
### Local stdio server
Some MCP servers communicate over stdio instead of exposing a remote HTTP
@@ -214,16 +284,22 @@ For a confidential OAuth client, store the client secret before registering
the server. There is no `--client-secret` flag:
```console
$ sbx secret set mcp:slack.client_secret
$ sbx secret set mcp:slack:client_secret
$ sbx mcp add slack --url https://slack.example.com/mcp \
--client-id <CLIENT_ID>
```
The client secret stays in the encrypted host credential store and isn't
The client secret stays in the host credential store and isn't
written to the MCP registration. If the server requires a confidential client
and no secret is stored, registration succeeds but authorization is skipped.
Store the secret, then run `sbx mcp auth <server>`.
MCP OAuth client secrets use the name `mcp:<server>:client_secret`. The store
also maintains a `mcp:<server>:client_secret:identity` record that binds the
secret to the OAuth client. For secrets stored by a version that used
`mcp:<server>.client_secret`, set the secret again using the colon-separated
name.
### Set OAuth scopes
Use the repeatable `--scope` flag to record the default scopes requested during
@@ -247,21 +323,26 @@ the following order:
1. Scopes passed to `sbx mcp auth --scope`
2. Default scopes recorded by `sbx mcp add --scope`
3. Scopes that the protected resource says it requires
4. Whichever of `openid`, `email`, `profile`, and `offline_access` the
authorization server advertises
If none of these provide a scope set, `sbx` omits the OAuth `scope` parameter so
the authorization server applies its default grant. The authorization server's
full advertised scope set is never requested automatically.
the authorization server applies its default grant. Other advertised scopes
aren't included in the fallback.
Pass `--no-scope` to suppress both the recorded defaults and the resource's
required scopes for one authorization:
Pass `--no-scope` to suppress the recorded defaults, resource-required scopes,
and advertised scope fallback for one authorization, without changing the
stored defaults:
```console
$ sbx mcp auth serverx --no-scope
```
You can't combine `--no-scope` with `--scope`. If the authorization server
advertises supported scopes, each scope you choose must be in that set. The
authorization server can still refuse an advertised scope for a particular
You can't combine `--no-scope` with `--scope`. Scopes you choose are checked
against the authorization server's advertised scopes and the resource's
required scopes. If either source publishes scopes, a scope present in neither
produces a warning but is still requested. The authorization server can still
refuse an advertised scope for a particular
client. For a local authorization flow, `sbx` lists the requested, advertised,
and refused scopes and suggests a retry command. If the server identifies the
refused scopes, the command removes them. Otherwise, it uses `--no-scope`.