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.

154 lines (153 loc) 6.67 kB
import { ServerTool, ToolDefinition } from '@tanstack/ai'; import { ClientOptions } from '@modelcontextprotocol/sdk/client/index.js'; import { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js'; import { TransportInput } from './transport.js'; /** A bare tool definition (from `toolDefinition({...})`, no `.server()`/`.client()` called). */ export type AnyToolDefinition = ToolDefinition<any, any, string>; /** * The `mcp` block stamped onto every tool this package produces * (`tool.metadata.mcp`), on BOTH the auto-discovery and explicit * `tools(defs)` paths. * * You rarely name this type: `tools()` returns {@link McpServerTool}s, whose * `metadata.mcp` is already typed as this shape, so the read needs no * annotation and no cast: * * ```ts * const tools = await mcp.tools() * for (const tool of tools) { * if (tool.metadata.mcp.annotations?.readOnlyHint) { * // e.g. skip the approval prompt for a read-only tool * } * } * ``` */ export interface McpToolMetadata { /** Server-native (UNPREFIXED) tool name, even when the client sets a `prefix`. */ serverToolName: string; /** * Human-readable display name, resolved with the MCP spec's precedence: * `title` → `annotations.title` → `name`. Always set, so a UI can render it * without re-implementing the fallback chain. */ title: string; /** The owning client's `prefix` (the value a widget sends as `serverId`). */ serverId?: string; /** MCP Apps widget link, from the tool def's `_meta.ui.resourceUri`. */ uiResourceUri?: string; /** * The server's `annotations` for this tool, forwarded verbatim (absent when * the server declares none). All fields are **hints** — useful for display * and for shaping an approval UI, never a security boundary. */ annotations?: ToolAnnotations; } /** * A `ServerTool` produced by this package — structurally a plain `ServerTool` * (so it drops straight into `chat({ tools })`) with one difference: its * `metadata.mcp` block is statically known to be present and typed as * {@link McpToolMetadata}. * * `ServerTool['metadata']` is `Record<string, any> | undefined`, so reading * `tool.metadata.mcp` off a bare `ServerTool` neither compiles (possibly * undefined) nor type-checks the fields under it (`any`). Every `tools()` * overload returns these instead, which makes the natural read work and a * misspelling a compile error: * * ```ts * const [tool] = await mcp.tools() * tool.metadata.mcp.title // string * tool.metadata.mcp.annotaions // compile error (typo) * ``` */ export type McpServerTool<TTool extends ServerTool<any, any, any> = ServerTool> = Omit<TTool, 'metadata'> & { metadata: Record<string, any> & { mcp: McpToolMetadata; }; }; /** Compile-time-only descriptor of an MCP server, emitted by the codegen CLI. */ export interface ServerDescriptor { tools: Record<string, { input: unknown; output: unknown; }>; resources: Record<string, { uri: string; data: unknown; }>; prompts: Record<string, { args: unknown; messages: unknown; }>; capabilities: Record<string, unknown>; } /** The "no generated types" default — discovery yields untyped tools. */ export type AutomaticDescriptor = ServerDescriptor; export interface MCPClientOptions { transport: TransportInput; /** Tool-name prefix (e.g. 'github' → 'github_search'). Default: none. */ prefix?: string; /** Client identity sent to the server. */ name?: string; version?: string; /** * Options forwarded verbatim to the MCP SDK's `Client`. * * The one that matters in practice is `jsonSchemaValidator`. The SDK * validates a tool's `structuredContent` against its declared `outputSchema`, * and its default validator is AJV — which compiles each schema by building * JavaScript source and passing it to `new Function`. Edge runtimes forbid * that: on Cloudflare Workers every call to a tool with an `outputSchema` * fails with `Code generation from strings disallowed for this context`, * wrapped by AJV as `Error compiling schema`. * * The SDK ships the fix (`CfWorkerJsonSchemaValidator`, backed by the * optional peer `@cfworker/json-schema`) but it can only be installed through * `ClientOptions`, which this package did not expose. * * ```ts * import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/cfworker' * * const mcp = await createMCPClient({ * transport: { type: 'http', url: 'https://mcp.example.com/mcp' }, * clientOptions: { jsonSchemaValidator: new CfWorkerJsonSchemaValidator() }, * }) * ``` */ clientOptions?: ClientOptions; } export interface ToolsOptions { /** Mark tools `lazy: true` to defer schema-sending via LazyToolManager. */ lazy?: boolean; } /** * Per-element ServerTool type from a tool definition. `def.server(execute)` * already returns a fully-typed `ServerTool<TInput, TOutput, TName>`, so a * mapped tuple over the passed definitions preserves per-tool types. Wrapped * in {@link McpServerTool} because the explicit path stamps `metadata.mcp` too. */ export type ServerToolFromDef<TDef> = TDef extends ToolDefinition<infer TInput, infer TOutput, infer TName> ? McpServerTool<ServerTool<TInput, TOutput, TName>> : never; export type MappedServerTools<TDefs extends ReadonlyArray<AnyToolDefinition>> = { -readonly [K in keyof TDefs]: ServerToolFromDef<TDefs[K]>; }; /** * ServerTool named by one descriptor tool key `TKey`. * * Only the tool **name** survives into the discovery result — input/output * stay `any` because `ServerTool`'s generics are *schema* types * (`extends SchemaInput`), while the descriptor carries plain *value* types * emitted by the codegen CLI. Per-tool argument/result typing comes from the * explicit `tools(defs)` overload via `MappedServerTools`. */ type DescribedTool<TKey extends string> = McpServerTool<ServerTool<any, any, TKey>>; /** * Discovery result typed from the generated descriptor: an array whose * elements' `name` is the union of the descriptor's tool-name literals. * Arguments/results are untyped (`any`) on this path — use the `tools(defs)` * overload for typed args. When TServer is the AutomaticDescriptor (no * generated types), this collapses to `Array<ServerTool>`. */ export type DescriptorTools<TServer extends ServerDescriptor> = Array<{ [K in keyof TServer['tools'] & string]: DescribedTool<K>; }[keyof TServer['tools'] & string]>; export {};