import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { toolName } from "../brand.js"; import { z } from "zod"; import { Env } from "../types.js"; /** * arcrun_search_components — 語意搜尋零件庫 * 呼叫 Component Registry GET /components/search?q=... */ export function registerSearchComponents(server: McpServer, env: Env, orgNamespace: string) { server.tool( toolName("search_components"), "用自然語言語意搜尋零件庫,找出符合需求的零件。例如:「查詢 Google Sheets 資料」、「發送 LINE 訊息」、「驗證 JSON 格式」。回傳零件清單含 canonical_id、描述、評分。", { query: z.string().describe("自然語言搜尋詞,如「查詢 Google Sheets 資料」"), }, async ({ query }) => { try { if (!env.COMPONENT_REGISTRY) { return { content: [{ type: "text", text: "Error: COMPONENT_REGISTRY service binding is not configured." }], isError: true, }; } const response = await env.COMPONENT_REGISTRY.fetch( `http://component-registry/components/search?q=${encodeURIComponent(query)}`, { method: "GET", headers: { "Content-Type": "application/json" } }, ); if (!response.ok) { const errorText = await response.text(); return { content: [{ type: "text", text: `Search failed: ${errorText}` }], isError: true, }; } const result = await response.json() as { data?: { results?: unknown[]; count?: number } }; const results = result.data?.results ?? []; const count = result.data?.count ?? 0; if (count === 0) { return { content: [{ type: "text", text: [ `registry 裡找不到「${query}」——但**先別急著造零件**(零件走 PR、專業等級)。`, ``, `⚠️ registry 目前可能是空的(已知問題:唯一寫入觸發點隨 .github/workflows 移除而蒸發),`, ` 「搜不到」不代表「沒有這個能力」。請改用下列順序找:`, ``, `1. **語意搜尋知識庫**(最強,能找到你沒猜中的用詞):`, ` kbdb_search(q="我想達成什麼(用一句話描述)", mode="semantic")`, `2. **看現成的服務整合**:\`acr auth-recipe list\`(26 個:GitHub/Notion/Gemini…)`, ` 要用哪個 → \`acr auth-recipe scaffold <服務>\` 直接吐 credentials 範本+workflow 範例`, `3. **看零件全集**:\`acr parts\`(21 顆通用零件,含 http_request 可打任意 API)`, `4. **看有沒有現成 workflow 可直接接**:\`acr list\``, ``, `💡 多數需求的正解是「用 recipe 設定既有零件」或「接既有 workflow」,不是新造零件。`, ].join("\n"), }], }; } return { content: [{ type: "text", text: `找到 ${count} 個零件:\n${JSON.stringify(results, null, 2)}`, }], }; } catch (error) { return { content: [{ type: "text", text: `Internal Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true, }; } } ); }