mcpField
A
ag2-shell-tool

ag2-shell-tool

github.com/ag2aiAgent workflows1264 installsUpdated 13w ago
Repository

About ag2-shell-tool

A guide to AG2 shell tools for agents that need local or provider-managed command execution. It distinguishes provider-agnostic SandboxShellTool from the OpenAI Responses-only ShellTool, with workspace and command-policy controls.

Key features

  1. 01Run commands through local subprocesses
  2. 02Use temporary or persistent workspaces
  3. 03Restrict commands with allow and block rules
  4. 04Enable read-only command access
  5. 05Configure timeouts and output limits
  6. 06Use OpenAI Responses managed containers

Use cases

  1. 01Let an agent inspect or edit a project directory
  2. 02Keep files available across agent turns
  3. 03Limit shell access to approved commands
  4. 04Run shell commands in an OpenAI-managed container

MCP support

This project describes shell tools for AG2 agents and does not state an MCP integration.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/ag2ai/ag2-skills --skill ag2-shell-tool

After installing, type/ag2-shell-toolin Claude Code to confirm it loaded.

Details

From the project README

Shell tools

When to use

Two distinct tools, both named "shell" — pick deliberately:

NeedUseWhy
Works with any model provider; full control over what runs and whereSandboxShellToolClient-side subprocess (via LocalEnvironment). You own the sandbox.
Provider-managed sandbox (container, network policy) on OpenAI ResponsesShellToolServer-side execution. No local subprocess.

SandboxShellTool is the workhorse. Reach for it unless you specifically need provider-managed isolation and you're on OpenAI Responses.

60-second recipe — SandboxShellTool

from ag2 import Agent
from ag2.config import AnthropicConfig
from ag2.tools import SandboxShellTool

agent = Agent(
    "coder",
    "You write and run Python code.",
    config=AnthropicConfig(model="claude-sonnet-4-6"),
    tools=[SandboxShellTool()],
)

reply = await agent.ask("Write a hello world script and run it.")
print(await reply.content())

SandboxShellTool is provider-agnostic — swap AnthropicConfig for OpenAIConfig(model="gpt-4.1"), GeminiConfig(model="gemini-2.5-pro"), etc. Make sure you've installed the matching ag2[<provider>] extra and set the matching env var (see ag2-quickstart → Prerequisites).

With no arguments, SandboxShellTool defaults to a LocalEnvironment() that creates a temporary working directory (prefixed ag2_sandbox_) and cleans it up when the process exits. Pass a LocalEnvironment with a path to use a specific directory:

from pathlib import Path
from ag2.tools import LocalEnvironment, SandboxShellTool

SandboxShellTool(LocalEnvironment("/tmp/my_project"))
SandboxShellTool(LocalEnvironment(Path("/tmp/my_project")))

When a path is given, the directory is created if it does not exist and is not deleted on exit. Inspect the resolved working directory via tool.workdir.

Sandboxing (LocalEnvironment + tool-level filters)

