UNPKG

@tanstack/ai-mcp

Version:

Host-side Model Context Protocol client for TanStack AI: discover and run MCP server tools, resources, and prompts in any adapter's chat() loop, with generated end-to-end types.

64 lines (63 loc) 3.18 kB
import { MCPClient } from '../client.js'; import { MCPClients } from '../pool.js'; import { McpAppCallRequest, McpSessionStore } from './session-store.js'; /** * A single MCP client or a pool of clients (or an array of either). These are * the same client/pool instances created with `createMCPClient` / * `createMCPClients` and passed to `chat({ mcp: { clients: [...] } })`. The * handler reads each client's connection descriptor via `getInfo()` / * `getServers()` so it can reconnect per-call without a separate config map. */ export type McpAppClientsInput = MCPClient | MCPClients | Array<MCPClient | MCPClients>; export interface McpAppCallHandlerOptions { /** * The MCP client(s) to serve widget tool calls for — the same instances you * pass to `chat({ mcp: { clients } })`. Accepts a single client, a pool, or * an array of either. The handler reads each one's connection descriptor and * reconnects per-call (stateless/serverless-safe). */ clients: McpAppClientsInput; /** * Opt-in dynamic/stateful resolution (e.g. inMemoryMcpSessionStore). When * provided, the store WINS for any thread+serverId it has an entry for; on a * store miss (null) the handler falls back to the static `clients` registry. * So `clients` is always the base and the store is an override on top. */ store?: McpSessionStore; /** * Additional per-call authorizer. The server-exposure check is ALWAYS * enforced first (any tool the server does not expose is rejected). When * `allowTool` is provided, a request must satisfy BOTH — it is AND-ed on * top of the server-exposure check, not a replacement for it. */ allowTool?: (req: McpAppCallRequest) => boolean | Promise<boolean>; /** * Optional server-side observability hook. The handler is otherwise opaque on * failure — it returns a fail-soft `{ ok: false, error }` to the (untrusted) * widget and logs nothing, so on a serverless backend there is no trace of * WHY a proxied call failed. `onError` is invoked (and awaited if async) with * the caught error and the originating request before that result is * returned. `phase` distinguishes a `'call'` failure (connect/exposure * lookup/execution/serialization) from a `'close'` failure (per-call client * cleanup, which is swallowed and never affects the result). This library * never writes to `console`; wire your logger here to capture failures. */ onError?: (error: unknown, info: { phase: 'call' | 'close'; req: McpAppCallRequest; }) => void | Promise<void>; } /** * Creates a server-side handler that resolves an MCP server descriptor from the * provided client(s), reconnects per-call (stateless/serverless-safe), enforces * a same-server allowlist, and proxies `callTool` to the underlying MCP server. * * Always closes the per-call client in `finally`. Never returns transport config. */ export declare function createMcpAppCallHandler(opts: McpAppCallHandlerOptions): (req: McpAppCallRequest) => Promise<{ ok: true; result: unknown; } | { ok: false; error: string; }>;