import type { ExtensionAPI, ToolDefinition as PiToolDefinition } from "@earendil-works/pi-coding-agent"; import { discoverProviders } from "./providers"; import type { Provider } from "./providers/base"; import { createHttpClient } from "./shared/http"; import { cacheManager, chunkingManager } from "./shared/instances"; import { createSearchLibraryDocsTool } from "./tools/search-library-docs"; import { createGetCacheInfoTool, createGetCacheEntriesTool } from "./tools/cache-admin"; import { createGetNextChunkTool } from "./tools/get-next-chunk"; /** * rtfd-pi — native pi extension port of the rtfd MCP server. * * Registers every provider tool (one per provider `getTools()` entry) plus the * server-level tools: `search_library_docs` (aggregator), `get_cache_info`, * `get_cache_entries`, `get_next_chunk`. * * Mirrors python `server._register_provider_tools()` + the server-level * `@mcp.tool(...)` registrations. * * No background resources are started at factory time (per pi extension guidance). */ export default function rtfdPi(pi: ExtensionAPI): void { const providerCtors = discoverProviders(); // Instantiate all providers with the shared HTTP client factory. const providers: Record = {}; for (const [name, ProviderCtor] of Object.entries(providerCtors)) { try { providers[name] = new ProviderCtor(createHttpClient); } catch (error) { // Defensive init: log but don't crash (mirrors python `_get_provider_instances`). console.error(`Warning: Failed to initialize provider ${name}: ${error}`); } } // Register each provider's tools (mirror `_register_provider_tools`). for (const [providerName, provider] of Object.entries(providers)) { const metadata = provider.getMetadata(); if (!metadata.exposeAsTool) continue; const tools = provider.getTools(); for (const toolDef of Object.values(tools)) { // Our internal ToolDefinition uses `parameters: unknown` and a permissive execute // signature so providers stay decoupled from pi's exact types (easier to test). // The runtime values are Typebox schemas + compatible execute fns, so the cast at // this single boundary is safe. Verified by the parity + integration test suites. pi.registerTool(toolDef as unknown as PiToolDefinition); } void providerName; } // Server-level tools (mirror the `@mcp.tool` registrations in server.py). pi.registerTool(createSearchLibraryDocsTool({ cacheManager, providers }) as unknown as PiToolDefinition); pi.registerTool(createGetCacheInfoTool({ cacheManager }) as unknown as PiToolDefinition); pi.registerTool(createGetCacheEntriesTool({ cacheManager }) as unknown as PiToolDefinition); pi.registerTool(createGetNextChunkTool({ chunkingManager }) as unknown as PiToolDefinition); } // Re-export singletons for tests and external use. export { cacheManager, chunkingManager };