知识

为什么你的 MCP 服务器需要一层代理

会抓取实时网页数据的 MCP 服务器,会撞上和任何爬虫一样的封禁与地理墙。本文讲清楚原因,以及如何把 MCP 服务器的请求路由经住宅代理。

Chris Collins

Chris Collins

2026年6月20日 · 2 分钟阅读

Model Context Protocol(MCP)先解决了问题里错误的那一半,这不是批评,只是事情发生的顺序。MCP 给了我们一种干净、标准的方式,把语言模型连接到工具和数据上。它刻意没做的,是解决当其中一个工具伸手去够开放的互联网、而开放的互联网用一个 403 回敬它时该怎么办。

如果你构建或运行过一个会取 URL、抓页面、查搜索结果、或为模型拉取实时数据的 MCP 服务器,你很可能已经撞上过这堵墙。服务器在你笔记本上跑得好好的。你把它部署到云上的一台机器,让你的 agent 指向它,然后突然一半的 fetch 回来的是 CAPTCHA 页面、“访问被拒绝”、或者跳到错误国家站点的地理重定向。你的 MCP 代码什么都没变。变的是 IP。

这篇文章讲的就是这道缝隙:为什么取网页数据的 MCP 服务器会被封、为什么这不是 MCP 该修的问题、以及一层住宅代理如何用几行代码把它补上。

快速回顾:网页访问发生在哪里

MCP 有一套干净的角色分工。一个 host(Claude Desktop、一个 IDE、一个 agent 运行时)运行一个 MCP client,client 跟一个或多个 MCP 服务器对话。每个服务器暴露工具、资源和 prompt。当模型决定使用某个工具时,调用沿 host → client → 服务器 流动,服务器做真正的工作,结果再流回来。

对本次讨论重要的部分是:服务器才是真实世界副作用发生的地方。当你构建一个 fetch 工具、一个 search 工具或一个 scrape_page 工具时,出站的 HTTP 请求是从 MCP 服务器进程发出的——无论那个进程恰好在哪里运行。不是模型发的请求。不是 host。是服务器。

所以目标网站看到的 IP,是 MCP 服务器的 IP。这就是全部问题所在。

取网页的 MCP 服务器为什么会被封

三件事叠加在一起,而且它们叠加起来专门针对 MCP 服务器,针对一个带浏览器的人类时却不会这样。

你的 MCP 服务器几乎肯定在一个数据中心 IP 上。 如果你把它部署到 AWS、GCP、Fly、Render、一台 VPS 或任何云主机上,它的出站 IP 属于一个托管商的 ASN。反爬系统(Cloudflare、Akamai、DataDome)把数据中心 ASN 当作有罪推定。一个从 AWS IP 发往受保护站点的请求,在服务器还没发出一个请求头之前就被标记了。这是”本地能跑”变成”生产被封”的最大原因:你家里的连接是住宅的,你的云机器不是。

Agent 流量是阵发且集中的。 Agent 不像人那样浏览。它在一个很窄的时间窗里连发一串请求,常常打向同一个域名,然后安静下来。从一个 IP 看,这种模式立刻被读成自动化。我们另文写过 AI agent 流量的形状,简短版是:人类永远不会触发的速率限制和行为检测,agent 却会不停触发,因为所有流量都来自同一个地址。

你没有地理控制。 你的 MCP 服务器跑在一个区域。如果模型需要以一个德国购物者的身份看一个产品页、以一个东京用户的身份看一条搜索结果、或以一个美国客户的身份看一个价格,一个单区域服务器从物理上就做不到。站点会对服务器的 IP 做地理定位,然后悄无声息地提供错误国家的内容。模型于是基于它以为正确、其实并不正确的数据去推理。

这些都不是你 MCP 服务器里的 bug。它们是”它跑在哪里、它是什么形状”的属性。MCP 在做的正是它的本职——传递工具调用。它从来就不是用来洗 IP 的。

为什么这不该由 MCP 来解决

