Ian Chou's Blog

Cursor 怎麼跟 Server 講話?不是用 PID 嗎? (Pipe vs PID)

Cursor 怎麼跟 Server 講話?不是用 PID 嗎? (Pipe vs PID)

這是一個非常敏銳的問題!因為在人類的邏輯裡,要寄信給某人通常需要「地址」或「身分證字號(PID)」。

但在程式設計(特別是 Parent/Child Process 模型)中,答案是:不是用 PID 來傳資料,是用「物件參考 (Object Reference)」或是「檔案描述符 (File Descriptor)」

為了讓您完全理解,我們試著假裝自己是 Cursor 的開發者,用 Node.js 寫一段「控制中心」的程式碼。看完這段 Code,您就會瞬間明白。

1. 模擬 Cursor 內部的運作 (The "Parent's View")

當 Cursor 啟動您的 MCP Server 時,它會執行類似這樣的 Node.js 程式碼:

const { spawn } = require('child_process');

// 1. 啟動行程 (Spawn)
// 這裡會回傳一個 "ChildProcess 物件",我們把它存入變數
const myMcpServer = spawn('bun', ['run', 'server.ts']);

// 此時,myMcpServer 這個變數就是那條「專屬電話線」的控制端
// Cursor 把這個變數存在記憶體裡,可能是一個 Map:
// toolsMap.set('my-math-server', myMcpServer);

// -------------------------------------------------

// 2. 當 Cursor 想傳資料給您的 Server 時
// 它不需要去查 PID,它只需要拿出上面那個變數,往它的 stdin 寫入即可

const message = { jsonrpc: "2.0", method: "tools/call", params: { name: "add", arguments: { a: 1, b: 2 } } };

// 【關鍵動作】直接寫入該物件的 stdin 串流
myMcpServer.stdin.write(JSON.stringify(message) + "\n");

// -------------------------------------------------

// 3. 當您的 Server 回傳資料時
// Cursor 監聽這個物件的 stdout 事件
myMcpServer.stdout.on('data', (data) => {
  console.log(`收到來自 MCP Server 的回應: ${data}`);
});

2. 為什麼不用 PID? (The "Leash" Analogy)

想像您是一個遛狗的人(Cursor),您手裡牽著兩條狗(兩個 MCP Server)。

當您想讓左邊那隻狗停下來時,您會:

  1. 查名牌法(PID):先看它的名牌是 9527,然後對著空氣大喊:「編號 9527 的狗,停下!」(這就像網路通訊,需要地址)。
  2. 拉繩子法(Stream Reference):您根本不在乎它的編號是多少,您只是拉動左手握著的那條繩子

Cursor 就是用拉繩子法。因為它就是創造這個 Process 的人,它天生就持有這個 Process 的輸入/輸出控制權(Handle/Stream)。

3. 技術底層:OS Kernel 怎麼對應?

雖然 Cursor 程式碼層級是用變數(物件)來操作,但到了作業系統(Ubuntu Kernel)層級,這確實是對應到數字(File Descriptor):

  1. Cursor (Parent) 呼叫 write()
  2. Node.js 查詢 myMcpServer.stdin 底層對應的 File Descriptor (例如 FD 14)
  3. Cursor 告訴 Kernel:「把資料寫進 FD 14」。
  4. Kernel 查表發現:FD 14 連接的是一條 Pipe,這條 Pipe 的另一端連著 PID 5678 (Child)FD 0 (stdin)
  5. 資料流進 Child 的 stdin。

總結

所以,您的 Server 就像是 Cursor 手中的風箏,線一直都在 Cursor 手上,它不需要叫風箏的名字就能傳遞訊號。