MCP Hands-On - Registering Servers with sbx mcp
flowchart LR
subgraph VM["MicroVM (sandbox)"]
AGENT["Agent<br/>/mcp → one mcp-gateway"]
end
AGENT == "SBX_MCP_URL" ==> GW{"which gateway?"}
GW -- "localhost:8811" --> LOCAL["Local gateway<br/>(Compose / Desktop)"]
GW -- "gateway.docker.com" --> HOSTED["Hosted control plane<br/>Cedar policy · audit"]
LOCAL --> BE["backends<br/>local-wiki · Notion<br/>docker.io image"]
HOSTED --> BE
GW -. "registry.modelcontextprotocol.io" .-> BAD["catalog, not a gateway → fails"]
classDef vm fill:#ecfdf5,stroke:#10b981,color:#000
classDef gw fill:#eff6ff,stroke:#3b82f6,color:#000
classDef bad fill:#fef2f2,stroke:#ef4444,color:#000
class AGENT vm
class LOCAL,HOSTED,GW,BE gw
class BAD bad
SBX_MCP_URL must point at a real gateway — local or Docker-hosted. The agent talks to one aggregated mcp-gateway, every backend sits behind it, and the public registry is a catalog that can't carry the flow. (Full architecture diagram below.)
What's Next framed Pillar 2 - MCP Tool Governance as roadmap. This section gets your hands on the part that's already shipping: the sbx mcp subcommand for registering MCP servers, fronted by the Docker MCP Gateway, that your sandboxed agents can call.
By the end you will have:
- An
sbxwith themcpsubtree enabled - A gateway fronting your servers - run one locally, or use Docker's hosted control plane
- A registered MCP server, attached to a sandbox, and verified inside the agent
Time: ~15 minutes
Prerequisites: Setup and Why AI Governance, plus sbx login.
Where this fits — the overall architecture
Policy is authored centrally in Docker Hub → AI Governance (the Settings UI
or the AI Governance API) and synced to the laptop at docker login — it takes
precedence; developers can't override it. The coding agent runs in a container
inside a MicroVM on the host, and all enforcement — network proxy, network
policy, and filesystem policy — lives on the host in the sbx daemon, around
the sandbox. The MCP Gateway the agent calls (set by SBX_MCP_URL) is either
local (on your laptop) or remote (Docker-hosted). Every decision is audited.
flowchart TB
subgraph HUB["Docker Hub — AI Governance"]
direction TB
SPACER[" "]
SETTINGS["Governance Settings (UI)"]
API["AI Governance API"]
SPACER ~~~ SETTINGS ~~~ API
end
subgraph HOST["HOST — developer laptop"]
subgraph VM["MicroVM"]
subgraph CON["Container"]
AGENT["Coding agent"]
end
end
subgraph DAEMON["sbx daemon — policy + audit"]
NET["Network proxy<br/>+ network policy"]
FS["Filesystem policy"]
end
LOCALGW["Local MCP Gateway<br/>localhost:8811"]
AUDIT[("Audit log")]
end
REMOTEGW["Remote MCP Gateway<br/>gateway.docker.com"]
INTERNET["Internet"]
BLOCK["Blocked"]
HUB -. "policy synced at docker login · takes precedence" .-> DAEMON
AGENT -- "network" --> NET
AGENT -- "file access" --> FS
AGENT -- "MCP calls · local" --> LOCALGW
AGENT -- "MCP calls · remote" --> REMOTEGW
NET -- "allow" --> INTERNET
NET -- "deny" --> BLOCK
FS -- "deny" --> BLOCK
NET -. log .-> AUDIT
FS -. log .-> AUDIT
classDef hub fill:#eef2ff,stroke:#6366f1,color:#000
classDef vm fill:#ecfdf5,stroke:#10b981,color:#000
classDef pol fill:#fff7ed,stroke:#f59e0b,color:#000
classDef gw fill:#eff6ff,stroke:#3b82f6,color:#000
classDef deny fill:#fef2f2,stroke:#ef4444,color:#000
classDef hidden fill:none,stroke:none,color:none
class SPACER hidden
class SETTINGS,API hub
class AGENT vm
class NET,FS pol
class LOCALGW,REMOTEGW gw
class BLOCK deny
Tip
Full version — policy authoring, the MCP Gateway, and the audit stream: overall architecture.
The one concept: SBX_MCP_URL must point at a gateway
The sbx mcp command exists in recent sbx builds but is hidden until an environment variable enables it:
SBX_MCP_URL is not set; MCP is not enabled
Setting SBX_MCP_URL to an absolute http/https URL does two things: it unlocks the mcp subtree in sbx --help, and it tells sbx which gateway to talk to. That gateway is what provisions the connection, proxies tool calls, and applies governance.
Important
Point SBX_MCP_URL at a real gateway — a local one (http://localhost:8811) or Docker's hosted control plane (https://gateway.docker.com). Not the public MCP registry (registry.modelcontextprotocol.io): a registry is a catalog, not a gateway, so sbx mcp add appears to work but attaching fails (501 / "No MCP servers configured").
Step 1 - Install or upgrade sbx
The stable release may lag behind on MCP features - use the latest build for your platform.
Use the nightly Homebrew tap:
brew install docker/tap/sbx@nightly
If you already have stable installed, switch the symlink:
brew unlink sbx 2>/dev/null; brew link --overwrite sbx@nightly
Grab the latest pre-release DockerSandboxes.msi from the releases page and install it:
msiexec /i DockerSandboxes.msi /quiet
Grab the latest pre-release .deb/.rpm asset from the releases page and install it, e.g.:
sudo apt install ./DockerSandboxes-linux-amd64-ubuntu2604.deb
Verify your version:
sbx version
Step 2 - Choose your gateway
Pick one of the two methods below. Both end with SBX_MCP_URL exported and the sbx mcp subtree unlocked - the rest of the lab is identical either way.
Note
- Method 1 (local gateway) is self-contained, works offline, and needs no org enablement - best for learning the mechanics.
- Method 2 (
gateway.docker.com) is MCP Gateway Enterprise: the org-governed path where policy and audit actually apply.
Method 1 - run a local Docker MCP Gateway
You need a gateway listening on localhost:8811. Get one either way below - they produce the same gateway.
Option A - Compose (self-contained, no Docker Desktop needed)
The open-source docker/mcp-gateway is the data plane - it proxies MCP traffic to backing servers. Pull the lab's Compose file and start it:
mkdir -p ~/workdemo/mcp-gateway-lab && cd ~/workdemo/mcp-gateway-lab
curl -fsSL https://raw.githubusercontent.com/ajeetraina/labspace-docker-ai-governance/main/labspace/assets/mcp-gateway-compose.yaml -o compose.yaml
docker compose up -d
Tip
Docker CE / WSL2 without Docker Desktop: if the gateway errors with Docker Desktop is not running, set export DOCKER_MCP_IN_CONTAINER=1 before docker compose up -d.
Option B - Docker Desktop MCP Toolkit
Docker Desktop 4.62+ ships the same gateway, managed for you:
- Docker Desktop → Settings → MCP Toolkit → enable it.
- In the MCP Toolkit view, enable at least one server (e.g. DuckDuckGo) so the gateway has something to proxy.
- Leave Desktop running - the gateway stays up on
localhost:8811.
Point sbx at it
export SBX_MCP_URL=http://localhost:8811
sbx daemon stop && sbx daemon start -d
Method 2 - Docker's hosted control plane (gateway.docker.com)
The endgame of Pillar 2: instead of running your own localhost:8811, SBX_MCP_URL points at Docker's hosted MCP control plane, which provisions a governed gateway per sandbox - the same control plane that enforces the network and filesystem policies you proved in Network Enforcement Demo and Filesystem Enforcement Demo. This is MCP Gateway Enterprise.
There's nothing to stand up - point sbx at it and restart the daemon:
export SBX_MCP_URL=https://gateway.docker.com
sbx daemon stop && sbx daemon start -d
When you attach a server (Step 4), the daemon calls this control plane to provision a gateway and the agent connects to it. A successful attach logs a clean 200 and mcp gateway started ... backends:N in sandboxd/daemon.log. The gateway can reject a registration that violates org policy, inject backend secrets per request, and write every tool call to the audit trail (Audit Logging). Check docker.com/products/ai-governance for your org's enablement status.
Confirm the subtree is unlocked
Either method leaves SBX_MCP_URL exported. Confirm the commands appear:
sbx mcp --help
Available Commands:
add Register an MCP server
auth Authorize MCP servers
bundle Manage MCP server bundles
inspect Show MCP server details
load Load an already-registered MCP server into a running sandbox
ls List registered MCP servers
rm Remove a registered MCP server
Note load (attaches into a running sandbox) and that the attach flag on sbx run is --static-mcp, not --mcp (Step 4).
Step 3 - Register a server
We'll register the Wikipedia MCP server as a local stdio container - the most reliable path, needs nothing beyond your machine:
sbx mcp add local-wiki --command docker --args "run,-i,--rm,mcp/wikipedia-mcp"
--command is an executable path (not a shell string) and --args is a comma-separated list - these map to docker run -i --rm mcp/wikipedia-mcp. Confirm it landed:
sbx mcp ls
sbx mcp inspect local-wiki
sbx mcp inspect shows only the registration record, not live tools — the real proof comes inside the agent in Step 5.
Warning
Local stdio servers run on the HOST, not in the sandbox — with your full user permissions. Use them for development, not untrusted code. This is exactly the risk the gateway exists to govern.
Two other registration modes work the same way against either gateway:
- Remote OAuth:
sbx mcp add notion --url https://mcp.notion.com/mcp(must behttps; add--skip_authto register before completing OAuth). - docker.io image:
sbx mcp add ddg-image --url docker.io/mcp/duckduckgo(OCI refs must be ondocker.io; the gateway pulls and runs it with container isolation).
Step 4 - Attach the server to a sandbox
Warning
Registering only records the server — attaching is separate. The attach flag is --static-mcp, not --mcp (--mcp fails with unknown flag).
# Bring up a sandbox with the server attached from the start
cd ~/workdemo
sbx run claude --static-mcp local-wiki
# ...or load it into a sandbox that's already running
sbx mcp load local-wiki
Step 5 - Verify inside the agent
The real proof is in the running agent. In the sandbox's Claude Code, run:
/mcp
You'll see one server - the gateway - aggregating every backend you attached:
Manage MCP servers
1 server
mcp-gateway · ✔ connected · 24 tools
Important
The agent connects to one mcp-gateway endpoint, not your servers directly — your backend's tools are aggregated behind it, namespaced mcp__mcp-gateway__<tool>. That single governed endpoint every tool call flows through is the whole point of Pillar 2.
If /mcp instead lists claude.ai … connectors, you're in your host Claude Code, not the sandbox — switch to the window sbx run launched.
Now make the agent actually call a tool. Esc out of /mcp and prompt it:
Use the wiki tools to search Wikipedia for "Eiffel Tower", then give me the
summary and 3 key facts. Tell me which tool(s) you called.
A tool-call line such as mcp-gateway · search_wikipedia (approve it if prompted) and an answer drawn from the live article confirm the complete chain: sbx → mcp-gateway → local-wiki → Wikipedia, every call through the governed gateway.
Warning
If every tool call is denied, that's expected when MCP governance is on. MCP invocation is fail-closed default-deny (like network/filesystem); with no MCP policy yet, calls are blocked with policy denied local-wiki/search_wikipedia: implicit. There's no local unblock (sbx policy allow is network-only) — add a Cedar MCP policy in Hub (AI governance → MCP policy → Create policy, scope Organization):
permit(principal, action == MCP::Action::"invoke", resource is MCP::Tool)
when { resource.server == "local-wiki" };
Then sbx policy reset and re-run. See Govern it below for how the rules work.
Step 6 - Clean up
sbx mcp rm local-wiki 2>/dev/null; sbx mcp ls
If you ran the Compose gateway (Method 1, Option A), stop it too:
cd ~/workdemo/mcp-gateway-lab && docker compose down
Govern it - MCP access policy in Docker Hub (admin)
Steps 1-6 were developer-side: you chose which servers to register. The governance side is where an org admin decides which servers and tools agents may call at all - authored once in Docker Hub, enforced at the gateway for every developer in <your-org>.
Substitute <your-org> with your Docker Hub organization (where you have admin rights).
When you add an MCP policy
Add one the moment agents in your org can reach MCP tools through the hosted gateway (SBX_MCP_URL=https://gateway.docker.com) and you want to constrain which tools they may invoke - the same trigger as network/filesystem policy. Until a policy exists, the org relies on defaults; once it exists, it is the allow-list every tool call is checked against.
Where it lives
Open app.docker.com/accounts/
- Organization - applies to all org members.
- Teams - layer stricter rules on top for specific teams.
Same author-once, sync-everywhere model as the network and filesystem policies - developers can't override it.
How the rules work
The model is an allow-list over (server, tool) pairs, evaluated on every tool invoke. This policy permits exactly one tool - get_me on the github-official server:
permit(
principal,
action == MCP::Action::"invoke",
resource is MCP::Tool
)
when {
resource.server == "github-official" &&
resource.name == "get_me"
};
Reading it:
action == MCP::Action::"invoke"- the rule governs calling a tool.resource is MCP::Tool, withresource.serverandresource.name- the exact tool being called.- Because MCP governance is default-deny, this
permitis the whole allow-list:get_meongithub-officialis allowed, and every other tool and every other server is blocked. To allow more, add morepermitclauses (or broaden thewhencondition).
The local-wiki policy for this lab
To make the Wikipedia prompt from Step 5 work, the org policy must permit the tools your agent actually calls. Scope it to the exact three tools the prompt exercises rather than the whole server - the tighter rule proves you can constrain which tools run, not just which server:
permit(
principal,
action == MCP::Action::"invoke",
resource is MCP::Tool
)
when {
resource.server == "local-wiki" &&
[
"search_wikipedia",
"get_summary",
"extract_key_facts"
].contains(resource.name)
};
A call to any other local-wiki tool (e.g. get_links) is denied and audited - exactly the behaviour you want to demonstrate. To allow the whole server instead, drop the resource.name clause and keep only resource.server == "local-wiki".
Note
The resource.name values must match the server's real tool names. If a call you expected to work is denied, list them with sbx mcp tools local-wiki and adjust the .contains([...]) list. This is an MCP (Cedar) rule, separate from the network allow rule that lets the mcp/wikipedia-mcp container reach *.wikipedia.org:443 - both layers must be open for the prompt to succeed.
How it's enforced
Every MCP call routes through the gateway - the single chokepoint - so the policy is evaluated on each invocation, and the call is authenticated, authorized, and logged before it reaches the backend. It's the same policy engine that enforced your network and filesystem rules, so there's no separate surface and no bypass path. A developer can register any server they like with sbx mcp add; if the org policy doesn't permit its tools, the agent's calls are denied and audited (Audit Logging). You prove exactly this in the Putting It All Together capstone (Step 7).
How this connects to Pillar 2
Everything in Steps 1-6 was on the developer side: registering servers from your CLI, fronted by the Docker MCP Gateway. The governance side - the MCP access policy above, plus injecting per-request secrets and auditing every call - sits in front of the same sbx mcp machinery and the same gateway that SBX_MCP_URL points at.
With Method 1 you front your own gateway and control what's registered. With Method 2 (gateway.docker.com) your org admin's Cedar policy controls what's invocable, and every tool call lands in the audit trail you'll explore in Audit Logging.
Quick recap
You proved:
sbx mcpis gated behindSBX_MCP_URL, which must point at a real gateway - a local one (localhost:8811) or Docker's hosted control plane (https://gateway.docker.com). The public registry is a catalog and cannot carry the flow.- A local gateway can be run via your own Compose stack or Docker Desktop's MCP Toolkit - interchangeable.
- The attach flag is
--static-mcp(not--mcp);sbx mcp loadattaches into an already-running sandbox. - Inside the agent, the gateway appears as a single aggregated
mcp-gatewayserver - the governed endpoint every tool call flows through.