首先我们要明确一个核心概念——进程:执行一个应用程序,就会启动一个进程,操作系统会为其分配独立的内存空间和系统资源。当应用程序执行完毕,系统分配的资源会被自动回收。
进程之间要实现通信,有一个最基本的前提:进程不能结束。就像我们常用的微信、QQ,启动后不会自动关闭,核心原因就是它们一直在“监听”用户操作和消息。
对应到代码中,我们可以通过监听输入让进程保持运行状态,以Node.js为例:
// 监听输入,让进程持续运行
process.stdin.on("data", () => {});
除了让单个进程持续运行,一个进程还可以启动另一个进程,这就是操作系统中常用的「父子进程模型」。
比如我们在控制台输入以下命令启动Node程序时:
node index.js
此时,控制台就是「父进程」,启动的Node程序就是「子进程」,两者可以进行通信。
当我们解决了进程持续运行的问题,下一步就是实现进程间的通信。其中最基础、最常用的方式就是 stdio(standard input and output,标准输入输出)。
每一个进程启动后,都会默认留出两个对外通信的接口:
标准输入接口(standard in):用于接收外部数据(比如用户输入、其他进程传递的数据)
标准输出接口(standard output):用于向外部输出数据(比如程序运行结果、向其他进程传递消息)
结合前面的父子进程模型,进程间的stdio通信可以用下图直观表示:
我们通过一个实际案例,看看终端(父进程)和Node程序(子进程)如何通过stdio通信。首先创建两个文件:server.js(服务端子进程)和client.js(客户端父进程)。
先看client.js的代码,它的作用是启动server.js子进程,并与子进程进行数据交互:
const { spawn } = require("child_process");
// 启动 server.js 子进程(相当于在终端执行 node server.js)
const serverProcess = spawn("node", ["server.js"]);
// 监听服务端子进程的输出,并将其输出到当前进程(终端)
serverProcess.stdout.on("data", (data) => {
process.stdin.write(data.toString()); // 将子进程的输出写入当前进程的输入(终端显示)
});
// 向子进程发送3条测试消息,每秒发送一条
const messages = ["生命有意义吗?", "宇宙有尽头吗?", "再见!"];
messages.forEach((msg, index) => {
setTimeout(() => {
console.log(`-->${msg}`); // 终端打印发送的消息
serverProcess.stdin.write(msg); // 向子进程发送消息
}, index * 1000);
});
运行client.js后,终端与Node子进程的通信效果如下:
这里需要注意:stdio通信高效、简洁,但仅适用于本地进程间通信。如果需要实现远程进程通信,就需要用到HTTP等其他方式。
进程间传递数据时,需要约定统一的格式,否则会出现数据混乱。常用的通信格式有XML、JSON、普通字符串等,其中最常用、最规范的是 JSON-RPC2.0。
JSON-RPC2.0 是一种远程函数调用协议,通过JSON格式传递请求和响应,核心分为「请求(request)」和「响应(response)」两部分。
客户端向服务端发送请求时,需要包含以下核心字段:
jsonrpc:固定为 "2.0",表示使用JSON-RPC2.0协议
method:要调用的函数/方法名
params:调用函数时需要传递的参数
id:请求的唯一标识,用于匹配响应和请求
{
"jsonrpc": "2.0",
"method": "sum", // 要调用的方法(求和)
"params": {
"a": 5,
"b": 6
},
"id": 1 // 唯一标识
}
服务端收到请求后,会返回响应,核心字段包括:
jsonrpc:固定为 "2.0"
result:函数调用的结果(如果调用成功)
id:与请求的id一致,确保请求和响应对应
{
"jsonrpc": "2.0",
"result": 11, // 求和结果:5+6=11
"id": 1 // 与请求id一致
}
了解了进程通信的基础(stdio)和格式(JSON-RPC2.0)后,我们正式进入MCP协议的核心。MCP是一套标准协议,它在JSON-RPC2.0的基础上,进一步规范了应用程序之间的通信方式和格式。
MCP协议规定的通信方式主要有两种:
stdio:推荐使用,高效、简洁,适用于本地进程通信
HTTP:支持远程通信,包括StreamHTTP、SSE两种方式
通信格式:基于JSON-RPC2.0的进一步规范,下面我们重点讲解MCP协议的3个核心操作。
两个应用程序要开始通信,首先需要完成初始化操作——客户端向服务端发送初始化请求,告知服务端自身信息和支持的能力,服务端返回自身信息和可提供的能力。
客户端请求(request):
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize", // 固定方法名:initialize
"params": {
"protocolVersion": "2024-11-05", // MCP协议版本
"capabilities": { // 客户端支持的能力
"roots": {
"listChanged": true
},
"sampling": {},
"elicitation": {}
},
"clientInfo": { // 客户端信息
"name": "ExampleClient",
"title": "Example Client Display Name",
"version": "1.0.0"
}
}
}
服务端响应(response):
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05", // 与客户端协议版本一致
"capabilities": { // 服务端支持的能力
"logging": {},
"prompts": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"tools": {
"listChanged": true
}
},
"serverInfo": { // 服务端信息
"name": "ExampleServer",
"title": "Example Server Display Name",
"version": "1.0.0"
},
"instructions": "Optional instructions for the client" // 可选的客户端提示
}
}
初始化完成后,客户端需要知道服务端有哪些工具函数可以调用,此时可以发送 tools/list 请求,服务端会返回所有可用工具的详细信息(名称、描述、参数格式等)。
客户端请求(request):
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list" // 固定方法名:tools/list
}
服务端响应(response):
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "get_weather", // 工具名(调用时需使用)
"title": "Weather Information Provider", // 工具标题
"description": "Get current weather information for a location", // 工具描述
"inputSchema": { // 工具的参数格式要求
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name or zip code" // 参数说明
}
},
"required": ["location"] // 必传参数
}
}
]
}
}
知道了服务端的可用工具后,客户端就可以通过 tools/call 请求调用具体的工具,传递对应的参数,服务端返回工具调用结果。
客户端请求(request):
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call", // 固定方法名:tools/call
"params": {
"name": "get_weather", // 要调用的工具名(与tools/list返回的name一致)
"arguments": { // 工具参数(与inputSchema格式一致)
"location": "New York"
}
}
}
服务端响应(response):
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [ // 工具结果必须放在content字段中,多结果用数组
{
"type": "text", // 结果类型(支持多种类型,参考官方文档)
"text": "72°F" // 工具调用结果
}
]
}
}
补充说明:工具返回的结果类型有多种(如text、image、table等),具体可参考 MCP官方文档。
既然MCP是一套标准协议,那么只要是遵循MCP协议的应用程序,都可以与MCP服务端进行通信——这也是MCP协议的核心价值:统一通信标准,实现跨应用、跨进程的互联互通。
开发MCP服务端时,我们可以使用官方提供的调试工具,快速查看通信过程、排查问题。操作非常简单:
在MCP服务端所在目录,直接在终端运行以下命令:
npx @modelcontextprotocol/inspector
运行后,工具会自动监听MCP服务端的通信过程,帮助我们直观看到请求和响应数据,大幅提升调试效率。
今天我们从进程基础入手,逐步拆解了MCP协议的核心内容:
进程是应用程序的运行实例,通过监听可以保持持续运行,父子进程模型是进程通信的基础;
stdio是本地进程通信的首选方式,高效简洁,适用于近距离进程交互;
JSON-RPC2.0是进程通信的通用格式,通过请求和响应实现规范的数据传递;
MCP协议在JSON-RPC2.0基础上,规范了初始化、工具发现、工具调用三大核心操作,支持本地和远程通信;
官方调试工具可以快速排查MCP通信问题,提升开发效率。