Files
Arcrun/mcp/src/tools/arcrun_introspection.ts
T
Claude 5fc7277c3c refactor(mcp): u6u_* tool 全面 rename 為 arcrun_* + 前綴收斂單一來源
零 u6u 洩漏(leo 要求 1):connector tool 清單只剩 arcrun_*,並移除壞掉/
重複的 u6u_ workflow tool(deploy/execute/get/list_workflows 由 arcrun_
push/run/get/list_workflows 取代);MCP server name 也由 u6u-mcp-server
改 arcrun-mcp-server。

前綴單一真相源(leo 要求 2):新增 mcp/src/brand.ts(TOOL_PREFIX + toolName()
helper),所有 server.tool() 註冊一律走 toolName("suffix"),前綴不再明碼散寫。
未來 rebrand 只改 brand.ts 一行。

- 12 個獨有能力 tool rename(component/tag/gui/search_workflows)
- 4 個重複/壞掉 tool 移除
- inter-tool hint、描述字串、註解內 u6u_ 名字全數更新
- tsc exit 0;vitest 19/19 綠

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015d5jDbuqT5Htwv3Q88XXKk
2026-07-07 05:21:34 +00:00

215 lines
7.8 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.
/**
* 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)。資料來源是 ANALYTICS_KV 90 天保留期。",
{
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);
}