import type { ExtensionContext } from "@earendil-works/pi-coding-agent"; import type { CallToolResult, ReadResourceResult, RequestOptions } from "@modelcontextprotocol/client"; import { type ProgrammaticCachedTool } from "./programmatic-cache.ts"; import type { McpAdapterOptions, McpInitialSource, McpProgrammaticRuntime, McpRuntimeCapabilities, McpSourceIdentity, McpSourceRegistration, McpSourceRemoveResult, McpSourceReplaceRequest, McpSourceReplaceResult, McpSourceStatus, McpSourceValidationResult } from "./programmatic-types.ts"; interface ProgrammaticConnection { client: { callTool(params: { name: string; arguments?: Record; }, resultSchema?: unknown, options?: RequestOptions): Promise; readResource(params: { uri: string; }, options?: RequestOptions): Promise; }; tools: readonly { name: string; description?: string; inputSchema?: unknown; }[]; resources: readonly { uri: string; name: string; description?: string; }[]; instructions?: string; transport?: { sessionId?: string; }; status: "connected" | "closed" | "needs-auth"; } export interface ProgrammaticExecution { readonly connection: ProgrammaticConnection; readonly signal: AbortSignal; close(signal?: AbortSignal): Promise; } /** * Source authority used by the public factory and its Pi extension. The class * itself is package-internal; callers receive the narrow lifecycle interface. */ export declare class ProgrammaticMcpRuntime implements McpProgrammaticRuntime { readonly options: Required>; private readonly records; private manager; private context; private operationTail; /** * Discovery inventory: visible tool names/descriptions per qualified server * key, persisted across sessions so the gateway can render the system-prompt * discovery block without launching servers. Schemas stay in * `schemaMemory` (session-scoped) — they are fetched fresh via * `getToolSchemas` and only reused to enrich same-session tool errors. */ private inventory; private schemaMemory; private inventoryLoaded; constructor(options: Required>); installInitialSources(initialSources: readonly McpInitialSource[]): void; private createRecord; attachSession(context: ExtensionContext): Promise; detachSession(): Promise; capabilities(signal: AbortSignal): Promise; validateSource(registration: McpSourceRegistration, signal: AbortSignal): Promise; private exclusive; replaceSource(request: McpSourceReplaceRequest, signal: AbortSignal): Promise; removeSource(identity: McpSourceIdentity, signal: AbortSignal): Promise; inspectSource(identity: McpSourceIdentity, signal: AbortSignal): Promise; inspectSources(signal: AbortSignal): Promise; private recordFor; /** * Resolve a record by one of its source-local server keys. Server keys * are derived from the exact source identity, so a key match is as exact * as passing the identity JSON — a stale key simply matches nothing. * * Agents read the human-readable native key from status output * (`nativeKey · key`) and naturally call with it, but native keys are not * source-local keys — pi-plugins derives opaque `mcp-server-v1:` * keys. Resolution is therefore phased: an exact own-property key match * wins globally; a key-shaped token (`mcp-server-v1:` prefix) never falls * back to names, so a stale key cannot be captured by another server's * nativeKey; otherwise a unique exact nativeKey match selects the record. * Native keys are plugin-local names and may repeat across sources — any * ambiguity rejects. */ private recordForServerKey; /** * Map a caller-supplied server token to the record's source-local key, * under the same phased rules as recordForServerKey: exact own-property * keys win, key-shaped tokens never resolve via names, and a nativeKey * match must be unique within the record. */ private resolveServerKey; private recordForCall; private bindingFor; openExecution(identity: McpSourceIdentity, serverKey: string, signal: AbortSignal): Promise; callTool(identity: McpSourceIdentity | undefined, serverKey: string, tool: string, args: Record, signal: AbortSignal): Promise; listTools(identity: McpSourceIdentity | undefined, serverKey: string, signal: AbortSignal): Promise; getServerInstructions(identity: McpSourceIdentity | undefined, serverKey: string, signal: AbortSignal): Promise; /** * Fan out listTools across every server in one source and filter by * name/description. A server that will not start is reported as * unsearchable rather than sinking the whole query — best-effort like * every other hook/inspection boundary in the host. */ searchTools(identity: McpSourceIdentity | undefined, query: string, options: Readonly<{ regex?: boolean; limit?: number; offset?: number; }>, signal: AbortSignal): Promise[]; unavailableServers: readonly string[]; total: number; hasMore: boolean; nextOffset: number | null; }>>; private ensureInventoryLoaded; private warmInventory; /** * Cached visible tools for one server, or undefined when the server has * never been reached (in this or a previous session). Class-internal * gateway support — deliberately not on the McpProgrammaticRuntime * package boundary. */ cachedServerTools(identity: McpSourceIdentity, serverKey: string): readonly ProgrammaticCachedTool[] | undefined; /** * Same-session schema lookup used to enrich tool errors — warm after any * successful connect, so a failed call can append the exact input schema * without another round-trip. Never launches a server. */ cachedToolSchema(identity: McpSourceIdentity | undefined, serverKey: string, tool: string): unknown | undefined; /** * Batched schema fetch for the gateway's schema action: one server launch * (via listTools, which also warms the inventory) serves any number of * requested tools. Unknown names are reported, not thrown. */ getToolSchemas(identity: McpSourceIdentity | undefined, serverKey: string, toolNames: readonly string[], signal: AbortSignal): Promise<{ schemas: readonly { name: string; description?: string; inputSchema?: unknown; }[]; missing: readonly string[]; }>; private closeExecutions; private closeRecord; } export {};