Files
Arcrun/mcp/src/tools/arcrun_introspection.ts
uncle6me-web 3447efc94e docs(mcp): list_recent_executions 說明文字跟上 P7(保留期),不再寫「無固定保留期」
08-07 事故修復後,這支工具的說明文字曾誠實標「無固定保留期」——那時保留期
確實還沒做。前一個 commit(P7)補上保留期可設定(預設 90 天,可調,可設不刪
除),這支說明文字若不跟著改,會誤導以後讀到它的人以為系統仍然無限保留。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 00:45:19 +08:00

215 lines
8.0 KiB
TypeScript
Raw Permalink 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.
/**
* Introspection / debug MCP tools — LI SDD M2.2
*
* arcrun_validate_yaml — dry-run YAML 校驗,不部署
* arcrun_get_execution_trace — 看 paused workflow statetask_id 細節)
* arcrun_list_paused_executions — 列當前所有等 callback 的 workflow
* arcrun_list_recent_executions — 列某 workflow 最近 N 次執行 verdict
*
* 對應 cypher-executor 新路由(commit 989fbeb+ 既有 /validate。
* 所有 tool 都需要 api_key (ak_xxx) 參數 — 跟 MCP partner-auth 的 pk_live 是兩層 auth。
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { toolName } from "../brand.js";
import { z } from "zod";
import type { Env } from "../types.js";
import { cypherFetch, errorResponse, successResponse } from "../lib/cypher-client.js";
const apiKeyDesc =
"你 (用戶) 的 arcrun api_key (ak_xxx)。從 https://arcrun.dev/me 取得。注意:跟 MCP 連線用的 pk_live token 是不同層 auth — pk_live 給 MCP 用,ak_xxx 給 workflow 操作用";
export function registerValidateYaml(server: McpServer, env: Env) {
server.tool(
toolName("validate_yaml"),
"Dry-run YAML 校驗。不部署、無 side effect。回 {valid, errors?, nodeCount, edgeCount}。**永遠先 call 此 tool 再 push_workflow**,避免反覆 deploy 失敗。",
{
api_key: z.string().describe(apiKeyDesc),
graph: z.object({
nodes: z.array(z.unknown()).describe("workflow 節點陣列"),
edges: z.array(z.unknown()).describe("workflow 邊陣列 (cypher binding 三元組)"),
}).passthrough().describe("workflow graph object(已 parse 過 YAML 的結構,非 raw YAML string"),
},
async ({ api_key, graph }) => {
try {
const res = await cypherFetch(env, "/validate", {
apiKey: api_key,
method: "POST",
body: graph,
});
const body = await res.json().catch(() => null) as {
valid?: boolean;
errors?: unknown[];
nodeCount?: number;
edgeCount?: number;
} | null;
if (!res.ok || !body?.valid) {
return errorResponse(
"validation_failed",
body?.errors ? `校驗失敗,${(body.errors as unknown[]).length} 個錯誤` : `校驗失敗 HTTP ${res.status}`,
[
"依 errors 陣列逐項修改 YAML",
"若 errors 提到 '未知關係詞',看 design.md §3 列出的合法關係詞",
"若 errors 提到 'node 不存在',檢查 edges 的 from/to 是否拼錯",
],
JSON.stringify(body?.errors ?? body),
);
}
return successResponse(body, [
`校驗通過:${body.nodeCount} 個節點 / ${body.edgeCount} 條邊`,
"可以 call arcrun_push_workflow 部署了",
]);
} catch (e) {
return errorResponse(
"internal_error",
`validate 內部錯:${e instanceof Error ? e.message : String(e)}`,
["重試一次", "若持續失敗,告訴 leo 並貼錯誤訊息"],
);
}
},
);
}
export function registerListPausedExecutions(server: McpServer, env: Env) {
server.tool(
toolName("list_paused_executions"),
"列當前 api_key 下所有 paused workflow(等 daemon callback resume 的)。給 debug 用:claude_api 等 async 零件會把 workflow 暫停,此 tool 告訴你哪些還沒回來。",
{
api_key: z.string().describe(apiKeyDesc),
limit: z.number().int().min(1).max(100).optional().describe("最多回幾個(預設 20,最多 100)"),
},
async ({ api_key, limit }) => {
try {
const res = await cypherFetch(env, "/executions/paused", {
apiKey: api_key,
query: limit ? { limit } : undefined,
});
const body = await res.json().catch(() => null);
if (!res.ok) {
return errorResponse(
"fetch_failed",
`撈 paused 列表失敗 HTTP ${res.status}`,
["檢查 api_key 是否正確", "稍後重試"],
JSON.stringify(body),
);
}
return successResponse(body);
} catch (e) {
return errorResponse(
"internal_error",
e instanceof Error ? e.message : String(e),
["重試一次"],
);
}
},
);
}
export function registerGetExecutionTrace(server: McpServer, env: Env) {
server.tool(
toolName("get_execution_trace"),
"看單一 paused workflow 的 state 細節(trace、graph、context、pending_result)。task_id 從 paused 錯誤訊息或 list_paused_executions 取得。",
{
api_key: z.string().describe(apiKeyDesc),
task_id: z.string().describe(
"Paused workflow 的 task_id。來源:workflow 觸發後若 pausederror 訊息含 'waiting for task task_XXX';或 list_paused_executions 回的 task_id 欄位",
),
},
async ({ api_key, task_id }) => {
try {
const res = await cypherFetch(env, `/executions/${encodeURIComponent(task_id)}`, {
apiKey: api_key,
});
const body = await res.json().catch(() => null);
if (res.status === 404) {
return errorResponse(
"not_found",
`task_id "${task_id}" 沒對應的 paused state`,
[
"call list_paused_executions 看當前所有 paused,確認 task_id 正確",
"若該 workflow 不是 paused 型,看 list_recent_executions 查歷史 verdict",
],
);
}
if (!res.ok) {
return errorResponse(
"fetch_failed",
`撈 execution trace 失敗 HTTP ${res.status}`,
["檢查 task_id 格式是否正確"],
JSON.stringify(body),
);
}
return successResponse(body);
} catch (e) {
return errorResponse(
"internal_error",
e instanceof Error ? e.message : String(e),
["重試一次"],
);
}
},
);
}
export function registerListRecentExecutions(server: McpServer, env: Env) {
server.tool(
toolName("list_recent_executions"),
"列某 workflow 最近 N 次執行 verdict(成功 / 失敗 / duration)。資料來源是 D1 執行紀錄表,稽核資料——預設保留 90 天(3 個月)過期即清,管理者可在 portal 改保留天數或設為不刪除;用量過大時系統會自動降成只記失敗、甚至暫停記錄——工作流本身執行不受影響。",
{
api_key: z.string().describe(apiKeyDesc),
workflow_name: z.string().describe("workflow 名稱(acr push 時的 name 欄)"),
limit: z.number().int().min(1).max(100).optional().describe("最多回幾筆(預設 10,最多 100)"),
},
async ({ api_key, workflow_name, limit }) => {
try {
const res = await cypherFetch(
env,
`/workflows/${encodeURIComponent(workflow_name)}/executions`,
{
apiKey: api_key,
query: limit ? { limit } : undefined,
},
);
const body = await res.json().catch(() => null);
if (res.status === 404) {
return errorResponse(
"not_found",
`workflow "${workflow_name}" 不存在或不屬於你`,
[
"call list_workflows 看你有什麼 workflow",
"確認 workflow 名稱拼寫正確",
],
);
}
if (!res.ok) {
return errorResponse(
"fetch_failed",
`撈執行歷史失敗 HTTP ${res.status}`,
["稍後重試"],
JSON.stringify(body),
);
}
return successResponse(body);
} catch (e) {
return errorResponse(
"internal_error",
e instanceof Error ? e.message : String(e),
["重試一次"],
);
}
},
);
}
export function registerAllIntrospectionTools(server: McpServer, env: Env) {
registerValidateYaml(server, env);
registerListPausedExecutions(server, env);
registerGetExecutionTrace(server, env);
registerListRecentExecutions(server, env);
}