值得说明白,因为有时人们在等协议长出一个不会来的功能。MCP 是一个用于模型-工具通信的协议。它标准化的是一个工具如何被描述、被调用,以及结果如何回来。client 和服务器之间的传输(stdio,或 Streamable HTTP)关乎 agent 的管道,而不关乎服务器怎么到达互联网。

你的 fetch 工具如何跟外部世界对话,是你服务器的实现细节,就跟你用哪个 HTTP 库、怎么解析响应一样。协议不该规定它,也确实没规定。这意味着网页访问这一层是你要自己加的。好消息是,它是一层很小、被充分理解的东西:把服务器的出站请求路由经住宅 IP。

解法:在 fetch 工具背后放一个住宅代理

模式很简单。与其让你 MCP 服务器的工具从它的数据中心 IP 直接发请求,不如让它通过一个住宅代理网关发请求。目标站点看到的是一个在正确国家的真实消费者 IP,带着一条真实家庭连接的信任画像,请求就过去了。

下面是一个最小的 Python MCP 服务器,带一个路由经 Shifter 网关的 fetch_url 工具。MCP 脚手架是标准的 FastMCP;重要的部分是 HTTP 客户端上的 proxies 接线。

import os
import httpx
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("web-fetch")
# 一个网关端点,定位编码在用户名里。
# 凭证放在 env 里,绝不要写进工具定义。
USER = os.environ["SHIFTER_USER"] # 例如 "customer-<id>"
PASS = os.environ["SHIFTER_PASS"]
GATEWAY = "p.shifter.io:443"
def proxy_url(country: str = "us") -> str:
return f"http://{USER}-country-{country}:{PASS}@{GATEWAY}"
@mcp.tool()
async def fetch_url(url: str, country: str = "us") -> str:
"""通过给定国家的一个住宅 IP 取一个 URL。"""
proxies = proxy_url(country)
async with httpx.AsyncClient(proxy=proxies, timeout=30) as client:
r = await client.get(url, follow_redirects=True)
r.raise_for_status()
return r.text
if __name__ == "__main__":
mcp.run()

这就是全部的改动。模型调用 fetch_url("https://example.com/product", country="de"),请求经一个德国的住宅 IP 离开,站点把德语页面提供给一个看起来像德国购物者的来访者。换一下 country,模型就能用另一个市场的眼睛看同一个页面,不用重新部署,也不用第二台服务器。

地理定位才是 agent 最需要的那部分

封禁问题是人们最先注意到的,但地理问题才是悄悄把结果搞坏的那个。一个被钉在单一区域的 MCP 服务器,就是一个透过一个猫眼看世界的 LLM。

因为 Shifter 网关把定位编码在用户名里,你的 fetch 工具可以接受一个 country(或州、城市,甚至 ASN)参数,让模型按调用来选。这就把一个单区域服务器变成了一个全球的。一个跨市场比价的研究 agent、一个检查站点在五个国家如何渲染的本地化 QA agent、一个以本地用户身份拉 Google 结果的 SERP 监测 agent——它们都需要的正是这个,而一个普通的、托管在云上的 fetch 工具给不了。

对于 agent 需要在多次调用间保持同一个 IP 的多步骤流程(先登录,再走一串需要认证的页面),通过在用户名里包含一个会话 ID 和一个 TTL 来加一个 sticky 会话,同一个 IP 会在 TTL 的生命周期内保持:

def sticky_proxy_url(country: str, session: str, ttl: int = 600) -> str:
return f"http://{USER}-country-{country}-sid-{session}-ttl-{ttl}:{PASS}@{GATEWAY}"

现在一个 browse_session 工具就能在一项任务的各个步骤间携带一个稳定的身份,而不是在流程中途换 IP、触发站点上的每一项会话检查。

需要记住的几点

一层代理不是一个”永不被封”的魔法开关,把它当成那样,正是人们浪费带宽的方式。几点实践提示:

