1. 先把 MCP、HTTP、WebSocket 的层级关系说清楚
很多人第一次配 MCP 服务时会卡在一个很基础的问题上:MCP 到底是不是一种新的网络协议?它和 HTTP、WebSocket 是什么关系?为什么我明明写的是http://开头的地址,日志里却能看到Upgrade: websocket?
我先把结论摆出来:MCP 是应用层的协议规范,它定义的是「消息长什么样、有哪些方法、怎么握手、怎么调用工具」,而 HTTP 和 WebSocket 是它可以选择使用的传输通道。这三者不是竞争关系,而是分层协作关系。
你可以用寄快递来类比。MCP 是「包裹里装什么、面单怎么填、收件人怎么签收」这套规则;HTTP 是「普通快递」,一问一答,送完就结束;WebSocket 是「专线电话」,接通之后双方可以一直聊。MCP 的包裹既可以走普通快递,也可以走专线电话,包裹本身的格式不变。
从分层角度看,大概是这样的:
应用层: MCP 协议 ← 定义 initialize / tools/list / tools/call 等语义 传输层: WebSocket / SSE / stdio / Streamable HTTP 网络层: TCP / IP关键点在于 MCP 设计上是「传输无关」的。同一套 MCP 消息,可以走本地标准输入输出(stdio),可以走 SSE,可以走 WebSocket,也可以走 HTTP 的 POST 请求。你在settings.json里写的transport字段,决定的就是「这个 MCP 服务用哪种通道」。
那为什么大家总把 MCP 和 WebSocket 绑在一起讲?因为 WebSocket 提供了全双工长连接,服务端可以主动向客户端推送消息,这对需要长时间会话、流式返回、服务端主动通知的场景很合适。而 HTTP 的初始握手(那个Upgrade请求)恰好是建立 WebSocket 连接的第一步,所以你会看到「MCP 用 HTTP 握手,然后升级成 WebSocket」这种说法。
理解了这个分层,后面配置settings.json时你就不会迷糊:url填的是传输地址,transport填的是传输类型,而 MCP 的协议语义是藏在消息体里的,跟传输方式无关。
这一节想让你记住一句话:MCP 不是替代 HTTP 的协议,而是构建在 HTTP/WebSocket 之上的专门协议。想清楚这一点,Nginx 能不能代理 MCP、为什么能代理,也就顺理成章了。
2. 接入前的准备:TaoToken 的 Base URL、Key 与 Model ID
在真正写settings.json之前,得先把「连到哪个模型服务」这件事定下来。MCP 本身只是工具调用的协议层,它需要一个能理解工具调用的大模型来驱动。我这边实测用的是 TaoToken 提供的统一接入端点,它同时兼容 Anthropic 风格和 OpenAI 风格的调用,配置起来比较省事。
你需要准备三样东西,我把它叫做「三件套」:
| 配置项 | 说明 | 示例值 |
|---|---|---|
| Base URL | 接口根地址,不带具体路径 | https://taotoken.net/api |
| API Key | 身份凭证,形如sk-开头 | sk-xxxxxxxx |
| Model ID | 具体模型标识 | claude-sonnet-4-5等 |
Base URL 用https://taotoken.net/api,注意这里不要加多余的斜杠或路径,很多客户端会自动拼接/v1/messages或/v1/chat/completions。API Key 在控制台的 API Keys 页面创建,创建后只显示一次,记得及时保存。
如果你用的是 Claude Code 这类工具,它读取的是环境变量或配置文件里的ANTHROPIC_BASE_URL和ANTHROPIC_AUTH_TOKEN;如果你用的是 Cline、Roo Code 这类 VS Code 插件,通常在设置界面里填 Base URL、API Key、Model ID 三个字段。不管哪种,本质都是把上面三件套填进去。
这里有个容易踩的坑:有人把 Base URL 填成了带/v1的完整路径,结果客户端又拼了一次,变成/v1/v1/messages,直接 404。正确做法是只填到/api这一层。
准备好三件套之后,先别急着配 MCP,建议先用最简方式验证一下模型通道是通的。你可以打开模型对话页面,发一句「你好,请回复 ok」,确认能正常返回。这一步能排除掉 Key 错误、额度不足、模型名写错等基础问题。等模型通道确认没问题,再往上叠加 MCP 服务,排障时就能快速定位是「模型层」还是「MCP 层」的问题。
顺便说一句,如果你打算长期跑编码类 Agent 任务,可以考虑用 Coding Plan 这类套餐,比按量计费更划算;如果只是临时验证 MCP 连通性,用按量调用就够了。两种方式共用同一套 Base URL 和 Key,切换成本很低。
3. settings.json 骨架:http 与 websocket 两种传输怎么写
这一节是重点,我直接给你可复制的配置骨架。不同客户端读取的配置文件名不一样,Claude Code 用的是.mcp.json或settings.json里的mcpServers字段,Cline 用的是插件自己的cline_mcp_settings.json,但结构大同小异,核心都是command/args或url/transport这两类。
先看一个本地 stdio 类型的 MCP 服务,这是最常见的:
{ "mcpServers": { "filesystem": { "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-filesystem", "/Users/yourname/projects" ] } } }再看远程 HTTP 类型的 MCP 服务,注意transport字段:
{ "mcpServers": { "remote-tools": { "url": "https://mcp.example.com/mcp", "transport": "http", "headers": { "Authorization": "Bearer sk-xxxxxxxx" } } } }WebSocket 类型的写法:
{ "mcpServers": { "ws-tools": { "url": "wss://mcp.example.com/mcp/ws", "transport": "websocket", "headers": { "Authorization": "Bearer sk-xxxxxxxx" } } } }如果你用的是 Claude Code,并且想让它同时走 TaoToken 的模型通道,配置通常长这样(放在~/.claude/settings.json或项目级.claude/settings.json):
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "sk-xxxxxxxx", "ANTHROPIC_MODEL": "claude-sonnet-4-5" }, "mcpServers": { "remote-tools": { "url": "https://mcp.example.com/mcp", "transport": "http" } } }这里要强调「三件套」的完整性:Base URL、Key、Model ID 一个都不能少。我见过有人只填了 Base URL 和 Key,忘了 Model ID,结果客户端用了一个默认模型名,报model not found。所以无论你用什么工具,检查配置时先看这三项齐不齐。
关于transport的取值,不同客户端支持的枚举不完全一样,常见的有stdio、sse、http、websocket。如果客户端不认websocket,可以试试ws,或者查一下该客户端的文档。配置写完后,很多客户端需要重启才能加载新的 MCP 服务,别改完就急着测,先重启。
还有一个细节:headers里的Authorization是给 MCP 服务端做鉴权用的,跟模型通道的 Key 是两回事。有些 MCP 服务不需要鉴权,那就可以省略headers。但如果你的 MCP 服务部署在公网,强烈建议加上鉴权,否则任何人都能调用你的工具。
4. 一次可复现的连通性验证:从握手到工具调用
配置写完,怎么确认真的通了?我给你一套可复现的验证动作,分三步走。
第一步,验证传输层能连上。如果你配的是 WebSocket,可以用wscat或websocat直接连:
npx wscat -c "wss://mcp.example.com/mcp/ws" -H "Authorization: Bearer sk-xxxxxxxx"连上后你会看到连接建立,如果服务端有欢迎消息会直接打印。这一步只验证「通道通不通」,不涉及 MCP 语义。
第二步,验证 MCP 握手。MCP 会话的第一步是initialize,你可以手动发一条:
{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2024-11-05", "capabilities": { "tools": {} }, "clientInfo": { "name": "ConnectivityCheck", "version": "1.0.0" } } }正常返回会包含serverInfo和capabilities,说明服务端认了这个客户端。如果返回错误,通常是protocolVersion不匹配或鉴权失败。
第三步,列出并调用工具。发tools/list:
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {} }返回里会有工具名和参数 schema。挑一个无副作用的工具调用,比如加法:
{ "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": { "name": "add", "arguments": { "a": 5, "b": 3 } } }如果返回result.content[0].text是5 + 3 = 8,恭喜,整条链路通了:传输层 → MCP 握手 → 工具调用 → 结果返回。
如果你不想手动发 JSON,也可以用 Python 脚本一次性跑完:
import asyncio import json import websockets async def check(): async with websockets.connect( "wss://mcp.example.com/mcp/ws", extra_headers={"Authorization": "Bearer sk-xxxxxxxx"} ) as ws: await ws.send(json.dumps({ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2024-11-05", "capabilities": {"tools": {}}, "clientInfo": {"name": "check", "version": "1.0.0"} } })) print("initialize:", await ws.recv()) await ws.send(json.dumps({ "jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {} })) print("tools/list:", await ws.recv()) asyncio.run(check())跑通之后,你再去客户端里用自然语言让模型调用工具,比如「帮我算一下 5 加 3」,模型会自己走 MCP 调用。这时候如果失败,问题多半在模型通道或客户端的 MCP 集成层,而不是 MCP 服务本身。
5. 常见报错排查:401、local proxy failed、reading choices、OAuth
配 MCP 的过程中,报错基本集中在几类。我按真实遇到的顺序列一下。
401 Unauthorized:最常见。先检查Authorization头有没有带、格式对不对(Bearer后面有个空格)。如果 MCP 服务端和模型通道用的是不同的 Key,别搞混了。还有一种情况是 Key 过期或被禁用,去控制台重新生成一个。
local proxy failed / connection refused:通常是本地 MCP 服务没启动,或者端口写错了。stdio 类型的服务不需要端口,如果你配了url却指向localhost:8000,先确认那个端口上真的有服务在监听。用lsof -i :8000或netstat -an | grep 8000查一下。
reading choices / choices 字段为空:这个报错一般出现在模型返回层,不是 MCP 层。说明模型通道返回的响应结构不符合客户端预期,常见原因是 Base URL 填错导致请求打到了不兼容的端点,或者 Model ID 写成了不存在的模型。回到三件套检查一遍,特别是 Base URL 不要带/v1。
OAuth 相关报错:有些远程 MCP 服务用 OAuth 做鉴权,客户端会弹浏览器授权。如果卡在回调或报invalid_client,检查回调地址是否和 OAuth 应用注册的一致。这类问题跟 MCP 协议本身无关,是鉴权流程的问题。
Upgrade header 缺失 / websocket handshake failed:如果你用了 Nginx 反代,很可能是没配proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection "upgrade";。这两行是 WebSocket 升级的关键,缺了就会握手失败。
tools/list 返回空数组:说明服务端没注册任何工具,或者工具注册代码没被执行。检查服务端启动日志,确认工具注册那部分逻辑跑到了。
排查时有个通用思路:先分层,再定位。传输层用wscat或curl单独测;MCP 层用initialize和tools/list单独测;模型层用一句简单对话单独测。三层都单独通了,再合起来用,问题就好找了。
6. 把 MCP 接进日常开发流:几个实用建议
连通性验证通过只是开始,真正用起来还有几个细节值得注意。
第一,MCP 服务的超时设置。WebSocket 是长连接,如果中间有反向代理或负载均衡,空闲连接可能被掐断。Nginx 里把proxy_read_timeout和proxy_send_timeout调大,比如1d,能减少断连。客户端侧也要看有没有心跳机制。
第二,工具命名要清晰。MCP 工具名会直接暴露给模型,名字太模糊模型容易选错。比如add不如math_add明确,query不如db_query_users明确。参数 schema 里写清楚description,模型选工具时会参考。
第三,别把生产库直连暴露成 MCP 工具。MCP 工具一旦被模型调用,执行的就是真实操作。涉及写操作、删除操作的工具,要么加二次确认,要么只读。这是安全底线。
第四,配置版本化。settings.json和.mcp.json建议纳入 Git 管理,但 Key 不要提交,用环境变量或本地覆盖文件。团队协作时,别人 clone 下来只需要填自己的 Key 就能跑。
第五,模型通道和 MCP 通道分开排障。我习惯先确认模型能正常对话,再确认 MCP 能tools/list,最后才让模型调工具。这样任何一层出问题都能快速定位,不会眉毛胡子一把抓。
如果你还没开始配,建议从本地 stdio 类型的 MCP 服务入手,比如 filesystem server,它不需要网络和鉴权,最容易跑通。跑通之后再换成远程 HTTP 或 WebSocket,逐步增加复杂度。模型通道这边,Base URL 用https://taotoken.net/api,Key 在控制台创建,Model ID 按需选,三件套填齐,基本就能跑起来。