@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.
123 lines (122 loc) • 6.62 kB
TypeScript
import { Client, ClientOptions, GetPromptResult, Prompt, ReadResourceResult, Resource, ResourceTemplateType, Transport } from '@modelcontextprotocol/client';
import { DescriptorFromServer, DirectClientOptions, DirectMCPClient } from './direct-client.js';
import { MCPServer } from './server/create-server.js';
import { TransportConfig } from './transport.js';
import { AnyToolDefinition, AutomaticDescriptor, DescriptorTools, MCPClientOptions, MappedServerTools, ServerDescriptor, ToolsOptions } from './types.js';
type CallToolResult = Awaited<ReturnType<Client['callTool']>>;
/**
* The raw MCP result of `callTool`. When the tool output type is known,
* `structuredContent` has that type. An untyped tool keeps the SDK type.
*/
export type TypedCallToolResult<TOutput> = unknown extends TOutput ? CallToolResult : Omit<CallToolResult, 'structuredContent'> & {
structuredContent?: TOutput;
};
export interface MCPClient<TServer extends ServerDescriptor = AutomaticDescriptor> {
readonly capabilities: TServer['capabilities'];
/**
* Auto-discovery: every server tool as a ServerTool. With a generated
* descriptor, tool names are typed as the descriptor's name literals;
* args/results stay untyped — use the `tools(defs)` overload for typed args.
*
* Both overloads yield {@link McpServerTool}s, so `tool.metadata.mcp` (the
* server's title / annotations) is typed without an annotation or a cast.
*/
tools: {
(options?: ToolsOptions): Promise<DescriptorTools<TServer>>;
/**
* Explicit: bind these TanStack toolDefinitions to the server (typed +
* validated, allowlist). Note: when the client has a `prefix`, the
* runtime tool name is `${prefix}_${def.name}` while the static `TName`
* stays the unprefixed definition name.
*/
<const TDefs extends ReadonlyArray<AnyToolDefinition>>(defs: TDefs, options?: ToolsOptions): Promise<MappedServerTools<TDefs>>;
};
resources: () => Promise<Array<Resource>>;
/**
* Reads one resource. With a typed server, `uri` is one of its resource URIs.
*/
readResource: (uri: TServer['resources'][keyof TServer['resources']]['uri']) => Promise<ReadResourceResult>;
resourceTemplates: () => Promise<Array<ResourceTemplateType>>;
prompts: () => Promise<Array<Prompt>>;
/**
* Renders one prompt. With a typed server, `name` is one of its prompt
* names and `args` has that prompt's argument type. MCP sends each
* argument as a string.
*/
getPrompt: <TName extends keyof TServer['prompts'] & string>(name: TName, args?: TServer['prompts'][TName]['args']) => Promise<GetPromptResult>;
/**
* Call a tool directly and return its raw MCP result. Tools declaring
* `execution.taskSupport: 'required'` automatically use task execution when
* the server declares the tasks capability for tools/call. Pass
* `options.signal` to abort — an in-flight task is best-effort cancelled on
* the server.
*
* With a typed server, `name` is one of its tool names and `args` has
* that tool's input type. The result is the raw MCP result. For a tool
* with an output schema, `structuredContent` has the tool output type.
*/
callTool: <TName extends keyof TServer['tools'] & string>(name: TName, args?: TServer['tools'][TName]['input'], options?: {
signal?: AbortSignal;
}) => Promise<TypedCallToolResult<TServer['tools'][TName]['output']>>;
/**
* The ORIGINAL connection descriptor this client was created from — the
* `transport` input and `prefix` passed to `createMCPClient`. Used by
* `createMcpAppCallHandler` to reconnect per-call (serverless-safe) without
* a separate transport-config map.
*
* `transport` is `undefined` when the client was built from a ready-made
* `Transport` instance rather than a serializable config — either via
* `createMCPClientFromTransport` (test-only) or `createMCPClient({ transport:
* <instance> })`. A live `Transport` instance is single-use and cannot be
* reconnected, so only serializable `TransportConfig`s are retained here.
*/
getInfo: () => {
transport: TransportConfig | undefined;
prefix: string | undefined;
/**
* The options this client was built with, so a caller that reconstructs it
* from this descriptor keeps them. Without it a rebuilt client silently
* reverts to the SDK defaults — including the AJV validator that edge
* runtimes cannot compile.
*
* Optional so an existing hand-rolled `MCPClient` keeps compiling.
*/
clientOptions?: ClientOptions;
toolFilter?: MCPClientOptions['toolFilter'];
needsApproval?: MCPClientOptions['needsApproval'];
};
close: () => Promise<void>;
[Symbol.asyncDispose]: () => Promise<void>;
}
/**
* Connects to an MCP server.
*
* Pass `transport` for a server at a URL, on SSE, or on stdio.
* The client speaks MCP over that transport, so server auth applies.
*
* To type a transport client from a TanStack server, pass `typeof server`
* as the type argument. Import the server with `import type`.
*
* Pass `server` to call a `createMCPServer` result in this process.
* That client calls the tool functions directly. It opens no connection,
* and the server `auth` option does not run.
*
* @param options - A transport, or a TanStack MCP server in this process
*
* @example
* ```ts
* const remote = await createMCPClient<typeof server>({
* transport: { type: 'http', url: 'https://mcp.example.com/mcp' },
* })
* await remote.callTool('get_weather', { city: 'Paris' })
*
* const local = await createMCPClient({ server })
* await local.callTool('get_weather', { city: 'Paris' })
* ```
*/
export declare function createMCPClient<TDescriptor extends ServerDescriptor = AutomaticDescriptor>(options: MCPClientOptions): Promise<MCPClient<TDescriptor>>;
export declare function createMCPClient<TServer extends MCPServer>(options: MCPClientOptions): Promise<MCPClient<DescriptorFromServer<TServer>>>;
export declare function createMCPClient<TServer extends MCPServer>(options: DirectClientOptions<TServer>): Promise<DirectMCPClient<TServer>>;
/** Test-only: connect directly from a transport instance (skips resolveTransport). */
export declare function createMCPClientFromTransport<TServer extends ServerDescriptor = AutomaticDescriptor>(transport: Transport, prefix?: string, clientOptions?: ClientOptions): Promise<MCPClient<TServer>>;
export {};