先前的文章,我把「給人類使用」的 API(下單、查詢、WebSocket 市場資訊…)都交代完了。從今天起切入 MCP(Model Context Protocol)整合:我在 eap-order 裡新增了一支 McpApiController,專門提供 LLM/Agent 可直接呼叫的介面。這篇要講清楚:
(1)為什麼要特別做一支 Controller 給 MCP 用;
(2)它和原本「給人類」的 API 有什麼差異;
(3)入口拆分帶來的好處。
LLM 與人類的使用模式不同:
結構化、可預期:Agent 需要固定欄位與錯誤碼,避免自然語言冗字。
工具語意清晰:placeOrder / cancelOrder / getUserOrders / getOrderBook / getMarketMetrics 明確對應行為,便於權限與審計。
版本與可觀測性:以 /mcp/v1/** 版號治理契約,日誌可綁 tool → execId → 結果 做灰度與回滾。
安全防呆:狀態變更操作集中管理(例如需要嚴格驗證、限流、白名單 Token)。
因此我做了 McpApiController:相同的業務能力,不同的介面設計與運維策略。
面向 | 人類入口 | MCP 入口(/mcp/v1/** ) |
---|---|---|
回應風格 | 友善文案、彈性欄位 | 嚴格 DTO、固定 success/failure 結構 |
參數 | 表單/Query 友善 | 扁平、強型別(便於校驗與工具化) |
權限 | 以使用者為核心 | 以服務帳號/工具範圍為核心 |
觀測 | user/session | tool/execId/readOnly 指標 |
版本 | 可能隨前端演進 | 路徑版號固定,契約穩定 |
為了對齊 MCP「工具化」語意,我把常用操作(下單、查狀態、查用戶掛單、撤單)以穩定 DTO + 統一回應包裝的方式提供,並且加入「唯讀/狀態改變」的執行保護。
以 單一入口 同時支援買/賣,LLM 只要給出 side/price/qty/userId 即可;內部路由到對應 service,回傳統一 DTO。
@Operation(summary = "統一下單", description = "支援買賣雙向的統一下單接口")
@PostMapping("/orders")
public ResponseEntity<PlaceOrderResponse> placeOrder(@Valid @RequestBody PlaceOrderRequest request) {
log.info("收到 MCP 下單請求: {}", request);
if (!request.isValid()) return ResponseEntity.badRequest().body(PlaceOrderResponse.failure(request.getValidationError()));
try {
UUID orderId;
if (request.isBuy()) {
PlaceBuyOrderReq buyReq = PlaceBuyOrderReq.builder()
.bidPrice(request.getPriceAsInt())
.amount(request.getQtyAsInt())
.bidder(UUID.fromString(request.getUserId()))
.build();
orderId = placeBuyOrderService.execute(buyReq);
} else if (request.isSell()) {
PlaceSellOrderReq sellReq = new PlaceSellOrderReq();
sellReq.setSellPrice(request.getPriceAsInt());
sellReq.setAmount(request.getQtyAsInt());
sellReq.setSeller(UUID.fromString(request.getUserId()));
orderId = placeSellOrderService.placeSellOrder(sellReq);
} else {
return ResponseEntity.badRequest().body(PlaceOrderResponse.failure("Invalid side: " + request.getSide()));
}
return ResponseEntity.ok(PlaceOrderResponse.success(
orderId.toString(), request.getSide(), request.getType(), request.getPrice(), request.getQty(), request.getSymbol()));
} catch (Exception e) {
log.error("下單失敗", e);
return ResponseEntity.internalServerError().body(PlaceOrderResponse.failure("下單失敗: " + e.getMessage()));
}
}
這些端點延續「結構化回應 + 輕度保護」原則程式碼部分與上述雷同就不再贅述,主旨在於區分人類及機器使用入口主要業務邏輯與之前介紹的差異不大:
撤單 DELETE /mcp/v1/orders/{orderId}:集中呼叫Feigin EapMatchEngine;
查用戶訂單 GET /mcp/v1/orders?userId=&status=:pending/matched/全部 三態合一;
訂單簿 GET /mcp/v1/orderbook?depth=n:上限 20 層,避免回傳過量;
市場指標 GET /mcp/v1/metrics?depth=n:以訂單簿衍生 spread、流動性 等指標,封裝為機器友善欄位;
健康檢查 GET /mcp/v1/health:直接曝露依賴元件狀態,方便 Agent 前置檢查。
契約穩定:/mcp/v1/** 版號固定,不被人類介面需求牽動。
安全分層:針對工具行為做白名單、限流、審計,與人類權限隔離。
測試容易:可直接對 MCP 介面做 Contract/Load Test。
可觀測:統一 DTO,execId 串起 trace,錯誤碼可量測。
效能可控:深度與 payload 有硬上限,利於吞吐與延遲優化。
prompt 友善:語意穩、欄位穩,Agent 學一次可長用。
McpApiController 並不是重做一套業務,而是把既有能力「以機器可安全操控的方式」重新包裝:一致入口、強校驗、結構化回應、嚴格上限。下一篇我會把這些端點註冊成 Spring AI 工具,讓我的這些端點包裝成spring ai 可以註冊的mcp tool讓後續串接上ai agent能自動調用工具發送api請求。