Strands Shell Quickstart
This guide gets you from zero to a running sandboxed command. You’ll install Strands Shell, create a shell with a single bound directory, and run a command against it. By the end you’ll have a working sandbox you can hand to an agent.
Strands Shell offers three surfaces. Pick the one that matches how you build:
- MCP server works with any agent framework that speaks the Model Context Protocol. Nothing to write in your own language.
- Python API embeds the shell directly in a Python program.
- Node.js API embeds the shell directly in a JavaScript or TypeScript program.
MCP server
Section titled “MCP server”The fastest way to give an existing agent a sandboxed shell is the built-in MCP server. It doesn’t need any code, just point your MCP client at the strands-shell command and the agent gets four tools (shell, read_file, write_file, list_dir).
Add this to your MCP client configuration:
{ "mcpServers": { "shell": { "command": "uvx", "args": ["strands-shell", "--mcp"] } }}This starts a bare in-memory sandbox without access to files, network, or credentials. To grant access, write a TOML config file and pass it before the --mcp flag:
{ "mcpServers": { "shell": { "command": "uvx", "args": ["strands-shell", "--config", "sandbox.toml", "--mcp"] } }}The MCP Server page documents the four tools, their parameters, and how to expose other MCP servers as Lua modules inside the shell.
Python
Section titled “Python”Install the shell, using Python 3.10 or later:
pip install strands-shellCreate a shell, bind a directory into it, and run a command. Only bound directories are visible inside the sandbox, so /my/project on your host appears as /workspace and the agent can’t see anything else.
import strands_shell
shell = strands_shell.Shell( binds=[strands_shell.Bind("/my/project", "/workspace", mode="copy")],)
result = shell.run("grep -rn TODO /workspace")print(result.stdout)run returns an Output with three fields: stdout, stderr, and status (the exit code). It doesn’t raise when a command fails, so check status to branch on success.
result = shell.run("test -f /workspace/pyproject.toml")if result.status == 0: print("found pyproject.toml")State carries across calls. Export a variable or change directory in one run, and the next run sees it.
shell.run("cd /workspace && export PROJECT=demo")result = shell.run("echo $PROJECT in $(pwd)")print(result.stdout)
# Typical output:# demo in /workspaceReading and writing files
Section titled “Reading and writing files”You can touch the sandbox filesystem directly, without going through a shell command. This is the path to use when your own code needs to seed an input file or collect a result.
shell.write_file("/workspace/note.txt", b"hello")data = shell.read_file("/workspace/note.txt")print(data.decode())
entries = shell.list_files("/workspace")for entry in entries: print(entry.name)read_file and write_file work in bytes. A missing path raises strands_shell.FileNotFoundError, which also subclasses the built-in FileNotFoundError, so existing error-handling code catches it without a translation shim.
Node.js
Section titled “Node.js”Install the shell using Node.js 18 or later.
npm install @strands-agents/shellCreate a shell with Shell.create, which returns a promise. Then bind a directory, then run a command.
import { Shell } from '@strands-agents/shell'
const shell = await Shell.create({ binds: [{ source: '/my/project', destination: '/workspace', mode: 'copy' }],})
const result = await shell.run('grep -rn TODO /workspace')console.log(result.stdout)Every method returns a promise. run resolves to an Output with stdout, stderr, and status, and it resolves even when the command exits non-zero, so check status rather than catching an error.
const result = await shell.run('test -f /workspace/package.json')if (result.status === 0) { console.log('found package.json')}Reading and writing files
Section titled “Reading and writing files”File operations take and return Uint8Array. A missing path rejects with NotFoundError, which carries a .code of 'ENOENT' and the offending .path.
const enc = new TextEncoder()const dec = new TextDecoder()
await shell.writeFile('/workspace/note.txt', enc.encode('hello'))const data = await shell.readFile('/workspace/note.txt')console.log(dec.decode(data))
const entries = await shell.listFiles('/workspace')for (const entry of entries) { console.log(entry.name)}What you built
Section titled “What you built”You created a sandbox with exactly one directory visible to it and ran a command that couldn’t reach anything else: not your home directory, not your credentials, not the network. That’s the whole model. You add capability by adding binds, credentials, and allowed URLs, and the agent gets nothing you didn’t grant.
Next steps
Section titled “Next steps”- Configuration: the difference between
copyanddirectbinds, credential injection, the network allowlist, and the TOML format. - Commands: which commands and flags are supported, and where they diverge from GNU coreutils.
- Security Model: what the sandbox guarantees, what it doesn’t, and when to add OS-level isolation.