/** * MCP (Model Context Protocol) client for Z.AI services using UTCP * * Supports four MCP servers: * - Vision: Image and video analysis (stdio) * - ZRead: GitHub repository exploration * - Web Search: Real-time web search * - Web Reader: Web page content extraction */ import { type Tool } from "@utcp/sdk"; import "@utcp/mcp"; import type { ReaderRawResponse } from "../capabilities/reader.js"; /** * Resolve the Z.AI MCP request timeout from `Z_AI_TIMEOUT` (#214): * parse with the env fallback, then clamp to the setTimeout 32-bit * signed maximum so an out-of-range override cannot surface in * TimeoutError metadata unclamped. */ export declare function resolveZaiMcpTimeoutMs(env: NodeJS.ProcessEnv): number; export interface ZReadSearchResult { title: string; content: string; url?: string; type?: string; } export interface WebSearchResult { refer: string; title: string; link: string; media: string; content: string; icon: string; publish_date?: string; } /** * Minimal structural surface of the UTCP client consumed by * {@link ZaiMcpClient}. Production code uses `UtcpClient.create()` which * structurally satisfies this interface. The `close` method is optional * because test doubles may omit it — the real `UtcpClient` always has it. * This keeps the lib → providers dependency direction intact (no import * of the providers port) while avoiding an `as unknown as` cast at the * adapter boundary. */ interface McpUtcpClient { registerManual(template: unknown): Promise<{ success: boolean; errors: string[]; }>; getTools(): Promise; callTool(name: string, args: Record): Promise; /** Optional: the real UtcpClient has it; test doubles may omit it. */ close?(): Promise; } /** * Constructor options for {@link ZaiMcpClient}. * * `utcpFactory` is a behaviour-preserving injection seam: when omitted the * production path uses `UtcpClient.create()`. Tests inject a fake to avoid * touching process globals. * * `disableRetry` (P2-03) lets the Z.AI Search Adapter hand retry policy * to shared execution. When omitted, the client retains its existing * direct-client retry behaviour. */ export interface ZaiMcpClientOptions { enableVision?: boolean; noCache?: boolean; disableRetry?: boolean; utcpFactory?: () => Promise; /** * T2b — Credential view: the resolved env (injected env + file keys) * captured at handler dispatch. When omitted, ambient `process.env` * is used so existing direct constructors keep working. When supplied, * the credential, mode, base URL, retry knobs, cache fingerprint, and * configured-secret scrub set all derive from this view rather than * `process.env`. */ env?: NodeJS.ProcessEnv; } /** * Unified MCP client for all Z.AI MCP services */ export declare class ZaiMcpClient { private client; private initPromise; private isInitialized; private options; private readonly timeoutMs; private readonly retryBaseMs; private readonly retryMaxMs; private readonly retryJitterMs; constructor(options?: ZaiMcpClientOptions); /** * Initialize the UTCP client and register all MCP servers */ private init; private _doInit; /** * #117 — classify the real HTTP status behind an initialization failure * with ONE cheap authenticated `initialize` against the MCP endpoint the * client was going to use (the same request and Bearer credential the * registration template carries). * * Z.AI rejects bad credentials as a JSON body (`{"code":401,...}`) * inside HTTP 200, and UTCP's registration error collection drops the * body's values, so the status must be re-read here. Classification * reads the HTTP status and the body's numeric `code` field ONLY — no * byte of the body ever reaches the public error message (NFR-006). * * Returns 401/403 when the failure is an auth rejection, `null` when * the probe is inconclusive (any other status, unreachable endpoint, * unparsable body, missing credential) so the caller keeps today's * error shape. Runs exclusively on the already-failed init path. */ private probeAuthStatusOnFailure; /** * Call an MCP tool */ private callTool; private callToolUncached; private getRetryCount; private isRetriableError; private resolveEnableVision; /** * Build the tool-cache config (D4 — adapter encapsulates the * config + endpoints + vision-resolution inputs that the extracted * `tool-cache.ts` needs to compute a stable key). Owned here because * the inputs come from this class's options and the shared config * module; the extracted module stays pure of `loadConfig` / `getMcpEndpoints`. * * T2b: `loadConfig` consults the captured env so tool-cache identity * follows the same invocation credential view as registration. */ private getToolCacheConfig; /** * List all discovered tools from registered MCP servers. * * Returns the PUBLIC projected view: internal UTCP names (e.g. * `scoutline_zai.search.web_search_prime`) are rewritten to the * stable dotted form (e.g. `scoutline.zai.search.web_search_prime`). * The private unprojected discovery list is retained for invocation * through {@link getTool} / {@link resolveToolName}. */ listTools(refresh?: boolean): Promise; /** * Private unprojected discovery list. Tools keep their exact UTCP * names so {@link getTool} can resolve public aliases back to the * internal invocation identity. * * Tool-cache I/O is delegated to the extracted {@link tool-cache.ts} * module (D1 — owns its I/O directly against the `tools/` subdir). */ private discoverTools; /** * Find a tool by exact internal name, public dotted name, or leaf * suffix. Resolution order: * 1. Exact discovered name (e.g. `scoutline_zai.search.web_search_prime`). * 2. Public dotted name (e.g. `scoutline.zai.search.web_search_prime`): * derive the provider-relative suffix after the public prefix and * match exactly one discovered name ending in `.`. Zero or * multiple matches fail. * 3. Legacy short-suffix fallback: a single discovered name ending in * `.` (e.g. for callers that pass only `web_search_prime`). * * Public names never replace the private discovered-name record — the * returned {@link Tool} always carries its internal UTCP name. */ getTool(toolName: string): Promise; private findToolByResolvedName; /** * Resolve a tool name, accepting full names or suffixes */ resolveToolName(toolName: string): Promise; /** * Call a tool by full name or suffix. * * The return type is `T | string` because the underlying transport may * return a bare string that is not valid JSON (e.g. an MCP error * envelope like `"MCP error -500: ..."`). Callers that expect a * structured `T` must narrow at the boundary. */ callToolRaw(toolName: string, args: Record): Promise; /** * P6-01A: invoke a tool while preserving the public dotted tool name as * the cache identity, then resolve to the internal sanitized identity * only on a cache miss. * * The legacy v0.2 repository cache contract (P0–P2) keyed entries under * the public dotted name and returned them before any transport work. * A naive `callToolRaw` migration routes through `resolveToolName` first, * which forces discovery, registration, and `init()` even when a v0.2 * hit is present. This helper restores the legacy cache identity and * skips discovery on hits while keeping the translation fix for misses. * * Other callers (`callTool`, `callToolRaw`, Vision, Search, Reader, raw * tools) are unchanged. */ private callToolWithPublicCacheIdentity; /** * Search documentation and code in a GitHub repository */ zreadSearch(repo: string, query: string, language?: "zh" | "en"): Promise; /** * Get the directory structure of a GitHub repository */ zreadTree(repo: string, dirPath?: string): Promise; /** * Read a file from a GitHub repository */ zreadFile(repo: string, path: string): Promise; /** * Search the web using WebSearchPrime */ webSearch(params: { query: string; count?: number; domainFilter?: string; recencyFilter?: "oneDay" | "oneWeek" | "oneMonth" | "oneYear" | "noLimit"; contentSize?: "medium" | "high"; location?: "cn" | "us"; }): Promise; /** * Read and parse web page content */ webRead(params: { url: string; timeout?: number; noCache?: boolean; format?: "markdown" | "text"; retainImages?: boolean; withLinksSummary?: boolean; noGfm?: boolean; keepImgDataUrl?: boolean; withImagesSummary?: boolean; }): Promise; visionAnalyze(params: { imageSource: string; prompt: string; }): Promise; visionUiToArtifact(params: { imageSource: string; outputType: "code" | "prompt" | "spec" | "description"; prompt: string; }): Promise; visionExtractText(params: { imageSource: string; prompt: string; programmingLanguage?: string; }): Promise; visionDiagnoseError(params: { imageSource: string; prompt: string; context?: string; }): Promise; visionDiagram(params: { imageSource: string; prompt: string; diagramType?: string; }): Promise; visionChart(params: { imageSource: string; prompt: string; focus?: string; }): Promise; visionDiff(params: { expectedImageSource: string; actualImageSource: string; prompt: string; }): Promise; visionVideo(params: { videoSource: string; prompt: string; }): Promise; /** * Close the MCP client and cleanup resources */ close(timeoutMs?: number): Promise; } export declare class ZReadMcpClient extends ZaiMcpClient { constructor(options?: ZaiMcpClientOptions); searchDoc(repo: string, query: string, language?: "zh" | "en"): Promise; getRepoStructure(repo: string, dirPath?: string): Promise; readFile(repo: string, path: string): Promise; } export {}; //# sourceMappingURL=mcp-client.d.ts.map