第 3 课

给 AI 智能体配浏览器:MCP stdio 与 HTTP

按官方文档把 lightpanda mcp 配进 Claude Desktop、Cursor、Windsurf,用一个只依赖 Python 标准库的脚本走 stdio 完成握手、列出 32 个工具和 extract,再开 HTTP 模式用 curl 加 jq 走完选作者、等 AJAX、选标签、点搜索、提取名言的流程,了解会话、404 不报错和安全边界。

图文20 分钟Lightpanda 官方文档 ↗

课程目录第 3 / 3 课

学习位置仅保存在当前浏览器,有效期 180 天。

01 / 图文教材

图文教材

本课对照的官方页面: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 DesktopSettings > 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_KEY
  • markdown 可以用 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 智能体当浏览器用。