Skip to content

Strands Shell Quickstart

This guide gets you from zero to a running sandboxed command. You will install Strands Shell, create a shell with a single bound directory, and run a command against it. By the end you will 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 needs no code: 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: no host files, no network, no 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. You need Python 3.10 or later.

Terminal window
pip install strands-shell

Create a shell, bind a directory into it, and run a command. The bind is the grant: /my/project on your host becomes /workspace inside the sandbox, and nothing else on your filesystem is visible.

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 does not 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. You need Node.js 18 or later.

Terminal window
npm install @strands-agents/shell

Create a shell with Shell.create, which returns a promise. 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 could not reach anything else: not your home directory, not your credentials, not the network. That is the whole model. You add capability by adding binds, credentials, and allowed URLs, and the agent gets nothing you did not 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 does not, and when to add OS-level isolation.