MCP STDIO 是什么?一文介绍及使用

本文深入解析 Model Context Protocol (MCP) 的 STDIO 传输机制,从标准流的工作原理到 JSON-RPC 消息格式,详解初始化握手流程,并提供 Python 模拟服务端的实战示例,帮助开发者掌握本地 MCP 通信的核心技术。

用 Apifox,节省研发团队的每一分钟

MCP STDIO 是什么?一文介绍及使用

免费使用 Apifox

相关推荐

最新文章

API

一体化协作平台

API 设计

API 文档

API 调试

自动化测试

API Mock

API Hub

立即体验 Apifox
目录

MCP 通信基础与架构位置

Model Context Protocol (MCP) 作为一种连接 AI 大模型与外部上下文的标准协议,其核心在于定义了双方如何交换信息。协议本身主要关注数据结构与交互流程,比如请求的格式、响应的规范以及错误处理机制。然而,这些数据结构要在客户端(Client)和服务端(Server)之间真正流动起来,必须依赖具体的传输层(Transport)。

   

协议规范将传输层与协议逻辑进行了分离。这意味着 MCP 的消息内容(基于 JSON-RPC)保持不变,但承载这些消息的底层通道可以灵活选择。目前的 MCP 规范定义了两种标准的传输机制:Streamable HTTPstdio。相比于基于网络的 HTTP 传输,stdio 是一种更为基础且紧密的通信方式,常用于本地环境下的进程间通信。

   

stdio 传输模式下,MCP 客户端并不通过网络端口连接服务端,而是直接将服务端作为一个子进程(Subprocess)启动。这种架构决定了通信的高效性与私密性,因为数据仅在本地内存与标准输入输出流之间传递,不涉及网络协议栈的开销。对于大多数在本地运行的 MCP 服务(如本地文件系统访问、本地数据库查询工具)而言,stdio 是首选且默认的传输方式。

       

STDIO 传输机制的工作原理

理解 stdio 传输机制的关键,在于掌握操作系统中标准流的概念。当客户端启动服务端进程时,操作系统会为该子进程分配三个默认的数据流通道。MCP 协议巧妙地利用了这些通道来实现全双工通信与日志分离。

   

客户端通过写入子进程的标准输入(stdin)来发送请求。服务端进程会持续监听这个输入流,一旦检测到完整的消息,便进行解析与处理。处理完成后,服务端将响应数据写入自身的标准输出(stdout),客户端通过读取该输出流来获取执行结果。这两个通道构成了 MCP 协议的主通信回路。      

STDIO 传输机制的工作原理

除了数据交换,服务端在运行过程中难免需要输出调试信息或错误日志。如果这些非结构化的日志直接混入标准输出,将会破坏 JSON-RPC 消息的格式,导致客户端解析失败。因此,MCP 规范明确规定,标准错误(stderr)通道用于传输日志与调试信息。客户端可以独立捕获该通道的内容进行记录或展示,而不干扰主通信流程。

     

下表详细对比了这三个通道在 MCP stdio 传输中的具体职责与行为规范:

通道名称 (Channel) 数据流向 (Direction) 主要内容 (Content) 行为约束 (Constraint)
Standard Input (stdin) 客户端 -> 服务端 JSON-RPC 请求、通知 必须是合法的 JSON-RPC 消息,不得写入无关数据
Standard Output (stdout) 服务端 -> 客户端 JSON-RPC 响应、通知 严禁输出任何非协议格式的文本(如 print 打印的调试信息)
Standard Error (stderr) 服务端 -> 客户端 结构化日志、调试文本 允许输出任意 UTF-8 字符串,客户端可选择性捕获或忽略

         

消息编码与格式规范

在建立了传输通道之后,传输的内容必须遵循严格的编码规范。MCP 协议基于 JSON-RPC 2.0 标准,所有通过 stdio 传输的消息必须是 UTF-8 编码的文本。为了确保流式读取的准确性,消息之间采用换行符(Newline)作为分隔符。

   

