/* RFC-0290: mechanical projection of the Agent Surface Manifest + active capability records into the MCP `tools/list` result. One tool per knowledge domain (read-only) and one per active action. Do not call tools here — that is mcp/handler.ts. RFC-0290: initial tools projection. RFC-1112: mark sideEffect "none" tools as side-effect-free previews. RFC-1115: render agentGuidance into multi-sentence tool descriptions via renderAgentGuidanceDescription. */ import type { AgentSurfaceManifest } from "@warpgogol/werkstatt-shared/agent"; import { renderAgentGuidanceDescription } from "@warpgogol/werkstatt-shared/agent"; import type { CapabilityRecord } from "@warpgogol/werkstatt-shared/ontology"; export interface McpTool { name: string; description: string; inputSchema: unknown; } const EMPTY_INPUT_SCHEMA = { type: "object", properties: {}, additionalProperties: false } as const; /** Pure: derive the tool list from the manifest's refs + the matching catalog records. */ export function buildToolsList( manifest: AgentSurfaceManifest, catalog: CapabilityRecord[], ): McpTool[] { const byId = new Map(catalog.map((c) => [c.id, c])); const tools: McpTool[] = []; for (const ref of manifest.knowledge) { tools.push({ name: `knowledge.${ref.domain}.get`, description: `Read the ${ref.domain} knowledge file (${ref.schema}).`, inputSchema: EMPTY_INPUT_SCHEMA, }); } for (const ref of manifest.actions) { const record = byId.get(ref.id); if (!record) continue; const base = record.description[manifest.languages.default] ?? ref.id; // RFC-1115: agentGuidance composes a model-oriented multi-sentence // description; absent guidance keeps the RFC-1112 fallback. const description = record.agentGuidance ? renderAgentGuidanceDescription(base, record.agentGuidance) : record.sideEffect === "none" ? `${base} (Side-effect-free preview — validates input and returns a draftId; dispatches nothing.)` : base; tools.push({ name: `action.${ref.id}`, description, inputSchema: record.input, }); } return tools; }