iT邦幫忙

2025 iThome 鐵人賽

DAY 19
0
Software Development

事件驅動電力交易平台:Spring Boot 實戰系列 第 19

Day 19 | 在 order-service 增加 McpApiController:把「給機器用」與「給人用」的入口拆開

  • 分享至 

  • xImage
  •  

先前的文章,我把「給人類使用」的 API(下單、查詢、WebSocket 市場資訊…)都交代完了。從今天起切入 MCP(Model Context Protocol)整合:我在 eap-order 裡新增了一支 McpApiController,專門提供 LLM/Agent 可直接呼叫的介面。這篇要講清楚:
(1)為什麼要特別做一支 Controller 給 MCP 用;
(2)它和原本「給人類」的 API 有什麼差異;
(3)入口拆分帶來的好處。

為什麼需要一支「給機器用」的 Controller?

LLM 與人類的使用模式不同:

  • 結構化、可預期:Agent 需要固定欄位與錯誤碼,避免自然語言冗字。

  • 工具語意清晰:placeOrder / cancelOrder / getUserOrders / getOrderBook / getMarketMetrics 明確對應行為,便於權限與審計。

  • 版本與可觀測性:以 /mcp/v1/** 版號治理契約,日誌可綁 tool → execId → 結果 做灰度與回滾。

  • 安全防呆:狀態變更操作集中管理(例如需要嚴格驗證、限流、白名單 Token)。

因此我做了 McpApiController:相同的業務能力,不同的介面設計與運維策略。

和「給人用」API 的差異在哪?

面向 人類入口 MCP 入口(/mcp/v1/**
回應風格 友善文案、彈性欄位 嚴格 DTO、固定 success/failure 結構
參數 表單/Query 友善 扁平、強型別(便於校驗與工具化)
權限 以使用者為核心 以服務帳號/工具範圍為核心
觀測 user/session tool/execId/readOnly 指標
版本 可能隨前端演進 路徑版號固定,契約穩定

McpApiController:專為 Agent 打造的Controller

為了對齊 MCP「工具化」語意,我把常用操作(下單、查狀態、查用戶掛單、撤單)以穩定 DTO + 統一回應包裝的方式提供,並且加入「唯讀/狀態改變」的執行保護。

1) 統一下單 /mcp/v1/orders

以 單一入口 同時支援買/賣,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()));
    }
}

2) 撤單、查單、訂單簿、指標、健康檢查

這些端點延續「結構化回應 + 輕度保護」原則程式碼部分與上述雷同就不再贅述,主旨在於區分人類及機器使用入口主要業務邏輯與之前介紹的差異不大:

  • 撤單 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請求。


上一篇
Day 18 |推送面向與主題一覽 市場摘要、 訂單簿近況、 逐筆成交、 最近成交
下一篇
Day 20 | 我的MCP服務 基礎設定與相關依賴使用
系列文
事件驅動電力交易平台:Spring Boot 實戰22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言