# MCP

Source: https://gortexa.sheng.page/transports/mcp/

MCP（Model Context Protocol）bridge 把 proto 裡標了 `gortexa.ai.v1` annotations 的方法暴露成 tools。AI agent 對 `/mcp` 發 JSON-RPC，bridge 經 in-process gRPC loopback 呼叫原本的服務——AI 的呼叫跟一般請求過同一條攔截器鏈，auth 與 validation 一段不少。

## 標注方法

在 rpc 上加 `gortexa.ai.v1.ai_tool` annotation：

```protobuf
rpc GetResource(GetResourceRequest) returns (Resource) {
  option (google.api.http) = {get: "/v1/resources/{id}"};
  option (gortexa.ai.v1.ai_tool) = {
    expose: true
    name: "get_resource"
    description: "Fetch a single resource by id and return its current state."
    read_only: true
  };
}
```

`read_only` 與 `destructive` 會進入 tool schema 的行為提示。沒標 `expose: true` 的方法不會出現在 tools 清單。

## Endpoint

- 路徑：`/mcp`（Streamable HTTP）
- Protocol versions：`2025-03-26`（預設）與 `2024-11-05`
- Request body 上限 1 MiB、讀取逾時 30 秒
- JSON-RPC batch 最多 100 個 element；超過回 413 與 JSON-RPC error `-32000 "batch too large"`（在進入攔截器鏈之前就擋下）
- Bridge 是無狀態的：不發放也不讀取 `Mcp-Session-Id`，不保留任何 session 狀態，每個請求各自以自己的憑證授權
- 被拒的 `Origin` 與超量的 batch 各自以 WARN 經 `slog.Default()` 記錄（附 origin／element 數與 client IP）——先前這兩者只回一個狀態碼、對維運沒有任何紀錄
- Browser-based client 需要 CORS 時開 `server.enable_cors`，並把允許的 origin 列進 `server.cors_origins`——這個 allowlist 就是防 DNS rebinding 的機制，`Origin` 沒列在上面的請求會收到 `403 forbidden origin`，且 `"*"` 在這裡會被忽略（每個 browser origin 都要明確列出）

## tools/list

```bash
curl -XPOST localhost:8080/mcp \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

回傳所有標注方法的 tool schema。清單是 memoized 的（約 142 ns 一次），不會每次呼叫重算。

## tools/call

`tools/call` 的參數被序列化成 protobuf 訊息、經 loopback 打到原服務，結果再轉回 JSON-RPC。沒過 auth 就得到 unauthenticated 的 error envelope，跟 gRPC、HTTP 的行為一致。`Authorization` 與 `X-Request-Id` 只在位元組是合法 gRPC metadata（可列印 ASCII）時才轉送到 loopback；無效值直接丟棄而不傳播，呼叫端因此仍拿到鏈的真實答案（例如 `unauthenticated`）而非不透明的 `internal`，request id 則由下游重新鑄造。超過 8 KiB 的 `Authorization` 值同樣會被丟棄：真實憑證頂多幾 KB，而 bridge 會把這個標頭重新編碼到 batch 中**每一個** element 的 loopback 呼叫上（最多 100 次），所以過大的值是放大攻擊向量而不是憑證。

## Schema 匯出

同一份 `gortexa.ai.v1` annotations 可以離線輸出成 provider-ready 格式：

```bash
gortexa export --format=mcp
gortexa export --format=openai   # OpenAI strict function calling
gortexa export --format=gemini
```

三種輸出都有 golden test 鎖定：proto 改了，schema 跟著變，測試會抓到意外的漂移。

## 常見問題

### 怎麼把 gRPC service 開成給 LLM 用的 MCP tools？

在 proto 的 method 上標 `gortexa.ai.v1` annotations，然後把該 service 加進 `mcp.ServiceDescriptors(...)` 並用 `mcp.NewBridge` 掛到 `/mcp`。schema 由 proto 產生，不必另外手寫工具定義。

### MCP 的 tool call 會經過我的 auth interceptor 嗎？

會。`tools/call` 透過 in-process loopback 派送回同一個 gRPC server，所以它和任何一般請求一樣，要走完 recover、request-id、logging、load shedding、rate limiting、斷路器、auth 與 validation。

### MCP endpoint 需要 session 嗎？

不需要。Bridge 是無狀態的：不發放也不讀取 `Mcp-Session-Id`，每個請求各自以自己的憑證授權。

### 瀏覽器裡的 MCP client 要怎麼設定？

開 `server.enable_cors` 並把允許的 origin 列進 `server.cors_origins`。那份 allowlist 同時是 DNS rebinding 的防線——沒列進去的 origin 發出的請求會被以 `403 forbidden origin` 拒絕，並記一行 WARN。CORS 視為全部允許的 `"*"`，這道防線刻意忽略，所以每個 browser origin 都要明確列出。
