Skip to content

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.

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.

Install the shell, using Python 3.10 or later:

Terminal window
pip install strands-shell

Create 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 /workspace

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.

Install the shell using Node.js 18 or later.

Terminal window
npm install @strands-agents/shell

Create 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')
}

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)
}

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.

  • Configuration: the difference between copy and direct binds, 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.