☰
Model Context Protocol(MCP)超全解析:从 JSON-RPC 原理到 SDK 实战,一篇就够!
2026/10/2 6:05:20 网站建设 项目流程

1. 为什么你需要亲手写一个 MCP Server

如果你正在做 AI 应用,大概率遇到过这个场景:模型本身很聪明,但它不知道你本地的文件长什么样、查不了你数据库里的订单、也调不动你内部那套工单系统。你想给它接上这些能力,于是写了一个又一个 function calling 的适配层,每换一个模型厂商就要重写一遍参数格式,维护成本高得离谱。

Model Context Protocol(简称 MCP)就是来解决这件事的。它是一套基于 JSON-RPC 2.0 的开放协议,把「AI 应用怎么安全、实时地拿到外部数据和工具」这件事标准化了。你可以把它理解成 AI 世界的 USB-C 接口:Host(比如 Claude Desktop、Cursor、各类 IDE 插件)是电脑,Server 是你插上去的 U 盘、键盘、显示器,只要双方都遵守接口规范,插上就能用,不需要为每个设备单独写驱动。

这篇文章面向的是想自建 MCP Server 的开发者。我会从 JSON-RPC 的通信原理讲起,然后带你用官方 SDK 写一个能跑通的 Server,给出可复制的配置片段,最后完成一次完整的请求-响应验证。看完你就能自己动手,让大模型真正「长出手脚」。

适合谁看:有 Python 或 TypeScript 基础、想给 AI 应用接外部能力的后端/全栈开发者;正在做 Agent 产品、被 function calling 适配层折磨的工程师;以及想搞清楚 MCP 底层到底怎么通信的技术爱好者。不需要你事先了解 MCP,但需要你会用命令行、能看懂 JSON。

整篇的节奏是:先讲清楚协议分层和 JSON-RPC 报文长什么样,再进入 SDK 实战,中间穿插配置和排障。技术部分我会尽量给全命令和参数,你可以边看边敲。

2. MCP 的 JSON-RPC 通信原理与协议分层

要自建 Server,先得搞明白 MCP 到底在传什么。MCP 的协议栈分成两层:数据层和传输层。数据层定义「传什么内容」,传输层定义「怎么传」。

数据层用的就是 JSON-RPC 2.0。这是一个非常轻量的远程调用协议,一条请求报文只有四个关键字段:jsonrpc固定为"2.0",id用来匹配请求和响应,method是要调用的方法名,params是参数对象。响应报文则带result或error。就这么简单,没有复杂的头信息,也没有强制的序列化格式要求。

MCP 在 JSON-RPC 之上定义了三类核心 Primitive,这是你写 Server 时最常打交道的:

类型作用关键方法
Tools可被模型调用的函数tools/list枚举、tools/call执行
Resources只读数据源,类似文件resources/list、resources/read
Prompts可复用的提示词模板prompts/list、prompts/get

连接建立后,第一件事是握手(initialize)。客户端发一条initialize请求,带上自己支持的协议版本和 capabilities;Server 回一条响应,说明自己的名称、版本和能力。握手完成后,客户端会发一条notifications/initialized通知,表示准备就绪。这个流程和 TCP 三次握手思路类似,目的是让双方在正式干活前先对齐能力边界。

握手报文长这样,你可以对照着理解字段含义:

