Files
Arcrun/mcp/src/tools/kbdb_graph.ts
T
Claude 7eeab9f26f feat(mcp): kbdb_graph_neighbors — graph 鄰居查詢薄殼(#68)
MCP 面補齊 D17 KBDB RAG 套餐第三模式(關鍵字/語義/圖):
- 新 tool kbdb_graph_neighbors:subject(必填)/depth(預設 1)/kbdb_base/
  template(預設 graph_triplet)/directed,形狀對齊
  registry/examples/graph-neighbors/workflow.yaml 的 input
- 薄殼調 GET /q/{orgNamespace}/graph_neighbors 同步查詢端點(#28 地基),
  走既有 CYPHER_EXECUTOR service binding,不新增 binding、不碰 D1、
  不動 workflow 引擎本體
- workflow 未部署 → 404 誠實回 workflow_not_installed +安裝指引,不 crash;
  HTTP 200 但 workflow 層 success:false 也不假綠
- graph_traverse 不加:repo 內無該 workflow 定義可對齊 input 形狀,不猜
- 測試:tests/unit/tools/kbdb-graph.test.ts(7 測,全綠);tsc --noEmit 乾淨;
  mcp 全套 vitest 59/59 綠

merge 後需 gated redeploy arcrun-mcp(leo 閘)。

關聯 #68

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUmjwkHLVBHM3ydhT1WSW3
2026-07-19 07:55:16 +00:00

159 lines
8.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* KBDB graph 查詢 MCP 薄殼(issue #68D17 KBDB MCP 面)
*
* rule 07 §5(薄殼鐵律):能力長在 workflowgraph_neighborsregistry/examples/graph-neighbors),
* MCP 只做介面轉換 + 暴露,無業務邏輯——不碰 D1、不動 workflow 引擎本體。
*
* D17「KBDB MCPRAG 套餐」三模式:關鍵字(kbdb_search keyword)/語義(kbdb_search semantic
* /圖(本檔)。本檔補上 MCP 面缺的第三模式:關係遍歷(1-hop/N-hop 鄰居)。
*
* 走 #28 地基的同步查詢端點(cypher-executor webhooks-named.ts):
* GET /q/:ns/:name — 同步執行 workflow,直接回「最終節點輸出」本身(200 直出,非 202、非信封)。
* 本工具打 GET /q/{orgNamespace}/graph_neighbors(經既有 CYPHER_EXECUTOR service binding
* 不新增 binding),namespace 用 MCP token 解析出的 orgNamespace(與 whoami 同源)。
*
* workflow 未部署(用戶沒裝 graph_neighbors)→ 404 → 誠實回錯誤+怎麼裝,不 crash(鐵律)。
*
* graph_neighbors workflow 的 input 形狀(registry/examples/graph-neighbors/workflow.yaml):
* node(起點)、depth(預設 1)、templatetriplet template 名)、namespaceowner_id)、
* kbdb_base(呼叫者自己的 KBDB 對外 URL——workflow 刻意不寫死任何一家的庫)、directed。
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import type { Env } from "../types.js";
import { cypherFetch, errorResponse, successResponse } from "../lib/cypher-client.js";
/** graph 查詢 workflow 名(與 registry/examples/graph-neighbors/workflow.yaml 的 name 一致)。 */
export const GRAPH_NEIGHBORS_WORKFLOW = "graph_neighbors";
/** 未裝 workflow 時的安裝指引(404 與內層錯誤共用,訊息一致)。 */
const INSTALL_HINTS = [
`graph 查詢由「${GRAPH_NEIGHBORS_WORKFLOW}」workflow 承載(薄殼設計,MCP 本身不算圖)`,
"安裝:拿 registry/examples/graph-neighbors/workflow.yamlcall arcrun_push_workflow 部署(name 必須是 graph_neighbors",
"或用 CLIacr push registry/examples/graph-neighbors/workflow.yaml",
];
/** 註冊全部 KBDB graph 查詢工具(issue #68)。 */
export function registerAllKbdbGraphTools(server: McpServer, env: Env, orgNamespace: string) {
registerGraphNeighbors(server, env, orgNamespace);
// graph_traverserepo 內目前只有 graph-neighbors 有 workflow 定義(registry/examples/),
// traverse 尚無可對齊的 input 形狀 → 不猜、不過度工程;等 workflow 進 registry 再加薄殼。
}
/**
* kbdb_graph_neighbors — knowledge graph 1-hop/N-hop 鄰居查詢。
* 薄殼調 GET /q/{ns}/graph_neighbors,結果(最終節點輸出)原樣回給 MCP client。
*/
export function registerGraphNeighbors(server: McpServer, env: Env, orgNamespace: string) {
server.tool(
"kbdb_graph_neighbors",
"knowledge graph 鄰居查詢(1-hop/N-hop 關係遍歷):給一個節點名,沿 KBDB triplet" +
"subject-predicate-object)記錄做 BFS,回傳 depth 跳內的鄰居清單" +
"[{node, predicate, from, depth}])。與 kbdb_search(關鍵字/語義)互補:" +
"找「跟 X 有關係的東西」用本工具,找「內容含關鍵字的東西」用 kbdb_search。" +
"需要 namespace 裡已部署 graph_neighbors workflow(沒裝會回安裝指引,不會 crash)。",
{
subject: z.string().min(1).describe(
"起點節點名(graph triplet 的 subject/object 值),如 'Arcrun'",
),
depth: z.number().int().min(1).max(10).optional().describe(
"最大跳數(N-hop),預設 1(只看直接鄰居)",
),
kbdb_base: z.string().min(1).describe(
"你自己部署的 KBDB 對外 base URL(如 https://arcrun-kbdb.<你的subdomain>.workers.dev " +
"或 KBDB custom domain)。workflow 刻意不寫死任何一家的庫——" +
"帶錯(或照抄別人的值)=查詢打進別人的庫",
),
template: z.string().optional().describe(
"triplet 記錄的 template 名,預設 'graph_triplet'(以實際部署的 kbdb-graph-plugin " +
"triplet template 為準,不確定可 kbdb_list_templates 查)",
),
directed: z.boolean().optional().describe(
"true=只走 subject→object 有向邊;預設 false(把 triplet 當雙向邊,無向鄰居)",
),
},
async ({ subject, depth, kbdb_base, template, directed }) => {
if (!orgNamespace) {
return errorResponse(
"no_namespace",
"此 MCP 連線沒有解析出 namespace,無法定位 graph workflow",
["call arcrun_whoami 確認身份", "確認 MCP token / OAuth 設定正確"],
);
}
try {
// input 形狀對齊 graph_neighbors workflownode/depth/template/namespace/kbdb_base/directed)。
const query: Record<string, string | number> = {
node: subject,
depth: depth ?? 1,
template: template ?? "graph_triplet",
namespace: orgNamespace,
kbdb_base,
};
if (directed) query.directed = "true";
// /q/:ns/:namenamespace 走 pathcypher opaque-key 模型,orgNamespace 即分區 key),
// 走既有 CYPHER_EXECUTOR service bindingcypherFetch),不新增 binding。
const res = await cypherFetch(
env,
`/q/${encodeURIComponent(orgNamespace)}/${GRAPH_NEIGHBORS_WORKFLOW}`,
{ apiKey: orgNamespace, query },
);
if (res.status === 404) {
// workflow 沒裝 → 誠實 + 給安裝路徑(鐵律:不 crash、不假綠)。
return errorResponse(
"workflow_not_installed",
`namespace「${orgNamespace}」尚未部署 ${GRAPH_NEIGHBORS_WORKFLOW} workflowgraph 查詢無法使用`,
INSTALL_HINTS,
);
}
const bodyText = await res.text();
let data: unknown = null;
try {
data = bodyText ? JSON.parse(bodyText) : null;
} catch {
data = bodyText;
}
if (!res.ok) {
// 409=paused(無法同步查詢)、413=輸出過大、500=執行失敗,端點都回 {error,...}。
const err = (data ?? {}) as { error?: string };
return errorResponse(
"graph_query_failed",
`graph 查詢失敗 HTTP ${res.status}: ${err.error ?? "unknown"}`,
[
"確認 kbdb_base 是你自己 KBDB 的對外 URL 且可被 cypher-executor fetch1042 陷阱:優先 custom domain",
"確認 template 名對得上實際 triplet templatekbdb_list_templates",
"call arcrun_list_recent_executions('graph_neighbors') 看 trace",
],
typeof data === "string" ? data : JSON.stringify(data),
);
}
// 200 = workflow 跑完,body 即最終節點輸出本身。但 workflow 內部仍可能回
// { success:false, error }(如缺參數)——不把它假裝成成功結果。
const out = data as { success?: boolean; error?: string; count?: number } | null;
if (out && typeof out === "object" && out.success === false) {
return errorResponse(
"graph_query_failed",
out.error ?? "graph_neighbors workflow 回報失敗",
["確認 subject 非空", "確認參數形狀(depth 為正整數)"],
JSON.stringify(out),
);
}
// 原樣回給 MCP client(薄殼:不加工、不重排)。
return successResponse(out, [
`${out?.count ?? 0} 個鄰居(depth 上限 ${depth ?? 1}`,
"count=0 且不確定資料有沒有進圖:kbdb_query(template='graph_triplet') 看 triplet 記錄",
"找關鍵字內容改用 kbdb_search;取單筆全文用 kbdb_get_record",
]);
} catch (e) {
return errorResponse("internal_error", e instanceof Error ? e.message : String(e), ["稍後重試"]);
}
},
);
}