你能找到的大多數 Model Context Protocol 教學都是用 TypeScript 或 Python 撰寫,因為這些語言有官方 SDK。這讓我們當中不少人——那些維護存放多年商業資料的 PHP 應用程式的人——懷疑這個協定對我們是否適用。
它是適用的。MCP 是一種傳輸協定,而非函式庫。只要你的語言能從標準輸入讀取一行資料並回傳 JSON,就能用它實作伺服器。這篇文章將說明協定實際要求、PHP 實作的大致樣貌,以及——我低估的部分——當你把內部系統暴露給模型時會發生什麼變化。
MCP 到底是什麼
Model Context Protocol 是一項開放標準,用於將 AI 助理連接到外部系統:你的資料、你的工具、你的 API。它所解決的問題是組合性。在標準出現之前,每個助理都需要與每個資料來源進行客製化整合。MCP 定義單一介面,讓任何相容的客戶端都能與任何相容的伺服器通訊。
在底層,它是 JSON-RPC 2.0。請求包含 jsonrpc 版本、method、params 物件和 id;回應包含相對應的 id 以及 result 或 error。通知是不帶 id 的請求,不會有回覆。如果你曾經實作過 JSON-RPC 服務,你已經知道 80% 的傳輸機制。
伺服器會暴露三種基本元件:
- 工具(Tools) — 模型可以呼叫的動作。每個工具都有名稱、描述,以及描述其輸入的 JSON Schema。這是會執行動作的基本元件:查詢資料庫、建立記錄、發送請求。模型會決定何時呼叫它們。
- 資源(Resources) — 模型可以讀取的資料,以 URI 定址。檔案、記錄、產生的文件。這些是用於提供上下文,而非執行動作,客戶端通常會決定要擷取哪些內容。
- 提示(Prompts) — 使用者可以主動呼叫的可重複使用提示範本,通常會在客戶端 UI 中以斜線指令或選單項目呈現。
工具與資源之間的區別比乍看之下更重要。一個粗略的規則是:工具由模型控制,資源由應用程式控制。 如果模型應該決定是否要擷取某些內容,就做成工具。如果是由使用者或主機應用程式決定,就做成資源。
兩種傳輸方式
MCP 定義了兩種標準傳輸方式,選擇正確的方式是首要的架構決策。
stdio。 客戶端將你的伺服器作為子程序啟動,並透過標準輸入和輸出與它通訊——以換行符號分隔的 JSON,每行一則訊息。這是最簡單的設定:沒有連接埠、沒有 HTTP 伺服器、沒有驗證層,因為唯一能與你的程序通訊的就是啟動它的父程序。這是適用於在客戶端同一台機器上執行的伺服器的正確選擇。
使用 stdio 有兩條規則,這兩條規則在 PHP 中很容易被違反:
-
絕對不要將協議訊息以外的任何內容寫入 stdout。 一個意外的
echo、除錯時留下的var_dump,或是印到 stdout 的 PHP 警告,都會破壞訊息串流,導致客戶端無法解析。請將診斷資訊傳送到 stderr,客戶端通常會將其轉送到日誌。 - 關閉輸出緩衝並在每次寫入後刷新,否則你的回應會停留在緩衝區,而客戶端仍在等待。
Streamable HTTP。 伺服器以普通的 HTTP 端點形式執行。客戶端 POST JSON-RPC 訊息到它;伺服器會回覆單一 JSON 回應,或在需要為一個請求推送多則訊息時回覆 server-sent events 串流。這是用於伺服器在使用者機器以外位置執行的傳輸方式——對大多數 PHP 團隊來說,這是我們已經知道如何操作的部署模式,因此是值得關注的情況。
(較早版本的規格中存在舊的 HTTP+SSE 傳輸方式。新的工作應以 Streamable HTTP 為目標。)
交握程序
無論你選擇哪種傳輸方式,對話的開始方式都相同。客戶端傳送 initialize,包含它使用的協議版本和支援的功能。你的伺服器會回覆自己的協議版本、功能,以及名稱和版本。然後客戶端傳送 initialized 通知,正常操作隨即開始。
功能是用來協商雙方能力的。如果你的伺服器沒有實作資源,就不要宣告 resources 功能,行為良好的客戶端就不會呼叫 resources/list。不要宣告你還沒有建置的功能。
交握後,重要的方法是可以預測的:
| 方法 | 功能說明 |
|---|---|
tools/list |
回傳你暴露的工具,包含描述和輸入結構描述 |
tools/call |
使用給定的參數執行一個工具,並回傳其結果 |
resources/list |
回傳可用資源及其 URI |
resources/read |
回傳一個資源的內容 |
prompts/list |
回傳可用的提示範本 |
prompts/get |
回傳一個已填入的提示 |
一個最小但真正有用的伺服器是 initialize 加上 tools/list 加上 tools/call。其他都是可選的。
PHP 端看起來是什麼樣子
傳輸方式無關的結構形狀:
讀取 JSON-RPC 訊息
→ 根據 `method` 進行分派
→ 建置結果(或錯誤)
→ 使用相同的 `id` 寫入回應
進入全螢幕模式 退出全螢幕模式
對於 stdio,這是一個在 fgets(STDIN)、json_decode、對方法名稱進行 match,以及 fwrite(STDOUT, json_encode($response) . "\n") 上的迴圈。對於 Streamable HTTP,這是一個解碼請求主體並回傳編碼回應的單一端點。中間的分派層是相同的;只有讀取和寫入端會改變。從一開始就這樣撰寫,你就可以從一個程式碼庫同時支援兩種方式。
在開始之前,有三個 PHP 特有的要點值得了解:
工具結構描述。 每個工具都需要 JSON Schema 來描述其輸入。手動將這些撰寫為巢狀陣列很快就會變得乏味。從你已經維護的東西衍生它們——驗證規則集、DTO、透過反射讀取的型別化建構函式參數——可以防止結構描述與實際實作產生分歧。結構描述分歧是「模型一直呼叫工具錯誤」最常見的原因。
錯誤處理。 區分兩種失敗。格式錯誤的請求或未知方法是 協議錯誤:回傳 JSON-RPC error 物件。工具執行但失敗——找不到記錄、驗證拒絕輸入——是 工具錯誤:回傳正常的結果,並設定錯誤標記和人類可讀的訊息。這種區別很重要,因為第二種錯誤會回傳給模型,模型可以讀取訊息並進行調整。協議錯誤只會告訴它某個地方出錯了。將 PHP 例外轉換為第二種錯誤,只要失敗是模型有合理機會恢復的。
長時間執行的工作。 PHP 的請求每程序模型很適合 stdio(程序會持續到工作階段結束),但如果工具需要數分鐘,對於 HTTP 來說就有點尷尬。保持工具呼叫簡短。如果工具啟動了耗時的操作,請立即回傳工作識別碼,並暴露第二個工具來回報狀態。模型很能處理這種模式;它們無法很好地處理逾時的請求。
連接到 Claude
伺服器執行後,有三種主要方式可以連接到它。
本機,透過 stdio。 桌面和 CLI 客戶端——Claude Desktop、Claude Code,以及各種 IDE 整合——讓你可以透過指定要執行的命令及其參數(php,加上伺服器指令碼的路徑)來註冊伺服器,它們會為你管理子程序。這是從「它回應 tools/list」到「我正在使用它」最快的方法。
遠端,透過 HTTP。 部署在 URL 的 Streamable HTTP 伺服器可以向支援遠端連線的客戶端註冊。Claude API 也有一個 MCP 連接器:你可以在請求中宣告伺服器的 URL,API 會在伺服器端建立連線,因此模型可以呼叫你的工具,而無需你撰寫客戶端迴圈。請注意,託管的 MCP 伺服器通常使用 OAuth 持有人權杖進行驗證,而不是服務本身的原生 API 金鑰——這些是不同的驗證系統,假設後者有效是常見的初期錯誤。
從你自己的程式碼。 如果你正在建置代理而非使用現有客戶端,大多數 AI SDK 都可以將 MCP 工具定義轉換為其原生工具格式,因此 MCP 伺服器成為你控制的迴圈的工具來源。
無論哪種方式,先透過 stdio 進行除錯。失敗模式更簡單:沒有 TLS、沒有驗證、沒有代理、沒有 CORS。先讓協議正確,然後再移到 HTTP。
我低估的部分:暴露
當你在現有系統前放置 MCP 伺服器時,會發生變化的一件事是。你暴露的每個工具都是授予模型的能力,而模型至少部分是由從外部世界讀取的文字所引導的。如果模型被說服呼叫你的工具,你的工具就會執行。
實際後果:
工具範圍要狹窄。 接受任意 SQL 的通用 run_query 工具是最方便建置的,也是最不應該發布的。偏好具有型別化參數的特定工具——find_customer_by_email、list_orders_in_range——並將每個參數視為來自匿名 HTTP 請求一樣在伺服器端進行驗證。實際上就是如此。
讀取和寫入是不同的風險類別。 唯讀工具有揭露風險。寫入工具有這實際發生了的風險。將它們分開,並將任何具有破壞性或不可逆轉性的操作放在主機應用程式的明確確認之後,而不是相信模型會小心。
描述是安全面的一部分。 工具描述是模型讀取的指令。模糊的描述會誘使模型在你不打算的情況下呼叫工具。明確規定工具應該在何時使用——不僅是它做什麼——可以顯著改善正確性和安全性。
不要洩漏你不打算暴露的內容。 只回傳任務所需的欄位,而不是整個記錄。內部註記、成本價格或不應該出現在面向客戶答案中的個人電話號碼,也不應該出現在工具結果中。在伺服器端進行過濾,而不是在提示中。
記錄每一次呼叫。 工具名稱、參數、呼叫者、結果。當有人問「為什麼它會這樣做」時,日誌是你唯一能提供的答案。
這些都不是新奇的東西——這與你應用於公開 API 端點的紀律相同。不同之處在於呼叫者是語言模型而非閱讀文件開發人員,因此歧義會以你未預期的方式解決,而不是以支援票證的形式浮現。
值得嗎?
對於建置在多年累積商業資料上的 PHP 應用程式來說,MCP 是我找到的最便宜的橋樑,可以在這些資料和實際能對其進行推理的助理之間建立連線。不需要等待 SDK。這是透過管道或 HTTP 端點傳輸的 JSON-RPC——這兩件事 PHP 已經擅長二十年了。
從三個唯讀工具開始,它們可以回答組織中某人每週都會問的問題。透過 stdio 發布給一個人。看看他們實際要求什麼。這比你事先設計的任何規格都要好得多。
如果你曾在沒有官方 SDK 的語言中建置 MCP 伺服器,我很好奇是什麼絆倒了你——是傳輸方式、結構描述,還是範圍界定。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.