契約工具
投影透過現有的工具目錄公開選定的契約操作。契約仍然是 Effect 值。
- 本地服務與受管 Remote 門面使用相同的綁定。
- 每個工具明確宣告中繼資料和輸出處理方式。
- 更新視圖保留傳輸連線與已准入的呼叫。
import { Context, Effect, Schema } from "effect"import { Rpc } from "effect/rpc"
import { Plugin } from "@graftland/graft"import * as Projection from "@graftland/plugin-mcp-server/Projection"import { Contract } from "@graftland/plugin-remote"
class Forecast extends Context.Service< Forecast, { readonly get: (city: string) => Effect.Effect<string> }>()("example/Forecast") {}
const Get = Contract.operation( "example/forecast/Get", 1, Rpc.make("Get", { payload: { city: Schema.String }, success: Schema.String, }),).annotate(Projection.Annotation, { name: "forecast", title: "City forecast", description: "Read a city forecast", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, output: "json",})
export default Plugin.make({ name: "forecast-tools", inject: [Forecast], apply: () => Projection.offer("forecast-tools", [ Projection.operation(Forecast, Get, (forecast, { city }) => forecast.get(city), ), ]).pipe(Effect.asVoid),})使用契約套件匯出的服務鍵和 RPC。呼叫使用貢獻插件注入的服務,因此受管部署透過 fork 持有的門面呼叫。註解可以放在契約套件中,也可以放在宿主明確宣告的 MCP 視圖中。
| 宣告 | 規則 |
|---|---|
name、title、description |
全部必填。工具名稱由 1–128 個字母、數字、底線、點或連字號組成。 |
| 四個提示 | 全部必填;直接對應到 MCP 註解,不做推斷。 |
output: "json" |
用可攜式編解碼器編碼成功值,將 { value } 作為結構化內容和文字回傳。 |
output: "void" |
捨棄成功值。 |
| 服務與操作 | 每個組合在視圖中只出現一次;視圖中的工具名稱唯一。 |
輸入 schema 從操作載荷的可攜式編解碼器產生,採用 MCP 預設的 JSON Schema 2020-12 方言。它只是外部客戶端視圖,絕不參與契約身分、來源紀錄或相容性檢查。執行時仍以 Effect 解碼為準。
ProjectionError
指明服務、操作和原因。缺少身分或註解、串流操作、RPC 中介軟體、遞迴或不透明載荷,以及沒有物件根的 schema,都會在發布前被拒絕。Schema 不得需要額外的解碼或編碼服務。這些是投影的限制,不是契約的限制;普通契約操作在 MCP 之外仍然有效。
| 結果 | MCP 行為 |
|---|---|
| 工具參數無效 | 回傳輸入無效的 isError 結果;不呼叫服務。 |
| 已宣告的業務錯誤 | 回傳帶 kind: "business" 和可攜式錯誤值的 isError。 |
| 系統失敗、未宣告錯誤或缺陷 | 回傳經過清理的 isError,帶 kind: "system"。 |
| 輸出編碼失敗 | 回傳經過清理的系統結果。 |
| 取消 | 中斷受管呼叫並等待其清理完成。 |
| 格式錯誤的協定請求 | MCP 協定錯誤。 |
日誌保留完整失敗 cause,並標註服務鍵和操作身分,包括編碼失敗。業務錯誤值會有意公開;不要在其中放置憑證。
呼叫方取消或貢獻插件釋放時,即使呼叫或編碼器的清理也失敗,取消仍然優先。日誌保留完整的混合 cause,不會將其轉換為業務或系統工具結果。伺服器發起的取消以協定錯誤回傳客戶端。
發布變更後的視圖
Section titled “發布變更後的視圖”Projection.offer 回傳控制器。只修改宿主視圖時使用
controller.update。視圖屬於部署變更時,使用
controller.updateAfter(action, bindings, accepted):將 Graft.transition
或發布 apply 作為 action,僅接受其 Accepted
結局。新視圖在 action 開始前完成驗證。Action 與後續發布歸貢獻插件的 scope 持有,因此取消呼叫方不會遺失已接受的更新。
並行的控制器 action 會在 action 開始前以 ProjectionError
失敗,不會排隊。註冊與歷史紀錄以原子方式發布。
宿主必須提供新的帶註解視圖。僅改變 worker 程式碼或發布物,不會將新註解匯入宿主。失敗的 action 保留舊視圖。移除貢獻插件會取消其持有的工作,並先撤下工具,再向已連線的客戶端傳送清單變更通知。
發布會向支援通知的 MCP 會話發出 notifications/tools/list_changed。
Projection.Changed 記錄修訂號及變更前後的中繼資料,包括服務鍵和操作身分。
controller.history
保留最近 32 個修訂;需要持久稽核時,由應用持久化該事件。通知和紀錄不授予批准,也不實作重新批准。
整合閘道範例 使用獨立的 MCP 客戶端檢查工具清單、呼叫、描述變更通知,以及已准入的 v1 呼叫跨越已接受的 v2 轉換而無需重新連線。
透過 stdio 或支援通知訂閱的傳輸發布視圖變更。Effect 4.1.2 的舊版 Streamable
HTTP 轉接器不提供閒置 GET 事件串流(405);此路徑尚未實作主動傳送工具清單變更通知。使用該路徑的客戶端仍可無需重新連線地列出和呼叫工具。上游追蹤:Effect #10436。
對受政策管理的項目,投影中繼資料屬於精確的核准聲明。發布變更後,新呼叫會被阻止,直到重新核准;快取舊工具清單的用戶端也不例外。清單變更通知不是核准,已接納的呼叫仍使用原來的 serving attempt。
投影驗證 MCP 欄位,並以 mcp/tools
標籤發布核准資料。撤下貢獻者會移除其線上工具,但保留核准事實。重新掛載相同中繼資料無需再次核准;發布不同中繼資料會在公開變更前阻止准入。
給 Projection.offer 傳入穩定名稱,例如
"public/forecast-tools"。每個名稱在目前 Graft 執行時識別一個邏輯視圖。重複線上擁有者回傳
ProjectionConflict;釋放時撤銷擁有權,但保留最新核准事實。更新只取代該視圖的事實。還原中繼資料會恢復相符的核准。發行稽核日誌保存修訂,線上陳述僅保留最新值。稽核與撤銷規則見信任與隔離。