{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-06-18", "capabilities": { "elicitation": {} }, "clientInfo": { "name": "csdn-client", "version": "1.0.0" } } }

传输层有两种选择。stdio 用于本地进程通信,Host 直接把你写的 Server 当子进程启动,通过标准输入输出收发报文,零网络开销,适合本地工具类 Server。Streamable HTTP 用于远程 Server,支持 SSE 流式回包、Bearer Token 和 OAuth 2.0,适合部署在服务器上给多个客户端共用。

还有一个容易被忽略但很重要的机制:通知(Notifications)。Server 可以在运行中主动推送状态变化,比如工具列表更新了,就发一条:

{ "jsonrpc": "2.0", "method": "notifications/tools/list_changed" }

Host 收到后会自动重新调用tools/list刷新工具列表,不需要重启。这意味着你的 Server 可以动态增删工具,模型侧无感知。这个设计在需要热更新能力的场景里非常实用。

理解了这两层,你就知道写一个 MCP Server 本质上就是:实现若干 JSON-RPC 方法,选一种传输方式把它们暴露出去。剩下的交给 SDK。

3. 用官方 SDK 写一个可复制的 MCP Server

原理讲完,进入动手环节。官方提供了 Python、TypeScript、Java、Go 等多语言 SDK,我这里用 Python 演示,因为它的异步写法最直观,也最容易和现有后端服务集成。

先装依赖。建议用虚拟环境,避免污染全局:

python -m venv mcp-env source mcp-env/bin/activate pip install "mcp[cli]"

装完后,新建一个weather_server.py。这个 Server 提供一个查询天气的工具,工具本身返回模拟数据,重点是让你看清 SDK 的注册和调用链路:

from mcp.server.fastmcp import FastMCP mcp = FastMCP("weather-server") @mcp.tool() def weather_current(location: str, units: str = "metric") -> dict: """查询指定城市的当前天气。 Args: location: 城市名称,例如 "San Francisco" units: 单位,metric 或 imperial """ fake_data = { "San Francisco": {"temp": 18, "condition": "foggy"}, "Beijing": {"temp": 26, "condition": "clear"}, } data = fake_data.get(location, {"temp": 20, "condition": "unknown"}) if units == "imperial": data["temp"] = round(data["temp"] * 9 / 5 + 32) return {"location": location, "units": units, **data} if __name__ == "__main__": mcp.run(transport="stdio")

这里有几个关键点。FastMCP是 SDK 提供的高层封装,@mcp.tool()装饰器会把函数自动注册成一个 Tool,函数的 docstring 会成为工具描述,参数类型注解会成为 JSON Schema。模型看到的就是这份 Schema,所以 docstring 写清楚很重要,它直接影响模型会不会正确调用你的工具。

mcp.run(transport="stdio")表示用标准输入输出通信。如果你要部署成远程服务,改成transport="streamable-http"并指定端口即可。

接下来是 Host 侧的配置。以 Claude Desktop 为例,配置文件路径在 macOS 上是~/Library/Application Support/Claude/claude_desktop_config.json,Windows 上是%APPDATA%\Claude\claude_desktop_config.json。写入以下内容:

{ "mcpServers": { "weather": { "command": "/absolute/path/to/mcp-env/bin/python", "args": ["/absolute/path/to/weather_server.py"] } } }

注意command一定要写虚拟环境里 Python 的绝对路径,不要写python,否则 Host 启动子进程时找不到你的依赖。这是新手最常踩的坑之一。

如果你用的是 Cline 这类支持 MCP 的编辑器插件,配置结构类似,但字段名可能不同,通常是mcpServers下配command和args,远程 Server 则配url和headers。Cline 的 MCP 配置里如果要接远程服务,需要同时写全三件套:Base URL、API Key、Model ID,缺一个都会连不上。

配置保存后重启 Host,你的工具就会出现在工具列表里。整个过程不需要你手写任何 JSON-RPC 报文,SDK 全帮你处理了。

4. 一次完整的请求-响应验证与结果解读

配置写完,怎么确认真的跑通了?最直接的办法是用官方调试工具 MCP Inspector。它是个可视化界面,能列出你的工具、资源、提示词,还能一键发请求。

启动方式:

npx @modelcontextprotocol/inspector python /absolute/path/to/weather_server.py

命令跑起来后,终端会打印一个本地地址,通常是http://localhost:6274,浏览器打开就能看到界面。左侧会显示连接状态,中间是工具列表,你应该能看到weather_current。

点开这个工具,填入参数{"location": "San Francisco", "units": "imperial"},点执行。右侧会返回结果:

{ "location": "San Francisco", "units": "imperial", "temp": 64, "condition": "foggy" }

看到这个返回,说明整条链路通了:Inspector 作为 Client 发tools/call,你的 Server 执行函数并回包,JSON-RPC 的id匹配正确,结果解析无误。

如果你想看底层报文,Inspector 的日志面板会显示原始 JSON-RPC 消息。你会看到类似这样的请求:

{ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "weather_current", "arguments": {"location": "San Francisco", "units": "imperial"} } }

以及对应的响应:

{ "jsonrpc": "2.0", "id": 2, "result": { "content": [ {"type": "text", "text": "{\"location\": \"San Francisco\", ...}"} ] } }