这意味着每一个发出的 JSON-RPC 消息必须被压缩为单行文本。在消息体内部,不能包含未转义的换行符,否则接收端会错误地将其识别为消息结束的标志。接收端(无论是客户端还是服务端)通常采用“按行读取”的策略:持续读取输入流,直到遇到换行符,然后将缓冲区中的字符串尝试解析为 JSON 对象。

   

一个典型的通信过程包含请求与响应。当客户端希望服务端执行操作时,会构造一个包含 jsonrpc 版本号、消息 ID (id)、方法名 (method) 以及参数 (params) 的对象。该对象被序列化为无换行符的字符串后写入标准输入。

   

服务端接收并处理后,通过标准输出返回响应。响应对象同样包含 jsonrpc 版本号、对应的消息 ID 以及执行结果 (result) 或错误信息 (error)。这种严格的请求-响应对应关系,使得即便在异步处理的情况下,客户端也能准确匹配每一次调用的结果。

   

协议生命周期:初始化阶段

任何 MCP 连接建立后的首要任务是完成初始化(Initialization)。这是一个强制性的握手过程,用于协商协议版本与功能能力(Capabilities)。在 stdio 模式下,子进程启动后,客户端必须作为发起方,发送第一条 initialize 请求。

     

初始化请求不仅仅是打个招呼,它承载了至关重要的配置信息。客户端需要声明自己支持的协议版本(例如 2025-06-18),并告知服务端自身具备哪些能力(如是否支持采样 sampling 或根目录列表 roots)。

     

以下是一个标准的初始化请求示例,展示了客户端如何向服务端发送自身的元数据与能力声明:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": {
      "roots": {
        "listChanged": true
      },
      "sampling": {}
    },
    "clientInfo": {
      "name": "MyMCPClient",
      "title": "My MCP Client",
      "version": "1.0.0"
    }
  }
}

   

服务端接收到此请求后,必须进行版本匹配。如果服务端支持客户端请求的协议版本,它将返回相同的版本号;如果不支持,它应返回自己支持的最新版本。同时,服务端也会在响应中列出自己的能力(如是否支持工具调用 tools、资源订阅 resources 或提示词 prompts)。

   

只有当客户端收到初始化响应,并确认双方能力匹配后,才会发送一条 notifications/initialized 通知。这条通知标志着握手阶段的正式结束,双方随即进入正常操作阶段,开始发送实际的业务请求(如列出工具、读取资源等)。在此之前,除了 Ping 消息外,双方不应进行其他通信。

   

使用 Python 模拟 MCP STDIO 服务端

为了更直观地理解 stdio 的工作方式,我们可以编写一个极简的 Python 脚本来模拟 MCP 服务端。这个脚本不依赖复杂的 SDK,而是直接操作标准输入输出流,从而揭示底层的通信逻辑。

     

这个模拟服务端的主要逻辑是:循环读取 sys.stdin 的每一行,将其解析为 JSON,如果是初始化请求,则按照规范格式化输出响应写入 sys.stdout,并强制刷新缓冲区以确保消息即时发送。

   

创建一个名为 simple_mcp_server.py 的文件,写入以下代码:

import sys
import json

def main():
    # 持续监听标准输入
    for line in sys.stdin:
        try:
            # 去除行末换行符并解析 JSON
            request = json.loads(line.strip())
            
            # 简单的路由逻辑
            if request.get("method") == "initialize":
                # 构造符合 MCP 规范的初始化响应
                response = {
                    "jsonrpc": "2.0",
                    "id": request.get("id"),
                    "result": {
                        "protocolVersion": "2025-06-18",
                        "capabilities": {
                            "tools": {"listChanged": True},
                            "logging": {}
                        },
                        "serverInfo": {
                            "name": "SimplePythonServer",
                            "version": "0.1.0"
                        }
                    }
                }
                
                # 写入标准输出,必须添加换行符作为分隔
                sys.stdout.write(json.dumps(response) + "\n")
                # 必须刷新缓冲区,否则客户端可能无法立即收到
                sys.stdout.flush()
                
                # 向 stderr 写入日志,模拟调试信息
                sys.stderr.write("[INFO] Initialization handled successfully\n")
                sys.stderr.flush()

            elif request.get("method") == "notifications/initialized":
                # 收到初始化完成通知,记录日志
                sys.stderr.write("[INFO] Client initialized. Ready for operations.\n")
                sys.stderr.flush()
            
            # 这里可以添加更多的 method 处理逻辑
            
        except json.JSONDecodeError:
            sys.stderr.write("[ERROR] Invalid JSON received\n")
            sys.stderr.flush()

