@@ -0,0 +1,195 @@
/**
* KBDB 資料層 MCP 薄殼(kbdb-base Phase 9.1, HANDOFF §2)
*
* rule 07 §5(薄殼鐵律):能力長在基本盤 API,MCP 只做介面轉換 + 暴露,無業務邏輯。
* 全走既有 kbdbFetch( KBDB service binding)打基本盤 HTTP API( kbdb/src/routes/*)。
*
* KBDB 鐵律(leo 2026-06-14,頂層 DECISION-kbdb-v3-baseplane.md):
* - 任何人不准動表;**不提供建表 / SQL tool**。
* - AI 想存新類型的資料時只有「建 template( name+slots) + 填 record( slot→content)」可用
* ——類 Supabase 萬用表,schema 由 template/slot 表達,不是真的 CREATE TABLE。
* - 薄殼只調基本盤 HTTP API,不直連 D1、不寫 SQL。
*
* 基本盤 API 契約(已存在,kbdb/src/routes):
* POST /templates { name, slots[], description?, created_by? } → { template }
* GET /templates → { templates[], count }
* GET /templates/:idOrName → { template }
* POST /records { template, values:{slot:content}, owner_id? } → { record }
* GET /records/by-template/:t ?owner_id= → { records[], count }
* GET /records/:recordId → { record }
* GET /entries/search ?q=&owner_id= → { entries[], count, mode:'keyword' }
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" ;
import { z } from "zod" ;
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 ) {
registerCreateTemplate ( server , env ) ;
registerListTemplates ( server , env ) ;
registerCreateRecord ( server , env ) ;
registerGetRecord ( server , env ) ;
registerQuery ( server , env ) ;
registerSearch ( server , env ) ;
}
/**
* kbdb_create_template — 建一個 template(= 萬用表裡的一種「虛擬表/資料形狀」)。
* 這是 AI 想存「新類型資料」時的唯一入口:沒有建表 API,改用 template + slots 描述欄位。
*/
export function registerCreateTemplate ( server : McpServer , env : Env ) {
server . tool (
"kbdb_create_template" ,
"建一個 KBDB template(萬用表裡的一種資料形狀,類 Supabase 的虛擬表)。KBDB 不能建真的資料表——" +
"要存「新類型」的結構化資料時,就建一個 template 並用 slots 列出它的欄位名,之後用 kbdb_create_record 填值。" +
"例:name='contact', slots=['name','email','phone']。" ,
{
name : z.string ( ) . min ( 1 ) . describe ( "template 名稱(唯一識別,之後填 record 用這個名字),如 'contact' / 'note'" ) ,
slots : z.array ( z . string ( ) . min ( 1 ) ) . min ( 1 ) . describe ( "欄位名清單,如 ['name','email','phone']" ) ,
description : z.string ( ) . optional ( ) . describe ( "這個 template 用途的簡述(選填)" ) ,
created_by : z.string ( ) . optional ( ) . describe ( "建立者標記(選填)" ) ,
} ,
async ( { name , slots , description , created_by } ) = > {
try {
const res = await kbdbFetch ( env , "/templates" , {
method : "POST" ,
headers : { "Content-Type" : "application/json" } ,
body : JSON.stringify ( { name , slots , description , created_by } ) ,
} ) ;
if ( ! res . ok ) {
return errorResponse ( "create_template_failed" , ` 建 template 失敗 ` , [ "檢查 name 是否重複" , "確認 slots 是非空字串陣列" ] , await res . text ( ) . catch ( ( ) = > "" ) ) ;
}
const data = await res . json ( ) ;
return successResponse ( data , [
` template「 ${ name } 」已建。用 kbdb_create_record(template=' ${ name } ', values={...}) 填一筆資料 ` ,
] ) ;
} catch ( e ) {
return errorResponse ( "internal_error" , e instanceof Error ? e.message : String ( e ) , [ "稍後重試" ] ) ;
}
} ,
) ;
}
/** kbdb_list_templates — 列出所有已建的 template(看有哪些資料形狀可用)。 */
export function registerListTemplates ( server : McpServer , env : Env ) {
server . tool (
"kbdb_list_templates" ,
"列出 KBDB 裡所有 template(已定義的資料形狀)。要存資料前先看有沒有現成 template 可用,沒有再 kbdb_create_template。" ,
{ } ,
async ( ) = > {
try {
const res = await kbdbFetch ( env , "/templates" ) ;
if ( ! res . ok ) return errorResponse ( "list_templates_failed" , ` 列 template 失敗 ` , [ "稍後重試" ] , await res . text ( ) . catch ( ( ) = > "" ) ) ;
const data = await res . json ( ) ;
return successResponse ( data , [ "每個 template 的 slots_json 是它的欄位清單" , "填資料用 kbdb_create_record" ] ) ;
} catch ( e ) {
return errorResponse ( "internal_error" , e instanceof Error ? e.message : String ( e ) , [ "稍後重試" ] ) ;
}
} ,
) ;
}
/** kbdb_create_record — 依某 template 填一筆 record( slot → 內容)。 */
export function registerCreateRecord ( server : McpServer , env : Env ) {
server . tool (
"kbdb_create_record" ,
"依某 template 填一筆 record(一列資料)。values 是 {slot名: 內容}, slot 名要對得上 template 的 slots。" +
"template 不存在會失敗——先 kbdb_list_templates 確認,或 kbdb_create_template 建一個。" ,
{
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)" ) ,
} ,
async ( { template , values , owner_id } ) = > {
try {
const res = await kbdbFetch ( env , "/records" , {
method : "POST" ,
headers : { "Content-Type" : "application/json" } ,
body : JSON.stringify ( { template , values , owner_id } ) ,
} ) ;
if ( ! res . ok ) {
return errorResponse ( "create_record_failed" , ` 填 record 失敗 ` , [
` 確認 template「 ${ template } 」存在(kbdb_list_templates) ` ,
"values 的 slot 名要對得上 template 的 slots" ,
] , await res . text ( ) . catch ( ( ) = > "" ) ) ;
}
const data = await res . json ( ) ;
return successResponse ( data , [ ` 已存入。用 kbdb_query(template=' ${ template } ') 列出此 template 的所有 record ` ] ) ;
} catch ( e ) {
return errorResponse ( "internal_error" , e instanceof Error ? e.message : String ( e ) , [ "稍後重試" ] ) ;
}
} ,
) ;
}
/** kbdb_get_record — 用 record_id 取單筆 record。 */
export function registerGetRecord ( server : McpServer , env : Env ) {
server . tool (
"kbdb_get_record" ,
"用 record_id 取一筆 record 的所有欄位內容。record_id 從 kbdb_create_record 回傳或 kbdb_query 列出取得。" ,
{
record_id : z.string ( ) . min ( 1 ) . describe ( "record 的 id( rec_xxx) " ) ,
} ,
async ( { record_id } ) = > {
try {
const res = await kbdbFetch ( env , ` /records/ ${ encodeURIComponent ( record_id ) } ` ) ;
if ( res . status === 404 ) return errorResponse ( "not_found" , ` record「 ${ record_id } 」不存在 ` , [ "確認 record_id 正確" , "用 kbdb_query 列出某 template 的 record 取 id" ] ) ;
if ( ! res . ok ) return errorResponse ( "get_record_failed" , ` 取 record 失敗 ` , [ "稍後重試" ] , await res . text ( ) . catch ( ( ) = > "" ) ) ;
const data = await res . json ( ) ;
return successResponse ( data ) ;
} catch ( e ) {
return errorResponse ( "internal_error" , e instanceof Error ? e.message : String ( e ) , [ "稍後重試" ] ) ;
}
} ,
) ;
}
/** kbdb_query — 列出某 template 底下的所有 record(結構化查詢)。 */
export function registerQuery ( server : McpServer , env : Env ) {
server . tool (
"kbdb_query" ,
"列出某 template 底下的所有 record(結構化查詢,按 template 取整批資料)。要按關鍵字找內容用 kbdb_search。" ,
{
template : z.string ( ) . min ( 1 ) . describe ( "template 的 name 或 id" ) ,
owner_id : z.string ( ) . optional ( ) . describe ( "只取某歸屬的 record(選填)" ) ,
} ,
async ( { template , owner_id } ) = > {
try {
const path = ` /records/by-template/ ${ encodeURIComponent ( template ) } ` + ( owner_id ? ` ?owner_id= ${ encodeURIComponent ( owner_id ) } ` : "" ) ;
const res = await kbdbFetch ( env , path ) ;
if ( ! res . ok ) return errorResponse ( "query_failed" , ` 查詢 record 失敗 ` , [ ` 確認 template「 ${ template } 」存在 ` ] , await res . text ( ) . catch ( ( ) = > "" ) ) ;
const data = await res . json ( ) ;
return successResponse ( data , [ "用 kbdb_get_record(record_id) 取單筆全文" , "按關鍵字找內容改用 kbdb_search" ] ) ;
} catch ( e ) {
return errorResponse ( "internal_error" , e instanceof Error ? e.message : String ( e ) , [ "稍後重試" ] ) ;
}
} ,
) ;
}
/** kbdb_search — 對 entries 做 D1 LIKE 關鍵字搜尋(基本盤,非語義)。 */
export function registerSearch ( server : McpServer , env : Env ) {
server . tool (
"kbdb_search" ,
"對 KBDB 內容做關鍵字搜尋(D1 LIKE,基本盤層;語義搜尋是另外的 embed 模組,基本盤沒有)。" +
"回命中的 entries。要按 template 取整批結構化資料用 kbdb_query。" ,
{
q : z.string ( ) . min ( 1 ) . describe ( "搜尋關鍵字" ) ,
owner_id : z.string ( ) . optional ( ) . describe ( "限定某歸屬範圍內搜(選填)" ) ,
} ,
async ( { q , owner_id } ) = > {
try {
const path = ` /entries/search?q= ${ encodeURIComponent ( q ) } ` + ( owner_id ? ` &owner_id= ${ encodeURIComponent ( owner_id ) } ` : "" ) ;
const res = await kbdbFetch ( env , path ) ;
if ( ! res . ok ) return errorResponse ( "search_failed" , ` 搜尋失敗 ` , [ "稍後重試" ] , await res . text ( ) . catch ( ( ) = > "" ) ) ;
const data = await res . json ( ) ;
return successResponse ( data , [ "mode:keyword = D1 LIKE(基本盤)" , "找不到時換個關鍵字,或用 kbdb_query 按 template 列出" ] ) ;
} catch ( e ) {
return errorResponse ( "internal_error" , e instanceof Error ? e.message : String ( e ) , [ "稍後重試" ] ) ;
}
} ,
) ;
}