Skip to main content
ClaudeWave
Skill2.8k repo starsupdated today

ai-mcp

The `@tanstack/ai-mcp` package connects Claude agents and chat loops to Model Context Protocol servers, enabling access to MCP tools and resources through a unified API. Use it when you need server-side tool execution from external MCP servers via HTTP, SSE, or stdio transports, or when you want to load MCP resources and prompts into chat messages. Do not use for browser or client-side code.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/TanStack/ai /tmp/ai-mcp && cp -r /tmp/ai-mcp/packages/ai-mcp/skills/ai-mcp ~/.claude/skills/ai-mcp
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# `@tanstack/ai-mcp`

This skill covers the `@tanstack/ai-mcp` package. Read `ai-core/tool-calling/SKILL.md`
first — MCP tools flow into `chat()` the same way hand-written tools do.

## When to use this package

Use `@tanstack/ai-mcp` when:

- A third-party MCP server exposes tools you want an agent or chat loop to call.
- You want to read MCP server resources (files, text, data) or prompts into a
  `chat()` message list.
- You want generated TypeScript types for an external MCP server's tool
  signatures (via the bundled `generate` CLI).
- You are running tool execution on the server side and want to connect to MCP
  servers with HTTP (Streamable HTTP or SSE) or stdio transports.

Do NOT use this package for browser/client-side code — MCP connections are
server-side only.

## Install

```bash
pnpm add @tanstack/ai-mcp
```

The package has two subpath exports:

- `.` — main client API (`createMCPClient`, `createMCPClients`, converters, types)
- `./stdio` — Node-only stdio transport factory (`stdioTransport`); import it
  separately so edge bundles stay clean

## `createMCPClient` — single server

```typescript
import { createMCPClient } from '@tanstack/ai-mcp'

const client = await createMCPClient({
  transport: { type: 'http', url: 'https://mcp.example.com/mcp' },
  prefix: 'weather', // optional: prefixes all tool names (e.g. 'weather_get_forecast')
  name: 'my-app', // optional: client identity sent to the server
})
```

`createMCPClient` connects immediately and returns an `MCPClient`. Throws
`MCPConnectionError` if the connection fails.

### Transports

#### Streamable HTTP (default for internet-facing servers)

```typescript
const client = await createMCPClient({
  transport: {
    type: 'http',
    url: 'https://mcp.example.com/mcp',
    headers: { Authorization: 'Bearer sk-...' },
  },
})
```

#### SSE

```typescript
const client = await createMCPClient({
  transport: {
    type: 'sse',
    url: 'https://mcp.example.com/sse',
    headers: { Authorization: 'Bearer sk-...' },
  },
})
```

#### stdio (Node-only — import from `/stdio` subpath)

```typescript
import { createMCPClient } from '@tanstack/ai-mcp'
import { stdioTransport } from '@tanstack/ai-mcp/stdio'

const client = await createMCPClient({
  transport: stdioTransport({
    command: 'npx',
    args: ['-y', 'my-mcp-server'],
    env: { API_KEY: process.env.API_KEY ?? '' },
  }),
})
```

#### Custom transport (escape hatch)

Pass any SDK `Transport` instance directly:

```typescript
import { createMCPClient } from '@tanstack/ai-mcp'
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'

const [clientTransport] = InMemoryTransport.createLinkedPair()
const client = await createMCPClient({ transport: clientTransport })
```

### Authentication

Two levels:

- **Static tokens** — pass `headers` on the `http`/`sse` config (sent with
  every request): `headers: { Authorization: 'Bearer ...' }`.
- **OAuth 2.1 (MCP authorization spec)** — pass `authProvider` on the
  `http`/`sse` config. It accepts any `OAuthClientProvider` from
  `@modelcontextprotocol/sdk/client/auth.js`; the SDK transport attaches
  tokens, refreshes them, and retries on 401.

```typescript
import { createMCPClient } from '@tanstack/ai-mcp'
import type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js'

declare const myOAuthProvider: OAuthClientProvider // backed by stored tokens

const client = await createMCPClient({
  transport: {
    type: 'http',
    url: 'https://mcp.example.com/mcp',
    authProvider: myOAuthProvider,
  },
})
```

Caveat: interactive authorization-code flows need `transport.finishAuth(code)`,
and `createMCPClient` does not expose its internal transport. For redirect
flows, construct the `StreamableHTTPClientTransport` yourself with the
`authProvider`, keep a reference, call `finishAuth(code)` in the OAuth
callback route, then pass the transport via the escape hatch above. For
server-side providers backed by pre-provisioned/refreshable tokens, the
config form is sufficient.

## Three type-safety modes

### Mode 1 — Auto-discovery (no types needed)

`client.tools()` lists every tool the server exposes. Args are typed `unknown`
at compile time but the tool's JSON Schema is forwarded to the LLM.

```typescript
const tools = await client.tools()
// tools: ServerTool[]  (args unknown)

const stream = chat({
  adapter: openaiText('gpt-5.5'),
  messages,
  tools,
})
```

Use `{ lazy: true }` to defer schema sending via the existing `LazyToolManager`:

```typescript
const tools = await client.tools({ lazy: true })
```

### Mode 2 — Typed via `toolDefinition` instances

Pass bare `toolDefinition()` instances (no `.server()` call) to `client.tools([...])`.
The MCP client binds a `callTool` proxy as the execute function while
input/output validation and TypeScript types come from the definitions' Zod schemas.
Only the named tools are returned (allowlist = the definitions' `name`s).
Throws `MCPToolNotFoundError` if the server does not expose a tool with that name.

```typescript
import { toolDefinition } from '@tanstack/ai'
import { createMCPClient } from '@tanstack/ai-mcp'
import { z } from 'zod'

const getWeatherDef = toolDefinition({
  name: 'get_weather',
  description: 'Current weather for a city',
  inputSchema: z.object({ city: z.string() }),
  outputSchema: z.object({ temperature: z.number(), conditions: z.string() }),
})

const client = await createMCPClient({
  transport: { type: 'http', url: 'https://mcp.example.com/mcp' },
})

// Returns MappedServerTools<typeof defs> — fully typed per definition.
const tools = await client.tools([getWeatherDef])
```

### Mode 3 — Generated types (via `generate` CLI)

Run `npx @tanstack/ai-mcp generate` to introspect live servers and emit a
`ServerDescriptor` interface per server. Pass the generated interface as the
generic to `createMCPClient<WeatherServer>(...)` to narrow discovered tool
names to the server's literals (args stay untyped — use Mode 2 for typed args