if __name__ == "__main__":
    main()

此代码展示了 stdio 传输的核心操作:读取一行、处理逻辑、写入一行、刷新缓冲。sys.stderr.write 的使用演示了如何在不破坏协议流的前提下输出日志。在实际部署中,客户端(如 Claude Desktop)会自动执行此脚本,并接管其输入输出流。

 

  1. 打开终端,进入该文件所在的目录。输入命令并回车:  
python3 simple_mcp_server.py

此时你会发现程序“没反应”,实际上它正在后台静静等待你给它发指令。

   

2. 复制下面这段 JSON 文本,粘贴到终端里,然后按回车

{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}}

 

结果(你会看到): 程序会立刻打印出两行内容:

  • JSON 响应(这是发给 AI 看的): {"jsonrpc": "2.0", "id": 1, "result": {"protocolVersion": "2025-06-18", ...}}
  • 日志信息(这是发给开发者看的): [INFO] Initialization handled successfully

 

使用 Python 模拟 MCP STDIO 服务端

   

错误处理与日志的最佳实践

stdio 传输模式下,错误处理分为协议层面的错误和传输层面的异常。协议层面的错误(如方法未找到、参数无效)应通过 JSON-RPC 的 error 响应对象返回给客户端。这属于正常的通信流程,数据依然通过 stdout 传输。

   

然而,对于服务端内部的运行时异常、调试堆栈或性能监控数据,绝不能直接打印到控制台。在许多编程语言中,print() 函数默认输出到 stdout,这在开发普通命令行工具时很方便,但在开发 MCP 服务时是致命的。一旦一段纯文本(如 Value is 10)被写入 stdout,客户端在解析 JSON 时会抛出语法错误,导致连接中断。

   

因此,开发 MCP 服务端时,必须严格配置日志系统,将其重定向到 stderr。大多数成熟的日志库都支持配置输出流。如果不确定所用库的行为,最安全的做法是显式捕获所有未处理的异常,并将其格式化后写入 stderr,同时确保主进程在发生非致命错误时不会意外退出。

 

连接关闭与资源清理

MCP 协议定义了清晰的生命周期,这也包括连接的终止(Shutdown)。在 stdio 模式下,关闭连接通常由客户端发起。

 

正常的关闭流程并不依赖特定的 JSON-RPC 消息,而是利用底层流的特性。客户端会首先关闭发送给服务端进程的标准输入流(stdin)。服务端进程在检测到输入流结束(End-of-File / EOF)后,应当理解为会话结束,随即执行清理工作(如关闭数据库连接、保存状态),然后自行终止进程。

   

如果服务端在输入流关闭后长时间未退出,客户端会采取更强制的措施。规范建议客户端先发送 SIGTERM 信号,给予服务端最后一次优雅退出的机会。若服务端仍无响应,客户端将发送 SIGKILL 信号强制杀掉子进程。因此,在实现服务端时,监听输入流的关闭事件或系统的终止信号,是保证资源不泄露的关键环节。

   

开发必备:API 全流程管理神器 Apifox

介绍完上文的内容,我想额外介绍一个对开发者同样重要的效率工具 —— Apifox。作为一个集 API 文档API 调试API 设计API 测试API Mock自动化测试等功能于一体的 API 管理工具,Apifox 可以说是开发者提升效率的必备工具之一。

 
如果你正在开发项目需要进行接口调试,不妨试试 Apifox。注册过程非常简单,你可以直接在这里注册使用

Apifox



注册成功后可以先看看官方提供的示例项目,这些案例都是经过精心设计的,能帮助你快速了解 Apifox 的主要功能。

 
使用 Apifox 的一大优势是它完全兼容 PostmanSwagger 数据格式,如果你之前使用过这些工具,数据导入会非常方便。而且它的界面设计非常友好,即使是第一次接触的新手也能很快上手,快去试试吧!

Apifox