把本地 MCP 工具临时暴露给 AI 客户端:用 cpolar 排查 Resource 为什么看不到
把本地 MCP 工具临时暴露给 AI 客户端用 cpolar 排查 Resource 为什么看不到搞了一个本地 MCP Server规规矩矩注册了两个 Resource本地跑起来一切正常。结果接到 AI 客户端一看——Resource 列表空空如也一个都看不到。这个问题在 MCP 开发者社区里太常见了掘金上甚至有一条热帖就在问同一件事。原因通常不是 Resource 注册错了而是客户端和服务器的网络链路没走通——尤其是当你的 MCP Server 跑在 SSE 或 Streamable HTTP 传输层上时客户端无法主动回连到你的本地端口resources/list请求根本没有到达服务器。这篇就记录一个我自己的排查办法用 cpolar 给本地 MCP Server 开一个临时公网地址让 AI 客户端能直接回调进来看看 Resource 列表到底有没有正常暴露。1 什么场景下 Resource 会看不到先明确一下这篇文章要解决的具体问题。你的 MCP Server 可以长这样——用 Python FastMCP 或者 TypeScript SDK 写的一个服务器在本地监听一个 HTTP 端口from mcp.server.fastmcp import FastMCP mcp FastMCP(demo-server) mcp.resource(config://app/settings) def get_settings() - str: 返回应用配置项 return themedark\nlanguagezh-CN\nmax_items50 mcp.resource(docs://help/about) def get_about() - str: 返回关于页面内容 return # About\n\nThis is a demo MCP server. if __name__ __main__: mcp.run(transportsse)启动之后服务器在http://localhost:8000/sse上等客户端连进来。问题出在当你把 MCP Server 配成 Streamable HTTP 或 SSE 模式时客户端和服务器是双向通信的。客户端需要先连接到你的 SSE 端点服务器才能通过这个长连接把 Resource 列表推回去。如果客户端在另一台机器上、或者在 Docker 容器里、或者在 AI Studio 的云端运行时里——它连不上你的localhost:8000resources/list请求就永远发不出来。这不是 Resource 注册错了这是网络链路没打通。2 环境准备先确认本地能跑通在动手暴露到公网之前先确认本地环境一切正常。这一步花不了两分钟但能帮你后面少走很多弯路。2.1 确认 MCP Server 正常启动终端执行python mcp_demo_server.py看到类似这样的输出INFO: Started server process [12345] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://localhost:8000说明服务器已经在本地 8000 端口上监听 SSE 连接了。2.2 用 curl 快速验证 SSE 端点开另一个终端执行curl -N http://localhost:8000/sse正常情况下你会看到 SSE 的初始化事件输出类似event: endpoint data: /message?session_idabc123 event: initialized data: {}如果你看到Connection refused或者curl: (52) Empty reply from server说明服务器本身就没起来先回去修不要急着往外穿透。2.3 用 MCP Inspector 本地测一次 Resource官方 MCP Inspector 是排查这类问题最趁手的工具npx modelcontextprotocol/inspector打开浏览器访问http://localhost:5173在连接方式里选 Streamable HTTP地址填http://localhost:8000/sse。连接成功后点Resources标签页你应该能看到刚才注册的两个 Resource。这一轮本地测试过了说明 Resource 注册本身没有问题。那为什么 AI 客户端看不见多半是客户端那端连不回来。3 用 cpolar 给 MCP Server 生成公网地址本地确认正常下一步就是让 AI 客户端能连到你的 MCP Server。你要做的不是改代码也不是重写 Resource而是在中间加一个公网跳板让客户端能把回调请求发进来。3.1 安装 cpolar如果你机器上还没装 cpolar按平台选一个命令macOSHomebrewbrew install cpolarLinux一键脚本curl -L https://www.cpolar.com/static/downloads/install-release-cpolar.sh | sudo bashWindows去官网下载页面 https://www.cpolar.com/download 下载 Windows 安装包双击安装。3.2 注册并获取 tokencpolar 需要一个 token 来绑定你的账号。注册地址https://dashboard.cpolar.com注册完成后进入仪表盘在Auth Token页面复制你的 token然后在终端执行cpolar authtoken 你的token这条命令会把 token 写入配置文件后续启动隧道时自动带上。3.3 启动 HTTP 隧道MCP Server 刚才监听的是 8000 端口cpolar 对 HTTP 隧道要映射的就是这个端口cpolar http 8000命令执行后终端会停留在前台输出类似Forwarding https://abc123.cpolar.cn - http://localhost:8000 Forwarding http://abc123.cpolar.cn - http://localhost:8000 Web Interface http://127.0.0.1:9200看到这一行说明隧道已经建成了。https://abc123.cpolar.cn就是你 MCP Server 的临时公网地址。注意这个地址是 cpolar 免费套餐生成的随机地址24 小时内会变化。这篇文章只做临时调试用用完之后关掉即可。如果后续需要长期固定地址考虑基础套餐的固定二级子域名。3.4 验证公网地址能访问 MCP Server用公网地址替换掉本机地址再跑一遍 curlcurl -N https://abc123.cpolar.cn/sse如果能看到和之前一样的 SSE 事件输出恭喜公网链路已经打通了。如果返回 404 或者连接超时先检查MCP Server 是否还在运行隧道是否显示online防火墙是否放行了 8000 端口检查隧道状态最方便的方式是打开http://127.0.0.1:9200在 Web UI 里看隧道是否在线。4 让 AI 客户端通过公网地址连接并验证 Resource公网地址到手了现在让 AI 客户端用这个地址去连 MCP Server。4.1 配置客户端连接地址不同的 MCP 客户端配置方式不一样这里列两个最常见的场景Claude Desktop或同类本地客户端在claude_desktop_config.json中把 MCP Server 的配置改为{ mcpServers: { demo-server: { command: npx, args: [ -y, modelcontextprotocol/inspector, --connect, https://abc123.cpolar.cn/sse ] } } }自定义 MCP ClientPythonfrom mcp import ClientSession from mcp.client.sse import sse_client async def test_resources(): async with sse_client(https://abc123.cpolar.cn/sse) as streams: async with ClientSession(streams[0], streams[1]) as session: await session.initialize() resources await session.list_resources() for r in resources: print(f {r.name}: {r.uri})4.2 验证 Resource 列表连接成功后在客户端里请求 Resource 列表。如果能看到你注册的那两个 Resource说明问题不在代码在网络——之前本地看不到纯粹是客户端连不回来。如果公网地址连上去之后 Resource 列表仍然为空那问题就出在服务器端的 Resource 注册逻辑上了。这个时候需要回来检查4.3 Resource 不可见的常见原因原因 1capabilities 声明缺失MCP 协议要求服务器在 initialize 阶段声明自己支持 Resource。检查你的服务器初始化代码是否正确声明了resourcescapability。如果用 FastMCP通常 SDK 会自动做这件事但如果你自己实现底层协议很容易漏掉。原因 2Resource URI 格式不对Resource 的 URI 必须符合 RFC 3986 规范。一个常见的踩坑是用了config://这样的 scheme。MCP 协议本身没有强制限定 scheme但客户端通常只会稳定渲染自己支持的 URI 形态。实际排查下来大部分看不到的问题出在客户端不支持非标准 scheme 的渲染而不是 Resource 注册失败。原因 3list_resources handler 没返回如果用低层 SDK需要手动实现list_resources回调# 低层写法容易忘记返回完整的 resource 列表 server.list_resources() async def handle_list_resources(): return [ Resource( uriconfig://app/settings, nameApp Settings, description应用配置参数, mimeTypetext/plain ), Resource( uridocs://help/about, nameAbout Page, description关于页面内容, mimeTypetext/markdown ) ]检查确认你确实返回了Resource对象列表而不只是打印了日志。原因 4SSE 长连接断开了MCP 的 SSE 传输层依赖持久化长连接。如果网络不稳定、客户端重连太频繁、或者 cpolar 隧道因为闲置超时而被回收SSE 连接就会断开。遇到这种情况重启隧道后重新连接即可。5 通过 cpolar 4040 检查回调链路如果连着公网地址但 Resource 还是看不到还有一个排查手段cpolar 提供的 4040 请求检查面板。启动隧道时cpolar 同时在本地启动了http://127.0.0.1:4040作为 HTTP 检查界面。打开这个地址你能看到 cpolar 接收到的每一次 HTTP 请求的详情包括请求路径和方法请求头包括Mcp-Session-Id请求体JSON-RPC 消息内容这个面板在排查客户端到底有没有发resources/list请求过来这个问题时特别好用。具体来说让 AI 客户端发起一次 Resource 列表请求然后切到 4040 页面看看有没有对应的POST /message请求到达。如果有说明网络链路没问题如果没有说明客户端根本没成功建立连接。# 直接在浏览器打开 open http://127.0.0.1:4040在请求列表里搜索resources/list的关键字如果能找到就把响应体里的result和本地 MCP Inspector 测出来的结果对比一下。6 验证完成后关闭隧道MCP Resource 排查结束之后第一件事就是关掉 cpolar 隧道。临时调试隧道不需要长期运行关掉的方式很简单在 cpolar 前台窗口按Ctrl C终端会提示隧道已关闭。确认隧道已经离线的办法刷新http://127.0.0.1:9200在线隧道列表如果空了说明已经全部关停。安全提醒这篇文章全程操作的都是测试 Resource不包含任何敏感数据没有 API Key、没有数据库密码、没有用户信息。如果是排查生产环境的 MCP Server不要在公网上暴露管理端口不要传入真实凭证确认完成后立刻断网。cpolar 生成的是随机临时地址非长期固定地址而且隧道关了地址立刻失效安全风险可控。但也正是这个原因它特别适合做 MCP 调试场景——用完即弃。7 总结折腾了大半天说回最核心的结论MCP Resource 在客户端看不到90% 是因为客户端回连不到你的本地服务器不是 Resource 注册代码写错了。排查链路其实很简单先用 MCP Inspector 在本地验证一遍 Resource 列表是否正常再用 cpolar 开一个 HTTP 隧道把本地 MCP Server 的 SSE 端点暴露成公网地址让 AI 客户端通过这个公网地址重新连接看 Resource 列表是否出现如果还看不到用 cpolar 的 4040 请求检查面板确认回调链路是否真的走到了服务器端排查完毕关闭隧道不要让临时地址长期开放这个流程不需要改一行 MCP Server 代码不需要重写 Resource也不需要给 AI 客户端开网络白名单。一条 cpolar 隧道配上 4040 面板就能把网络链路不通和Resource 注册有问题这两类原因快速拆开。如果你也在写 MCP Server 并且卡在Resource 客户端看不到这一步不妨试试这个办法——先排除网络链路再回头查代码。