@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
TypeScript
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 {};