第 3 课
给 AI 智能体配浏览器:MCP stdio 与 HTTP
按官方文档把 lightpanda mcp 配进 Claude Desktop、Cursor、Windsurf,用一个只依赖 Python 标准库的脚本走 stdio 完成握手、列出 32 个工具和 extract,再开 HTTP 模式用 curl 加 jq 走完选作者、等 AJAX、选标签、点搜索、提取名言的流程,了解会话、404 不报错和安全边界。
课程目录第 3 / 3 课
学习位置仅保存在当前浏览器,有效期 180 天。
图文教材
本课对照的官方页面:MCP 快速开始、MCP 用法、MCP 工具参考、mcp 参数。命令与输出在 Linux x86_64 云主机上实测(Lightpanda 1.0.2),输出为节选。在 Claude Desktop、Cursor、Windsurf 中配置的步骤未实测,按官方文档整理。
MCP(Model Context Protocol)是让 AI 应用调用外部工具的通用协议。lightpanda mcp 把浏览器包装成 MCP 服务器,AI 应用连上后,大模型就能自己打开网页、读取内容、填表单、点按钮。这一课先把它配进 AI 应用,再用 stdio 和 HTTP 两种方式手动调用,弄清楚智能体在背后发了什么请求。
第 1 步:配置进 AI 应用(未实测)
AI 应用会自己启动 lightpanda mcp 进程,通过标准输入输出和它通信。应用不是从你的终端启动的,读不到你设置的 PATH,所以配置里要写二进制文件的完整路径。先查出路径:
command -v lightpanda
对照结果(测试机):
/home/box/.local/bin/lightpanda
再打开 AI 应用的 MCP 配置文件,官方列出的位置:
| 应用 | 配置文件位置 |
|---|---|
| Claude Desktop | Settings > Developer > Edit Config,打开 claude_desktop_config.json |
| Cursor | 项目里的 .cursor/mcp.json,或对所有项目生效的 ~/.cursor/mcp.json |
| Windsurf | ~/.codeium/windsurf/mcp_config.json |
加入下面这段,把 command 换成你查到的路径:
{
"mcpServers": {
"lightpanda": {
"command": "/home/box/.local/bin/lightpanda",
"args": ["mcp"]
}
}
}
想让它遵守网站的 robots.txt,把 args 改成 ["mcp", "--obey-robots"]。不想上报遥测,可以在这段配置里按应用的写法加环境变量 LIGHTPANDA_DISABLE_TELEMETRY=true(多数 MCP 客户端用 env 字段,具体以应用文档为准)。
重启应用后,可以用官方给的提示词试一下:
Go to quotes.toscrape.com/search.aspx and find which tags
Albert Einstein's quotes are filed under, then give me his
inspirational one.
(为方便阅读折成三行,粘贴时可以写成一行。)
官方文档说明,智能体会依次调用 goto、detectForms、selectOption、click、waitForState、extract 完成任务。具体调哪些工具由模型自己决定,每次可能不同。第 4 步会用 curl 手动走一遍这个流程。
第 2 步:用 Python 脚本走一遍 stdio 对话
不接 AI 应用,也能直接和 MCP 服务器对话。stdio 模式下,客户端往进程的标准输入写 JSON-RPC 消息,每条一行,回复也是每条一行。下面这个 mcp_stdio.py 只用 Python 标准库,做的事情和 AI 应用一样:启动 lightpanda mcp,握手,列出工具,打开页面,提取前两条名言。
import json
import subprocess
proc = subprocess.Popen(
["lightpanda", "mcp"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
text=True,
)
def send(msg):
proc.stdin.write(json.dumps(msg) + "\n")
proc.stdin.flush()
def request(id, method, params):
send({"jsonrpc": "2.0", "id": id, "method": method, "params": params})
return json.loads(proc.stdout.readline())
def call(id, name, arguments):
reply = request(id, "tools/call", {"name": name, "arguments": arguments})
return reply["result"]["content"][0]["text"]
init = request(1, "initialize", {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "kun-test", "version": "1.0"},
})
print("server:", init["result"]["serverInfo"])
send({"jsonrpc": "2.0", "method": "notifications/initialized"})
tools = request(2, "tools/list", {})["result"]["tools"]
names = ", ".join(t["name"] for t in tools[:6])
print(len(tools), "tools:", names, "...")
print(call(3, "goto", {"url": "https://quotes.toscrape.com/js/"}))
schema = {
"quotes": [
{"selector": ".quote", "limit": 2,
"fields": {"text": ".text", "author": ".author"}}
]
}
data = json.loads(call(4, "extract", {"schema": json.dumps(schema)}))
for q in data["quotes"]:
print(q["author"], "|", q["text"][:50])
proc.stdin.close()
proc.wait()
运行:
export LIGHTPANDA_DISABLE_TELEMETRY=true
python3 mcp_stdio.py
对照结果:
server: {'name': 'lightpanda', 'version': '0.1.0'}
32 tools: goto, search, markdown, html, screenshot, links ...
Navigated. HTTP 200 OK.
Albert Einstein | “The world as we have created it is a process of o
J.K. Rowling | “It is our choices, Harry, that show what we truly
几个要点:
- 握手是两条消息:先发
initialize请求,收到回复后再发不带id的notifications/initialized通知,通知没有回复 - 工具调用的方法名都是
tools/call,params里写工具名name和参数arguments extract的schema参数要求是 JSON 字符串,所以先用json.dumps把字典转成字符串。写法和第 2 课 Python 包的选择器表相同,"limit": 2表示最多取两条- 页面在调用之间一直保留,
goto之后的extract不用再传网址
第 3 步:认识 32 个工具
tools/list 返回的 32 个工具,按官方文档分成几组:
| 分组 | 工具 |
|---|---|
| 打开与搜索 | goto、search |
| 读取页面 | markdown、html、screenshot、tree、links、nodeDetails、findElement、interactiveElements、structuredData、detectForms |
| 提取与脚本 | extract、evaluate |
| 操作页面 | click、fill、scroll、hover、press、selectOption、setChecked |
| 等待 | waitForSelector、waitForScript、waitForState |
| 状态与调试 | getUrl、getCookies、getEnv、consoleLogs |
| 会话 | save、session_new、session_list、session_close |
几条官方说明值得记住:
search不打开网页,只返回搜索结果列表。不配 API Key 时走 Keenable 的公共接口(按 IP 限流),也可以设置BRAVE_API_KEY、TAVILY_API_KEY、EXA_API_KEY或KEENABLE_API_KEYmarkdown可以用selector只读一部分,用maxBytes限制长度,官方建议尽量不读整页screenshot画的是文字排版,没有图片、字体和颜色getEnv只用来读取LP_开头的非敏感配置。密码等机密在工具参数里写$LP_变量名占位,不要让模型直接读出来
第 4 步:用 HTTP 方式跑完整流程
加上 --port,MCP 服务改走 HTTP(POST 到 /mcp),可以同时服务多个客户端,并支持多个独立的浏览会话(第 5 步介绍)。开一个终端运行:
export LIGHTPANDA_DISABLE_TELEMETRY=true
lightpanda mcp --port 8000
对照结果(排版折行,$time 为时间戳):
$time=... $scope=mcp $level=note $msg="mcp http server running"
address=127.0.0.1:8000
这一步要用到 jq(Debian/Ubuntu 上 sudo apt install jq,一键安装脚本本来也需要它)。另开一个终端,把下面两个小函数存成 helpers.sh。t 调用任意工具,用 jq 拼出 JSON-RPC 请求再交给 curl;x 专门调用 extract,把选择器表转成 JSON 字符串:
t() {
jq -nc --arg n "$1" --argjson a "$2" \
'{jsonrpc:"2.0", id:1, method:"tools/call",
params:{name:$n, arguments:$a}}' |
curl -s http://localhost:8000/mcp \
-H "Content-Type: application/json" -d @- |
jq -r '.result.content[0].text'
}
x() {
t extract "$(jq -nc --arg s "$1" '{schema:$s}')"
}
加载这两个函数:
. ./helpers.sh
下面按智能体会走的顺序,一步步完成第 1 步那条提示词的任务。先打开搜索页,看看表单里有哪些字段:
t goto '{"url":"https://quotes.toscrape.com/search.aspx"}'
t detectForms '{}' |
jq -c '.[0].fields[] | {name, options: (.options | length)}'
对照结果:
Navigated. HTTP 200 OK.
{"name":"author","options":50}
{"name":"tag","options":1}
作者下拉框有 50 个选项(第一个是占位的 ----------),标签下拉框现在只有 1 个占位选项。选中作者后,页面会用 AJAX 重新加载标签列表,所以要先等网络空闲,再读标签:
t selectOption '{"selector":"#author","value":"Albert Einstein"}'
t waitForState '{"state":"networkidle"}'
x '{"tags": [{"selector": "#tag option", "attr": "value"}]}' |
jq -c '.tags[1:6], (.tags | length)'
对照结果(前三行实际是一行输出,排版折行):
Selected option 'Albert Einstein' (selector: #author).
Page url: https://quotes.toscrape.com/filter.aspx, HTTP 200 OK,
title: Quotes to Scrape
Page reached networkidle.
["change","deep-thoughts","thinking","world","inspirational"]
25
标签下拉框变成 25 个选项,去掉第一个没有 value 的占位项(提取结果里是 null),爱因斯坦的名言共有 24 个标签,上面只显示了前 5 个。接着选 inspirational,点搜索按钮,等结果加载完再提取:
t selectOption '{"selector":"#tag","value":"inspirational"}'
t click '{"selector":"input[name=submit_button]"}'
t waitForState '{"state":"networkidle"}'
x '{"quotes": [{"selector": ".quote",
"fields": {"text": ".content", "author": ".author", "tag": ".tag"}}]}' |
jq .
对照结果(第 1–2 行、第 3–4 行各是一行输出,排版折行;名言文字有删节):
Selected option 'inspirational' (selector: #tag).
Page url: https://quotes.toscrape.com/filter.aspx, HTTP 200 OK, ...
Clicked element (selector: input[name=submit_button]).
Page url: https://quotes.toscrape.com/filter.aspx, HTTP 200 OK, ...
Page reached networkidle.
{
"quotes": [
{
"text": "“There are only two ways to live your life. ...”",
"author": "Albert Einstein",
"tag": "inspirational"
}
]
}
任务完成。接入 AI 应用后,模型做的也是这些调用,只是由它自己决定顺序和参数。
第 5 步:会话与常见坑
上面的调用都没有带 Mcp-Session-Id 请求头,所以都落在始终存在的 default 会话里。发一次 initialize 会新建一个会话,会话编号在响应头里:
jq -nc '{jsonrpc:"2.0", id:1, method:"initialize",
params:{protocolVersion:"2024-11-05", capabilities:{},
clientInfo:{name:"curl-test", version:"1.0"}}}' |
curl -s -i http://localhost:8000/mcp \
-H "Content-Type: application/json" -d @- |
grep -i 'mcp-session-id'
对照结果:
mcp-session-id: s1
之后的请求带上 -H "Mcp-Session-Id: s1",就会在这个独立会话里操作,有自己的页面和 Cookie。多个智能体共用一个服务时,各用各的会话,互不干扰。不用的会话用 session_close 关闭释放内存,default 会话不能关闭。
goto 打开 404 页面时不会报错。实测:
t goto '{"url":"https://quotes.toscrape.com/nope-404"}'
对照结果:
HTTP 404 Not Found: this is likely an error or rate-limit page,
not the requested content.
Navigated. HTTP 404 Not Found.
(第一行排版折行。)
返回的 isError 是 false,状态码要从文字或 structuredContent.httpStatus 里判断。官方文档还说明,页面加载超时也只返回一句提示(“Navigation started (HTTP 状态码) but the page did not finish loading before the timeout.”),只有域名解析失败这类真正的导航失败(如 navigation failed: CouldntResolveHost)才直接返回错误。自己写客户端时要检查状态码。
安全方面:--host 默认只监听本机。改成 0.0.0.0 后,网络里任何人都能用这个浏览器访问你内网的地址,不要在公网机器上这样开。Lightpanda 1.0 起默认执行 CORS 跨域检查,官方博文解释了原因:智能体常会打开没人审过的网页,恶意页面可能借浏览器去请求内网服务。
练习完回到第一个终端按 Ctrl+C 停掉服务。
小结
- AI 应用通过
{"command": "lightpanda 的完整路径", "args": ["mcp"]}接入,路径用command -v lightpanda查 - stdio 模式每行一条 JSON-RPC,先
initialize再发notifications/initialized,适合 AI 应用;--port开 HTTP 模式,适合远程或多个客户端 - 32 个工具覆盖打开、读取、操作、等待、提取,
extract的 schema 要写成 JSON 字符串 - 读 AJAX 内容前先
waitForState等到networkidle goto遇到 404 和超时不报错,要自己检查状态码- 不加
Mcp-Session-Id时用default会话,initialize会新建独立会话
三课学完,你可以用 fetch 批量把网页转成 Markdown,用现有 Puppeteer、Playwright 脚本换一个更省资源的浏览器,也可以把 Lightpanda 交给 AI 智能体当浏览器用。
