Provisioning writes tokens, skills, MCP servers, and house rules into the files each CLI expects.
Provisioning lets you declare all of it once, on defineWorkspace(). At bootstrap, each harness adapter writes it in the format its CLI reads.
import {
bearer,
createSecrets,
defineSandbox,
defineWorkspace,
fileSkill,
gitSkill,
githubRepo,
mcpSkill,
} from '@tanstack/ai-sandbox'
import { dockerSandbox } from '@tanstack/ai-sandbox-docker'
const secrets = createSecrets({
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY ?? '',
GH: process.env.GH_TOKEN ?? '',
SENTRY: process.env.SENTRY_TOKEN ?? '',
})
export const repoSandbox = defineSandbox({
id: 'repo-agent',
provider: dockerSandbox({ image: 'node:22' }),
workspace: defineWorkspace({
source: githubRepo({ repo: 'owner/repo', ref: 'main' }),
// This example uses Claude Code. Install the CLI for your harness.
setup: [
'npm install -g @anthropic-ai/claude-code --include=optional && claude --version',
],
secrets,
skills: [
// A public skill repo.
gitSkill({ repo: 'owner/tanstack-skills' }),
// A private skill repo. The token is used only for the clone.
gitSkill({ repo: 'owner/private-skills', secret: secrets.GH }),
// A third-party MCP server, with a bearer token header.
mcpSkill('sentry', {
url: 'https://mcp.example.com',
headers: { Authorization: bearer(secrets.SENTRY) },
}),
// Any file you want in the workspace.
fileSkill({ path: '.agent-hints.md', content: '# Hints\nPrefer pnpm.' }),
],
plugins: ['@anthropic/plugin-foo'],
instructions: 'Always run `pnpm test` before proposing a change.',
}),
})Start a run with this sandbox and claudeCodeText. The agent can use the skills and the MCP server, and it reads your instructions from AGENTS.md.
To limit what the agent is allowed to do (which commands run, which ask, which are denied), use Policy, not instructions.
createSecrets keeps the real strings in a non-enumerable, symbol-keyed registry on the object it returns. Each property you read (secrets.GH) is a SecretRef token, not the string. So:
secrets is declared on the workspace. The harness adapters write secret-bearing MCP config again on every run, with fresh values. So a snapshot never serves a stale or rotated secret.
Pass a ref straight to any field that takes one. For gitSkill, pass secret. The token is resolved only when the repo is cloned.
In MCP headers, use a ref as is, or wrap it with bearer(ref) to get Bearer <value>:
import { bearer, createSecrets, mcpSkill } from '@tanstack/ai-sandbox'
const secrets = createSecrets({
GH: process.env.GH_TOKEN ?? '',
SENTRY: process.env.SENTRY_TOKEN ?? '',
})
export const sentry = mcpSkill('sentry', {
url: 'https://mcp.example.com',
headers: {
Authorization: bearer(secrets.SENTRY), // "Bearer <value>"
'X-Token': secrets.GH, // the raw token value
},
})| Builder | What it provisions |
|---|---|
| gitSkill | A skill repo cloned into the workspace, with optional secret and clone path. |
| mcpSkill | A third-party MCP server, with url and headers. |
| fileSkill | Any file written into the workspace. |
| agentSkill | A public skill by bare name. Every bundled harness warns and skips it, so use gitSkill instead. |
These MCP servers are third-party services. To give the agent your own app's tools (a chat() server tool whose execute() runs on your host), use Tools.
into sets where the repo is cloned. It is an absolute path inside the sandbox. The default is .tanstack-skills/<repo-basename> under the workspace root. Bootstrap creates the parent directory before the clone.
A path that starts with /workspace uses the portable workspace root, and the provider maps it to its real workdir. Keep /workspace/... unless you must pin a provider-specific path.
import {
createSecrets,
defineWorkspace,
gitSkill,
githubRepo,
} from '@tanstack/ai-sandbox'
const secrets = createSecrets({ GH: process.env.GH_TOKEN ?? '' })
export const workspace = defineWorkspace({
source: githubRepo({ repo: 'owner/repo' }),
secrets,
skills: [
gitSkill({
repo: 'owner/private-skills',
secret: secrets.GH,
into: '/workspace/.skills/private',
}),
],
})| Harness | MCP servers | gitSkill repos | plugins |
|---|---|---|---|
| Claude Code | .mcp.json | .claude/skills/<name> | claude plugin install <name> --scope project |
| Codex | .codex/config.toml | .codex/skills/<name> | Warns and skips. |
| Grok Build | .grok/config.toml | .grok/skills/<name> | Warns and skips. |
| OpenCode | opencode.json | Warns and skips (cloned, not linked). | Warns and skips. |
For a linked skill repo, the projector looks for folders that hold a SKILL.md:
Links are symlinks. If ln -s fails, the projector copies the folder. You never write the links yourself.
Bootstrap writes instructions to AGENTS.md at the workspace root. It then creates CLAUDE.md and GEMINI.md as symlinks to it. If the sandbox cannot make symlinks, they are copies. Either way, every supported CLI reads the instructions with no extra config.