关于 MeshArc
MeshArc 是通过 API 抓取、爬取和搜索网页的 Python 客户端,面向需要提取页面内容或跟踪网站变化的开发者。它还能将一次性爬取转为定期监控项目,并记录页面增删改动。
主要功能
- 01抓取页面并输出 Markdown、文本等内容
- 02批量抓取多个 URL
- 03按站点地图映射 URL
- 04搜索网页并抓取结果页面
- 05创建定期爬取项目并记录变化
适用场景
- 01提取网页正文或结构化字段
- 02批量读取多个页面
- 03检查站点地图中的页面
- 04追踪网站页面的新增、删除和修改
MCP 相关
MeshArc 提供 MCP 工具,供 MCP 客户端调用其网页抓取、爬取、映射、搜索及 Agent 能力;README 提到部分功能尚未在 mesharc.dev 上线。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://mcp.mesharc.dev/mcpclaude mcp add --transport http dev-mesharc-mesharc https://mcp.mesharc.dev/mcp{"mcpServers":{"dev-mesharc-mesharc":{"type":"http","url":"https://mcp.mesharc.dev/mcp"}}}在 Cursor 中打开{"servers":{"dev-mesharc-mesharc":{"type":"http","url":"https://mcp.mesharc.dev/mcp"}}}在 VS Code 中打开{"mcpServers":{"dev-mesharc-mesharc":{"type":"http","url":"https://mcp.mesharc.dev/mcp"}}}{"mcpServers":{"dev-mesharc-mesharc":{"serverUrl":"https://mcp.mesharc.dev/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
详细介绍
mesharc
MeshArc API 的 Python 客户端:MeshArc:输入 URL,输出干净内容,并记录发生的变更。
- 抓取单个页面或一批页面——获取 Markdown、文本、HTML、链接、结构化字段和截图。
- 爬取整个网站,无需预先设置项目;如果发现值得持续关注,也可以将其设为项目。
- 映射网站在站点地图中声明的内容,之后再抓取。
- 搜索网页,找到 URL 未知的页面,并在同一次调用中读取每个结果页面。
- 提问Agent:它会搜索、读取页面并作答;如果你提供架构,它会按该架构输出答案,并附上答案所依据的页面。(mesharc.dev 上尚未启用。)
- 监控搜索或问题:每小时、每天或每周重新查询,将每次答案与上次比较,并在答案发生变化时通过 webhook 通知。(mesharc.dev 上尚未启用。)
- 监视网站随时间的变化:项目、定时运行和变更记录——逐字段记录新增、移除和修改的页面。
支持 Python 3.10 或更高版本。只有一个依赖项(httpx)。类型标注完整。
安装
pip install mesharc
身份验证
每次调用都需要 API 密钥。在应用的 Settings → API keys 中创建密钥——它只会显示一次——然后将其交给客户端,或者将其放入 MESHARC_API_KEY,并在构造客户端时不传入参数:
from mesharc import MeshArc
arc = MeshArc("mesharc_...")
# or, with MESHARC_API_KEY in the environment:
arc = MeshArc()
# options: MeshArc(api_key, timeout=150.0, max_retries=2)
密钥只会作为 bearer 请求头发送到 api.mesharc.dev。
密钥包含创建时设置的作用域(read、write、admin),还可以包含允许其访问的项目集合、过期时间和速率限制。密钥无权使用的路由会返回 403;密钥无权访问的项目会返回 404。
快速开始
from mesharc import MeshArc
arc = MeshArc("mesharc_...")
page = arc.scrape("https://example.com/pricing")
print(page["markdown"])
print(page["verdict"], page["method"], page["credits"]) # ok crawler 1
scrape 会一直保持请求打开,直到页面返回(默认 60 s),因此抓取普通页面时无需轮询。
阅读页面
单个页面
page = arc.scrape("https://quotes.toscrape.com/js/", config={"render_js": "always"})
config 可以是项目接受的任意设置,使用其 API 名称(render_js、only_main_content、formats、max_tier、wait_for_selector、actions 等)。完整列表及默认值请参见 mesharc.dev/docs/configuration。
page = arc.scrape_one(
"https://example.com/",
formats="markdown,text,cleanHtml", # which bodies to return
timeout_s=120, # how long the API holds the request (120 max)
idempotency_key="pricing-2026-09-18", # the same key returns the first answer for 24 h
)
多个页面
URL 列表会作为批次处理:按主机分组,根据配置允许的情况并行抓取,并为每个 URL 返回一行。
batch = arc.scrape(["https://a.com/", "https://b.com/x"], config={"concurrency": 4})
for row in batch["pages"]:
print(row["url"], row["httpStatus"], row["verdict"], row["credits"])
arc.scrape(urls, wait=False) 会立即返回批次 ID;arc.batch(id, wait=True) 会在之后完成该批次。传入 webhook_url=,即可通过通知获知结果,而无需轮询(batch.finished,使用一次性返回的密钥签名)。
页面是什么样的
每一行页面数据都包含相同的字段,无论它来自抓取、爬取还是项目:
| 字段 | 含义 |
|---|---|
markdown、text、cleanHtml、html、links、fields、screenshot | 你请求的内容 |
httpStatus | 网站返回的状态 |
verdict | ok、thin(页面较短,但确实是页面)、blocked(拒绝访问或返回 404)、skipped |
errorCode | OK,或出错原因:BLOCKED、NOT_FOUND、TIER_LIMIT、CAPTCHA、LOGIN_REQUIRED、RATE_LIMITED、TIMEOUT 等 |
shape、warnings、signals | 对于标记能说明内容类型的短页面,可能是 listing / table / form;short;以及判定结果的原因 |
method、tier、climbedTo | 读取页面的引擎(crawler、tls、minted、browser、browser-residential 等)、层级,以及被拒绝的页面经过了多少层级 |
credits、billedAs | 费用;如果计费层级与实际抓取层级不同,则显示计费所用层级 |
words、language、head、reason、crawledAt | 大小、语言、head 字段、判定说明和抓取时间 |
网站拒绝访问的页面费用为 0,404 页面也一样。
抓取网站
job = arc.crawl(
"https://docs.example.com",
limit=200, # page budget
maxDepth=3, # link hops from the seed
includePaths=["/docs/*"],
crawlMode="sitemap_first", # what the sitemap declares first, then links
maxTier="browser", # how far a refused page may climb
scrapeOptions={"formats": ["markdown", "links"]},
config={"crawl_delay_ms": 500}, # any project setting, directly
)
for page in job.pages(): # follows the cursor while the crawl runs
print(page["url"], page["words"])
print(job.status, job.envelope["counts"], job.envelope["creditsUsed"])
crawl 会立即返回一个句柄;job.pages() 会在页面到达时逐个返回,并在抓取结束时停止。wait=True 会阻塞直到抓取完成;job.wait()、job.refresh()、job.cancel() 的作用顾名思义;arc.get_crawl(id) 可重新连接到从其他地方启动的抓取。
一次性抓取会在 30 天后过期。如果某个网站值得持续监控:
project = job.keep(name="Docs", schedule="weekly")
选项中的 webhook=({"url", "events", "metadata", "include"})会收到 crawl.started、crawl.page(每条消息包含 50 个页面)和 crawl.completed 事件;签名密钥只会返回一次,作为 job.webhook_secret。设置 "include": ["markdown"] 后,crawl.page 消息也会携带每个页面的 Markdown,单条消息最多约 1 MB:如果页面内容超出限制或未被存储,则会以 "markdown": null 返回,并附带 markdownOmitted(too_large 或 not_stored)以及 contentUrl。contentUrl 是一个使用你的密钥读取的 API 路径(job.page(url) 也能读取相同内容)。这需要在事件中包含 crawl.page;如果省略 events,默认就会包含该事件。
映射网站
for u in arc.map("https://docs.example.com"):
print(u["url"], u["lastmod"])
details = arc.map_details("https://www.gov.uk/", search="visa", limit=500)
print(details["totals"], details["creditsUsed"]) # {'files': 29, 'urls': 508431, …} 29
读取每个站点地图文件需要消耗 1 个 credit;大多数网站只有一个文件。
搜索网络
out = arc.web_search(
"python packaging guide",
limit=5, # 1 to 10 results
freshness="month", # hour | day | week | month | year
include_domains=["python.org"], # exclude_domains= too; an exclude wins
scrape=True, # each result's page as markdown as well
)
if out["status"] == "blocked": # every engine refused; nothing was charged
print(out["attempts"])
else:
for hit in out["data"]:
print(hit["position"], hit["title"], hit["url"], hit["snippet"])
if hit.get("page"):
print(hit["page"].get("markdown", "")[:200])
print(out["engine"], out["cached"], out["creditsUsed"])
again = arc.get_search(out["id"]) # a search started earlier, by id
for s in arc.searches(q="python"): # the workspace's searches, newest first
print(s["id"], s["query"], s["status"], s["resultCount"], s["creditsUsed"])
news = arc.web_search("python release", news=True, freshness="day") # the engines' news results
for hit in news["data"]:
print(hit["publisher"], hit["age"], hit["title"]) # who published it, and how long ago ('20h')
more = arc.web_search("python packaging guide", page=2) # results 11-20
web_search 会等待结果,并返回完整的响应对象,搜索结果位于 data 中。搜索状态为 queued、running、done、blocked 或 error:blocked 表示所有搜索引擎都拒绝了请求,会直接返回,不会抛出错误;error 会抛出 MeshArcError。如果搜索会抓取页面,则会一直处于 running 状态,直到页面到达。scrape= 也接受字典,并会按原样发送({"formats": ["markdown", "links"], "maxCredits": 20});country= 和 lang= 指定搜索的地区和语言;wait=False 会立即返回;timeout_s 指定 API 保持请求的时长(默认 60 秒,最长 120 秒),之后客户端会轮询。
news=True 会搜索各引擎的新闻结果,而非网络搜索结果(每次搜索只能二选一);每条新闻结果还会带有 publisher 和 age,这些值由搜索引擎提供(如 "20h")。page= 指定结果页,范围为 1 到 10:第 2 页包含第 11–20 条结果,每一页都是独立搜索,并单独计费。news= 和 page= 需要使用支持这些功能的 API:它们位于 API 的功能分支中,目前尚未发布到 mesharc.dev。
搜索需要可写入的密钥,并会消耗 credits。结果页按读取该页面的搜索引擎收费;如果所有搜索引擎都拒绝提供结果页,则不收费。在一次搜索完成后的 1 小时内(新闻搜索为 10 分钟),若再次执行相同搜索(query、news、page、country、lang、freshness 和域名均相同),结果会从缓存中返回(cached 为 true),且结果页不收费。无论是否命中缓存,抓取的页面都会收费。
arc.search(project_id, q) 是另一种搜索:它会搜索项目已存储的页面,而非网络(见下文)。
向 Agent 提问
mesharc.dev 上的 Agent 路由(/agent)目前尚未上线;网页搜索已上线。客户端和 MCP 工具已准备就绪。Agent 的 webhook、connection_id、continue_() 和 field_sources 也需要 API 支持这些功能:它们目前位于 API 的功能分支上,尚未部署到 mesharc.dev。
Agent 会接收问题、自行搜索网页、梳理网站并阅读页面,然后根据所用页面给出回答。提供一个 schema,回答就会按该结构返回:
run = arc.agent(
"What does each plan on example.com cost per month?",
urls=["https://example.com/pricing"], # pages to start from, up to 20; omit to let it search
allowed_domains=["example.com"], # keep it to these sites, up to 20
schema={ # a JSON Schema of type object or array
"type": "object",
"properties": {"plans": {"type": "array", "items": {"type": "object", "properties": {
"name": {"type": "string"}, "monthly": {"type": "number"}}}}},
},
max_credits=500, # the most the run may spend: 1 to 100,000, 2,000 by default
max_steps=20, # the most steps it may take: 1 to 100, 40 by default
)
out = run.wait() # blocks until it finishes; the envelope
print(out["status"], out["creditsUsed"], out["steps"])
print(run.data) # {'plans': [...]}, matching the schema
for src in run.sources: # the pages the answer rests on
print(src["url"], src["title"])
print(run.field_sources) # {'plans[0].monthly': {'url': ..., 'pageId': ...}, ...}
agent 会立即返回一个 AgentRun,其状态为已排队或正在运行;提示词最多可有 10,000 个字符。timeout_s= 会要求 API 为回答暂缓返回请求(最多 120 秒)。如果没有 schema,回答会是 {"text": ...}。field_sources 会说明回答中每个值的来源:它的路径("plans[0].monthly",或列表回答中的 "[2].name")对应到 {url, pageId},即本次运行读取过的页面。
run = arc.agent(
"What does each plan on example.com cost per month?",
webhook={"url": "https://hooks.example.com/mesharc", "events": ["agent.completed", "agent.failed"]},
connection_id="conn_...", # run on one of the workspace's own LLM connections
)
print(run.webhook_secret) # once: the secret the webhook's messages are signed with
webhook= 可以是一个 URL,也可以是 {"url", "events", "metadata},用于接收 agent.started、agent.action、agent.completed、agent.failed 和 agent.cancelled 通知(默认包含全部五种事件);其签名密钥只会返回一次,位于 run.webhook_secret 中;continue_() 返回的句柄会沿用已停止运行的密钥(新运行会保留同一 webhook),而通过 get_agent 重新打开的运行不会包含密钥。connection_id= 会让 Agent 使用工作区自己的某个 LLM 连接运行——使用该连接的模型、密钥,并由该连接计费。
要查看运行过程,请跟踪事件:
run = arc.agent("Who maintains the httpx library, and where is it developed?")
for event in run.trace(): # follows the run until it ends
print(event["seq"], event["kind"], event["text"])
print(run.wait()["data"]) # {'text': '...'}
kind 可以是 start、resume、continue、model、search、fetch、render、map、select、extract、lookup(数据提供方的记录)、propose、tool、busy 或 finish。trace(after=seq) 会从已查看过的事件之后继续;follow=False 会返回现有事件,然后结束。
run.cancel() # {'id': ..., 'status': 'cancelled'} if it was queued, 'cancelling' if running
run.refresh() # the run as it stands now
again = arc.get_agent(run.id) # a run started earlier, by id
for r in arc.agent_runs(status="done"): # the workspace's runs, newest first
print(r["id"], r["status"], r["creditsUsed"], r["prompt"])
from datetime import date
for r in arc.agent_runs(model="openai:gpt-5.4-mini", since="2026-10-01", until=date.today()):
print(r["id"], r["model"], r["createdAt"])
运行状态可以是 queued、running、done、error、cancelled 或 credit_limit。wait() 会返回状态为 done、cancelled 或因达到 max_credits 而停止在 credit_limit 的运行——已找到的内容位于 data["partial"] 中;如果状态为 error,则会抛出 MeshArcError 502 job_failed,并附带 API 的错误文本;对于其他未知的结束状态,行为与 Node 客户端相同。取消操作会立即停止已排队的运行,并在正在运行的任务进入下一步之前将其停止;已使用的额度仍会扣除。无需句柄,也可以通过运行 ID 调用 arc.cancel_agent(id) 执行相同操作。
agent_runs 可按 status、按运行所用模型名称筛选,也可按运行创建时间筛选:时间在 since 或之后,以及在 until 之前;两者都可以是 ISO 8601 日期或日期时间字符串,也可以是 date / datetime。日期表示 UTC 午夜;不带时区的日期时间会被视为 UTC(因此,朴素的 datetime.now() 会被当作 UTC 时间读取你的本地时间——请改用 datetime.now(timezone.utc))。
因达到 credit_limit 而停止的运行可以使用新的额度继续:
out = run.wait()
if out["status"] == "credit_limit":
more = run.continue_(max_credits=1_000) # a new run, on the same thread
print(more.continues_run_id) # run.id
print(more.wait()["data"]) # the full answer, replacing the partial one
continue_() 会返回新运行的 AgentRun,从已停止运行中断的位置继续,保留已读取的页面(不会再次收费)及其 webhook;新句柄的 webhook_secret 会继续携带该 webhook 的密钥。max_credits 和 max_steps 属于新运行自己的设置;如果省略,则沿用已停止运行的设置。刷新后,已停止运行的 continued_by 会记录新运行。若运行并非因达到额度上限而停止、已继续过,或没有保存的进度,则会抛出代码为 conflict 的 MeshArcError 409;若已超过保留期限,则会抛出 410 expired。
运行需要具有写入权限的密钥,并会消耗额度:读取的每个页面都会按一次读取计费(被拒绝访问的页面免费);模型 token 则按模型提供商的价格加收 20% 计费——如果使用工作区自己的连接,则每 1,000 个 token 收取 1 个额度;所有费用都计入 max_credits。运行会保留 7 天;之后,get_agent 会抛出状态为 410、代码为 expired 的 MeshArcError,而 trace() 会静默结束。
后续跟进与对话
**尚未在 mesharc.dev 上线:**本节中的所有功能都需要 API 支持,这些功能目前位于 API 的功能分支上。
first = arc.agent("Which plans does example.com sell?", urls=["https://example.com/pricing"])
first.wait()
nxt = first.follow_up("And which of them include SSO?") # the same conversation
print(nxt.wait()["data"])
for c in arc.conversations(q="example.com"): # the latest message first
print(c["id"], c["messages"], c["title"])
thread = arc.conversation(first.id) # every run in it, oldest first, under "runs"
后续跟进会回答此前对话中提出和发现的所有内容,并且可以免费重新读取对话读取过的任何页面。它的请求独立于对话本身——follow_up 接受与 agent 相同的参数——并为自身的工作付费。只有对话的最新消息可以在处理完成后跟进一次;否则会返回 MeshArcError 409 conflict(错误中会指出最新消息),或者在超过 30 条消息后无法跟进。
已保存的登录信息、数据提供方和提案
**尚未在 mesharc.dev 上线:**本节中的所有功能都需要 API 支持,这些功能目前位于 API 的功能分支上。
run = arc.agent(
"How many employees does Stripe have, and what was Apple's revenue last year?",
data_connections=["conn_pdl", "conn_fmp"], # the workspace's data providers, one per provider
logins="auto", # the saved logins it may sign in with (the default)
proposals=True, # it may propose things to watch
)
data_connections= 最多指定工作区的两个数据提供方连接(见下文的 arc.connections):People Data Labs 用于公司数据,Financial Modeling Prep 用于金融数据。此后,Agent 可以使用你自己的密钥查询数据——费用由提供方收取,MeshArc 不收取额度——每条找到的记录都会成为 run.sources 中的一项,格式为 {url, title, provider},其中 url 是记录来源的请求(不会包含你的密钥)。Agent 会以未登录状态读取页面;若某个页面需要账户,则会使用工作区为该页面所属网站保存的登录信息(arc.logins),逐页登录。logins= 用于指定它可以使用哪些登录信息(默认使用全部,也可以指定 "none" 或 ID 列表);login_id= 是用于指定单个 ID 列表的旧版形式。登录信息需按部署单独启用。proposals=True 允许 Agent 提议抓取某个网站,或按计划保存搜索或问题;只有成员批准后才会创建(arc.proposals)。
监控器:按计划重复执行的搜索或提问
监控器(arc.monitors,/monitors 路由)需要 API 提供相应功能:该功能目前位于 API 的功能分支中,尚未上线到 mesharc.dev。因此,下文介绍其上限、提醒、记录和语气。
monitor = arc.monitors.create(
"search", # or "agent"
{"query": "mesharc", "freshness": "day"}, # a POST /search or POST /agent body, in the API's own names
"daily", # hourly | daily | weekly
name="Mentions",
webhook="https://hooks.example.com/mesharc", # told search.changed / agent.changed
)
print(monitor["webhookSecret"]) # once
for r in arc.monitors.runs(monitor["id"]): # newest first
print(r["createdAt"], r["status"], r["changed"], r["summary"])
press = arc.monitors.create("search", {"query": "mesharc", "sources": ["news"]}, "hourly") # a news monitor
arc.monitors.update(monitor["id"], schedule="weekly") # only what is not None is sent
arc.monitors.update(monitor["id"], webhook="") # the empty string removes the webhook
每次运行都是一次常规搜索或 Agent 运行,按一次计费,并与上一次已返回结果的运行进行比较:搜索会比较新增、移除和位置变动的结果;Agent 运行会比较答案中新增、更改和移除的值。运行的 refId 是它所创建的搜索或 Agent 运行(arc.get_search / arc.get_agent)。
request 是 API 中 POST /search 或 POST /agent 请求体使用的字段名称(驼峰命名),例如 {"query": ..., "includeDomains": [...]}、{"prompt": ..., "schema": ..., "maxCredits": ...},而不是 web_search 或 agent 的 Python 参数。因此,新闻监控器会指定 "sources": ["news"]:web_search(news=True) 会将其参数映射为该值,而 monitors.create 不会。搜索监控器仅读取结果页面,因此不接受 scrape。baseline_id= 接受一个已完成的搜索或 Agent 运行,且其请求必须与首次运行相同;该基线不会再次计费(对于搜索,基线的 limit 必须大于或等于监控器的值);若不指定,则从现在开始首次运行。
| 方法 | 功能 |
|---|---|
monitors.create(kind, request, schedule, name=None, webhook=None, baseline_id=None, idempotency_key=None, budget=None, alerts=None, add_to_project=None, rows=None, destination=None, sentiment=None) | 按计划持续运行搜索或 Agent 请求 |
monitors.list(kind=None) · monitors.get(id) | 工作区的监控器列表,每个监控器都包含 lastRun;或单个监控器 |
monitors.overview(kind=None) | 所有按计划运行的项目和监控器,包括下次和上次运行时间、本月支出、估算值和上限 |
monitors.update(id, name=None, schedule=None, status=None, webhook=None, budget=, alerts=, add_to_project=, rows=, destination=, sentiment=) | 更改非 None 的字段;status 可设为 active 或 paused,webhook="" 会移除 webhook |
monitors.pause(id) · monitors.resume(id) | 停止或重新启动计划;暂停期间错过的时段会在恢复时运行一次 |
monitors.run(id, idempotency_key=None) | 立即运行;运行期间会返回 409 conflict |
monitors.runs(id, limit=25) | 查看其运行记录,包含 changed、summary 和 diff,并可通过 next 获取后续记录 |
monitors.delete(id) | 删除监控器及其运行记录;它创建的搜索和 Agent 运行会保留 |
monitors.rows(id, active=None, q=None) · monitors.row(id, key) · monitors.row_events(id, run_id=None, kind=None) · monitors.export_rows(id, path, dataset="rows", fmt="csv") | 查看 Agent 监控器的记录、单条记录及其历史、变动记录,并将全部内容导出为文件 |
plans = arc.monitors.create(
"agent", {"prompt": "List example.com's plans with their monthly price", "urls": ["https://example.com/pricing"],
"allowedDomains": ["example.com"]}, "weekly",
budget={"perRun": 300, "perMonth": 1500}, # credits; 0 or left out is no cap
alerts={"email": True, "chatUrl": "https://hooks.slack.com/services/..."},
rows={"path": "plans", "key": "name"}, # follow the answer's list, plan by plan
)
for row in arc.monitors.rows(plans["id"]):
print(row["key"], row["attrs"], row["sources"]) # its columns, and the page each was read on
for event in arc.monitors.row_events(plans["id"]): # added, changed (before and after), gone
print(event["kind"], event["key"])
budget 限制每次运行的额度(perRun 会降低 Agent 监控器的 maxCredits)和每月额度(超过 perMonth 时会跳过到期运行,且 webhook 每月会收到一次 budget.reached)。当运行发现内容时,alerts 会发送工作区运行邮件和聊天消息(Slack、Discord、Teams 或 Google Chat 的传入 webhook)。搜索监控器的 add_to_project 会将该项目网站上的新增结果添加到项目中;Agent 监控器的 rows 会逐条跟踪列表答案中的记录,而 destination 会在每次运行后写出这些记录。新闻监控器的 sentiment={"connectionId": ..., "only": ["negative"]} 会使用工作区的某个大模型连接为每次运行中的新增结果评分(每 1,000 个 token 收取 1 个额度),并且只针对这些语气发送聊天和邮件。update 会整体替换 budget、alerts 和 sentiment(设为 {} 表示无);add_to_project="" 和 destination="" 会移除对应设置。
创建和运行监控器会消耗额度。除 monitors.list、monitors.get 和 monitors.runs 外,所有调用都需要具有写入权限的密钥。Webhook 密钥只会在设置它的调用中作为 webhookSecret 返回一次。arc.monitor() 是另一项功能:工作区的任务队列,可查看排队中和运行中的任务(见下文)。
表格上的 Agent
**尚未在 mesharc.dev 上线:**本节中的所有内容都需要具备这些功能的 API,目前这些功能位于 API 的功能分支上。
table = "Company,Website\nAcme,acme.com\nGlobex,globex.com\n"
ask = dict(csv=table, max_credits_per_row=300, urls=["{{Website}}"],
schema={"type": "object", "properties": {"plan": {"type": "string"}, "price_month": {"type": "number"}}})
print(arc.agent_batches.preview("What does {{Company}} charge for its cheapest plan?", **ask)["estimate"])
batch = arc.agent_batches.start("What does {{Company}} charge for its cheapest plan?", **ask, concurrency=3)
arc.agent_batches.wait(batch["id"])
for row in arc.agent_batches.rows(batch["id"]):
print(row["values"]["Company"], row["status"], row["data"])
arc.agent_batches.export(batch["id"], "prices.csv")
每行运行一个 Agent:提示词和起始 URL 从该行的列中填入,写作 {{column}};答案由 schema 塑造,类型为对象,并成为该行的新列。将表格作为 csv 或 rows 发送,后者是对象列表。必须提供 max_credits_per_row:每行到达上限时即停止,只按实际使用量付费;整个批次的花费绝不会超过行数 × 上限,如果开始时的余额更低,则不会超过该余额——无法再负担的行会被标记为 skipped,且不收费。如果不设置 allowed_domains,每行只能访问其自身 URL 所属的网站;设置为 [] 则不设限制。preview 会检查所有内容并估算费用,但不会启动任何任务。每行的 login_id 和 data_connections 均按 agent() 的方式传入。
| 方法 | 功能 |
|---|---|
agent_batches.preview(prompt, csv=, rows=, max_credits_per_row=, ...) | 查看将要运行的内容、无法运行的行及原因、首批提示词和费用估算;免费 |
agent_batches.start(prompt, ..., webhook=None, idempotency_key=None) | 启动批次;webhookSecret 只返回一次,webhook 会收到一次 agent.batch.finished 事件 |
agent_batches.get(id, offset=0, limit=50, status=None) · agent_batches.rows(id, status=None) · agent_batches.wait(id) | 获取一页行、所有行;阻塞等待批次结束 |
agent_batches.list() · agent_batches.cancel(id) · agent_batches.export(id, path, fmt="csv") | 查看批次;停止一个批次;将包含答案的表格保存为文件(可能以公式开头的 CSV 单元格会作为文本写入) |
批次会保留 7 天,之后 get 会抛出 MeshArcError 410 expired。
触发器、提案和月度费用
**尚未在 mesharc.dev 上线:**本节中的所有内容都需要具备这些功能的 API,目前这些功能位于 API 的功能分支上。
arc.triggers.create(
{"kind": "project", "id": project["id"]},
{"type": "agent", "prompt": "Summarise what changed on {{url}}", "maxCredits": 300},
when={"kinds": ["modified"], "paths": ["/pricing*"]},
limits={"cooldownMinutes": 60, "maxPerDay": 5},
)
for p in arc.proposals.list(): # what agents proposed to watch, waiting
print(p["kind"], p["name"], p["estimate"])
arc.proposals.approve(p["id"]) # or arc.proposals.dismiss(p["id"])
print(arc.estimate("agent", "weekly", max_credits=300)) # about what a month costs, before saving
| 方法 | 功能 |
|---|---|
triggers.create(source, action, when=None, limits=None, name=None, status=None) | 当项目变更或监控运行符合条件时,启动 Agent 运行、重新抓取、监控运行或重新检查 |
triggers.list(source_id=None, target_id=None) · triggers.get(id) · triggers.update(id, ...) · triggers.delete(id) | 查看和管理触发器 |
triggers.fires(id) · triggers.preview(id) | 查看每次来源运行匹配并启动的内容(保留 90 天);查看最新一次运行会如何处理,但不触发任何操作 |
proposals.list(status="proposed", run_id=None) · proposals.approve(id) · proposals.dismiss(id) | 批准后会创建项目或监控(监控的首次运行会立即启动) |
estimate(kind, schedule, project_id=, monitor_id=, max_pages=, max_tier=, max_credits=, month_cap=) | 估算按计划运行的抓取、搜索或 Agent 每月约需多少积分;尚未运行时返回 upTo |
子账户
**尚未在 mesharc.dev 上线:**本节中的所有内容都需要具备这些功能的 API,目前这些功能位于 API 的功能分支上。
Scale 或 Enterprise 工作区可以为自己的客户创建工作区:每个工作区都有自己的密钥、项目和数据,并在各自的额度上限内使用当前工作区的积分。每次调用都需要管理员密钥。
acme = arc.sub_accounts.create("Acme Ltd", monthly_cap=50_000, external_id="cust-42", idempotency_key="cust-42")
key = arc.sub_accounts.create_key(acme["id"], name="acme", scopes=["read", "write"]) # key["key"], once: for Acme
with arc.as_sub_account(acme["id"]) as acme_arc: # your key, acting on Acme: X-MeshArc-Tenant on every request
acme_arc.scrape("https://example.com")
arc.sub_accounts.update(acme["id"], suspended=True, reason="unpaid") # refuses its keys, cancels what it runs
| 方法 | 功能 |
|---|---|
sub_accounts.list() · sub_accounts.get(id) | {data, allowed, spent, remaining};查看单个子账户的月份、按类型划分的支出、费用,以及你的密钥具有的访问权限 |
sub_accounts.create(name, monthly_cap=None, external_id=None, idempotency_key=None) · sub_accounts.update(id, name=, monthly_cap=, suspended=, reason=) | 创建子账户;重命名、设置额度上限(0:无上限)、暂停或恢复 |
sub_accounts.keys(id) · sub_accounts.create_key(id, ...) · sub_accounts.revoke_key(id, key_id) | 管理供客户使用的专属密钥 |
as_sub_account(id) · MeshArc(key, tenant=id) | 创建客户端,使其每次请求都以该子账户的身份执行 |
连接和已保存的登录信息
尚未在 mesharc.dev 上线: 本节中的所有功能都需要使用具备这些特性的 API,目前这些特性位于 API 的功能分支上。
pdl = arc.connections.create("pdl", name="Company data", secrets={"api_key": "..."})
print(arc.connections.test(pdl["id"])) # {status: healthy | failing, error, detail}
print(arc.logins.list()["enabled"]) # saved logins: made in the app, from a live browser
connections 是工作区的连接,包括目标、模型和数据提供方:providers()、list()、create(provider, name=, config=, secrets=)、get(id)、update(id, ...)、delete(id) 和 test(id)。密钥会发送给 API,但不会返回。logins 用于列出工作区已保存的登录信息(list()),并对其执行 update(id, name=, expired=)、test(id) 和 delete(id);登录信息保存在应用中,并通过实时浏览器创建,其会话绝不会返回。两者都需要管理员密钥。
监控网站:项目和运行
project = arc.projects.create(
"https://docs.example.com",
name="Docs",
schedule="weekly", # manual | hourly | daily | weekly
config={"max_pages": 300, "include_paths": ["/docs/*"]},
)
run = arc.runs.start(project["id"], wait=True) # the first run
# ...a week later, or arc.runs.start again: the second run produces the change record
record = arc.changes(project["id"])
print(record["change"]["counts"]) # {'added': …, 'removed': …, 'modified': …, 'withheld': …}
diff = arc.page_diff(project["id"], "https://docs.example.com/pricing")
| 方法 | 功能 |
|---|---|
projects.list() · projects.get(id) · projects.update(id, name=, schedule=, retention=, config=) · projects.delete(id) | 项目 |
runs.list(project_id) · runs.start(project_id, wait=) · runs.wait(project_id, run_id) · runs.get(project_id, run_id) · runs.cancel(project_id, run_id) | 运行 |
pages(project_id, run_id=None) · page(project_id, url, run_id=None) | 某次运行的页面;完整查看单个页面 |
changes(project_id, run_id=None) · page_diff(project_id, url, run_id=None) | 变更记录;单个页面的逐词差异 |
search(project_id, q, mode="content" | "selector", run_id=None) | 哪些页面提及这些内容(词语、"phrases")或包含这些内容(CSS / XPath) |
recrawl(project_id, urls) | 立即重新抓取这些页面 |
sources(project_id) | 种子 URL、站点地图、URL 列表、信息源和模式,以及上次运行通过各来源发现的内容 |
export(project_id, path, dataset="pages", fmt="jsonl", run_id=None, urls=None) | 将数据集(pages、markdown、changes、fields、sitemap)以 jsonl 或 csv 格式流式写入文件 |
arc.export(project["id"], "pages.csv", dataset="pages", fmt="csv")
工作区
me = arc.me() # the workspace, its plan and limits, credits used and remaining, what this key may do
usage = arc.usage() # pages per day, this month by engine
queue = arc.monitor() # the job queue: what is queued and running (not arc.monitors)
meta = arc.meta() # verdict meanings, engine costs, the config defaults
keys = arc.keys()
key = arc.create_key("ci", scopes=["read", "write"], projects=[project["id"]], expires_in_days=90) # key["key"], once
arc.revoke_key(key["id"])
错误
所有失败都会引发 MeshArcError:
from mesharc import MeshArc, MeshArcError
try:
arc.crawl("https://example.com", limit=1_000_000)
except MeshArcError as exc:
print(exc.status, exc.code, exc.detail, exc.request_id)
code | 状态码 | 含义 |
|---|---|---|
validation | 400 / 422 | 请求中有内容不正确;detail 会说明具体问题 |
unauthorized | 401 | 未提供密钥,或密钥已撤销或过期 |
plan_limit | 402 | 套餐不包含此功能,或额度已用完 |
forbidden | 403 | 密钥的作用域不允许此操作 |
not_found | 404 | 不存在此对象,或此密钥无权查看 |
conflict | 409 | 请求与当前状态冲突 |
expired | 410 | Agent 运行已超过保留期限(7 天) |
rate_limited | 429 | 超出密钥的速率限制;X-RateLimit-Reset 会说明何时重置 |
internal | 500 | 联系支持时请提供 request_id |
request_id 是 API 添加到响应和自身日志中的 ID,因此联系支持时只需提供这个字符串。
另有两种情况:网络故障或请求超时会引发 MeshArcError,其 status == 0,且 code 为 network 或 timeout;客户端停止等待的任务会引发 MeshArcTimeoutError——它既是 MeshArcError,也是 TimeoutError——其中包含 job_id,方便你稍后轮询任务(arc.get_crawl(id)、arc.batch(id)、arc.get_search(id)、arc.get_agent(id))。
幂等性与超时
scrape、scrape_one、crawl、web_search、agent、AgentRun.continue_、AgentRun.follow_up、monitors.create、monitors.run、agent_batches.start和sub_accounts.create接受idempotency_key=:在 24 小时内再次发送相同的键,会返回首次的结果,而不是创建第二个任务。MCP 服务的search_web工具特意不发送幂等键:已完成的搜索在一小时内再次执行相同搜索时,无论如何都会从缓存返回结果。它的run_agent、continue_agent和create_monitor工具会自行发送幂等键,根据输入和十分钟时间窗口生成,因此在同一十分钟内重试会命中同一次运行,之后重新运行则会创建新的运行。- 等待调用接受
poll=(轮询间隔秒数)和timeout=(触发TimeoutError前的秒数),大多数还接受wait=:wait=False会立即返回信封。各调用的默认值不同:scrape、batch、crawl(以及Crawl.wait、Crawl.pages)和runs.start/runs.wait每隔 3 秒轮询一次,最长等待一小时;scrape_one和web_search每隔 2 秒轮询一次,最长等待十分钟;map/map_details和extract每隔 2 秒轮询一次,最长等待五分钟;AgentRun.wait和AgentRun.trace每隔 2 秒轮询一次,最长等待一小时。 - 单次抓取的
timeout_s表示 API 保持请求打开的时长(默认 60 秒,最长 120 秒);对于加载较慢的页面,会返回一个 ID,供后续轮询。 MeshArc(..., timeout=150.0)是每个请求的 HTTP 超时。请求在安全可重试时会重试(适用于 GET、DELETE,或带有幂等键的 POST),遇到 429、502、503、504 和网络故障时,最多重试max_retries次(默认 2 次),并遵循Retry-After。
点数
每个响应都会说明费用:页面上的 credits、任务信封中的 creditsUsed、HTTP 响应中的 X-MeshArc-Credits。页面按读取它的引擎计费——普通抓取 1 点,渲染 4 点;被拒绝的页面或 404 不收费。网页搜索结果页按读取它的引擎计费;如果所有引擎都拒绝,则不收费。已完成的搜索在一小时内(新闻搜索为十分钟)再次进行相同搜索(查询词、新闻或网页、结果页、国家/地区、语言、新鲜度和域名均相同),结果页会从缓存返回,不收费;但抓取的页面无论如何都会收费。Agent 运行会按读取的每个页面收费(被拒绝的页面免费),并按模型提供商的价格收取模型 Token 费用,另加 20%(使用工作区自有的大模型连接时,每 1,000 个 Token 收取 1 点),费用受其 max_credits 限制(默认 2,000 点);creditsUsed 统计这两项费用。监控器的运行按其搜索和 Agent 运行的费用计费。计费安排和套餐见 mesharc.dev/docs/billing。
MCP 服务
此软件包还提供 MeshArc MCP 服务,因此 Claude Desktop、Claude Code、Cursor 以及任何 MCP 客户端都可以将抓取、爬取、映射、网页搜索、向 Agent 提交问题以及读取变更记录作为工具使用。需要 Python 3.10 及以上版本。
我们提供托管服务,因此大多数人无需安装任何内容:
# Claude Code
claude mcp add --transport http mesharc https://mcp.mesharc.dev/mcp
它会打开浏览器一次,让你登录 mesharc.dev 并授权,之后即可使用。授权后会为应用签发专属 API 密钥,你可以在 Settings → API keys 中查看和撤销;除非你允许进行更改,否则该密钥为只读。任何支持远程 MCP URL 的客户端——Claude.ai 和 ChatGPT 连接器、Claude mobile、Cursor——都可以用相同方式连接。
或者,使用你自己的密钥,通过 stdio 自行运行:
pip install "mesharc[mcp]"
MESHARC_API_KEY=mesharc_... mesharc-mcp # serves over stdio
# Claude Code
claude mcp add mesharc -e MESHARC_API_KEY=mesharc_... -- mesharc-mcp
工具:scrape_urls、map_site、crawl_site、search_web、list_projects、get_project、create_project、update_project、delete_project、start_run、list_runs、list_pages、get_changes、get_job、cancel_job、run_agent、continue_agent、create_monitor、list_monitors、pause_monitor 和 get_monitor_rows(最后六个目前尚未在 mesharc.dev 上线:它们依赖 API 的 Agent 和监控功能,该功能位于其功能分支)。create_monitor 会按计划定期执行网页搜索或 Agent 提问,并将每次运行的结果与上次比较;list_monitors 会显示监控项及其运行记录,pause_monitor 可暂停或恢复某个监控项,get_monitor_rows 可读取 Agent 监控所跟踪的记录。create_project 接受 max_credits_per_run 和 max_credits_per_month。get_project 会显示项目的 webhook 及其近期投递记录,update_project 可设置 webhook 并向其发送测试(test_webhook=true)。每个工具都是通过此客户端调用的,其回答均由输出架构描述。工具调用失败时会返回 {error, status};如果 API 提供了 code 和 request_id,也会一并返回。
包含许多页面的结果只是地图,并非疆域本身:爬取结果会返回已读取页面的索引(最多 500 个;如预算需要留出空间,则会更少)、在最多 60,000 个字符的预算内提供的摘录(最多 50 个),以及每个页面的链接数量,而不是链接本身。使用 get_job(kind="crawl", id=…, url=…) 单独请求所需页面,或通过 cursor= 继续爬取。多 URL 抓取超出预算时,会按状态统计其余页面,并列出所有未成功返回的页面。单独请求一个页面时——使用带 url 的 list_pages,或使用单个 url 调用 scrape_urls(设置 full=true 可获取完整格式)——会返回完整页面,最多 12,000 个字符。search_web 的回答也会限制在相同的 60,000 个字符内,其中抓取结果页面以摘录形式呈现;使用 scrape_urls 并设置 full=true 可完整读取其中一个页面。Agent 的回答也有相同限制:超出预算时,会先省略 fieldSources(回答中各个值的来源),然后截断来源列表,保留前 50 个(如果会占用超过一半空间,则更少),最后才会将 data 替换为其 JSON 文本并进行截断,同时注明完整回答所在位置——GET /api/v1/agent/{id},或在此客户端中调用 get_agent(id)。
在托管模式下,连接可以获批为只读,而只读模式有一点值得注意:它可以读取整个工作区,但无法访问网站。任何会获取数据的操作都是写操作,因为它会离开工作区,通常还会消耗额度——map_site 只读取网站的站点地图,每个站点地图文件消耗 1 个额度,并与其余操作一样受到限制。list_projects、get_project、list_runs、list_pages、get_changes、get_job、list_monitors 和 get_monitor_rows 可以使用;会获取数据或写入的 13 个工具——scrape_urls、map_site、crawl_site、search_web、run_agent、continue_agent、create_monitor、pause_monitor、create_project、update_project、delete_project、start_run、cancel_job——会返回 {"code": "read_only"} 并说明如何处理,直到重新连接应用并勾选“Also allow changes”(写入权限)。delete_project 不仅需要写入权限,还需要管理员 API 密钥,而已连接的应用不会持有此密钥,因此只能在本地使用此类密钥运行。
在托管模式下,对于需要保持连接数分钟的工具——爬取、等待运行完成的请求、多 URL 抓取、网页搜索——在 MESHARC_MCP_WAIT 秒后(默认为 25 秒,因为许多 MCP 主机会更早超时)会返回一个任务。工作会在服务器端继续执行;get_job 可获取任务状态,cancel_job 可停止爬取、运行、批处理或 Agent 运行。对于会抓取页面的搜索,如果搜索结果已返回但页面仍在陆续到达,回答会包含搜索结果和一个用于获取页面的任务。在本地运行时,这些工具会像以往一样阻塞。
run_agent 会让指定了起始 urls 的 Agent 留在这些 URL 所属的网站上,除非 allowed_domains 另有规定,应用也是如此:Agent 读取的页面可能会指示它前往其他地方。allowed_domains=[] 表示可以读取任何网站;如果没有 urls,就没有任何限制。客户端的 agent() 会按收到的内容发送,因为 API 就是这样接收参数的:不提供 allowed_domains 不代表没有限制。
无论托管还是本地运行,run_agent 都不会等待:Agent 需要运行几分钟,因此它会立即返回一个作业,以及截至目前本次运行已花费的额度。使用 get_job(kind="agent", id=…) 跟踪作业——每隔 30 到 60 秒查询一次——作业完成后会返回结果;cancel_job(kind="agent", id=…) 会在 Agent 进行下一步之前停止它,但已使用的额度仍会扣除。运行记录会保留 7 天;之后,get_job 会返回 status: "expired"。如果重试时该运行已经完成,则会返回其结果。如果运行因达到预算上限而停止(status: "credit_limit",已找到的内容位于 data.partial),可通过 continue_agent(id=…, max_credits=…) 继续:它会在同一线程上启动新运行,从停止处继续,并保留已读取的页面,不会再次收费。它也会以相同方式返回新运行的作业。如果运行并非因达到上限而停止,则会以代码 conflict 拒绝;如果再次请求已继续过的运行——包括超过 10 分钟的幂等键有效期后重试——则会返回继续运行的作业,不会启动新运行。
自行托管
MESHARC_MCP_PUBLIC_URL=https://mcp.example.dev/mcp MESHARC_OAUTH_ISSUER=https://api.example.dev MESHARC_INTROSPECT_SECRET=... mesharc-mcp --http --host 127.0.0.1 --port 8040
服务端不保存任何密钥。每个请求都携带调用方的 OAuth 令牌;令牌会根据 MESHARC_OAUTH_ISSUER 验证,然后用于发起调用,因此一个进程可以服务多个工作区。在此模式下不会读取 MESHARC_API_KEY;如果设置了该变量,启动时会提示它将被忽略。
在反向代理后运行时,请注意,绑定 127.0.0.1 会使 MCP SDK 启用 DNS 重绑定保护,并仅允许 Host 为 localhost。因此,服务会根据 MESHARC_MCP_PUBLIC_URL 配置自己的允许列表;如果未设置该变量,所有经代理的请求都会被拒绝。使用 MESHARC_MCP_ALLOWED_ORIGINS(以逗号分隔)将基于浏览器的客户端添加到 Origin 允许列表中。
隐私与安全
客户端只与一个主机通信——API 基础 URL,除非 MESHARC_API_URL 或 base_url= 指定其他地址,否则为 https://api.mesharc.dev——不会与其他主机通信。密钥只通过 bearer 标头传输,且仅使用 HTTPS。不会向磁盘写入任何内容,也不会发送遥测数据。客户端只读取 MESHARC_API_KEY 和 MESHARC_API_URL;HTTP 模式下的 MCP 服务会读取 MESHARC_MCP_PUBLIC_URL、MESHARC_OAUTH_ISSUER、MESHARC_INTROSPECT_SECRET、MESHARC_MCP_WAIT 和 MESHARC_MCP_ALLOWED_ORIGINS,并且不使用自己的密钥。
MeshArc 会保存哪些关于你和你抓取的页面的信息,以及保存多久,请参阅隐私政策。有关服务安全措施,请参阅安全页面。如需报告此客户端或服务中的漏洞,请发送邮件至 [email protected],而不要公开提交 issue——请参阅 SECURITY.md。
其他信息
客户端是一个轻量封装:每个方法对应一次 API 调用,并以 dict 形式返回 API 的 JSON。完整参考文档见 mesharc.dev/docs/api。使用完毕后调用 arc.close(),或将客户端用作上下文管理器。
- 文档:mesharc.dev/docs
- Node 客户端:
npm install mesharc— mesharc-node - Issues 和拉取请求:mesharc-python
- 问题咨询:[email protected]
MIT。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。