/** * v2.7.18: shared MCP client wrapper for the boocontext sidecar. * * Calls into the existing multi-server MCP client infrastructure * (services/mcp-client.ts) which connects to boocontext as a stdio * MCP process defined in data/mcp.json (server name "boocontext", * command: `node /opt/forks/boocontext/dist/standalone.js`). * * The boocontext MCP server is initialized once at app boot in * index.ts via initMcp() and the actual MCP tool call routing is * handled by mcp-client.ts:callTool() — this module is a thin * convenience wrapper that prepends the "boocontext_" server prefix, * normalises the response, and applies inline truncation matching * the same pattern as codecontext_client.ts. * * Usage: * import { callBoocontext } from './services/boocontext_client.js'; * const resp = await callBoocontext({ * toolName: 'codesight_get_summary', * args: { directory: '/opt/boocode' }, * }); */ import { callTool } from './mcp-client.js'; import { truncateIfNeeded } from './truncate.js'; // ---- Exported types ---- export interface BoocontextRequest { /** Unprefixed tool name as defined on the boocontext MCP server * (e.g. "codesight_scan", "boocontext_overview", "codesight_get_summary"). */ toolName: string; /** Arguments to pass to the tool. */ args: Record; } export interface BoocontextResponse { /** The tool output text. */ result: string; /** Whether the result was truncated to fit the inline limit. */ truncated: boolean; /** Opaque id pointing at the full pre-slice content on tmpfs, set when * truncated=true and storage succeeded. */ outputPath?: string; } // ---- Constants ---- /** Must match the server name in data/mcp.json. */ const BOOCONTEXT_SERVER_NAME = 'boocontext'; /** Inline truncation limit, matching codecontext_client.ts. */ const TRUNCATION_LIMIT = 32_000; // ---- Public API ---- /** * Call a boocontext MCP tool by its unprefixed name. * * Prepends the "boocontext_" server prefix, delegates to the * multi-server MCP client's callTool(), and normalises the response * into a BoocontextResponse with inline truncation. * * @param req The tool name and arguments. * @param log Optional Fastify-compatible logger (for debug traces). * @returns The tool result, possibly truncated. * @throws If the boocontext server is not connected or the tool * returns an MCP-level error. */ export async function callBoocontext( req: BoocontextRequest, log?: { debug?: (obj: object, msg: string) => void; warn?: (obj: object, msg: string) => void }, ): Promise { const prefixedName = `${BOOCONTEXT_SERVER_NAME}_${req.toolName}`; log?.debug?.({ tool: prefixedName }, 'boocontext: calling tool'); const raw = await callTool(prefixedName, req.args); // callTool returns { error: true, output: string } on failure (both // for MCP-level isError and for network/protocol exceptions). if (typeof raw === 'object' && raw !== null && (raw as Record).error === true) { const errOutput = (raw as Record).output ?? 'Unknown MCP error'; throw new Error(`boocontext error: ${String(errOutput)}`); } const result = typeof raw === 'string' ? raw : JSON.stringify(raw); // Inline truncation at 32 kB, matching codecontext_client.ts. // The model gets a clear hint about how to narrow the next call // rather than a silent cut. if (result.length > TRUNCATION_LIMIT) { const truncated = result.slice(0, TRUNCATION_LIMIT); const omitted = result.length - TRUNCATION_LIMIT; const slicedWithMarker = `${truncated}\n\n[truncated, ${omitted} chars omitted; narrow with additional filters]`; const wrapped = await truncateIfNeeded({ fullContent: result, slicedContent: slicedWithMarker, wasTruncated: true, }); return { result: wrapped.content, truncated: wrapped.truncated, ...(wrapped.outputPath ? { outputPath: wrapped.outputPath } : {}), }; } return { result, truncated: false }; }