AIO Sandbox:把浏览器、Shell、MCP和VSCode装进同一个Agent沙箱

📅 发布时间:2026/9/26 8:20:52
AIO Sandbox:把浏览器、Shell、MCP和VSCode装进同一个Agent沙箱
做 Agent 项目的朋友应该都经历过这种循环先配好 Playwright 环境跑通一个浏览器自动化脚本接着要执行清理命令又得切到另一套容器数据落到文件里还得把卷挂出来让另一个服务读到。我自己之前维护的工具链就是这么碎改一次镜像就牵一发动全身——直到我实际用了一遍 AIO Sandbox这个开源项目做的事情用一句话就能讲完把浏览器、Shell、文件、MCP、VSCode 全部集成进同一个容器做成一个专门面向 Agent 的沙箱环境。这篇文章会从设计动机、组件分工、容器架构讲到实操部署再把我测试时踩过的坑摊开来讲希望能帮正在做 Agent 开发、AI 应用集成、工具调用的朋友少走点弯路。1. 为什么 Agent 项目需要一个把什么都能塞进去的沙箱1.1 工具链是碎的到底有多疼大多数 Agent 项目不是一开始就想到要用沙箱的是工具链碎到实在忍不了才去求一个容器。我举一个很常见的场景你要做一个网页数据采集 本地脚本清洗 落盘输出的 Agent。浏览器自动化你得准备一个环境至少要有 Chromium、Playwright、截图依赖shell 清洗脚本又得有 Python、ffmpeg、各种系统库输出文件还要和宿主机的某个目录打通。很多人第一反应是开两个容器一个跑浏览器一个跑脚本中间用 volume 或 API 传数据。听起来挺干净可实际维护起来完全是另一个故事两个容器的基础镜像要分别更新环境变量各有一套Python 版本还经常对不上。你在浏览器容器里装好的依赖shell 容器里根本没有shell 容器里生成的报表浏览器容器又读不到。这种割裂带来的配置漂移会在 Agent 跑批量任务时集中爆发。更难受的是排障。Agent 一次任务在浏览器容器里执行到第 7 步突然挂在文件读取上你第一反应是进容器去看日志结果发现路径不存在——为什么因为上一个容器里挂载的路径和这个容器不一样。为了一个路径映射问题我也见过有人硬编码了十几个环境变量最后还是每次都要手动对齐。工具链分散带来的不只是环境维护成本它会让 Agent 每一步的执行状态都变得不可预测。1.2 Agent 在执行时到底需要哪些能力大模型驱动的 Agent工作循环本质上就是规划、调用工具、观察结果、再规划。工具就是它和外界交互的手和脚一个务实的 Agent 至少需要三种能力能看懂网页的感知能力能执行命令的操作能力能读写文件的记忆与输出能力。这三种能力如果分别部署Agent 调用工具时就要跨进程、跨网络传递上下文。浏览器截完图文件在容器 A 里Shell 要去处理得先传过去。单步调用看着没问题一个长任务动辄几十步每多一次跨容器传递失败概率就翻一番。反过来如果这些能力全在一个容器里Agent 的每一步操作都落在同一个文件系统、同一组环境变量、同一个网络命名空间下。浏览器下载的附件就在 workspace 里shell 命令能直接处理它file 工具能读取结果人还能通过 VSCode 随时看到全过程。状态的一致性才是塞进同一个容器最大的价值。1.3 All-in-One 不等于盲目堆料有一点得说清楚All-in-One 不是把能装的软件全部装一遍那只会得到一个又大又脆的镜像。AIO Sandbox 这类项目的思路是把 Agent 运行必需的能力层收拢成一个可复现的单元而不是堆一个全功能桌面环境。每个组件有明确的职责边界之间通过统一的协议和服务接口协作。打个比方这就像厨房里把所有厨具放在同一个操作台上而不是把厨房改造成一间百货超市。操作台解决的是动线问题洗菜、切菜、炒菜不用来回跑。对于 Agent 来说这个动线就是工具调用的上下文与状态传递。所以你看这类沙箱项目重点通常不是装了多少个软件而是组件之间的目录怎么共享、服务怎么拉起、协议怎么统一。抓住了这个视角后面看它的架构和配置就不会懵。2. 拆开这个箱子浏览器、Shell、文件、MCP、VSCode 各自扮演什么角色2.1 浏览器组件给 Agent 一双眼睛浏览器组件是 Agent 感知能力最直接的表现。容器里通常会带一个 Chromium 内核配合无头模式或者虚拟帧缓冲让页面在没有任何显示器的环境里也能渲染出来。MCP 工具一般会暴露 navigate、snapshot、screenshot、click 这些操作。navigate 让 Agent 打开指定地址snapshot 提取页面可访问性树或简化文本screenshot 直接出图。这里有个很关键的细节纯文本模型依赖 snapshot 的内容多模态模型可以直接看截图。两个模式接口不同但解决的都是同一个问题——让 LLM 知道网页里到底有什么。在实际跑数据采集任务时浏览器组件的价值会被放大。网站如果是纯静态 HTMLsnapshot 就够了但遇到 React、Vue 这类前端渲染的页面除了等网络请求结束通常还得配合截图来确认界面渲染结果。这也是为什么很多 Agent 沙箱里不仅要有 headless Chromium还要预留出截图落盘的目录。否则 Agent 看到的是空 DOM推理再多也白搭。2.2 Shell 组件给 Agent 一双手Shell 工具解决的是怎么把计划变成现实。装依赖、跑脚本、清理临时文件、批量重命名都是通过执行命令完成的。你在沙箱里看到的是一个完整的 Linux 用户环境PATH 里已经预置了常用的运行和管理工具。面对 shell 能力要有个底线意识Agent 一旦有了通用命令执行权等于容器内有了一个低配管理员。所以合理的沙箱不会让 Agent 默认以 root 跑命令而是专门开一个低权限用户甚至把工作目录限定在具名空间再配合审计日志记录每一步执行。给 Agent手可以但得知道这双手能摸到哪。另外还要考虑命令的交互性。很多模型喜欢直接执行 python -c 或者 node -e而不是写脚本文件。这种写法适合短任务但长任务最好落到文件再运行既方便审计也能避免一行命令太复杂导致转义错误。我会在工具说明里明确引导 Agent复杂逻辑写进 /workspace/scripts 下的 .py/.sh 文件再执行输出结果写进文件而不是塞回工具返回值。2.3 文件组件一个所有组件共享的工作台如果只让我保留一个理由往 All-in-One 这个方向倾斜那就是共享文件系统。沙箱里通常会有一个 workspace 目录浏览器下载的文件落在里面shell 命令的输出也写到这里file 工具可以任意读写这里VSCode 打开的工作区也是这里。这种设计的真正价值在于你不需要知道某个文件在哪个容器的哪个路径你只需要知道它在 workspace 里任何组件都能访问它。对这种统一文件视图我的使用体会是管道效率提升非常明显——数据源和目标地之间的搬运几乎被消灭了。文件组件的工具形态一般是 file_read、file_write、file_list、file_search 这几类。对于 Agent 来说file_search 甚至比 shell 里的 grep 更好用因为返回的是结构化结果模型直接消费不需要自己解析文本流。如果你正在设计自己的沙箱工具集我建议把文件工具和 shell 工具分开让模型优先选文件工具只有真正需要系统级操作时才动 shell。2.4 MCP把能力变成标准插座MCP 是 Model Context Protocol 的缩写可以理解为一个把外部能力变成标准工具插座的协议。AIO Sandbox 里浏览器、Shell、文件这些能力最后都是通过 MCP server 暴露出来的。客户端只需要知道 server 地址和工具名就能像调用函数一样操作这些能力。调用协议本身基于 JSON-RPC消息里包含 tools/list、tools/call 这类方法模型通过 server 返回的结果继续往下规划。比如拉取工具列表实际就是这样一个请求{jsonrpc:2.0,id:1,method:tools/list,params:{}}server 返回的每个 tool 里会有 name、description、inputSchema。模型看到 description 就知道什么场景该用哪个工具、参数怎么填。AIO Sandbox 的价值在于它不只搭好了浏览器和 Shell还用 MCP 把这层能力做成了一套可被任意 MCP 客户端直接消费的标准接口。这层细节非常关键——因为 Agent 客户端只要支持 MCP就能无缝接入不需要为每个组件单独写适配。2.5 VSCode 组件人的观察窗和急救包Agent 是自动跑的但工程上你不能对它完全撒手。VSCode 组件通常是 code-server 的实现——一个跑在浏览器里的 VSCode直接打开容器内的工作区。它的两个价值最明显一是观察你能实时看到 Agent 在 workspace 里生成了哪些文件、日志里打印了什么二是急救任务失败时你不需要重新配置环境直接在 VSCode 里改文件、补代码、执行命令然后让 Agent 继续跑。这也是我把 VSCode 看作沙箱完整度重要指标的原因——没有这层人的接口沙箱只是一个黑盒。组件在沙箱中的角色Agent 视角常见实现端口示例浏览器感知navigate/snapshot/screenshot/clickPlaywright MCP Chromium3001 或 9222Shell操作执行命令、脚本MCP shell server bashstdio/HTTP文件记忆与输出读写工作区文件volume MCP file server-MCP协议粘合标准工具调用聚合 MCP server3001VSCode人机调试查看/干预执行现场code-server80803. 一个容器装下所有东西靠的不只是堆进程3.1 启动编排谁先起来谁后起来容器一启动里面同时装了很多服务排列组合很讲究。如果只是把所有进程一股脑丢进 Dockerfile 的 CMD你会发现服务之间互相抢端口、互相等不到依赖。常规做法是一个入口脚本或 supervisor 负责编排先确保 workspace 目录可写再拉起 code-servercode-server 起来后再启动各个 MCP server 进程最后才启动浏览器进程因为浏览器主要依赖前几个服务的端口和资源。docker compose 里的 healthcheck 再来一道把关避免容器显示 running 但服务其实还在爬坡。我自己在排查连上了但服务没就绪的问题时最常用的命令就是反复看 health 状态和日志。healthcheck 的写法也很直白比如给 MCP server 加一个services: mcp: healthcheck: test: [CMD, curl, -f, http://localhost:3001/mcp] interval: 15s timeout: 5s retries: 5有了这层compose 才能在依赖服务就绪后再启动浏览器这类重量级进程而不是一拥而上。3.2 MCP 传输方式如何影响部署MCP server 有两种传输方式直接影响你怎么部署。stdio 模式下MCP server 是客户端拉起的子进程适合本地原生客户端HTTP/SSE 模式下MCP server 是一个网络服务外部 Agent 通过 URL 连接适合容器化部署。AIO Sandbox 里一般会默认把 HTTP 模式打开因为 Agent 客户端很可能跑在宿主机或远端。配置上要注意 server 绑定地址默认绑 127.0.0.1 的话外部容器访问不到要改成 0.0.0.0。如果开启了 Bearer Token 认证客户端那里要配好 headers否则会出现工具列表拉得到、一调用就 401的诡异现象。3.3 共享文件系统串起所有组件的隐形关键前面讲过的 workspace到这里要从实现层面理解它通常是一个 Docker named volume 或 bind mount挂进容器后所有相关进程的工作目录都指向它。这种设计的价值在于文件不是传过去的而是生来就在同一个地方。浏览器把页面截图保存到 /workspace/downloadsAgent 下一步立刻可以读取Shell 命令把中间结果写进 /workspace/artifactsfile 工具马上就能引用code-server 打开 /workspace你看到的就是 Agent 的完整现场。没有共享文件系统的话组件间每一次数据交换都要显式传输那 All-in-One 就名存实亡了。权限这里也要提一句挂载卷的所有者如果和容器内用户不一致很容易出现Agent 读不了自己刚写的文件这种低级但见效的翻车事故。我建议第一次启动前就把 uid 映射好别等数据跑了半天再回头改。3.4 安全边界沙箱容纳的是聪明调用不是无限制管理员Agent 里跑的是大模型它什么时候会被 prompt 引导去做危险操作谁都不敢打包票。所以沙箱的安全边界不能只靠自觉。我实际会做的四件事第一不用 root 跑 Agent 相关进程第二把关键目录设置只读比如系统目录、配置目录第三对出网做限制默认允许访问业务需要的端口其余流量一律丢弃第四Shell 调用全部打日志会话粒度可审计。另外 Docker 自身还能加 --memory、--cpu 配额防止某个失控的工具调用把整个宿主机拖垮。要明白沙箱不是把命令执行权限给模型就完事了而是精心划定了一个它可以自由发挥但不能越界的区域。4. 从零把这套沙箱跑起来我的实操流程4.1 前置检查内存和 Docker 别省动手之前先看三样东西Docker 是否就绪、docker compose 是否有、内存够不够。浏览器是最吃资源的Chromium 多进程模式轻松占到 1.52GBcode-server 是 Node 进程保守算 500MB800MB几个 MCP server 加起来还要 300MB 上下。所以我建议主机至少留 4GB 内存给这个沙箱只有 2GB 的话纯测试能跑但浏览器一开就容易 OOM。docker --version docker compose version free -h这三个命令确认完再往下走。4.2 clone 启动靠 compose 而不是手搓启动命令把项目 clone 到本地一般从项目 Releases 或源码仓库拿进入目录后先看有没有 .env.example。很多这种 All-in-One 项目都会用环境变量控制端口、密码和挂载路径复制一份 .env 改成自己的值git clone aio-sandbox 仓库地址 aio-sandbox cd aio-sandbox cp .env.example .env docker compose up -d项目的目录结构通常长得像这样细节不同但骨架类似aio-sandbox/ ├── docker-compose.yml # 服务编排、端口、volume ├── .env.example # 端口/密码/workspace 路径模板 ├── config/ # MCP server 与 code-server 配置 ├── tools/ # 浏览器、shell、file 的 MCP 实现 └── workspace/ # 默认挂载为容器内 /workspace这里强烈建议别绕过 compose 手敲 docker run。compose 文件里通常已经编排好了端口映射、volume 挂载、健康检查和服务依赖手敲一个 docker run -d 只能启动单个容器根本表达不了AIO里的多层依赖关系。启动后看状态docker compose ps docker compose logs -fdocker compose ps如果显示 healthy说明关键组件都起来了日志里会有 MCP server 监听端口、code-server 初始化 workspace 的信息可以从中确认各组件实际监听的端口。4.3 检查 VSCode 和浏览器容器内部能力肉眼确认先打开浏览器访问 VSCode 界面默认端口我见过不少是 8080。页面会要求输入密码或 token这个值一般就在 .env 里直接填进去。进去之后打开文件夹 /workspace这时候你应该能看到一个待命的工作区。浏览器组件不一定有独立的可视化页面更多时候是通过 MCP 工具验证。你可以在 Agent 客户端里调一次 browser_navigate 访问一个本地服务的地址如果返回了页面快照说明浏览器组件正常。如果没反应最常见的病因是系统库缺失这个我放到第 5 节展开。4.4 在外面接一个 Agent 客户端MCP 地址和工具验证沙箱起来之后真正的重头戏是让 Agent 客户端连上它。现在主流客户端基本都支持 MCP添加一个 MCP server 地址即可http://localhost:3001/mcp如果客户端跑在另一台机器就把 localhost 换成沙箱所在主机的 IP。加上之后客户端会拉一次工具列表。正常情况下你应该能看到类似下面这些工具browser_navigatebrowser_snapshotshell_executefile_read / file_write / file_list看到这些工具说明浏览器、Shell、文件三大能力已经通过 MCP 打通了。如果工具列表拉到空先查 server 是否监听正确端口再查认证 token。4.5 最小闭环让 Agent 自己跑一个三件套任务全部就绪后我建议先跑一个很小但完整的任务验证整条链路是不是真的通。任务设计很简单让 Agent 读取 workspace 里的 README.md用 shell 统计一下项目文件数量再打开浏览器访问本地 VSCode 页面确认服务在线最后把所有结果写进一个 result.log。对应的工具调用大致像这样file_read(path/workspace/README.md) shell_execute(commandfind /workspace -type f | wc -l) browser_navigate(urlhttp://localhost:8080) browser_snapshot() file_write(path/workspace/result.log, content...)跑完之后切到 VSCode 刷新 workspace会看到 result.log 出现。这个闭环里每一步之间都没有跨容器传输所有结果都落在同一份 workspace 里——这就是前面聊的 All-in-One 真正舒服的地方。5. 实际用下来最容易踩的坑以及我最后的优化方案5.1 浏览器进程被 OOM 干掉容器直接退出我第一轮测试时容器在任务跑到第三个页面时退出日志里出现 killed processdocker inspect 看退出码是 137。根本原因是 Chromium 的多进程模型内存峰值太高加上 MCP server、code-server 叠在一起把容器的内存配额吃满了。解决思路分两层。一是给容器设置合理的 --memory 上限并给浏览器进程加 --max-old-space-size 之类的限制二是关掉不需要的特性无头模式比带 Xvfb 的完整渲染省不少内存。这里要提醒一句有些教程为了省事会让你加 --no-sandbox这个参数在容器里确实常用但它关闭了 Chromium 的沙箱机制非必要不要在生产环境沿用。5.2 MCP 长任务超时输出太大、会话太久都是坑Agent 调 shell_execute 执行一个几分钟的任务客户端大概率在几十秒就会报 timeout因为多数 MCP client 有个默认的超时设置。这不算 bug是长任务和同步调用之间的天然矛盾。我现在的做法是给 Agent 一套异步模式的提示词启动长命令时用 nohup 写到日志文件然后周期性 sleep tail 检查进度。另一件事是输出截断。一个 find 命令能把几十万字符灌回上下文模型直接变傻。所以我在工具说明里特意引导 Agent 用| tail、| head、只输出摘要。这些细节看着琐碎实际能避免大量无意义的上下文爆炸。5.3 挂载卷写出来的文件全是 root宿主机改不动容器内默认以 root 运行挂载卷里的文件自然都是 uid 0。宿主机普通用户删除或修改这些文件时会发现权限不够很膈应。解决办法是让容器内用户和宿主机用户 uid 对齐。在 compose 里给服务指定 user: 1000:1000或者在启动脚本里把工作用户固定成 uid 1000。这个事最好在第一次启动前就设置好不然后面要大批量 chown浪费几分钟。顺带一提如果 workspace 目录要在宿主机上直接编辑bind mount 比 named volume 更方便管理。5.4 浏览器启动报缺库无头也不是零依赖第一次调 browser_navigate 时工具返回错误里面写着缺 libnss3、libatk-bridge 这类系统库。我当时的反应是无头浏览器难道不是没有界面就没依赖实际不是Chromium 内核依然需要大量图形和媒体相关的系统库。如果你的 base 镜像比较精简建议直接装齐浏览器运行依赖常见的有 libnss3、libatk-1.0、libatk-bridge-2.0、libcups2、libxkbcommon、libgbm 这些。嫌麻烦的话直接基于官方带 Chromium 依赖的镜像改造会更省事。这点放在很靠前的位置能省下一小时。5.5 容器重启之后 Agent 状态全没了有朋友跑了一晚上 Agent 任务第二天起来发现容器所在机器重启过docker compose 拉起来之后 workspace 是空的。原因很常见docker run 时没有配置持久化数据放在容器可写层容器一删数据全没了。AIO 这类项目默认都会在 compose 里声明 named volume挂到 /workspace。但如果你自己手改过 compose或者用了 bind mount 但宿主机路径写错就很容易踩。建议启动前先确认 volume 真实存在再跑一个文件写入测试重启容器验证一次。别等到跑了半天任务才想起来。5.6 token 和绑定地址引发的连不上错觉MCP server 绑定 127.0.0.1 时外部 Agent 客户端能 ping 通宿主机但连不上 3001 端口或者开了 token 校验但客户端 headers 没配置工具列表都能拉一调用就报 401。这类问题最迷惑人。排查顺序先确认容器端口映射docker compose ps 看 3001 是否映射到宿主机再进容器 curl 一下 127.0.0.1:3001确认进程正常最后检查 MCP server 是否绑定了 0.0.0.0以及客户端是否带了认证头。三个点过一遍基本能定位。5.7 我目前的稳定配置思路跑了一段时间后我在 AIO Sandbox 上的稳定用法大概是这样的。首先内存配额设成 4GB运行期监控不够时优先关掉多模态截图这类重操作。其次workspace 持久化用 named volumeAgent 的状态、中间产物、日志都留在那里任何时刻都能恢复到前一天现场。再次Shell 工具不做完全白名单但坚持用低权限用户并且把重要系统目录设为只读命令全部审计。最后每个 Agent 项目分配独立的 workspace 子目录让多个 Agent 任务互不干扰。这套配置不复杂但能挡掉 90% 我在实际测试中遇到过的问题剩下的就只能靠对具体任务的日志观察来逐步优化了。如果你也刚好在折腾 AIO Sandbox或者正被散落的 Agent 工具链折磨欢迎把你这边的踩坑经验也整理出来这类项目最有意思的地方就在于每个真实场景都会逼出新的解决方案。