diff --git a/mcp/src/tools/kbdb_data.ts b/mcp/src/tools/kbdb_data.ts index 793b066..488f303 100644 --- a/mcp/src/tools/kbdb_data.ts +++ b/mcp/src/tools/kbdb_data.ts @@ -26,11 +26,18 @@ import type { Env } from "../types.js"; import { kbdbFetch } from "../lib/kbdb-client.js"; import { errorResponse, successResponse } from "../lib/cypher-client.js"; -/** 註冊全部 KBDB 資料層工具(kbdb-base Phase 9.1)。不含建表/SQL tool(鐵律)。 */ -export function registerAllKbdbDataTools(server: McpServer, env: Env) { +/** + * 註冊全部 KBDB 資料層工具(kbdb-base Phase 9.1)。不含建表/SQL tool(鐵律)。 + * + * D28(owner-mandatory):base 的 `POST /records` 已把 owner_id 設必填(缺 → 400)。 + * MCP 是薄殼、是 caller——owner **由機制注入**(org namespace),不靠 AI 記得帶。 + * 仿 arcrun_report_feedback:把 orgNamespace 傳進寫入 tool,寫入時當 owner_id。 + * 送到 base 的 owner_id 一定非空 → AI 漏不了。 + */ +export function registerAllKbdbDataTools(server: McpServer, env: Env, orgNamespace: string) { registerCreateTemplate(server, env); registerListTemplates(server, env); - registerCreateRecord(server, env); + registerCreateRecord(server, env, orgNamespace); registerGetRecord(server, env); registerQuery(server, env); registerSearch(server, env); @@ -92,8 +99,16 @@ export function registerListTemplates(server: McpServer, env: Env) { ); } -/** kbdb_create_record — 依某 template 填一筆 record(slot → 內容)。 */ -export function registerCreateRecord(server: McpServer, env: Env) { +/** + * kbdb_create_record — 依某 template 填一筆 record(slot → 內容)。 + * + * D28(owner-mandatory):base 的 `POST /records` owner_id 必填(缺 → 400)。 + * owner_id 由 MCP **機制注入**(orgNamespace),不靠 AI 帶: + * - AI 傳的 owner_id 當「選填覆寫」(想歸到別的租戶/專案時才用)。 + * - AI 沒傳 → 用注入的 orgNamespace。 + * - 兩者皆空 → 直接回錯(不送必然 400 的請求),保證送到 base 的 owner_id 一定非空。 + */ +export function registerCreateRecord(server: McpServer, env: Env, orgNamespace: string) { server.tool( "kbdb_create_record", "依某 template 填一筆 record(一列資料)。values 是 {slot名: 內容},slot 名要對得上 template 的 slots。" + @@ -101,14 +116,23 @@ export function registerCreateRecord(server: McpServer, env: Env) { { template: z.string().min(1).describe("template 的 name 或 id"), values: z.record(z.string()).describe("欄位內容 {slot名: 字串內容},如 {name:'Leo', email:'leo@x.com'}"), - owner_id: z.string().optional().describe("資料歸屬標記(選填,如專案 id / 用戶 id)"), + owner_id: z.string().optional().describe("資料歸屬標記(選填;預設用你的 org namespace,想歸到別的租戶/專案才傳)"), }, async ({ template, values, owner_id }) => { try { + // 機制注入:AI 傳的 owner_id 覆寫,缺則用 orgNamespace。兩者皆空 → 拒送(避免必然 400)。 + const resolvedOwner = (owner_id && owner_id.trim()) || orgNamespace; + if (!resolvedOwner) { + return errorResponse( + "owner_missing", + "無法決定 owner_id:AI 未傳 owner_id 且 org namespace 為空", + ["確認 MCP 連線帶了有效的 Authorization(org namespace)", "或明確傳入 owner_id"], + ); + } const res = await kbdbFetch(env, "/records", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ template, values, owner_id }), + body: JSON.stringify({ template, values, owner_id: resolvedOwner }), }); if (!res.ok) { return errorResponse("create_record_failed", `填 record 失敗`, [ diff --git a/mcp/src/tools/registry.ts b/mcp/src/tools/registry.ts index d333296..d791121 100644 --- a/mcp/src/tools/registry.ts +++ b/mcp/src/tools/registry.ts @@ -55,7 +55,8 @@ export function registerAllTools(server: McpServer, env: Env, orgNamespace: stri registerAllRecipeTools(server, env); // kbdb-base Phase 9.1: KBDB 資料層薄殼(template/record/query/search,HANDOFF §2) // 鐵律:不提供建表/SQL tool,AI 只有 template+slot 可用(類 Supabase 萬用表) - registerAllKbdbDataTools(server, env); + // D28: 傳 orgNamespace,寫入 record 時機制注入 owner_id(base owner-mandatory,AI 漏不了) + registerAllKbdbDataTools(server, env, orgNamespace); // §7.8 P1 D2: whoami(與 CLI acr whoami 對齊,AI 不繞 CLI 自己 curl 猜帳號) registerWhoami(server, env, orgNamespace); }