积极地缓存。 Agent 会不停地重复取相同的 URL。在你的 fetch 工具前面放一个小缓存,能大幅削减代理带宽(以及你的成本),对模型来说也更快。已经有答案的请求,就别再走代理了。

把国家透传进去,别写死。 全部价值就在于按调用做地理控制。把 country 做成一个模型可以设置的工具参数,带一个合理的默认值,而不是把某一个区域烤进服务器里。

尊重目标。 代理改变的是请求从哪个 IP 发出,而不是你是否应该发这个请求。在 robots.txt 起承载作用的地方尊重它、把请求速率保持在合理范围、别因为封禁消失了就猛锤一个站点。我们的可接受使用政策是”什么被允许”的最终依据。

让凭证待在工具 schema 之外。 代理的用户名和密码住在服务器的环境变量里,绝不在模型看得到的工具定义里。模型应该选一个国家,而不是持有你的网关密码。

对未受保护、地理中立的 fetch,数据中心就够了。 如果一个工具只打你自己的 API、或那些既不封禁也不做地理定位的公开端点,它不需要代理。把住宅这一层留给那些真正面临封禁、或需要地理定位的开放网页 fetch。(关于每种 IP 类型分别适合什么时候的更完整对比,见住宅代理 vs 数据中心代理。)

常见问题

MCP 有内置的代理支持吗? 没有,也不该有。MCP 标准化的是模型-工具通信,不是一个工具如何到达互联网。出站网页访问是你服务器的实现细节。你在工具内部、HTTP 客户端这一层加一个代理,就像上面展示的那样。

为什么不干脆从一条住宅连接上运行我的 MCP 服务器? 你可以,但它不可扩展、不给你按请求的地理控制,还把你 agent 的可靠性绑在一条家庭连接的在线时间和单个 IP 上。一个住宅代理网关给你一个大的、轮转的池子,以及按调用的国家定位,而不用在一条住宅线路上托管任何东西。

代理能完全阻止我的 agent 被封吗? 它移除了最大的原因(数据中心 IP)并给了你地理控制,但行为仍然重要。阵发的请求模式、缺失或不一致的请求头、无视速率限制,都仍然可能让你被标记。代理是必要的,不是充分的——把它和合理的请求行为搭配起来。

这对 TypeScript 的 MCP 服务器也适用吗? 适用。概念完全一样——把你的 HTTP 客户端(带 agent 的 fetch、axios、undici、got)配置为经网关路由。代理 URL 的格式和按请求的定位,跟语言无关,都是一样的。

模型能按请求选国家吗? 能,这正是推荐的模式。把 country(以及可选的州/城市)作为一个带默认值的工具参数暴露出来。模型根据任务来设置它,你的服务器再即时把它编码进代理用户名。

经代理路由会改变 Shifter 怎么向我计费吗? 不会。它就是标准的住宅网关,按通常的每 GB 价格计费。一个 MCP 服务器只是又一个连到同一个端点的客户端。带宽就是带宽。

结论

MCP 是对”一个模型如何调用一个工具”的一个干净回答。它从来就不是用来回答”那个工具如何从一个被标记的、在错误国家的数据中心 IP 去够一个有敌意的开放网页”的。第二个问题是真实的,是你要回答的,而答案就是在你的 fetch 工具背后放一层薄薄的住宅代理。

接线只有几行。回报是一个 MCP 服务器,它的网页工具在生产里真的能用、返回正确国家的数据、并且在 agent 第一次把它们指向一个受保护站点时不会翻车。如果你在构建连接网页的 agent,从住宅网关开始,从第一天起就把它放在你的工具背后——这比等封禁开始之后再来加要容易得多。关于如何给 agent 可靠的网页访问,更多内容见浏览网页的 AI agent 最佳代理

标签: mcp ai agents model context protocol residential proxies llm tools web access

准备好开始了吗?

试用 Shifter 住宅代理,205M+ 个 IP,195+ 个国家,低至 $1.00/GB。

立即开始