MCP + SSE:單向通信如何玩出雙向操作?
“ 你是否覺得 SSE 只能單向通信?MCP 用一招教你實現雙向通信!”
SSE 本身確實只能從服務器到客戶端單向通信,但 MCP(Model Context Protocol)通過一個巧妙的組合拳,把 SSE 和 HTTP POST 請求搭配在一起,成功實現了完整的雙向通信。
聽起來是不是很酷?其實原理很簡單——一個負責“說”,一個負責“聽”,組合起來就是“對話”了。
雙向通信的秘密
想象一下,客戶端和服務器在聊天,它們的對話是這樣開始的:
- 客戶端先打招呼:通過 GET 請求建立 SSE 連接,就像撥通了一個電話。
- 服務器回應:發送一個“endpoint”事件,告訴客戶端“我在這里等你發消息”。
- 客戶端開始說話:通過 HTTP POST 請求,把消息發到服務器提供的端點。
- 服務器回應:通過 SSE 連接,把回復送回客戶端。
- 服務器主動通知:隨時可以通過 SSE 推送消息,就像發短信一樣。
- 客戶端再次發言:繼續用 HTTP POST 發送新消息。
為了更直觀地理解這個過程,我們可以通過以下流程圖來展示雙向通信的每一步:
整個過程就像兩個人打電話,雖然用的是兩種不同的通信方式,但邏輯上完全實現了雙向對話。
客戶端的雙重身份
客戶端的工作分為兩部分:一是訂閱 SSE 事件流,二是通過 HTTP POST 發送消息。
// 連接建立過程
@Override
publicMono<Void>connect(Function<Mono<JSONRPCMessage>, Mono<JSONRPCMessage>> handler){
// 1. 訂閱 SSE 事件流,就像打開收音機
sseClient.subscribe(this.baseUri +this.sseEndpoint,newFlowSseClient.SseEventHandler(){
@Override
publicvoidonEvent(SseEvent event){
// 2. 處理 endpoint 事件,獲取消息發送端點
if(ENDPOINT_EVENT_TYPE.equals(event.type())){
String endpoint = event.data();
messageEndpoint.set(endpoint);
// 連接建立完成
}
// 3. 處理服務器發來的消息
elseif(MESSAGE_EVENT_TYPE.equals(event.type())){
JSONRPCMessage message =McpSchema.deserializeJsonRpcMessage(objectMapper, event.data());
handler.apply(Mono.just(message)).subscribe();
}
}
});
returnMono.fromFuture(future);
}
// 客戶端發送消息
@Override
publicMono<Void>sendMessage(JSONRPCMessage message){
// 使用 HTTP POST 發送消息到服務器提供的端點
// ...
}
簡單來說,客戶端就像一個既能聽又能說的人:一邊通過 SSE“聽”服務器的消息,一邊通過 HTTP POST“說”自己的話。
服務器的中轉站
服務器的工作更像一個中轉站,既要處理客戶端的請求,又要通過 SSE 推送消息。
// 處理 SSE 連接請求
privateServerResponsehandleSseConnection(ServerRequest request){
// 創建會話,就像給每個客戶端分配一個專屬頻道
String sessionId =UUID.randomUUID().toString();
// 發送初始 endpoint 事件,告訴客戶端“我在哪”
sseBuilder.id(sessionId)
.event(ENDPOINT_EVENT_TYPE)
.data(this.baseUrl +this.messageEndpoint +"?sessinotallow="+ sessionId);
// 創建會話傳輸層
WebMvcMcpSessionTransport sessionTransport =newWebMvcMcpSessionTransport(sessionId, sseBuilder);
McpServerSession session = sessionFactory.create(sessionTransport);
this.sessions.put(sessionId, session);
}
// 處理客戶端發來的消息
privateServerResponsehandleMessage(ServerRequest request){
// 從請求中獲取會話 ID,就像找到對應的聊天窗口
String sessionId = request.param("sessionId").orElse(null);
// 處理客戶端發來的消息
// ...
// 通過 SSE 連接發送響應
// ...
}
// 服務器向客戶端發送消息
@Override
publicMono<Void>sendMessage(McpSchema.JSONRPCMessage message){
returnMono.fromRunnable(()->{
try{
String jsonText = objectMapper.writeValueAsString(message);
sseBuilder.id(sessionId).event(MESSAGE_EVENT_TYPE).data(jsonText);
}
catch(Exception e){
// 錯誤處理
}
});
}
服務器的核心任務是管理會話,確保每個客戶端的消息都能準確無誤地傳遞。
雙向通信的四大支柱
- 初始連接:客戶端通過 GET 請求建立 SSE 連接,就像撥通了一個電話。
- 端點發現:服務器通過 SSE 發送“endpoint”事件,告訴客戶端“我在這里等你”。
- 雙向通信:
a.客戶端通過 HTTP POST 發送消息
b.服務器通過 SSE 推送消息到客戶端
- 會話管理:服務器為每個客戶端分配唯一的會話 ID,確保消息不會“串門”。
這種設計的厲害之處在于,它沒有試圖“改造”SSE,而是聰明地利用了它的實時推送優勢,同時用 HTTP POST 解決了反向通信的問題。
就像給一輛單缸摩托車裝上了副駕駛座,瞬間變成了能對話的“雙人通信車”。
更妙的是,這種組合拳式的實現方式,不僅保持了 SSE 的輕量化特性,還避免了 WebSocket 那種復雜的雙向協議開銷。
對于那些需要實時推送但又不想引入復雜技術棧的項目來說,MCP 的這套方案簡直是“降維打擊”。
雙向通信的更多可能
MCP 的這套組合拳,不僅解決了 SSE 的單向通信問題,還保留了實時推送的優勢。
未來,這種設計思路可以應用到更多場景中,比如物聯網設備通信、微服務之間的輕量級消息傳遞,甚至是 Web 應用中的實時協作功能。
想象一下,當你的前端應用需要和后端服務實時互動時,用 MCP 的這套方案,既簡單又高效,還能避免引入額外的技術復雜性。是不是很期待?趕緊試試吧!