关于 Parselbox
Parselbox 是面向 AI Agent 的可嵌入 Python 沙箱运行时,可将 MCP 服务、API 和 Shell 作为 Python 对象调用,并在磁盘工作区中组合执行。它基于 Deno 和 Pyodide,提供文件、网络权限控制及后台任务等能力。
主要功能
- 01将 MCP 工具及 API 封装为 Python 对象
- 02提供带权限控制的隔离运行时
- 03集成磁盘工作区、挂载和文件持久化
- 04支持后台任务与交互式会话
- 05支持 Python、npm、JavaScript 和 WASM
适用场景
- 01在一段 Python 代码中编排多个工具
- 02在受控工作区处理输入和输出文件
- 03将耗时工具调用放入后台任务
- 04让 Agent 按需发现并组合工具
MCP 相关
Parselbox 提供独立 MCP 服务,可通过 STDIO 或 HTTP 连接。配置其他 MCP 服务后,它会把相应工具带入沙箱,供 Agent 作为 Python 对象调用。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍

代码。文件系统。上下文。工具。
如果 Agent 有一个工具可以统御一切,会怎样?
Parselbox 是一个可嵌入的 Python 运行时,AI Agent 可以将工具作为代码调用——MCP 服务、API 和 shell 都会变成原生 Python 对象。内置磁盘支持的工作区、包和网络;由 Deno 和 Pyodide 驱动的单进程执行层。
https://github.com/user-attachments/assets/d4e43d16-3aa3-4e29-83c7-a1d3885b8045
提示
将 Parselbox MCP 与现有的 MCP 服务配置一起使用。Agent 可立即获得 Python 运行时、作为代码调用的 MCP 工具、技能支持和磁盘支持的工作区。
为什么选择 Parselbox?
Parselbox 将程序化工具调用打包为本地开源执行层。通过 uvx 将其添加到现有 MCP 配置中,或将 Python API 嵌入任何 Agent 技术栈。
除了代码执行,Parselbox 还提供有状态、磁盘支持的工作区,内含包、受控网络、后台任务和渐进式发现功能。
Deno + Pyodide 提供单进程运行时,并可明确控制文件系统和网络权限——无需容器或微型虚拟机。它还支持 JavaScript/npm 互操作、WASM/WASI 集成,以及通过 MCP Apps 实现的生成式 UI。
为什么需要执行层?
大多数 Agent 技术栈都将每项能力作为单独的工具提供。随着集成不断增加,工具架构会占用更多上下文。模型还必须保存中间结果、转换数据,并在调用之间传递值。
执行层将编排从上下文窗口转移到代码中。按需发现能力后,即可通过完整的控制流将其组合起来。单次执行便可协调多个工具,同时变量、状态和文件都保留在运行时中。
这样可以减少上下文膨胀和模型与工具之间的往返次数,并将复杂工作流表示为代码,而不是一连串独立调用。
功能
🔒 安全隔离
无需容器或虚拟机——只需一个轻量级的 Deno + Pyodide 进程(约 160 MB)。Deno 权限、内存上限、超时设置、网络允许列表。支持快照缓存和崩溃恢复。
🛠️ 将工具作为代码
MCP 服务、REST + OpenAPI、GraphQL、shell、函数和类——全部作为原生 Python 对象。有状态,可跨调用保留。自动转换为 Pydantic 类型。凭据保留在主机上。
🐍 多语言运行时
完整的 CPython,支持 js() 互操作——可将 JS 包作为原生 Python 使用。通过 require() 使用 npm、本地 TypeScript 和 .wasm 模块。虚拟 bash() 用于运行 shell。导入时自动安装包。
📦 WASM 工具
通过 require() 加载任意 .wasm 文件——库导出内容会变成 Python 方法,WASI 程序会变成可调用命令;将其放入 bin/,也可以通过 bash() 运行。进程内运行,继承沙箱的挂载点和权限,不会在主机上安装任何内容。
⚡ 后台任务
在任意调用后添加 .task()——使用 asyncio.gather 并行分发,检查进度、查看日志末尾、通过 send() 驱动交互式会话,并在之后等待结果。
📁 文件系统集成
由磁盘支持的工作区 — 宿主机挂载(ro/rw),输入文件位于 /files/,输出会持久保存到真实目录。每次调用都会检测并返回新建和修改的文件。
🔍 渐进式信息披露
help()、search()、inspect()、preview() — Agent 只在需要时发现所需内容。
🎨 生成式 UI
display() 会在聊天中内联渲染 HTML(MCP Apps),并注入 Tailwind + daisyUI。也可以提供完整应用 — 内置 HTTP 服务,支持静态文件、实时重载、文件上传,以及可跨工具组合的 @api 路由。
目录
快速入门
Parselbox 使用 Deno 作为安全的沙箱运行时。
1. 安装 Deno
# macOS / Linux
curl -fsSL https://deno.land/install.sh | sh
# Windows (PowerShell)
irm https://deno.land/install.ps1 | iex
2. 安装 Parselbox
pip install parselbox
Parselbox API
将任意工具接入沙箱 — MCP 服务、REST/GraphQL、shell、宿主对象 — 然后 Agent 就能像调用原生 Python 一样调用它们,在由磁盘支持的工作区以及 Python 和 npm 包生态中,通过真实控制流将它们组合起来。
示例:
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
Parselbox CLI 可运行独立的 MCP 服务 — 所有沙箱选项均可通过标志指定。
STDIO
提示
“回环”技巧:
- 将 Parselbox MCP 添加到现有 MCP 服务中。
- 将
--mcp指向同一个配置文件。- 启动时,Parselbox 会连接其他服务,在沙箱中公开其工具,并启动自己的 MCP 服务。
不必担心 — Parselbox 会检测并避免连接自身。不会陷入无休止的循环。
示例:
{
"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"
}
}
}
完整示例
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=...
Parselbox Agents
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())
用户指南
1. 代码即工具
上下文桥接器会在沙箱中公开宿主 Python 对象:
context— 函数和命名空间作为可调用工具。执行会暂停,由宿主执行并返回结果。globals— 静态值(字符串、数字、字典),会复制到沙箱中。mcp— MCP 服务配置(字典或路径)。在沙箱中显示为可调用的命名空间。
普通类会自动包装 — 每个公开方法都会成为可调用工具;以下划线 _ 开头的方法保持私有:
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)")
继承 Bridge 可创建嵌套命名空间(自动遍历);为参数添加 Pydantic 模型注解后,传入的字典会自动转换为该模型:
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 内置了用于 REST、GraphQL 和 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')")
可运行:bridges.py
2. 后台任务
每个 context 和 MCP 调用都有对应的 .task() 形式,可在宿主上运行而不阻塞沙箱 — 适用于并行扇出、长时间运行的作业和交互式会话:
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)])
MCP 工具会将进度和日志通知流式写入任务的日志文件。自定义 Bridge 方法也可通过 self.log() 发出相同内容,并通过 self.recv() 读取沙箱用 send() 排入队列的内容:
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"
交互式会话 — ShellBridge.shell() 会保持 stdin 打开,因此任务可以通过 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()
可选的首条命令会以会话形式启动任意 REPL — 例如 sh.shell.task("python3 -i")。
可运行:tasks.py
3. 文件系统集成
Parselbox 在 Pyodide 的虚拟文件系统上运行,工作目录、输入文件、挂载目录和软件包都由真实的宿主目录提供支持——访问权限由 Deno 在启动时通过权限控制进行限制。
| 方法 | 访问级别 | 说明 |
|---|---|---|
| files | 读 / 写 | /files/ 下的临时目录。输入文件会复制到此处;服务端上传的文件也存储在此处。 |
| mounts | 可配置 | 将宿主目录映射到 /mnt/{name}。模式:ro(默认)或 rw。 |
| output_dir | 读 / 写 | 将工作目录映射到宿主目录以持久保存文件。如果未提供,则默认为临时目录(关闭时清除)。 |
说明
/workspace始终由真实的宿主目录提供支持——output_dir(持久保存)或临时目录(关闭时清除)——因此可以使用 Deno 流、resolvePath()和require()加载本地模块。- 使用
sandbox.read_file(path)(文本返回str,二进制返回bytes)和sandbox.write_file(path, content)跨越边界;持久化的output_dir也可直接读取。target="skills"的挂载会通过sbx.info()报告,也可通过bash("ls /mnt/skills/")发现。
示例:
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")
也可以通过 bash() 在 shell 中访问相同的文件:
bash("echo 'hello from bash' > note.txt && cat note.txt") # shell over the workspace
可运行: filesystem.py · bash.py
4. 软件包与网络
软件包
Pyodide 支持纯 Python 软件包和许多 C 扩展软件包,这些软件包必须针对 Pyodide 预构建——numpy、pandas 等软件包已包含在内。
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)
说明
安装软件包时会直接写入磁盘——默认写入临时目录(退出时清除)。设置
package_dir可在会话之间保留软件包,这样下次启动时无需重新下载,可立即完成。
网络
初始软件包加载完成后,网络默认处于阻止状态。可通过 Deno 的权限控制使用 --allow-net / --deny-net 配置访问权限。来自沙箱代码的所有 HTTP 请求(requests、httpx、fetch)都会通过 Deno 的 fetch() 路由。
# Block everything (default)
Parselbox(network=False)
# Allow specific domains (Python API only)
Parselbox(network=["api.github.com:443"])
# Allow everything
Parselbox(network=True)
说明
CLI 的
--network标志仅为布尔开关。域名允许列表可通过 Python API 配置。
代理与凭据注入
Pyodide 不是安全边界——沙箱代码可以通过 js('Deno.env.get("KEY")') 读取环境变量,因此绝不要在 env 中传入真实凭据。应改为在宿主上运行注入凭据的代理,并将沙箱限制为仅访问该代理:
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(...)")
大多数 SDK 都支持覆盖 base_url。若要以与 SDK 无关的方式拦截请求,可改为设置 HTTP_PROXY/HTTPS_PROXY/DENO_CERT,并通过中间人代理路由所有流量——Deno 的 fetch() 会在进程级别遵循这些设置。
可运行: basics.py
5. JavaScript 互操作
Parselbox 通过 Pyodide 在 Deno 的 V8 引擎中运行 Python,因此 Python 和 JavaScript 共享同一进程内存——互操作非常顺畅。
js() — 从 Python 执行 JavaScript
# 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()")
每次调用 js() 都会在全新、无状态的作用域中运行。Python 可调用对象会自动代理,并在调用后清理。二进制数据也会自动转换——Uint8Array/ArrayBuffer 结果会转换为 Python bytes,bytes 参数则会转换为 Uint8Array。
require() — 导入 npm 软件包、本地模块和 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()
Deno 流处理(大文件)
对于过大而无法装入内存的文件,可通过 resolvePath() 使用 Deno 流:
js("""
const path = resolvePath("sample.txt");
const info = await Deno.stat(path);
return { size: info.size, isFile: info.isFile };
""")
流处理管道中也可以使用 Python 回调——由 Deno 读取、JS 解析,再由 Python 对每一行进行分类。
编写和导入模块
# 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"
甚至可以在沙箱中将某种语言编译为 WebAssembly,然后使用 require() 导入编译结果。
bash() — Shell 命令
基于纯 JavaScript 的 bash([just-bash](https://github.com/vercel-labs/just-bash),运行于同一工作区。管道和 coreutils 均可使用,curl 由 fetch 提供支持。每次调用彼此隔离(cd/export 不会持久生效);文件系统更改会保留。
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
可运行: javascript.py · bash.py
6. WASM 工具
Pyodide 只能加载专为它构建的包,因此无法使用 pandoc、ruby 或 shellcheck,而且单进程沙箱中没有 apt-get。Parselbox 通过 WASI 弥补这一缺口:任何编译为 WebAssembly 的程序都能成为工具,无需在宿主机上安装。
缺少的能力,只需一个文件即可补足。
两种 .wasm
require() 会检查模块并选择相应的形式:
# 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>'
命令会返回 {"exit": int, "stdout": bytes, "stderr": str, "missing": [...]}——missing 会列出二进制文件调用但尚未实现的系统调用,让缺失能力以数据形式显现,而不是导致崩溃。
重要
Emscripten 构建产物并非 WASI 构建产物。 npm 中许多“wasm”(
sql.js、ffmpeg.wasm、tesseract.js)都是使用 Emscripten 编译的,需要各自的 JavaScript 胶水代码——应将它们作为 npm 包导入(require("sql.js")),而非直接导入.wasm文件。两种方式都可用;require()会告诉你二进制文件需要哪种方式。
run(args=None, stdin="", env=None, preopens=None, argv0=None)
stdin—str或bytes;stdout始终以bytes返回。preopens— 授予额外的来宾目录,例如,对于需要自带目录树的二进制文件,可使用preopens={"/usr": "vendor/usr"}。argv0— 某些二进制文件会根据程序名称进行分派(lld 会像 busybox 一样变为wasm-ld)。
将二进制文件用作 bash() 命令
挂载目录的 bin/ 中的 WASI 命令二进制文件(导出 _start 的 .wasm)会成为 Shell 命令,可与 bash() 的 JavaScript coreutils 一同使用。每次调用时都会发现二进制文件,因此在会话中途写入的工具可以立即使用。
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
以只读方式挂载 bin/ 文件夹,即可提供固定工具集供 Agent 使用但无法修改——宿主机上无需安装任何内容。也可以让它在运行时将 .wasm 下载到 bin/,即使沙箱网络仅允许访问一个加入允许列表的主机,也能正常工作。
你甚至可以在进程内从源代码构建工具——将 WASI clang 和 wasm-ld 下载到 bin/,把 C 编译为 .wasm,然后对结果调用 require()。无需宿主机工具链,也无需安装任何内容。
说明
- 会自动检测为 WASI
preview1或wasi_unstable(preview0)。不支持:套接字、实际休眠、preview2 组件。- 编译后的模块会按路径缓存(重建时失效),因此 50MB 的二进制文件在每个会话中只需编译一次。
可运行: pandoc.py — 下载 WASI 二进制文件 · compile_c.py — 在沙箱中编译 C → wasm
7. 渐进式披露
sbx 工具包让 Agent 按需发现能力,而非一开始就将所有内容加载到上下文中。可在沙箱内通过 sbx.* 使用。
| 函数 | 说明 |
|---|---|
sbx.help() | 返回沙箱使用指南。 |
sbx.info() | 获取沙箱环境信息——包括上下文、包、网络、挂载、serve 等。 |
sbx.search(pattern) | 按名称、描述或参数搜索所有命名空间中的工具。 |
sbx.inspect(tools) | 获取工具的详细架构和文档。 |
sbx.preview(data) | 概述大型或嵌套的数据结构——保留键并截断内容。 |
沙箱还提供了 help() 内置函数,可用于查看对象的详细信息:
# 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)
示例:
# 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)
可运行: toolkit.py
8. 生成式 UI
Agent 可以通过两种方式呈现结果:使用 display() 内嵌在对话中,或使用 serve 构建完整的 Web 应用。
内联小组件 — display()
在支持 MCP Apps 的宿主环境中,Agent 传递给 display() 的任何 HTML 都会在结果下方渲染为小组件。
await sbx.execute_code("""
display("<h1>Q3 Revenue</h1><p class='text-lg'>Up <b>12%</b> to $4.1M</p>")
""")
Tailwind 和 daisyUI 会自动注入,因此无需构建步骤,普通标记也能应用样式;启用 serve 时,HTML 中的 pbx.call("/api/route", body) 会调用 @api 处理程序。display() 也接受工作区中的 HTML 文件路径。每次执行只显示一个视图——以最后一次调用为准。
默认启用。 run_mcp(ui=False) 会将其关闭,不再向 Agent 广告 display(),并从工具中移除渲染器。无论如何,渲染后的 HTML 始终位于 result.view 上:
result = await sbx.execute_code('display("<b>done</b>")')
result.view # full HTML document, or None if display() wasn't called
Web 应用 — serve
serve 选项会在沙箱内启动 Deno HTTP 服务器——Agent 可即时构建完整的 Web 应用。
# SDK
sandbox = Parselbox(serve=3000)
# CLI
uvx parselbox --serve 3000
静态文件: 写入 Pyodide 工作目录的所有文件都会自动提供服务:
open("index.html", "w").write("<h1>Hello World</h1>")
open("style.css", "w").write("h1 { color: blue; }")
文件会通过各自的路径提供服务,其中 / 解析为 index.html;上传和输入文件位于 /files/* 下。
API 处理程序: 使用 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"]}
路由会自动添加 /api/ 前缀。动词:@api.get/post/put/patch/delete。
处理程序可以调用 MCP 工具、上下文函数以及任何沙箱代码:
@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}
内置端点:
| 端点 | 方法 | 说明 |
|---|---|---|
/_upload | POST | 文件上传(multipart 表单数据) |
/_live | GET | SSE 流——静态文件变更时,已连接的浏览器会刷新(默认启用) |
/_routes | GET | 列出已注册的 API 处理程序 |
curl -F "[email protected]" http://localhost:3000/_upload
# {"uploaded": [{"name": "photo.png", "path": "/files/photo.png", "size": 12345}]}
可运行示例: display.py · serve.py
9. 沙箱钩子
钩子会拦截沙箱生命周期事件——记录执行情况、审批工具调用、实施策略。通过 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 — 内置钩子,使用 MCP elicitation 实现人工参与审批。通过 --elicit(CLI)或 run_mcp(elicit=True)(API)启用。只有 MCP 客户端声明支持 elicitation 时才会触发,否则不执行任何操作。
# CLI
uvx parselbox --mcp mcp.json --elicit
# API
await sandbox.run_mcp(elicit=True)
| 钩子 | 触发时机 | 用途 |
|---|---|---|
pre_execute | 代码运行前 | 日志记录、策略检查、代码清理 |
post_execute | 代码完成后 | 审计跟踪、结果验证 |
pre_tool_call | 上下文/MCP 调用前 | 审批门控、速率限制、阻止调用 |
post_tool_call | 上下文/MCP 调用返回后 | 日志记录、结果转换 |
可运行示例: hooks.py
配置参考
Parselbox 的配置选项如下:
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
},
)
架构
Parselbox 在单个 Deno 进程中通过 Pyodide(WebAssembly 中的 CPython)运行 Agent 代码——无需容器或虚拟机。受权限限制的沙箱在隔离的临时工作区中运行,没有网络,也无法访问宿主机(已授予挂载权限的内容除外);宿主机保存凭据。每次工具调用都是两者之间的一次往返:
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
在沙箱中,工具看起来像原生 Python,但实际在宿主机上执行——因此凭据绝不会进入沙箱。
安全性
Parselbox 的边界是 Deno 的权限系统——沙箱启动时不授予任何权限,只获取你配置的权限。
- 文件系统 — 隔离的临时工作区(退出时清空);仅对你传入的路径进行读写(
files、mounts,设为ro/rw,以及output_dir)。启动后,除非设置allow_runtime_packages=True,否则会锁定对包缓存的写入。 - 网络 — 默认关闭(在你的代码运行前撤销)。你可以通过
network=True、允许列表network=["host:port", ...]或allow_runtime_packages=True(仅限包域名)选择启用。对于需要身份验证的 API,请通过代理转发——参见代理与凭据注入。 - 编译型工具(WASI) — 不支持套接字,因此二进制程序无法自行联网;它只能访问你授权的挂载(由 Deno 强制执行
ro),超时后会终止失控的进程。 - 资源限制 — 在 V8 层面为每个实例限制 WASM 内存(默认 2048 MB),限制 JS 堆,设置单次执行超时时间(默认 60s →
KeyboardInterrupt),并在 Deno 进程退出时自动重新连接。 - 上下文桥接 — 只能访问你传入的对象,且仅限其公开方法;MCP 服务会公开其完整工具集。
相关项目
- 使用 MCP 执行代码(Anthropic)
- Code Mode(Cloudflare)
- smolagents(Hugging Face)
- Deno + Pyodide 沙箱(Simon Willison)
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。