注意result.content是个数组,这是 MCP 的标准返回结构,支持文本、图片等多种内容类型。你的函数返回的 dict 会被 SDK 序列化成文本塞进content里。

在真实 Host 里验证也很简单。重启 Claude Desktop 后,直接问它「旧金山现在天气怎么样」,模型会自动发现weather_current工具并调用。如果模型没调用,多半是 docstring 描述不够清晰,或者工具名和参数名让模型难以理解。

实测下来,从零到跑通这条链路,熟练的话半小时内能搞定。真正花时间的是把工具逻辑写扎实,以及处理各种边界情况。

5. 常见报错排查:401、local proxy failed 与 OAuth

跑通过程中你大概率会遇到几个典型报错,这里集中说一下。

401 Unauthorized。这个通常出现在远程 Server 场景。如果你用 Streamable HTTP 传输并开了鉴权,Client 请求头里必须带Authorization: Bearer <token>。检查你的 Host 配置里有没有正确填 API Key。如果是接第三方模型服务,Key 填错、过期、或者复制时带了空格,都会报 401。建议把 Key 单独存环境变量,配置里用引用而不是硬编码。

local proxy failed / connection refused。这个报错说明 Host 启动子进程失败,或者连不上你指定的地址。排查顺序:第一,command路径是不是绝对路径、文件有没有执行权限;第二,虚拟环境里的依赖是否装全,手动跑一遍python weather_server.py看会不会报 ImportError;第三,如果是远程 Server,确认端口没被占用、防火墙没拦。stdio 模式下这个错基本都是路径问题。

reading 'choices' 相关报错。这类错误一般不是 MCP 协议本身的问题,而是模型服务返回结构不符合预期。常见于你用了非标准接口,或者请求参数里model字段填了服务端不认识的 Model ID。检查三件套是否齐全:Base URL 指向正确的 API 端点、API Key 有效、Model ID 是服务端支持的型号。三者缺一不可。

OAuth 授权失败。远程 MCP Server 如果用 OAuth 2.0,Client 需要先走授权码流程拿到 token。报错通常是回调地址不匹配、scope 没申请对、或者 token 过期没刷新。调试阶段可以先用 Bearer Token 绕过 OAuth,把链路跑通再上完整授权。

工具列表为空。Host 连上了但看不到工具,先确认tools/list能不能正常返回。用 Inspector 连一下就知道。如果 Inspector 能看到而 Host 看不到,多半是 Host 缓存了旧列表,重启一下。如果 Server 是动态注册工具的,确认注册逻辑在mcp.run()之前执行完毕。

排障的核心思路是分层定位:先确认进程能不能起来,再确认 JSON-RPC 握手成不成功,最后确认具体方法调用有没有问题。Inspector 在这三步里都能帮你看到原始报文,比盲猜高效得多。

6. 把 MCP 接入你的日常开发流

Server 跑通只是起点。真正让它产生价值,是把它接进你每天用的工具里。如果你用 Claude Code 做日常编码,可以把 MCP Server 配到它的配置里,让模型在写代码时能直接查你的内部文档、读数据库 schema、调测试环境接口。配置方式和前面 Claude Desktop 类似,找到对应的配置文件,在mcpServers下加一段就行。

对于需要长期跑 Agent 任务的场景,建议把 Server 部署成远程服务,用 Streamable HTTP 传输,这样多个客户端可以共用,也方便做鉴权和限流。部署时记得把 Base URL、API Key、Model ID 这三件套在客户端配全,任何一环缺失都会导致连接失败。

如果你还没决定用哪套模型服务来驱动这些 MCP 调用,可以先去 https://taotoken.net/api 看看接入文档,把 API Key 申请好,再对照 https://taotoken.net/api-keys 配置。想先验证模型对话效果,可以直接在 https://taotoken.net/chat 里试;如果是长期编码或 Agent 场景,Coding Plan 会更合适,地址是 https://taotoken.net/coding-plan。文档里对 MCP 相关的接入方式有说明,照着配能少走弯路。

写 MCP Server 这件事,门槛比想象中低。协议本身不复杂,SDK 把脏活都干了,你真正要花心思的是工具设计:参数怎么定、描述怎么写、错误怎么返回,这些才决定模型用起来顺不顺手。先把一个最简单的工具跑通,再逐步加功能,比一上来就设计大而全的 Server 靠谱得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询