1. 为什么 MCP Inspector 连不上本地 Server
MCP Inspector 是官方给 MCP Server 做可视化调试的交互式工具,说白了就是 MCP 世界的 Postman:能列出 Server 暴露的 tools、resources、prompts,填入参数直接调用,看返回结果。适合正在写 MCP Server 的开发者、做 AI Agent 工具链的同学,以及想把内部能力封装成 MCP 接口但不确定通不通的人。
我最近在本地起了一个基于 FastMCP 的 Server,用mcp.run(transport="streamable-http")启动,命令行看着一切正常,端口也监听了,但 Inspector 里点 Connect 就是转圈,Server 端日志刷出一堆和 session、CORS 相关的报错。换成 SSE 模式又是另一套问题。折腾一圈才理清:问题不在 Inspector,而在 Server 的 ASGI 组装方式——FastMCP 自带的run()把 app 包得太死,跨域和 session header 没暴露出来,Inspector 拿不到Mcp-Session-Id,握手直接断。
这篇就把从连不上到跑通的完整链路写清楚:Starlette 怎么挂载 streamable_http_app、CORS 要放哪些 origin、Inspector 的 Transport Type 和 URL 怎么填、SSE 旧模式怎么切,以及怎么用 TaoToken 的统一 Key 给 Server 里的模型调用兜底。全程可复制,照着改就能复现。
2. TaoToken 前置:统一 Key 与 API 通道
MCP Server 本身只是协议层,真正干活时经常要调模型——比如一个summarizetool 内部要请求大模型。如果每个 tool 都自己配一套 key,本地调试会非常乱。我的做法是让 Server 统一走 TaoToken 的 API 通道,一个 Key 覆盖多家模型,调试时只关心协议通不通,不用来回换配置。
TaoToken 在这里的角色是「统一入口」:官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 端点是 https://taotoken.net/api 。你需要在控制台建一个 Key,然后把它写进 Server 的环境变量或配置文件。注意 API 地址不带 UTM 参数,直接写https://taotoken.net/api即可。
拿 Key 的入口在控制台,创建后复制那串sk-开头的字符串。建议不要硬编码进server.py,用.env或系统环境变量注入,后面 Inspector 调试时改配置不用动代码。如果你后面要做长期编码或 Agent 循环调用,可以看下 Coding Plan;只是验证模型连通性,用模型对话页面点几下就够了。
3. 可复制配置:Starlette + CORS 骨架
核心改动就一处:别用mcp.run(),改成手动组装 Starlette app,把streamable_http_app()挂到路由上,再套一层CORSMiddleware。下面是我实测能跑通的server.py骨架。
# server.py import os from contextlib import asynccontextmanager from starlette.applications import Starlette from starlette.middleware.cors import CORSMiddleware from starlette.routing import Mount from mcp.server.fastmcp import FastMCP import uvicorn mcp = FastMCP("demo-server") @mcp.tool() def add(a: int, b: int) -> int: """两数相加,用于验证 tool 调用链路""" return a + b @mcp.tool() def summarize(text: str) -> str: """调用 TaoToken 统一通道做摘要(示例占位)""" # 实际请求走 https://taotoken.net/api return f"received {len(text)} chars" @asynccontextmanager async def lifespan(app): async with mcp.session_manager.run(): yield app = Starlette( routes=[ Mount("/", app=mcp.streamable_http_app()), ], lifespan=lifespan, ) app = CORSMiddleware( app, allow_origins=[ "http://localhost:6274", "http://127.0.0.1:6274", ], allow_methods=["GET", "POST", "DELETE", "OPTIONS"], allow_headers=["*"], expose_headers=["Mcp-Session-Id"], ) if __name__ == "__main__": uvicorn.run(app, host="127.0.0.1", port=8000)几个关键点必须对上,错一个就连不上:
| 配置项 | 值 | 作用 |
|---|---|---|
| Mount 路径 | /挂streamable_http_app() | 实际端点变成/mcp |
| allow_origins | localhost:6274和127.0.0.1:6274 | Inspector 默认端口 |
| expose_headers | Mcp-Session-Id | 浏览器要读到这个头 |
| allow_methods | 含 DELETE | 关闭 session 用 |
| lifespan | mcp.session_manager.run() | 不写会报 session 未初始化 |
启动命令:
uv run server.py # 或 python server.py看到Uvicorn running on http://127.0.0.1:8000就说明 Server 起来了。此时先别急着开 Inspector,用 curl 探一下端点是否活着:
curl -i -X POST http://127.0.0.1:8000/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'返回里带Mcp-Session-Id响应头,说明协议层通了。这一步能省掉后面一半的排查时间。
4. 验证请求:Inspector 连接与 Run Tool
先启动 Inspector:
npx @modelcontextprotocol/inspector如果浏览器打不开,命令行里设一下 host:
set HOST=127.0.0.1 npx @modelcontextprotocol/inspector启动后 URL 会变成127.0.0.1:6274,浏览器就能访问了。进入界面后按下面填:
- Transport Type:
Streamable HTTP - URL:
http://127.0.0.1:8000/mcp - Connection Type:
Direct
点 Connect。连上后右侧出现资源面板,因为示例 Server 只定义了 tools,点 Tools 面板再点 List Tools,就能看到add和summarize。选中add,右侧出现参数表单,填a=3、b=4,点 Run Tool,返回7就说明整条链路通了。
如果要用 SSE 旧模式,Server 端把 Mount 那行换掉:
app = Starlette( routes=[ Mount("/", app=mcp.sse_app()), ], )注意 SSE 模式不需要lifespan,去掉即可。Inspector 里 Transport Type 选SSE,URL 填http://127.0.0.1:8000/sse,其余操作一样。不过 SSE 是旧实现,新项目建议直接用 Streamable HTTP。
5. 本篇常见错排查
连不上、Server 日志报 session 相关错误:八成是没写lifespan,或者用了mcp.run()而不是手动挂载。streamable_http_app()依赖 session manager 的生命周期,必须用lifespan包住。
浏览器控制台报 CORS:检查allow_origins是否同时包含localhost:6274和127.0.0.1:6274。只写一个,另一个访问方式就会被拦。
连上了但 List Tools 为空:确认 tool 是用@mcp.tool()装饰的,且函数有类型注解和 docstring。缺类型注解时 FastMCP 可能无法生成参数 schema。
Run Tool 报 400 或参数校验失败:Inspector 表单是按 schema 生成的,如果 schema 里参数是必填但你没填,会直接报错。对照右侧描述补全。
端口冲突:8000 被占用时换端口,但 Inspector 里的 URL 也要同步改,别只改一边。
curl 能通、Inspector 不通:基本锁定在 CORS 和expose_headers。Mcp-Session-Id没暴露,浏览器读不到,后续请求就带不上 session。
6. 继续调试与接入
跑通之后,日常调试就是改 tool、重启 Server、Inspector 里重新 List Tools 再 Run。Server 里如果涉及模型调用,统一走 TaoToken 的 API 通道,Key 在控制台管理,接入细节看接入文档。需要验证模型返回是否正常,直接用模型对话页面测;要做长期编码或 Agent 循环,Coding Plan 更合适。API Keys 页面负责建 Key 和轮换,别把 Key 写进代码提交到仓库。
实测下来,MCP Inspector 最大的价值是省掉了自己写 MCP Client 的成本——协议握手、session 管理、参数表单它都替你做了,你只需要专注 Server 端逻辑。把 Starlette 挂载和 CORS 这两处配对,后面基本不会再卡在连接上。