内容
# 本地LLM代理,读取并操作网页(camofox-browser + MCP)
*Build Your Own Claude Code*系列第6部分的代码:
*在网页上操作的小型本地LLM — 点击、输入、提交*。
到目前为止的系列:
- 第1部分:代理(CLI、工具、技能、历史、压缩)
- 第2部分:浏览器UI(FastAPI + WebSockets)
- 第3部分:网页搜索(通过MCP的SearXNG)
- 第4部分:网页浏览(通过MCP的camofox-browser)+ 与第3部分的搜索相结合
- 第5部分:读取整个页面而不截断 — 分段`extract`和`summarize`,以及一系列小型单用途读取工具
(`fetch_snippet`、`fetch_urls`、`fetch_structure`)
- 第6部分(此仓库):交互 — 持久标签生命周期
(`open_tab` / `read_tab` / `close_tab`) 加上操作工具
(`click`、`type_into`、`press_key`、`select_option`,或单个
`interact`工具,取决于配置)因此代理可以填写搜索框,点击分页列表的“下一步”,或提交表单,而不仅仅是读取页面上已有的内容。
本部分解决的问题:第5部分的读取工具是基于URL的 —
每个工具都会打开一个新的标签,生成快照,然后关闭它,因此没有可以操作的对象。实际任务(搜索站点、遍历结果、填写并提交表单)需要一个在调用之间保持打开的标签和在单击或输入之前存活的引用。camofox保持标签处于活动状态,直到被告知关闭它;本部分在上面添加了MCP管道 — 打开一个持久标签,读取其当前状态以查找元素的`[eN]`引用,对该引用执行操作,然后在下一次操作之前重新读取,因为每次操作都会重新编号引用。
## 安装
```bash
git clone https://github.com/jfjensen/local-LLM-agent-mcp-interaction.git
cd local-LLM-agent-mcp-interaction
python -m venv .venv
# Linux / macOS:
source .venv/bin/activate
# Windows PowerShell:
.\.venv\Scripts\Activate.ps1
pip install -e .
```
您还需要:
- **Docker** 来运行 camofox-browser 和 SearXNG(第 1 阶段将两者启动)。
- **Ollama** 带有具备工具能力的模型。默认模型是 `qwen3.5:9b`:
```bash
ollama pull qwen3.5:9b
```
SearXNG 预先配置好(设置文件被挂载到容器中),因此 JSON API 无需编辑任何内容即可工作。
## 配置
所有可调整的设置都位于存储库根目录的 `config.toml` 中:
- 模型名称、温度、思考;
- SearXNG 和 camofox URL;
- 分段和读取预算(页面快照大小由 camofox 自己限制,而不是在这里 — 参见 `config.toml` 的 `[browser]` 中的注释);
- `[acting]` — 第 6 部分的新部分:`action_tool_style` 在四个意图命名的操作工具(`click`、`type_into`、`press_key`、`select_option`)和单个 `interact(action=...)` 工具之间选择;`wait_timeout_ms` / `wait_for_network` 控制服务器在操作后等待页面稳定的时间;`action_result_snippet` 可选地将操作后页面的头部快照附加到操作的结果中;
- 日志详细程度(`logging.level = "DEBUG"` 显示每个工具调用、其参数和返回内容的预览)。
要查看当前生效的配置:
```bash
mcp-config-show
```
加载器首先查找当前工作目录中的 `config.toml`,然后回退到与存储库捆绑的配置文件。
## 如何运行
| 阶段 | 描述 | 运行方式 |
|-------|------------------------------------------------------------------|------------|
| 1 | 通过 Docker 启动 camofox-browser 和 SearXNG | `cd stage1 && docker compose up -d`(参见 `stage1/README.md` 了解 camofox 镜像构建步骤) |
| 2 | 完整的代理:搜索加上浏览器读取和操作工具 | `mcp-agent-stage2` |
浏览器 MCP 服务器(`mcp-browser-stage2`)公开了三组工具。
**基于URL的读取器**(来自第5部分;每个读取器都打开一个一次性标签,读取它,然后关闭它):
- **`fetch_snippet`** — 页面的头部,用于快速查看。
- **`fetch_urls`** — 页面的链接作为 `{text, url}` 对,绝对路径。
- **`fetch_structure`** — 标题大纲。
- **`extract`** — 命名字段为 JSON,通过整个页面的分段和合并(无截断)。
- **`summarize`** — 一个由每个分段摘要组合而成的摘要(默认情况下为映射-归约,或精炼)。
**持久标签生命周期和读取器**(第6部分新增;当您需要对页面执行操作,而不仅仅是读取它时):
- **`open_tab`** — 在一个保持打开状态的标签中打开一个页面,并返回一个 `tab_id`。
- **`read_tab`** — 读取打开的标签的**当前**状态:`mode="snippet"`(默认,显示您需要的 `[eN]` 元素引用以执行操作),`"urls"` 或 `"structure"`。
- **`summarize_tab`** / **`extract_tab`** — 适用于活动标签的整个页面、无截断的 `summarize` / `extract` 逻辑,而不是新的 URL 获取(在您导航或操作到一个无法通过 URL 重新获取的页面时使用,例如表单的 POST 目标)。
- **`close_tab`** — 关闭一个标签;**`close_all_tabs`** — 关闭会话中的所有标签(代理在关闭时自动调用此操作)。
**操作工具**(第6部分新增;对来自最新 `read_tab` 的 `[eN]` 引用执行操作)。模型看到的界面由 `config.toml` 的 `[acting]` 部分的 `action_tool_style` 选择:
- `"separate"`(默认)— 四个意图命名的工具:**`click`**、**`type_into`**(可选地 `submit=True` 以在之后按 Enter 键)、**`press_key`**、**`select_option`**(用于原生 `<select>` 下拉菜单)。
- `"interact"` — 一个 **`interact(tab_id, action, ...)`** 工具,具有一个操作枚举(`click` / `type` / `press` / `select`),映射到 camofox 自己的 `/act` 分发器。
每次操作都会重新快照标签并报告生成的 `url` 和新的 `refsCount`:引用在每次操作后重新编号,因此规则始终是 `read_tab` → 操作 → 再次 `read_tab`,然后再进行下一次操作。
另外,搜索服务器(`mcp-search-part3`),这是第3部分的SearXNG MCP服务器的副本,因此存储库是自包含的。
## 使用 `inspect_any.py` 探测 MCP 服务器
`inspect_any.py` 每次调用一个工具,这对于基于URL的读取器来说是可以的,但对于交互则不行(每个进程都会获得自己的标签)。对于生命周期/操作工具,请改用 `test_act_flow.py`(见下文)。
```bash
# 列出服务器的工具:
python inspect_any.py mcp_browser_02.main
# 调用读取器工具:
python inspect_any.py mcp_browser_02.main fetch_snippet --kv url=https://example.com
python inspect_any.py mcp_browser_02.main fetch_urls --kv url=https://example.com
# 调用 extract 并带有 JSON 模式(使用 --args 或 --args-file 表示嵌套参数):
python inspect_any.py mcp_browser_02.main extract --args-file extract_args.json
```
其中 `extract_args.json` 可能如下所示:
```json
{
"url": "https://en.wikipedia.org/wiki/Vleteren",
"schema": {
"type": "object",
"properties": {
"mayor": {"type": "string", "description": "The current mayor, from the infobox"},
"postal_code": {"type": "string", "description": "The postal code"},
"population": {"type": "string", "description": "The total population"}
}
}
}
```
## 使用 `test_act_flow.py` 测试操作工具
与 `inspect_any.py` 不同,该程序打开一个 MCP 标准输入/输出会话,并在单个进程中运行整个交互循环,因此持久标签在调用之间保持活跃:
```bash
python test_act_flow.py
# 或针对不同目标:
python test_act_flow.py https://en.wikipedia.org/wiki/Main_Page
```
它运行 `open_tab` → `read_tab` → `type_into(..., submit=True)` → `read_tab` → 故意重用现在过时的引用(以演示为什么每次操作后必须重新读取引用)→ `close_tab`。默认目标是 DuckDuckGo Lite,一个小 HTML 页面,其搜索框可以进行干净的提交前后比较。
## 使用 `probe_camofox.py` 直接探测 camofox
一个较低级别的探测器,直接与 camofox 的 HTTP API 通信(无 MCP 层),在操作工具中出现问题时很有用,您需要排除 MCP 服务器是原因的可能性。它首先检查 camofox 构建实际上回答的“打开标签”端点,然后驱动打开 → 快照 → 输入 → 提交 → 快照 → 过时引用检查 → 关闭针对真实公共表单:
```bash
python probe_camofox.py
# 或针对不同表单:
python probe_camofox.py https://html.duckduckgo.com/html/
```
## 注意事项
- 代理在首次运行时会在当前工作目录中创建一个 `history/` 文件夹。
- 存储库附带了第 3 部分的 SearXNG MCP 服务器的副本,作为 `mcp-search-part3`,字节与字节相同,因此您无需安装第 3 部分。
- 代理在关闭时自动调用 `close_all_tabs`,因为模型本身不能可靠地关闭标签;否则,持久标签将在 camofox 会话中累积。
## 许可证
MIT © 2026 Jes Fink-Jensen. 有关详细信息,请参见 [LICENSE](LICENSE)。
## 故障排除
- **Docker 构建失败,提示“dist not found”**。使用 `Dockerfile.ci` 代替默认的 `Dockerfile`。参见 `stage1/README.md`。
- **代理启动 MCP 服务器时出现 `FileNotFoundError: [WinError 2]`**。您的虚拟环境未激活。激活它,以便控制台脚本位于 `PATH` 上。
- **脚本在 Windows 上无声退出**。Windows 上的默认 asyncio 事件循环无法生成子进程。代理设置 `WindowsProactorEventLoopPolicy` 在启动时;如果您将代码复制到其他地方,请执行相同的操作。
- **`extract` 在您知道有数据的页面上返回空值**。使用分段提取,这应该很少见,但一个非常大的页面会调用许多模型。降低 `chunking.chunk_chars` 以获得更小、更多的分段,或提高它以获得更少、更大的分段。
- **`fetch_structure` 返回很少或没有标题**。一些页面(短存根、内容生活在表格或信息框中的页面)具有薄标题轮廓。对于那些页面,请使用 `summarize` 或 `extract`。
- **camofox 返回小或空快照**。一些页面需要超过默认的 1.5 秒的稳定时间。增加 `browser.settle_seconds`。
- **操作失败,引用过时或“页面可能已更改”错误**。每次操作都会重新编号标签的 `[eN]` 引用。在重试之前再次调用 `read_tab` 以获取新的引用;请勿重用操作前的引用。
- **结果页面上的操作似乎无声地针对错误的元素,或者页面与预期不符**。不要通过 URL 重新获取结果 — 而是使用 `read_tab` / `summarize_tab` / `extract_tab` 对标签进行操作。一些结果页面(例如表单的 POST 目标)仅在标签内存在,无法独立获取。
- **`select_option` 失败,即使下拉菜单可见**。camofox 仅为其可访问性快照分配了引用的 `<select>`/组合框元素;如果 `read_tab` 显示下拉菜单上没有 `[eN]` 引用,则无法在此构建中选择它。如果站点将它们呈现为自定义(非 `<select>`)小部件,请改用可见选项上的 `click`。
- **`probe_camofox.py` 找不到开放端点**。它首先尝试规范的 `POST /tabs` 路由,然后回退到旧的 `POST /tabs/open` 伪影。如果两者都未返回 `tabId`,则您的 camofox 构建的 API 可能已更改;粘贴脚本的输出以查看哪一步失败。