For anything beyond a throwaway demo, lock down what the agent can do. The environment (LocalEnvironment) decides where commands run and carries backend config (path, timeout, max_output, env_vars); the tool (SandboxShellTool) decides the agent-facing policy (allowed / blocked / ignore / readonly). Filtering is applied in this order on every call:

  1. allowed — if set, the command must match at least one prefix. In this restricted mode, shell operators (>, >>, |, ;, &&, ||, `, $() are also rejected.
  2. blocked — if set, the command must not match any prefix. Best-effort only (head-command prefix match; chaining can bypass it).
  3. ignore — literal path tokens in the command are checked against gitignore-style patterns; matches return "Access denied: <path>".
  4. Execute via the environment's subprocess.
from ag2.tools import LocalEnvironment, SandboxShellTool

sh = SandboxShellTool(
    LocalEnvironment(
        path="/tmp/my_project",
        timeout=30,
        max_output=50_000,
    ),
    allowed=["python", "uv run", "git"],
    blocked=["rm -rf", "curl", "wget"],
    ignore=["**/.env", "*.key", "secrets/**"],
)
Read-only mode

For inspection-only access (cat, head, tail, ls, grep, find, git log, git diff, git status, …):

from ag2.tools import LocalEnvironment, SandboxShellTool

sh = SandboxShellTool(LocalEnvironment(path="/my/codebase"), readonly=True)

Pass an explicit allowed=[...] to override the built-in read-only allowlist.

Parameter reference

LocalEnvironment (the environment — where and how commands run):

ParameterDefaultDescription
pathNoneWorking dir. None → temp dir (prefix ag2_sandbox_), deleted on exit
cleanupNoneNone → auto (True when path=None, False otherwise). Deletes path on close
timeout60Per-command timeout in seconds (returns "Command timed out after Ns" with exit code 124)
max_output100_000Max characters returned (truncated output is suffixed [truncated: …])
env_varsNoneExtra env vars merged into each command

SandboxShellTool (the tool — agent-facing command policy):

ParameterDefaultDescription
environmentNoneThe backend. None → LocalEnvironment() (local subprocess, temp dir)
allowedNoneWhitelist of command prefixes. None → all commands allowed
blockedNoneBlacklist of command prefixes (best-effort, not a security boundary)
ignoreNoneGitignore-style path patterns; matches block the command
readonlyFalseWhen True and allowed unset, restricts to a built-in read-only list

Stateful multi-turn workspaces

Files persist in workdir across ask() calls, so the agent can build on prior work:

from ag2.tools import LocalEnvironment, SandboxShellTool

sh = SandboxShellTool(LocalEnvironment(path="/tmp/counter_demo"))
agent = Agent("coder", "You manage files.", config=config, tools=[sh])

reply1 = await agent.ask("Create counter.txt with value 0")
reply2 = await reply1.ask("Increment the counter by 1")
reply3 = await reply2.ask("Read the counter and tell me the value")

Provider-native ShellTool (OpenAI Responses only)

ShellTool is a provider-executed capability flag. Only the OpenAI Responses API runs shell server-side. Anthropic's bash tool is client-side and is rejected with UnsupportedToolError — use SandboxShellTool there. Gemini is also unsupported.

from ag2.config import OpenAIResponsesConfig
from ag2.tools import ShellTool

agent = Agent("devops", config=OpenAIResponsesConfig(model="gpt-4.1"), tools=[ShellTool()])

OpenAI lets you configure the execution environment:

from ag2.config import OpenAIResponsesConfig
from ag2.tools import ContainerAutoEnvironment, NetworkPolicy, ShellTool

agent = Agent(
    "devops",
    config=OpenAIResponsesConfig(model="gpt-4.1"),
    tools=[
        ShellTool(
            environment=ContainerAutoEnvironment(
                network_policy=NetworkPolicy(allowed_domains=["pypi.org"]),
            ),
        ),
    ],
)

Environment options (OpenAI-only):

EnvironmentDescription
ContainerAutoEnvironmentProvider-managed container with optional NetworkPolicy
ContainerReferenceEnvironmentReference an existing container by ID

SandboxShellTool vs ShellTool

SandboxShellToolShellTool
ExecutionClient-side subprocessProvider-side container
Provider supportAny providerOpenAI Responses only
Environment controlFull (allowed, blocked, ignore, readonly, …)Limited (provider-dependent)
Local FS accessYes (you choose what's exposed)No
Network controlVia blocked / allowed patternsOpenAI: NetworkPolicy
Importfrom ag2.tools import SandboxShellTool, LocalEnvironmentfrom ag2.tools import ShellTool

Going deeper

  • website/docs/user-guide/tools/local_shell.mdx — full SandboxShellTool / LocalEnvironment reference, command-filtering semantics.
  • website/docs/user-guide/tools/builtin_tools.mdx#shell — provider-native ShellTool setup and environment configs.
  • For human-approval gating before each shell call, layer approval_required() middleware (see ag2-hitl).

Common pitfalls

  • Forgetting sandboxing in production — SandboxShellTool() with no filters runs anything anywhere with a 60s timeout. Set allowed, blocked, or readonly for any non-trivial use.
  • ignore only checks literal paths in the command string — variable substitution, command substitution (`cat secrets.key`), and dynamic glob expansion are not inspected. Layer in blocked=["cat", "less"] if you also want to block readers.
  • blocked is best-effort, not a security boundary — it only matches the head command's prefix, so chaining (echo x; rm -rf ~) bypasses blocked=["rm"]. Use allowed / readonly or an isolated container backend for real isolation.
  • Trying to use ShellTool on Anthropic or Gemini — unsupported, will raise UnsupportedToolError. Use SandboxShellTool instead.
  • Using a hardcoded path that another process is also touching — multiple agents sharing /tmp/my_project will race. Use tempfile.mkdtemp(prefix="...") for parallel runs.
  • Expecting ShellTool to access local files — it doesn't; it runs in the provider's container. Use SandboxShellTool for anything on your filesystem.
  • Trusting the LLM with shell access — even sandboxed, write prompts that scope what's allowed and consider pairing with approval_required() for destructive operations.

Comments

Markdown supported