You have a running MCP server and you call its tools through createMCPClient, but nothing checks the tool names you reference at compile time. By the end of this guide you'll have generated per-server interface types from the live server and wired them into createMCPClient, narrowing discovered tool names to the server's literal names and compile-checking pool config keys — with zero runtime overhead. This is Mode 3 of MCP type safety. (Tool arguments stay untyped on the discovery path — for Zod-validated, TypeScript-typed arguments, combine with the tools([toolDefinition(...)]) overload.)
The generate CLI introspects a live MCP server and emits TypeScript interface types that you pass as a generic to createMCPClient / createMCPClients.
Declare each server you want to generate types for. The defineConfig helper gives the config file full type checking and autocomplete.
// mcp.config.ts
import { defineConfig } from '@tanstack/ai-mcp'
export default defineConfig({
servers: {
github: {
transport: { type: 'http', url: 'https://github-mcp.example.com/mcp' },
},
linear: {
transport: { type: 'http', url: 'https://linear-mcp.example.com/mcp' },
prefix: 'linear', // must match runtime createMCPClient({ prefix })
},
},
outFile: './mcp-types.generated.ts',
})// mcp.config.ts
import { defineConfig } from '@tanstack/ai-mcp'
export default defineConfig({
servers: {
github: {
transport: { type: 'http', url: 'https://github-mcp.example.com/mcp' },
},
linear: {
transport: { type: 'http', url: 'https://linear-mcp.example.com/mcp' },
prefix: 'linear', // must match runtime createMCPClient({ prefix })
},
},
outFile: './mcp-types.generated.ts',
})npx @tanstack/ai-mcp generatenpx @tanstack/ai-mcp generateThe CLI connects to each declared server, introspects its tools, resources, and prompts, and writes the result to outFile.
The generator emits one interface per server plus a combined pool map:
// AUTO-GENERATED by `npx @tanstack/ai-mcp generate`. Do not edit.
import type { ServerDescriptor } from '@tanstack/ai-mcp'
export interface GithubServer extends ServerDescriptor {
tools: {
'search_repositories': { input: { query: string; limit?: number }; output: unknown }
'create_issue': { input: { repo: string; title: string; body?: string }; output: unknown }
}
resources: {}
prompts: {}
capabilities: { tools: {} } & Record<string, unknown>
}
export interface LinearServer extends ServerDescriptor {
tools: {
'linear_create_issue': { input: { title: string; teamId: string }; output: unknown }
}
resources: {}
prompts: {}
capabilities: { tools: {} } & Record<string, unknown>
}
export interface MCPServers extends Record<string, ServerDescriptor> {
'github': GithubServer
'linear': LinearServer
}// AUTO-GENERATED by `npx @tanstack/ai-mcp generate`. Do not edit.
import type { ServerDescriptor } from '@tanstack/ai-mcp'
export interface GithubServer extends ServerDescriptor {
tools: {
'search_repositories': { input: { query: string; limit?: number }; output: unknown }
'create_issue': { input: { repo: string; title: string; body?: string }; output: unknown }
}
resources: {}
prompts: {}
capabilities: { tools: {} } & Record<string, unknown>
}
export interface LinearServer extends ServerDescriptor {
tools: {
'linear_create_issue': { input: { title: string; teamId: string }; output: unknown }
}
resources: {}
prompts: {}
capabilities: { tools: {} } & Record<string, unknown>
}
export interface MCPServers extends Record<string, ServerDescriptor> {
'github': GithubServer
'linear': LinearServer
}Pass the generated type as a generic to createMCPClient (single server) or createMCPClients (pool). Tool names are narrowed to the literal types declared by the server, so a typo is a compile error.
Single server:
import type { GithubServer } from './mcp-types.generated'
import { createMCPClient } from '@tanstack/ai-mcp'
const mcp = await createMCPClient<GithubServer>({
transport: { type: 'http', url: process.env.GITHUB_MCP_URL! },
})
const tools = await mcp.tools()
// Each tool name is narrowed from GithubServer['tools']import type { GithubServer } from './mcp-types.generated'
import { createMCPClient } from '@tanstack/ai-mcp'
const mcp = await createMCPClient<GithubServer>({
transport: { type: 'http', url: process.env.GITHUB_MCP_URL! },
})
const tools = await mcp.tools()
// Each tool name is narrowed from GithubServer['tools']Multi-server pool:
import type { MCPServers } from './mcp-types.generated'
import { createMCPClients } from '@tanstack/ai-mcp'
const pool = await createMCPClients<MCPServers>({
github: { transport: { type: 'http', url: process.env.GITHUB_MCP_URL! } },
linear: {
transport: { type: 'http', url: process.env.LINEAR_MCP_URL! },
prefix: 'linear',
},
})
// Config keys are constrained to the declared servers — a typo is a compile error
const tools = await pool.tools()import type { MCPServers } from './mcp-types.generated'
import { createMCPClients } from '@tanstack/ai-mcp'
const pool = await createMCPClients<MCPServers>({
github: { transport: { type: 'http', url: process.env.GITHUB_MCP_URL! } },
linear: {
transport: { type: 'http', url: process.env.LINEAR_MCP_URL! },
prefix: 'linear',
},
})
// Config keys are constrained to the declared servers — a typo is a compile error
const tools = await pool.tools()Now that tool names and pool keys are compile-checked, hand the generated client to chat(). See Managed MCP with chat() to let chat() own discovery and lifecycle.