今天來介紹 Mcp Server使用的 client-server 的傳輸協議,在上一章我們用Notion MCP Server,眼睛好的同學可能有注意到,這個MCP Server是跑在我們主機上的,這就是所謂的 STDIO 模式,我們一起來看目前 MCP Server 有哪些通訊協議吧:
舉凡在本地跑的 MCP Server 都被歸在這類,背後就是由 client 啟動 subprocess 來運行 MCP server, 並且透過 stdin/stdout 交談,如果 client 關掉那 作為 subprocess 的 MCP Server 也會關掉,可以簡單想成是父程式中有子程式。他支援一般的一問一答、通知。
stadio 意思為 standard I/O, 伺服器會讀 input 並且回傳 output , 這個傳輸的訊息格式我們會用 JSON-RPC 明天會作為補充介紹。
接著讓我們來看 I/O 互動方式:
Client Process Server Process
┌───────────────────┐ ┌───────────────────┐
│ Write JSON-RPC │ --stdout------▶ │ Read stdin │
│ (e.g. request) │ │ (接收 request) │
│ │ ◀------stdin--- │ Write stdout │
│ Read JSON-RPC │ (response) │ (回傳 response) │
└───────────────────┘ └───────────────────┘
也因為兩者的溝通是利用 standard i/o 溝通的,所以這個通道就是專屬保留給 MCP 訊息的通道,這時我們的 server 如果有做 console.log 就會把訊息輸出到stdout了,在stdout 的東西就會被 Client 會當作 MCP 訊息解析,結果就會出錯,因為兩者的訊息格式是用 JSON-RPC。
這時候你可以選擇把他丟到 stderr,官方推薦使用現成的 logger 套件 / library,幫你把預設輸出到 stderr 而不是 stdout。
[ Data Layer ]
JSON-RPC 2.0 訊息格式
--------------------------------
{"jsonrpc":"2.0","method":"ping","id":1}\n
{"jsonrpc":"2.0","method":"ping","id":1}\n
[ Transport Layer ]
stdio (標準輸入 / 標準輸出)
--------------------------------
- Client stdout → Server stdin
- Server stdout → Client stdin
[ OS / Process Layer ]
Pipe (由作業系統提供的資料流)
--------------------------------
Client Process ────| pipe |──── Server Process
想實作有通知功能的 MCP Server 必須用這個方式,像是你的 gitlab AI 助手 可以在每天早上 10 點通知你今天有哪些 MR 要看。
對比傳統作法你只能自己主動在剛上班的時候問他:請問我今天有幾個 MR 要看,是不是方便多了,雖然他本來的初衷應該是用來處理長任務,能夠分段回應,但兩者都是應用情境。
長任務分段回應示意圖如下:
Client (使用者) Server (轉檔服務)
| |
|-- POST /upload (initialize) ----------->| // 上傳影片
|<-- HTTP 200 |
| Mcp-Session-Id: 123e4567 | // 建立 session
| |
|-- GET /progress | // 訂閱 SSE
| Mcp-Session-Id: 123e4567 | // 用 header 帶 session id
| Accept: text/event-stream |
|---------------------------------------->|
|<-- HTTP 200 |
| Content-Type: text/event-stream |
| |
|<-- event: progress |
| data: {"progress": "30%"} | // 傳輸進度(包在 data 裡)
|<-- event: progress |
| data: {"progress": "60%"} |
|<-- event: completed |
| data: {"url": "https://example.com"} |
當然你也不一定要支援通知這種長連線的SSE,只單純提供一問一答得的形式,這時如果有人想和你建立長連結只要回 405 Method Not Allow 即可。
這種 server 能主動推通知給 client 就叫做 SSE (Server-Sent Events),他是 HTTP 的一種用,一樣是用 HTTP 只是 Accept 會指定 text/event-stream 作為支援 SSE 的暗號,讓 server 知道接下來我想要的是事件流(分段回應),而不是一般的JSON 或 HTML 回應,讓我們看這三者的比較:
應用層 (Application Layer)
│
├── HTTP
│ - 一次性 Client request → Server response
│
├── SSE (Server-Sent Events)
│ - 基於 HTTP (Content-Type: text/event-stream)
│ - 單向推播 (Server → Client)
│ - 長連線,持續輸出事件
│
└── WebSocket
- 由 HTTP Upgrade 握手建立
- 換協議 → WebSocket 協議
- 雙向 (Client ↔ Server) full-duplex
─────────────────────────────────────────────
會話層 (Session Layer) 兩者處理方式不同
│
├── HTTP/SSE → 需 server 自行維護 session
└── WebSocket → 天生維護持續會話,長連線本身即 session,不需要另外處理
─────────────────────────────────────────────
傳輸層 (Transport Layer)
└── TCP (三者都依賴 TCP)
GET (SSE) → Client 建立持續連線(向server訂閱),接收來自 Server 的「streaming 訊息」
GET /mcp HTTP/1.1
Host: example.com
Accept: text/event-stream
與 STDIO 不同的是,需要透過網路傳遞,另外他跟 STDIO 一樣用 JSON-RPC 作為訊息格式規範。所以是傳輸方式不同但是資料格式相同。
POST: Client -> Server
POST /mcp HTTP/1.1
Host: example.com
Content-Type: application/json
{"jsonrpc":"2.0","method":"ping","id":1}\n
HTTP 本身支援 session 機制,常見案例:官網的購物車、上傳大檔案時網路斷掉,不能重傳整個檔案。
會在 Header 帶 Mcp-Session-Id。
// request
[
{"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
{"jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": "2"}
]
// response
[
{"jsonrpc": "2.0", "result": 19, "id": "2"},
{"jsonrpc": "2.0", "result": 7, "id": "1"}
]
這之後可以再拉章節詳細講解,今天先這樣吧!
1.JSON-RPC 訊息
每則訊息一行
不能有多行或奇怪格式
2.stdin/stdout (process pipe)
3.雙方嚴格遵守「stdout/ stdin = 只有 MCP 訊息」的規則
保證通訊可靠,不會被雜訊干擾
輸出 debug 訊息用stderr 通道,才不會干擾 MCP 訊息
1.JSON-RPC 訊息
2. 獨立部署
Server 為 獨立進程 (independent process),可同時服務多個 client。
3. 透過網路
簡單的舉例,HTTP是問答式客服一問一答,SSE像是廣播電台,他能主動跟你講話,像是講冷笑話給你,但你不能嗆回去說不好笑XD,WebSocket像是Google Meet通話