Proof of concept: two Docker Sandboxes (sbx) kits that give a sandboxed coding agent governed access to your APIs through a reShapr MCP server.
- sbx isolates the agent (VM, deny-by-default network, credentials kept on the host).
- reShapr turns REST/GraphQL/gRPC APIs into MCP tools, with API-key auth, audit and trimmed responses.
| Kit | reShapr runs | Image |
|---|---|---|
| reshapr-outside-sbx | on your host, reached through the sbx MCP gateway (the API key stays on the host) | docker.io/yadayac/reshapr-outside-sbx:0.3.0 |
| reshapr-inside-sbx | inside the sandbox, in its own Docker daemon (nothing on the host) | docker.io/yadayac/reshapr-inside-sbx:0.3.0 |
OUTSIDE INSIDE
┌─ sbx sandbox ─┐ ┌──────── host ────────┐ ┌────────── sbx sandbox ──────────┐
│ agent │ sbx │ reShapr :7777 ─► API │ │ agent ─► reShapr :7777 ─► API │
│ mcp-gateway ─┼─ MCP ──┼─► key added by sbx │ │ (compose, own daemon) │
└───────────────┘ gateway└──────────────────────┘ └─────────────────────────────────┘
Demo backend: the public Open-Meteo weather API (no account needed).
Both kits are published as multi-arch OCI images with provenance, and signed (keyless Sigstore). Verify with:
sbx kit verify docker.io/yadayac/reshapr-outside-sbx:0.3.0 \
--certificate-identity yacine@microcks.io \
--certificate-oidc-issuer https://github.com/login/oauth| Outside | Inside | Plain gateway (no kit) | |
|---|---|---|---|
| reShapr runs | on the host | in the sandbox | on the host |
| Host steps | make up, secret, sbx mcp add |
none | make up, secret, sbx mcp add |
| API key | stays on the host | none (no key) | stays on the host |
| Tool name the agent sees | get_v1_forecast via mcp-gateway |
reshapr server, get_v1_forecast |
same as outside |
| Agent knows about reShapr | yes (agent-context) | yes (agent-context) | no |
| Startup | instant | about 1 minute per boot | instant |
| Extra egress to allow | none | registry.reshapr.io, quay.io, cdn01.quay.io, raw.githubusercontent.com, api.open-meteo.com (set in the kit) | none |
| Memory | host | about 1 GB in the sandbox | host |
Use inside for an isolated, ready-made environment with a single sbx create. Use outside to share one reShapr server (and its audit) across sandboxes and keep the key on the host.
sbx 0.50.0 or later (tested on 0.49.2, 0.50.0 and 0.51.0), signed in (sbx login), and curl. For outside only: Docker (Compose v2), and the reShapr CLI (Node 22+): npm install -g @reshapr/reshapr-cli --allow-scripts=@scarf/scarf (docs). If reshapr is not on your PATH, the scripts run it with npx @reshapr/reshapr-cli@<release> (slower on the first run).
Outside mode needs free ports 5555 and 7777 on the host (3333 with UI=1).
Pick an agent, then one mode. Run every step in the same terminal, because the agent block sets BASE.
1. Pick your agent and copy one block. Each block stores the agent's own credential and sets BASE.
Claude (tested live). Paste your Anthropic key when asked; create it in a dedicated Anthropic Console workspace, not at org level:
sbx secret set anthropic
export BASE=docker.io/docker/sbx-kit-claude:2.1.314Copilot (tested live). This reads your gh login (run gh auth login first). See Copilot needs two secrets:
sbx secret set github --command "$(which gh) auth token"
sbx secret set copilot --command "$(which gh) auth token"
export BASE=copilotCodex (tested live; ask "…using the reshapr tool", because Codex otherwise prefers its built-in web search, even with the kit's agent-context note). Paste your OpenAI key when asked:
sbx secret set openai
export BASE=docker.io/docker/sbx-kit-codex:0.166.02a. Inside (nothing to start on the host):
sbx create "$BASE" --name reshapr-in --kit docker.io/yadayac/reshapr-inside-sbx:0.3.0
sbx run reshapr-inreShapr starts in the background about a minute after boot. Wait until sbx exec reshapr-in tail -3 /tmp/reshapr-start.log ends with an Endpoints line (the import is done), then start the agent. The agent talks to it on localhost:7777.
2b. Outside. Clone this repo, then start reShapr on the host and register it with the sbx MCP gateway. Set the secret first; otherwise sbx mcp add fails with a misleading OAuth discovery error:
make up
sbx secret set mcp:reshapr:api-key < .state/api-key
sbx mcp add reshapr --url http://localhost:7777/mcp/reshapr/open-meteo-weather-forecast-api-1-0-default \
--header 'x-reshapr-key: ${api-key}' --skip-ssrf-check
sbx mcp ls # expect: reshapr ready
sbx create "$BASE" --name reshapr-out --static-mcp reshapr \
--kit docker.io/yadayac/reshapr-outside-sbx:0.3.0
sbx run reshapr-out3. Ask: "give the forecast in SFO". The first call may time out (3 s backend timeout); the agent retries. In outside mode the agent sees one mcp-gateway with an unprefixed get_v1_forecast (plus code-mode and mcp-exec), not a server called reshapr. The kit's agent-context note tells it so.
Clean up:
sbx rm -f reshapr-in reshapr-out
sbx mcp rm --force reshapr
sbx secret rm --force mcp:reshapr:api-key
sbx secret rm --force mcp:reshapr:api-key:endpoint
make downYour agent's own secret (anthropic, github and copilot, or openai) stays stored; remove it with sbx secret rm --force <name>.
make up # outside only: reShapr on the host + API key
MODE=outside AGENT=claude make verify # MODE=outside|inside, AGENT=claude|codex|copilot
make down # remove sandbox, gateway registration, secrets, stack, state
make validate # sbx kit inspect on both kitsmake verify needs no agent or model login; it uses sbx exec and curl. Outside proves: the agent registers mcp-gateway, the kit's agent-context is installed, and the API key is not in the sandbox. Inside proves: reShapr starts and imports the API, the agent has reshapr registered, and an MCP initialize → tools/list → tools/call get_v1_forecast round trip works.
Results: all 6 combinations (3 agents × 2 modes) pass on sbx 0.49.2 and 0.50.0, and make verify passes again on 0.51.0 (Claude 2.1.314, Codex 0.166.0, built-in Copilot; the Copilot workload image sbx-kit-copilot has since disappeared from Docker Hub). The kit TCK (kit-tck 3.0.0-m.11) passes 19/19 on both published images. Live prompts were run for Claude, Codex and Copilot on both modes.
sbx exec reshapr-in bash -s -- http://localhost:7777/mcp/reshapr/open-meteo-weather-forecast-api-1-0-default < scripts/mcp-smoke.sh
sbx exec reshapr-in copilot mcp list # or claude / codexThe agent needs its own credential, stored on the host before the sandbox is created:
| Agent | sbx secret set … |
What to paste |
|---|---|---|
| claude | anthropic |
Console API key or Claude subscription login. Create the key in a dedicated workspace with its own spend limit |
| codex | openai |
OpenAI API key or ChatGPT login |
| copilot | both github and copilot |
gh auth token output (or a fine-grained PAT with Copilot Requests) |
Warning:
gh auth tokenreturns yourghlogin token, which has broad scopes. The sbx proxy injects it only on the Copilot domains and it never enters the sandbox, but a fine-grained PAT limited to Copilot Requests is the least-privilege choice.
| Service | Injected on | Used for |
|---|---|---|
github |
api.github.com, github.com |
Copilot validates its token here at startup |
copilot |
*.githubcopilot.com, copilot.github.com |
Model calls |
Symptoms (observed): without github, Copilot starts with Authentication token found but could not be validated. GitHub returned: Bad credentials; with github but without copilot, the model catalog returns 401.
A third cause (observed on sbx 0.48.0, not reproduced on 0.50.0): if you removed the github secret while a sandbox of that name existed, sbx logs secrets: credential revoked and the revoked state sticks to the sandbox name. Use another name (SANDBOX=<name> make verify, or sbx create --name <name>). the github secret while a sandbox of that name existed, sbx logs secrets: credential revoked and the revoked state sticks to the sandbox name. Use another name (SANDBOX=<name> make verify, or sbx create --name <name>).
Inside: pass kit args (see sbx create --help). Add the hosts of your OpenAPI document and backend to the sandbox allow list (sbx policy allow network <host>):
sbx create "$BASE" --kit docker.io/yadayac/reshapr-inside-sbx:0.3.0 \
--kit-arg openapi=<openapi-url> --kit-arg backend=<api-base-url> \
--kit-arg exposition=<org>/<exposition-name>Args: release (default 1.0.0), openapi, backend, exposition. The exposition path comes from the API's title and version, so the first run tells you the right value: sbx exec <sandbox> reshapr expo list (put ~/.npm-g/bin on PATH), then recreate with --kit-arg exposition=…. Tested with the Petstore API (openapi=https://petstore3.swagger.io/api/v3/openapi.json, backend=https://petstore3.swagger.io/api/v3, exposition reshapr/swagger-petstore-openapi-3-0-1-0-28-default, plus sbx policy allow network petstore3.swagger.io): Claude called get_pet_findbystatus through the reshapr server.
Outside: import the API into reShapr on the host (reshapr import -u <openapi-url> --backendEndpoint <api-base-url> --apiKey --audit, which prints the key once; reshapr expo list for the path), then use that key and URL in the sbx secret set and sbx mcp add steps. The CLI here keeps its session in .state/home, so run it with HOME=$PWD/.state/home.
docker.io/yadayac/reshapr-kit:0.2.0 (tag v0.2.0) stays published. 0.3.0 is a breaking change:
- The old kit's per-agent hooks,
reshaprcredential, network rule andscripts/bind.share gone. Registration now goes through the sbx MCP gateway (outside) or runs in the sandbox (inside). - The kit moved to
reshapr-outside-sbx(agent-context only). Outside mode now needssbx secret set mcp:reshapr:api-key,sbx mcp add … --skip-ssrf-checkand--static-mcp reshapr. - The tool is
get_v1_forecastbehindmcp-gateway, notreshapr-get_v1_forecast.
A v3 kit is an OCI image. Build with buildx and a docker-container builder (Docker Desktop's default builder pushes a plain image that sbx rejects with "no artifactType"):
docker buildx create --name kitpub --driver docker-container
K=reshapr-outside-sbx # or reshapr-inside-sbx
docker buildx build ./sbx-kits/$K -f ./sbx-kits/$K/$K.yaml \
--platform linux/amd64,linux/arm64 --provenance=true --sbom=false \
-t docker.io/<namespace>/$K:0.3.0 --builder kitpub --push
sbx kit sign docker.io/<namespace>/$K:0.3.0 # keyless; opens a browser loginThe kit's name comes from the repository name.
In outside mode the same server can be queried from the host. Auth is an API key in a custom header, not a bearer token:
- URL:
http://localhost:7777/mcp/<org>/<exposition-name> - Header:
x-reshapr-key: <key>(from.state/api-keywhilemake upis running) - In MCPJam, set authentication to none and add the header under "Edit server details".
| Path | Purpose |
|---|---|
sbx-kits/reshapr-outside-sbx/reshapr-outside-sbx.yaml |
Outside kit (v3 mixin): agent-context only |
sbx-kits/reshapr-inside-sbx/reshapr-inside-sbx.yaml |
Inside kit (v3 mixin): args, network allow list, long-running, install and startup hooks, agent-context |
scripts/up.sh |
reshapr run -r <release> on the host (outside), CLI login, API import, key saved to .state/api-key |
scripts/verify.sh, mcp-smoke.sh |
Sandbox creation and checks (MODE, AGENT) |
scripts/down.sh |
Cleanup |
The host stack is reShapr's own (reshapr run), pinned with RESHAPR_RELEASE (default 1.0.0). The CLI runs with HOME=.state/home, so your real ~/.reshapr is untouched.
- Outside: the host stack can go stale (expired CLI token, lost exposition): the gateway then lists only
code-modeandmcp-execand agents fall back to web search.make verifychecks the host exposition first; if it fails, runmake down && make up. - Outside: the sbx MCP gateway is host-side, so it reaches
localhost:7777and adds the key; the sandbox never sees it. The agent seesmcp-gateway, which is why the agent-context note names the tools. - Inside: the gateway can't reach a sandbox's
localhost, so the kit registers the server in each agent's own config (Claudeclaude mcp add, Codex~/.codex/config.toml, Copilot~/.copilot/mcp-config.json), with no key. - Inside startup timing: an agent session opened before the import finishes has no reShapr tools and may fall back to web search. Wait for the import to finish:
sbx exec <sandbox> tail -3 /tmp/reshapr-start.logshould end with theEndpointsline (the agent can listreshapras connected before that). Then start a new session if needed. Tested with a Claude subscription/login(no API-key secret): works. - Inside needs
long-running@1. Without it the sandbox auto-stops with no client attached, and after a reboot the server can start before the control plane and never recover. The startup hook restartsreshapr-gateway-01when:7777is unhealthy. - Inside startup: each boot runs
reshapr runand re-imports the API when the exposition is missing (Postgres is not persisted across stop/start). It takes about a minute and about 1 GB RAM.registry.reshapr.ioredirects blobs tocdn01.quay.io, so that host must be allowed; it may change. - The kits are schema v3. A v3 mixin must be composed with a v3 workload (the published
docker.io/docker/sbx-kit-<agent>:<version>, pinned inscripts/common.sh). Since sbx 0.51.0 the built-incopilotalso composes (there is no published Copilot workload), but the built-inclaudeandcodeximages have no Node/npm, so the inside kit's CLI install fails withnpm: not found; keep the pinned workloads for those two. - v3 source kits are built, not just parsed. The scripts set
SBX_KIT_BUILDER=sandbox;sbx kit validatedoesn't accept v3 source kits, somake validaterunssbx kit inspect. sbx secret setfails non-interactively if the secret exists (usesbx secret rm --forcefirst);sbx mcp rmneeds--force.- The first forecast call can hit reShapr's 3 s backend timeout;
make verifyretries. - Dev-only credentials (
admin/password, fixed keys in reShapr's local compose stack). Never reuse them.
Design notes, findings and the TODO list: docs/PLAN.md.
- One exposition per kit; API-key auth only (outside) or no auth (inside, sandbox-local); no OAuth2.
- Codex prefers web search unless the prompt says to use the reshapr tool.
- The e2e suite (
scripts/test-kit-e2e.sh) can't run for v3 source kits yet: docker/sbx-kits-contrib#446. - Next: remote reShapr server, several expositions, OAuth2, persistence for the inside kit, a "builder" kit.