Parselbox
O Parselbox é uma camada de execução local e de código aberto que transforma servidores MCP, APIs, shells e funções do host em objetos Python nativos. Agentes descobrem capacidades sob demanda e as compõem com código, arquivos, pacotes e tarefas em segundo plano em um único workspace com estado.
Documentação

Código. Sistema de arquivos. Contexto. Ferramentas.
E se os agentes tivessem uma única ferramenta para governar todas elas?
Parselbox é um runtime Python incorporável onde agentes de IA chamam ferramentas como código — servidores MCP, APIs e shells tornam-se objetos Python nativos. Workspace com suporte a disco, pacotes e rede integrados; uma camada de execução de processo único alimentada por Deno e Pyodide.
https://github.com/user-attachments/assets/d4e43d16-3aa3-4e29-83c7-a1d3885b8045
[!TIP] Adicione o Parselbox MCP junto às configurações existentes de servidores MCP. Os agentes obtêm instantaneamente um runtime Python, ferramentas MCP como código, suporte a skills e um workspace com suporte a disco.
Por que Parselbox?
Parselbox empacota chamadas programáticas de ferramentas como uma camada de execução local e de código aberto. Adicione-o a uma configuração MCP existente com uvx, ou incorpore a API Python a qualquer stack de agentes.
Além da execução de código, Parselbox fornece um workspace com estado e suporte a disco, com pacotes, rede controlada, tarefas em segundo plano e descoberta progressiva.
Deno + Pyodide fornecem um runtime de processo único com permissões explícitas de sistema de arquivos e rede — sem necessidade de contêineres ou microVMs. Também permite interoperabilidade JavaScript/npm, integração WASM/WASI e UI generativa por meio de MCP Apps.
Por que uma camada de execução?
A maioria dos stacks de agentes expõe cada capacidade como uma ferramenta separada. À medida que as integrações crescem, os esquemas de ferramentas consomem mais contexto. O modelo também deve carregar resultados intermediários, transformar dados e rotear valores entre chamadas.
Uma camada de execução move a orquestração para fora da janela de contexto e para dentro do código. As capacidades são descobertas sob demanda e então compostas com fluxo de controle completo. Uma única execução pode coordenar múltiplas ferramentas enquanto variáveis, estado e arquivos permanecem dentro do runtime.
O resultado é menos inchaço de contexto, menos idas e voltas entre modelo e ferramenta, e fluxos de trabalho complexos expressos como código em vez de cadeias de chamadas individuais.
Recursos
🔒 Isolamento Seguro
Sem contêineres, sem VMs — apenas um único processo leve Deno + Pyodide (~160 MB). Permissões Deno, limites de memória, timeouts, listas de permissão de rede. Cache de snapshots e recuperação de falhas.
🛠️ Ferramentas como Código
Servidores MCP, REST + OpenAPI, GraphQL, shell, funções e classes — todos objetos Python nativos. Com estado entre chamadas. Conversão automática com Pydantic. Credenciais permanecem no host.
🐍 Runtime Poliglota
CPython completo com interoperabilidade js() — use pacotes JS como Python nativo. require() para npm, TypeScript local e módulos .wasm. bash() virtual para shell. Instalação automática de pacotes no import.
📦 Ferramentas WASM
require() qualquer .wasm — exportações de bibliotecas tornam-se métodos Python, programas WASI tornam-se comandos chamáveis; coloque um em bin/ para executá-lo também a partir de bash(). Em processo, herda os mounts e permissões do sandbox, não instala nada no host.
⚡ Tarefas em Segundo Plano
Adicione .task() a qualquer chamada — fan-out paralelo com asyncio.gather, verifique o progresso, acompanhe logs, conduza sessões interativas com send(), aguarde depois.
📁 Integração com Sistema de Arquivos
Workspace com suporte a disco — mounts do host (ro/rw), arquivos de entrada em /files/, saídas persistidas em diretórios reais. Arquivos novos e modificados são detectados e retornados por chamada.
🔍 Divulgação Progressiva
help(), search(), inspect(), preview() — os agentes descobrem apenas o que precisam, quando precisam.
🎨 UI Generativa
display() renderiza HTML inline no chat (MCP Apps), com Tailwind + daisyUI injetados. Ou sirva um aplicativo completo — servidor HTTP integrado com arquivos estáticos, recarga ao vivo, upload de arquivos e rotas @api que compõem entre ferramentas.
Conteúdo
- Início Rápido
- Guia do Usuário
- Referência de Configuração
- Arquitetura
- Segurança
- Trabalhos Relacionados
Início Rápido
Parselbox usa Deno para o runtime de sandbox seguro.
1. Instale o Deno
# macOS / Linux
curl -fsSL https://deno.land/install.sh | sh
# Windows (PowerShell)
irm https://deno.land/install.ps1 | iex
2. Instale o Parselbox
pip install parselbox
API Parselbox
Conecte qualquer ferramenta ao sandbox — servidores MCP, REST/GraphQL, shells, objetos do host — e o agente as chama como Python nativo, compondo-as com fluxo de controle real sobre um workspace com suporte a disco e ambos os ecossistemas de pacotes Python e npm.
Exemplo:
import asyncio
import os
from textwrap import dedent
from parselbox import Parselbox
from parselbox.bridge import HTTPBridge, ShellBridge
class Analytics:
def summarize(self, repos: list) -> dict:
"""Aggregate repo stats."""
stars = [r["stars"] for r in repos]
return {"count": len(repos), "avg_stars": round(sum(stars) / len(stars))}
config = {"mcpServers": {"playwright": {"command": "npx", "args": ["@playwright/mcp@latest"]}}}
async def main():
async with Parselbox(
mcp=config,
context={
"analytics": Analytics(),
"github": HTTPBridge(base_url="https://api.github.com", token=os.environ["GITHUB_TOKEN"]),
"sh": ShellBridge("bash"),
},
network=True,
allow_runtime_packages=True,
packages=["numpy", "npm:lodash"],
output_dir="./workspace",
) as sbx:
# Discover available tools
await sbx.execute_code("sbx.search('navigate|get')")
# Scrape Hacker News for GitHub links in a real browser
await sbx.execute_code(dedent("""
import re
playwright.browser_navigate(url="https://news.ycombinator.com")
text = playwright.browser_snapshot()
repos = re.findall(r'github\\.com/([\\w.-]+/[\\w.-]+)', text)[:5]
"""))
# Fetch star counts in parallel, then summarize via the context bridge
await sbx.execute_code(dedent("""
import asyncio
results = await asyncio.gather(*[github.get.task(f"/repos/{r}") for r in repos])
repo_data = [{"name": r["data"]["name"], "stars": r["data"]["stargazers_count"]}
for r in results if r.get("ok")]
analytics.summarize(repo_data)
"""))
# Chart it — matplotlib auto-installs on import
result = await sbx.execute_code(dedent("""
import matplotlib.pyplot as plt
plt.barh([r["name"] for r in repo_data], [r["stars"] for r in repo_data])
plt.savefig("chart.png")
"""))
print(result.files) # ['chart.png']
image = sbx.read_file("chart.png")
# every result carries .output, .files, .stdout, .stderr, .error
# Serve the whole sandbox as an MCP server
await sbx.run_mcp()
asyncio.run(main())
Parselbox MCP
A CLI do Parselbox executa um servidor MCP autônomo — todas as opções do sandbox estão disponíveis como flags.
STDIO
[!TIP] O truque do "loopback":
- Adicione o Parselbox MCP junto aos seus servidores MCP existentes.
- Aponte
--mcppara o mesmo arquivo de configuração.- Na inicialização, o Parselbox conecta-se aos outros servidores, expõe suas ferramentas dentro do sandbox e inicia seu próprio servidor MCP.
Não se preocupe — o Parselbox detecta e evita conectar-se a si mesmo. Sem loops infinitos de destruição.
Exemplo:
{
"mcpServers": {
"github": {},
"linear": {},
"parselbox": {
"command": "uvx",
"args": ["parselbox", "--mcp", "/absolute/path/to/mcp.json"]
}
}
}
HTTP
uvx parselbox --mcp mcp.json --transport http --port 9000
{
"mcpServers": {
"parselbox": {
"type": "http",
"url": "http://localhost:9000/mcp"
}
}
}
Exemplo Completo
uvx parselbox \
--mcp ./mcp.json \
--transport http \
--host 0.0.0.0 \
--port 8080 \
--file hello.txt \
--mount ./datasets:/data:rw \
--output-dir ./outputs \
--packages pandas,matplotlib \
--package-dir ./cache \
--allow-runtime-packages \
--network \
--serve 3000 \
--memory 2048 \
--timeout 60 \
--env MY_API_KEY=...
Agentes Parselbox
import asyncio
from parselbox import Parselbox
from agents import Agent, Runner, function_tool
sandbox = Parselbox(
mcp={"mcpServers": {"playwright": {"command": "npx", "args": ["@playwright/mcp@latest"]}}},
output_dir="./outputs",
allow_runtime_packages=True,
)
agent = Agent(
name="Research Assistant",
model="gpt-5.5",
instructions=f"You are a world-class research assistant.\n\n{sandbox.get_prompt()}",
tools=[function_tool(sandbox.get_tool())],
)
async def main():
async with sandbox:
result = await Runner.run(
agent,
"Scrape Wikipedia's 'List of highest-grossing films' with the Playwright MCP. "
"Plot a bar chart of the top 10 and save it as ./plot.png",
max_turns=30,
)
print(result.final_output)
asyncio.run(main())
Guia do Usuário
1. Ferramentas como Código
A ponte de contexto expõe objetos Python do host dentro do sandbox:
context— funções e namespaces como ferramentas chamáveis. A execução pausa, roda no host e retorna o resultado.globals— valores estáticos (strings, números, dicts) copiados para o sandbox.mcp— configuração do servidor MCP (dict ou caminho). Aparece como namespaces chamáveis dentro do sandbox.
Classes simples são auto-encapsuladas — cada método público torna-se uma ferramenta chamável; métodos que começam com _ permanecem privados:
from parselbox import Parselbox
class Calculator:
def add(self, a: float, b: float) -> float:
"""Add two numbers."""
return a + b
async with Parselbox(context={"calc": Calculator()}) as sbx:
await sbx.execute_code("calc.add(a=10, b=20)")
Subclasse Bridge para namespaces aninhados (auto-rastreados); anote um parâmetro com um modelo Pydantic e dicts passados convertem-se automaticamente para ele:
from parselbox import Parselbox
from parselbox.bridge import Bridge
from pydantic import BaseModel
class Coordinate(BaseModel):
x: float
y: float
z: float = 0.0
class Sensors(Bridge):
def temperature(self) -> float:
"""Read temperature in celsius."""
return 23.5
class Robot(Bridge):
def __init__(self):
self.sensors = Sensors()
def move(self, to: Coordinate) -> dict:
"""Move robot to a position."""
return {"position": [to.x, to.y, to.z], "status": "reached"}
async with Parselbox(context={"robot": Robot()}) as sbx:
await sbx.execute_code("robot.move(to={'x': 1, 'y': 2})")
await sbx.execute_code("robot.sensors.temperature()")
Parselbox inclui pontes para REST, GraphQL e shell:
from parselbox import Parselbox
from parselbox.bridge import HTTPBridge, GraphQLBridge, ShellBridge
api = HTTPBridge(
spec="https://petstore3.swagger.io/api/v3/openapi.json",
base_url="https://petstore3.swagger.io/api/v3",
)
gql = GraphQLBridge("https://countries.trevorblades.com/graphql")
sh = ShellBridge("ssh -T user@host")
mcp = {"mcpServers": {"deepwiki": {"type": "http", "url": "https://mcp.deepwiki.com/mcp"}}}
async with Parselbox(context={"api": api, "gql": gql, "sh": sh}, mcp=mcp, network=True) as sbx:
await sbx.execute_code('api.search("GET /pet/*")')
await sbx.execute_code('api.get("/pet/1")')
await sbx.execute_code('gql.graphql(query="{ continents { name } }")')
await sbx.execute_code('gql.graphql(query="{ languages { code name } }")')
await sbx.execute_code('term = sh.shell.task()')
await sbx.execute_code('term.send("df -h")')
await sbx.execute_code("sbx.search('ask|read')")
await sbx.execute_code("deepwiki.read_wiki_structure(repoName='pyodide/pyodide')")
await sbx.execute_code("deepwiki.ask_question(question='What is Pyodide?', repoName='pyodide/pyodide')")
Executável: bridges.py
2. Tarefas em Segundo Plano
Cada chamada de contexto e MCP também tem uma forma .task() que roda no host sem bloquear o sandbox — para fan-out paralelo, trabalhos de longa duração e sessões interativas:
job = sh.exec.task(command="ffmpeg -i in.mp4 out.mp4") # returns a task immediately
job.status() # TaskStatus(state, elapsed, message, logfile)
job.tail(5) # last lines of the task's live log
job.send("q") # message a running interactive process
await job.wait(timeout=120) # block until done — or just `await job`
job.cancel()
# parallel fan-out
import asyncio
results = await asyncio.gather(*[api.get.task(f"/items/{i}") for i in range(5)])
Ferramentas MCP transmitem seu progresso e notificações de log para o arquivo de log da tarefa. Um método Bridge personalizado emite da mesma forma com self.log(), e lê o que o sandbox enfileirou via send() com self.recv():
from parselbox.bridge import Bridge
class Exporter(Bridge):
def run(self, rows: int) -> str:
for i in range(rows):
self.log(f"row {i}/{rows}") # appended to task.logfile → tail()
for msg in self.recv(): # messages queued by task.send()
self.log(f"got: {msg}")
return "done"
Sessões interativas — ShellBridge.shell() mantém o stdin aberto, então uma tarefa pode conduzir um processo ao vivo com send():
session = sh.shell.task() # a live shell — state persists within the session
session.send("x=21")
session.send("echo $((x * 2))")
import asyncio
await asyncio.sleep(1) # give it a beat
session.tail(1) # "42"
session.cancel()
Um primeiro comando opcional inicia qualquer REPL como a sessão — por exemplo, sh.shell.task("python3 -i").
Executável: tasks.py
3. Integração com Sistema de Arquivos
Parselbox roda no sistema de arquivos virtual do Pyodide, com o diretório de trabalho, arquivos de entrada, mounts e pacotes respaldados por diretórios reais do host — acesso controlado pelas permissões do Deno na inicialização.
| Método | Nível de Acesso | Descrição |
|---|---|---|
| files | Leitura / Escrita | Diretório temporário em /files/. Arquivos de entrada copiados aqui; uploads do servidor armazenados aqui. |
| mounts | Configurável | Mapeia diretórios do host para /mnt/{name}. Modo: ro (padrão) ou rw. |
| output_dir | Leitura / Escrita | Mapeia o diretório de trabalho para um diretório do host para persistir arquivos. Se não fornecido, usa um diretório temporário (apagado ao fechar). |
[!NOTE]
/workspaceé sempre respaldado por um diretório real do host —output_dir(persistente) ou um diretório temporário efêmero (apagado ao fechar) — permitindo streaming Deno,resolvePath()erequire()para módulos locais.- Atravesse a fronteira com
sandbox.read_file(path)(strpara texto,bytespara binário) esandbox.write_file(path, content); umoutput_dirpersistente também é legível diretamente.- Mounts com
target="skills"são relatados porsbx.info()e descobríveis viabash("ls /mnt/skills/").
Exemplo:
from parselbox import Parselbox, Mount
async with Parselbox(
files=["data.csv"], # Read/write at /files/data.csv
mounts=[
Mount("./datasets", "/data", "ro"), # Read-only at /mnt/data
Mount("./workspace", "/work", "rw"), # Read/write at /mnt/work
],
output_dir="./outputs" # Sandbox files persisted here
) as sandbox:
# Write a file into the sandbox from the host
sandbox.write_file("greeting.txt", "Hello from host!")
code = """
content = open('/files/data.csv').read() # input file
ref = open('/mnt/data/reference.json').read() # read-only mount
open('/mnt/work/processed.txt', 'w').write(content) # read/write mount
open('result.txt', 'w').write("Done!") # working dir -> output_dir
"""
result = await sandbox.execute_code(code)
# New / modified files are detected and returned
print(result.files) # ['result.txt', 'greeting.txt']
sandbox.read_file("result.txt")
Acesse os mesmos arquivos de um shell com bash():
bash("echo 'hello from bash' > note.txt && cat note.txt") # shell over the workspace
Executável: filesystem.py · bash.py
4. Pacotes e Rede
Pacotes
Pyodide suporta pacotes Python puros e muitos pacotes com extensões C, que devem ser pré-compilados para Pyodide — numpy, pandas e mais já incluídos.
from parselbox import Parselbox, Mount
# preload Python + npm packages on startup
Parselbox(packages=["numpy", "pandas", "npm:lodash"])
# local wheel — mount its dir so Deno can read the host path
Parselbox(packages=["file:///host/wheels/pkg.whl"],
mounts=[Mount("./wheels", "wheels", "ro")])
# remote wheel — needs network access
Parselbox(packages=["https://example.com/pkg.whl"], network=True)
# autoload as imports appear (only official domains when network=False)
Parselbox(allow_runtime_packages=True)
[!NOTE] Instalações de pacotes gravam direto no disco — um diretório temporário por padrão (apagado ao sair). Defina
package_dirpara persistir entre sessões, então o próximo boot é instantâneo sem re-download.
Rede
Após o carregamento inicial de pacotes, a rede é bloqueada por padrão. O acesso é configurado com os controles de permissão do Deno via --allow-net / --deny-net. Todo HTTP de código no sandbox (requests, httpx, fetch) passa pelo fetch() do Deno.
# Block everything (default)
Parselbox(network=False)
# Allow specific domains (Python API only)
Parselbox(network=["api.github.com:443"])
# Allow everything
Parselbox(network=True)
[!NOTE] A flag
--networkda CLI é apenas um alternador booleano. Listas de permissão de domínio estão disponíveis via API Python.
Proxy e Injeção de Credenciais
Pyodide não é uma fronteira de segurança — código no sandbox pode ler variáveis de ambiente via js('Deno.env.get("KEY")'), então nunca passe credenciais reais em env. Em vez disso, execute um proxy de injeção de credenciais no host e restrinja o sandbox a ele:
async with Parselbox(
network=["127.0.0.1:8900"], # sandbox can ONLY reach the proxy
env={
"OPENAI_BASE_URL": "http://127.0.0.1:8900/v1",
"OPENAI_API_KEY": "phantom-token", # harmless; the real key lives on the proxy
},
) as sbx:
await sbx.execute_code("import openai; openai.OpenAI().chat.completions.create(...)")
A maioria dos SDKs aceita uma substituição base_url. Para interceptação agnóstica de SDK, defina HTTP_PROXY/HTTPS_PROXY/DENO_CERT e roteie tudo por um proxy MITM — o fetch() do Deno os respeita no nível do processo.
Executável: basics.py
5. Interoperabilidade JavaScript
Parselbox roda Python dentro do motor V8 do Deno via Pyodide, então Python e JavaScript compartilham a mesma memória do processo — a interoperabilidade é perfeita.
js() — Executar JavaScript a partir de Python
# Basic — auto converts args and results
js("return data.map(x => x * 2)", data=[1, 2, 3]) # [2, 4, 6]
# Callbacks — Python functions auto-proxied, no create_proxy needed
js("return items.filter(fn)", items=[1,2,3,4,5], fn=lambda x, *_: x > 3) # [4, 5]
# Async + Web APIs (Intl, Crypto, URL, TextEncoder)
js("return crypto.randomUUID()")
Cada chamada js() roda em um escopo novo e sem estado. Chamáveis Python são auto-proxiados e limpos após a chamada. Binários também convertem — resultados Uint8Array/ArrayBuffer tornam-se bytes em Python, e argumentos bytes tornam-se Uint8Array.
require() — Importar Pacotes npm, Módulos Locais e WASM
# npm packages — returns proxy + auto-injects in js() scope (alias= to rename)
lodash = require("lodash")
lodash.chunk([1, 2, 3, 4], 2) # [[1, 2], [3, 4]]
# Callbacks work with require'd packages
lodash.sortBy(data, lambda x, *_: x["age"])
# Also available in js()
js("return lodash.invert({a: 1, b: 2})")
# Local TypeScript — compiled by Deno, hot-reloads; can import npm internally
require("./math_utils.ts").fibonacci(10)
# .wasm modules & WASI binaries load too — see WASM Tools
# Instances keep their methods — chain them
dayjs = require("dayjs")
dayjs("2026-06-15").add(30, "day").format("YYYY-MM-DD") # "2026-07-15"
# Class constructors auto-detect `new`
color = require("color")
color("red").darken(0.5).hex() # "#800000"
# Chains work with Python callbacks
lodash(data).filter(lambda x, *_: x["pay"] > 100).sortBy(lambda x, *_: -x["pay"]).value()
Streaming Deno (Arquivos Grandes)
Para arquivos grandes demais para caber na memória, use streams Deno via resolvePath():
js("""
const path = resolvePath("sample.txt");
const info = await Deno.stat(path);
return { size: info.size, isFile: info.isFile };
""")
Callbacks Python funcionam dentro de pipelines de streaming — Deno lê, JS analisa, Python classifica cada linha.
Escrevendo e Importando Módulos
# Python module — write it, import it
open("helpers.py", "w").write("def double(x): return x * 2")
from helpers import double
double(21) # 42
# TypeScript module — compiled by Deno
open("transform.ts", "w").write("export function upper(s: string) { return s.toUpperCase(); }")
require("./transform.ts").upper("hello") # "HELLO"
Você pode até compilar uma linguagem para WebAssembly no sandbox e então require() a saída.
bash() — Comandos Shell
Um bash puro em JavaScript (just-bash) sobre o mesmo workspace. Pipes e coreutils funcionam, e curl é respaldado por fetch. Cada chamada é isolada (cd/export não persistem); mudanças no sistema de arquivos persistem.
bash("echo hello > note.txt && cat note.txt | tr a-z A-Z") # "HELLO"
bash("grep -rn hello . | wc -l")
bash("curl -s https://api.github.com/zen") # network rules still apply
Executável: javascript.py · bash.py
6. Ferramentas WASM
O Pyodide só consegue carregar pacotes compilados para ele — portanto, pandoc, ruby ou shellcheck ficam fora do alcance, e não existe apt-get em um sandbox de processo único. A Parselbox preenche essa lacuna com WASI: qualquer programa compilado para WebAssembly vira uma ferramenta, sem instalação no host.
Uma capacidade ausente é apenas um arquivo.
Dois tipos de .wasm
require() inspeciona o módulo e escolhe o formato certo:
# Library module (no imports) — its exports become methods
require("./fib.wasm").fib(20) # 6765
# Command module (a WASI program) — becomes a callable command
pandoc = require("./pandoc.wasm")
r = pandoc(["-f", "markdown", "-t", "html5"], stdin="# Report")
r["stdout"].decode() # '<h1 id="report">Report</h1>'
Um comando retorna {"exit": int, "stdout": bytes, "stderr": str, "missing": [...]} — missing lista quaisquer syscalls que o binário solicitou e que não estão implementadas, então lacunas aparecem como dados, e não como uma falha.
[!IMPORTANT] Builds Emscripten não são builds WASI. Grande parte do "wasm" do npm (
sql.js,ffmpeg.wasm,tesseract.js) é compilada com Emscripten e precisa de sua própria cola JavaScript — importe-os como pacotes npm (require("sql.js")), não como arquivos.wasmpuros. Ambas as rotas funcionam;require()informa qual delas um binário precisa.
run(args=None, stdin="", env=None, preopens=None, argv0=None)
stdin—stroubytes;stdoutsempre retorna comobytes.preopens— concede diretórios convidados extras, ex.:preopens={"/usr": "vendor/usr"}para um binário que espera sua própria árvore.argv0— alguns binários despacham com base no nome do programa (lld virawasm-ldno estilo busybox).
Binários como comandos bash()
Um binário de comando WASI (um .wasm que exporta _start) no diretório bin/ de um mount vira um comando de shell, utilizável junto com os coreutils JavaScript do bash(). Os binários são descobertos por chamada, então uma ferramenta escrita no meio da sessão funciona imediatamente.
open("bin/pandoc.wasm", "wb").write(pandoc_bytes)
bash("pandoc -f markdown -t plain notes.md | head -3 | tr a-z A-Z")
# ^^ compiled pandoc ^^ just-bash builtins
Monte uma pasta bin/ somente leitura para enviar um conjunto fixo de ferramentas que o agente pode usar, mas não modificar — nada instalado no host — ou faça-o buscar um .wasm em bin/ em tempo de execução, o que funciona mesmo quando a rede do sandbox está restrita a um único host na allowlist.
Você pode até compilar um a partir do código-fonte no processo — busque um clang WASI + wasm-ld em bin/, compile C para .wasm e então require() o resultado. Sem toolchain no host, nada instalado.
[!NOTE]
- Auto-detectado como WASI
preview1ouwasi_unstable(preview0). Não suportado: sockets, sleeps reais, componentes preview2.- Módulos compilados são armazenados em cache por caminho (invalidados na recompilação), então um binário de 50MB compila uma vez por sessão.
Executável: pandoc.py — busca um binário WASI · compile_c.py — compila C → wasm no sandbox
7. Divulgação Progressiva
O kit de ferramentas sbx permite que agentes descubram capacidades sob demanda, em vez de carregar tudo no contexto de antemão. Disponível como sbx.* dentro do sandbox.
| Função | Descrição |
|---|---|
sbx.help() | Retorna um guia completo para usar o sandbox. |
sbx.info() | Obtém informações do ambiente do sandbox — contexto, pacotes, rede, mounts, serve, etc. |
sbx.search(pattern) | Busca ferramentas em todos os namespaces por nome, descrição ou parâmetro. |
sbx.inspect(tools) | Obtém schemas e documentação detalhados para ferramentas. |
sbx.preview(data) | Resume estruturas de dados grandes ou aninhadas — preserva chaves, trunca conteúdo. |
O sandbox também expõe um builtin help() para introspecção por objeto:
# Sandbox guide
help()
# Namespace tree view — shows all methods with hierarchy
help(robot)
# Remote namespace 'robot' — methods execute on the host and return results.
# Methods:
# ├── sensors
# │ └── temperature()
# └── move()
# Tool details — description, parameters, output schema
help(robot.move)
# {"description": "Move robot to position.", "parameters": {...}, "output": {...}}
# Works on local objects too
help(len)
Exemplo:
# Discover what's available
sbx.info()
# Search for tools across all namespaces
sbx.search("repo|query")
# Get tool signatures before calling
sbx.inspect(["github.search_repositories", "db.query", "robot.move"])
# Parallel execution with .task
import asyncio
results = await asyncio.gather(*[api.fetch.task(id=i) for i in ids])
# Inspect unknown response structure
sbx.preview(results)
Executável: toolkit.py
8. UI Generativa
Agentes podem apresentar resultados de duas formas: inline na conversa com display(), ou como um aplicativo web completo com serve.
Widgets inline — display()
Qualquer HTML que um agente passe para display() é renderizado como um widget abaixo do resultado, em hosts que suportam MCP Apps.
await sbx.execute_code("""
display("<h1>Q3 Revenue</h1><p class='text-lg'>Up <b>12%</b> to $4.1M</p>")
""")
Tailwind e daisyUI são injetados automaticamente, então marcação simples é estilizada sem etapa de build, e pbx.call("/api/route", body) dentro do HTML alcança handlers @api quando serve está ativo. display() também aceita um caminho para um arquivo HTML no workspace. Uma visualização por execução — a última chamada vence.
Ativado por padrão. run_mcp(ui=False) desativa, o que interrompe a propaganda de display() para o agente e remove o renderizador da ferramenta. O HTML renderizado está sempre em result.view independentemente:
result = await sbx.execute_code('display("<b>done</b>")')
result.view # full HTML document, or None if display() wasn't called
Aplicativos web — serve
A opção serve inicia um servidor HTTP Deno dentro do sandbox — agentes constroem aplicativos web completos em tempo real.
# SDK
sandbox = Parselbox(serve=3000)
# CLI
uvx parselbox --serve 3000
Arquivos estáticos: Quaisquer arquivos gravados no diretório de trabalho do Pyodide são servidos automaticamente:
open("index.html", "w").write("<h1>Hello World</h1>")
open("style.css", "w").write("h1 { color: blue; }")
Servidos em seus próprios caminhos, com / resolvendo para index.html; arquivos enviados e de entrada ficam sob /files/*.
Handlers de API: Defina endpoints usando decorators no estilo FastAPI:
@api.get("/items")
def list_items(params):
limit = int(params.get("limit", 10))
return items[:limit]
@api.post("/items")
def create_item(body):
return {"id": len(items) + 1, "name": body["name"]}
Rotas são prefixadas com /api/ automaticamente. Verbos: @api.get/post/put/patch/delete.
Handlers podem chamar ferramentas MCP, funções de contexto e qualquer código do sandbox:
@api.get("/dashboard")
async def dashboard(params):
import asyncio
sensors, orders = await asyncio.gather(
robot.sensors.temperature.task(),
store.get.task("/orders", params={"limit": 5}),
)
return {"temperature": sensors, "recent_orders": orders}
Endpoints integrados:
| Endpoint | Método | Descrição |
|---|---|---|
/_upload | POST | Upload de arquivo (dados de formulário multipart) |
/_live | GET | Stream SSE — navegadores conectados atualizam quando arquivos estáticos mudam (ativo por padrão) |
/_routes | GET | Lista handlers de API registrados |
curl -F "file=@photo.png" http://localhost:3000/_upload
# {"uploaded": [{"name": "photo.png", "path": "/files/photo.png", "size": 12345}]}
Executável: display.py · serve.py
9. Hooks do Sandbox
Hooks interceptam eventos do ciclo de vida do sandbox — registram execuções, aprovam chamadas de ferramentas, aplicam políticas. Passe-os via parâmetro hooks.
from parselbox import Parselbox, Callback, ExecutionResult
from parselbox.hooks import Hook
class AuditHook(Hook):
async def pre_execute(self, code: str):
print(f"Executing: {code[:80]}...")
async def post_execute(self, result: ExecutionResult):
print(f"Result: {result.output}")
async def pre_tool_call(self, callback: Callback):
if "drop" in str(callback.kwargs).lower():
raise PermissionError("DROP statements are blocked")
async def post_tool_call(self, callback: Callback, result):
print(f"Tool {callback.name} returned")
async with Parselbox(
context={"db": db},
hooks=[AuditHook()],
) as sbx:
await sbx.execute_code("db.query(sql='SELECT 1')")
ElicitHook — um hook integrado que usa elicitação MCP para aprovação humano-no-loop. Habilite via --elicit (CLI) ou run_mcp(elicit=True) (API). Só dispara se o cliente MCP anunciar capacidade de elicitação — caso contrário, é um no-op.
# CLI
uvx parselbox --mcp mcp.json --elicit
# API
await sandbox.run_mcp(elicit=True)
| Hook | Gatilho | Casos de uso |
|---|---|---|
pre_execute | Antes do código rodar | Registro, verificações de política, sanitização de código |
post_execute | Depois que o código completa | Trilhas de auditoria, validação de resultado |
pre_tool_call | Antes de uma chamada de contexto/MCP | Portões de aprovação, limite de taxa, bloqueio |
post_tool_call | Depois que uma chamada de contexto/MCP retorna | Registro, transformação de resultado |
Executável: hooks.py
Referência de Configuração
Parselbox tem as seguintes opções de configuração:
from parselbox import Parselbox, Mount
sandbox = Parselbox(
context=dict(db=db, notify=send_alert), # Proxied functions and namespaces
globals=dict(name="hi", threshold=0.5), # Static values copied into sandbox
files=["./input.txt"], # Read/write files at /files/
mounts=[
Mount("./datasets", "/data", "ro"), # Read-only mount
Mount("./workspace", "/work", "rw"), # Read/write mount
],
output_dir="./outputs", # Persist sandbox files
packages=["numpy", "npm:lodash"], # Install on startup (Python + npm)
package_dir="./cache", # Persist package cache across sessions
allow_runtime_packages=True, # Auto-install from imports (default: False)
network=True, # True, False, or ["domain:port", ...] (API only)
mcp="./mcp.json", # Connect MCP servers (path or dict)
serve=8080, # Enable web server on port
memory=2048, # WASM memory limit in MB (default: 2048)
timeout=60, # Execution timeout in seconds (default: 60, 0 disables)
hooks=[AuditHook()], # Lifecycle hooks
env={ # Custom env vars (available in Python os.environ)
"OPENAI_BASE_URL": "http://proxy/v1", # SDK base_url overrides for reverse proxy
"OPENAI_API_KEY": "phantom", # Phantom tokens (real keys on proxy)
"HTTP_PROXY": "http://proxy:8080", # Deno-level proxy (filtered from os.environ)
"DENO_CERT": "/path/to/ca.pem", # Custom CA for MITM proxy
},
)
Arquitetura
A Parselbox executa código de agente em um processo Deno com Pyodide (CPython em WebAssembly) — sem contêineres, sem VMs. O sandbox com permissões restritas trabalha em um workspace temporário isolado, sem rede e sem acesso ao host além dos mounts que você concede; o host detém as credenciais. Cada chamada de ferramenta é uma ida e volta entre eles:
1. exec HOST ──▶ SANDBOX your code runs, permission-jailed
2. callback HOST ◀── SANDBOX code calls a tool as native Python
3. result HOST ──▶ SANDBOX host runs it with the real credentials
Ferramentas parecem Python nativo dentro do sandbox, mas executam no host — então credenciais nunca entram no sandbox.
Segurança
A fronteira da Parselbox é o sistema de permissões do Deno — o sandbox começa com nada e recebe apenas o que você configura.
- Sistema de arquivos — workspace temporário isolado (apagado na saída); leitura/gravação apenas para caminhos que você passa (
files,mountscomoro/rw,output_dir). Gravações no cache de pacotes são bloqueadas após a inicialização, a menos queallow_runtime_packages=True. - Rede — desativada por padrão (revogada antes do seu código rodar). Opte com
network=True, uma allowlistnetwork=["host:port", ...], ouallow_runtime_packages=True(apenas domínios de pacotes). Para APIs autenticadas, coloque um proxy na frente — veja Proxy & Injeção de Credenciais. - Ferramentas compiladas (WASI) — sem sockets, então um binário não tem rede própria; ele vê apenas os mounts que você concede (
roaplicado pelo Deno), e um processo descontrolado é morto pelo timeout de execução. - Limites de recursos — memória WASM limitada por instância no nível V8 (padrão 2048 MB), heap JS limitado, timeout por execução (padrão 60s →
KeyboardInterrupt), reconexão automática se o processo Deno morrer. - Ponte de contexto — apenas os objetos que você passa são alcançáveis, e apenas seus métodos públicos; servidores MCP expõem seu conjunto completo de ferramentas.
Trabalhos Relacionados
- Execução de código com MCP (Anthropic)
- Code Mode (Cloudflare)
- smolagents (Hugging Face)
- Deno + Pyodide Sandbox (Simon Willison)