createMCPClient connects to an MCP server. Pass that client to chat() in the mcp option.
Note
MCP tools run on the server. Call createMCPClient in a server route or a serverless function, never in browser code.
Servers can also push signed webhooks through the experimental MCP Events extension. See MCP Events. A Start app that subscribes and receives a delivery is in Subscribe to MCP Events.
You need a working chat first. If you do not have one, start with the Quick Start.
npm i @tanstack/ai-mcp @modelcontextprotocol/clientpnpm add @tanstack/ai-mcp @modelcontextprotocol/clientyarn add @tanstack/ai-mcp @modelcontextprotocol/clientbun add @tanstack/ai-mcp @modelcontextprotocol/client// src/routes/api.chat.ts (TanStack Start)
import { createFileRoute } from '@tanstack/react-router'
import { chat, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { createMCPClient } from '@tanstack/ai-mcp'
export const Route = createFileRoute('/api/chat')({
server: {
handlers: {
POST: async ({ request }) => {
const { messages, threadId } = await request.json()
const mcp = await createMCPClient({
transport: {
type: 'http',
url: 'https://my-mcp-server.example.com/mcp',
},
})
// chat() finds the tools and closes the client when the run ends.
const stream = chat({
adapter: openaiText('gpt-6.1-sol'),
messages,
threadId,
mcp: { clients: [mcp] },
})
return toServerSentEventsResponse(stream)
},
},
},
})// src/components/Chat.tsx
import { useChat } from '@tanstack/ai-react'
import { fetchServerSentEvents } from '@tanstack/ai-client'
export function Chat() {
const { messages, sendMessage, status } = useChat({
connection: fetchServerSentEvents('/api/chat'),
threadId: 'support-chat',
})
return (
<div>
{messages.map((m) => (
<div key={m.id}>
<strong>{m.role}:</strong>{' '}
{m.parts.find((p) => p.type === 'text')?.content}
</div>
))}
<button
onClick={() => sendMessage({ content: 'Hello' })}
disabled={status === 'streaming'}
>
Send
</button>
</div>
)
}Now ask the model something that needs one of the server tools. The model calls the tool, your server runs it on the MCP server, and the answer streams back.
Note
MCP tool arguments have the type unknown at compile time. The client checks them against the JSON Schema of the server at runtime.
Pick the transport that your server uses.
HTTP (Streamable HTTP) is the usual choice for a remote server. Add headers when the server needs a token:
import { createMCPClient } from '@tanstack/ai-mcp'
const mcp = await createMCPClient({
transport: {
type: 'http',
url: 'https://my-mcp-server.example.com/mcp',
headers: { Authorization: `Bearer ${process.env.MCP_TOKEN}` },
},
})SSE is for servers that use the older SSE transport:
import { createMCPClient } from '@tanstack/ai-mcp'
const mcp = await createMCPClient({
transport: {
type: 'sse',
url: 'https://my-mcp-server.example.com/sse',
headers: { Authorization: `Bearer ${process.env.MCP_TOKEN}` },
},
})stdio starts a local MCP process. It works in Node.js only. It has its own import path, so edge bundles do not include Node modules:
import { stdioTransport } from '@tanstack/ai-mcp/stdio'
import { createMCPClient } from '@tanstack/ai-mcp'
const mcp = await createMCPClient({
transport: stdioTransport({
command: 'node',
args: ['./my-mcp-server.js'],
env: { API_KEY: process.env.API_KEY ?? '' },
}),
})For OAuth, custom transports, and timeouts, see MCP Server Tools: Advanced.
Note
This page is about MCP on your server. To expose browser actions to a browser agent, see WebMCP Tools. WebMCP calls go to the browser agent, and they do not become tool results in a TanStack chat run.
Package names for an @modelcontextprotocol/sdk import are in MCP SDK packages.
Every option of the client is on MCP Server Tools: Advanced: OAuth, timeouts, the three modes of type safety, tool annotations, pools, lifecycle, tool names, lazy discovery, and the error reference.