Agent skill

AI MCP

by TanStack in TanStack/ai

Host-side Model Context Protocol (MCP) for TanStack AI: connect to external MCP servers, host your own tools with createMCPServer, discover and run tools inside any adapter's chat() loop, read…

MITAuto-check passedAgent Workflows

Install AI MCP

skills CLI
$ npx skills add TanStack/ai --skill ai-mcp -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install TanStack/ai ai-mcp --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/TanStack/ai.git skills-src && mkdir -p .claude/skills && cp -r skills-src/packages/ai-mcp/skills/ai-mcp .claude/skills/ai-mcp && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
ai-mcp
GitHub stars
3.2k
Token cost
~11k tokens
SKILL.md length
2,951 words
Files
1
Skills in repo
24
Repo updated
First seen
Licence
MIT

At a glance

Host-side Model Context Protocol (MCP) for TanStack AI: connect to external MCP servers, host your own tools with createMCPServer, discover and run tools inside any adapter's chat() loop, read…

  • Tasks that involve MCP servers
  • SKILL.md covers When to use this package, Install, Host an MCP server and createMCPClient — single server, plus 12 more sections
  • Calls npx and pnpm; reaches mcp.github.com and mcp.linear.app; needs API_KEY and INTERRUPT_PAYLOAD_METADATA_KEY
  • Tasks that involve Type safety

What it does

AI MCP is an agent skill from TanStack/ai. Host-side Model Context Protocol (MCP) for TanStack AI: connect to external MCP servers, host your own tools with createMCPServer, discover and run tools inside any adapter's chat() loop, read resources and prompts, generate TypeScript types (typed tool names/pool keys) with the bundled CLI, and manage lifecycle with close()/await using.

Its SKILL.md is about 11k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Agent Workflows, covering MCP servers and Type safety. It works with Model Context Protocol and TanStack. The repository describes itself as: 🤖 Type-safe, provider-agnostic TypeScript AI SDK for streaming chat, tool calling, agents, and multimodal apps across OpenAI, Anthropic, Gemini, React, Vue, Svelte, and Solid. The licence is MIT.

When your agent uses it

  • Tasks that involve MCP servers
  • Tasks that involve Type safety

Example prompts

  • “/ai-mcp”

What it can do on your machine

Read from SKILL.md and the folder at commit 5a41239. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • npx
    • pnpm

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Hosts in commands or code, which the agent is likely to contact:

    • mcp.github.com
    • mcp.linear.app
    • slow.api

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names these keys or tokens, usually read from environment variables:

    • API_KEY
    • INTERRUPT_PAYLOAD_METADATA_KEY
    • LINEAR_KEY

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

AI MCP loads about 11k tokens when it runs. Until then it costs about 87 tokens; SKILL.md has 2,951 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~87
When it runs · the whole SKILL.md, loaded when a task matches
~11k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from TanStack/ai at commit 5a41239, republished under its MIT licence (© TanStack). 2,951 words, ~11,478 tokens.

Download SKILL.mdSave it as .claude/skills/ai-mcp/SKILL.md (or your agent's skills folder).
name
ai-mcp
description
Host-side Model Context Protocol (MCP) for TanStack AI: connect to external MCP servers, host your own tools with createMCPServer, discover and run tools inside any adapter's chat() loop, read resources and prompts, generate TypeScript types (typed tool names/pool keys) with the bundled CLI, and manage lifecycle with close()/await using.
type
sub-skill
library
tanstack-ai
library_version
0.2.5
sources
TanStack/ai:docs/tools/mcp.md, TanStack/ai:packages/ai-mcp/src/client.ts, TanStack/ai:packages/ai-mcp/src/pool.ts…

@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. If you host the tools yourself, use createMCPServer.

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.
  • You want to expose your own tools as an MCP server over HTTP or stdio.

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 these subpaths:

  • . exports createMCPClient, createMCPClients, converters, and types.
  • ./stdio exports the Node-only client transport stdioTransport.
  • ./server exports createMCPServer.
  • ./server/stdio exports serveMCPStdio.
  • ./apps exports createMcpAppCallHandler.

Import ./stdio and ./server/stdio only from Node code. Those entries use Node I/O.

Host an MCP server

Import createMCPServer from @tanstack/ai-mcp/server. Pass tools from toolDefinition().server(). Call server.fetch(request) in your HTTP route.

typescript
import { toolDefinition } from '@tanstack/ai'
import { createMCPServer } from '@tanstack/ai-mcp/server'
import { z } from 'zod'

const getWeather = toolDefinition({
  name: 'get_weather',
  description: 'Current weather for a city',
  inputSchema: z.object({ city: z.string() }),
}).server(async ({ city }) => {
  return { city, temperature: 18, conditions: 'clear' }
})

const server = createMCPServer({
  name: 'weather',
  version: '1.0.0',
  tools: [getWeather],
})

export function handleMcp(request: Request) {
  return server.fetch(request)
}

// Mount handleMcp on GET, POST, and DELETE.
// GET is the spec 2025 stream.
// DELETE closes a spec 2025 session.

createMCPServer speaks spec 2026-07-28. createMCPServer also speaks spec 2025. By default it keeps no spec 2025 session. Its tools, resources, and prompts are static. It advertises no list-change capability and rejects subscriptions/listen with JSON-RPC -32601.

stdioTransport from @tanstack/ai-mcp/stdio connects your client to a command. serveMCPStdio from @tanstack/ai-mcp/server/stdio serves your server on stdin and stdout. Write logs with console.error. stdout carries only protocol messages.

typescript
import { toolDefinition } from '@tanstack/ai'
import { createMCPServer } from '@tanstack/ai-mcp/server'
import { serveMCPStdio } from '@tanstack/ai-mcp/server/stdio'
import { z } from 'zod'

const getWeather = toolDefinition({
  name: 'get_weather',
  description: 'Current weather for a city',
  inputSchema: z.object({ city: z.string() }),
}).server(async ({ city }) => {
  return { city, temperature: 18, conditions: 'clear' }
})

const server = createMCPServer({
  name: 'weather',
  version: '1.0.0',
  tools: [getWeather],
})

serveMCPStdio(server)

You can also pass resources and prompts. Build them with resourceDefinition and promptDefinition from @tanstack/ai-mcp/server. A resource read(uri, variables, ctx) gets the requested URI, the template variables, and ctx.context (the handle context plus authInfo). A template resource can take list(ctx), which returns { resources } for resources/list.

A tool reads its hooks on ctx.context. Give .server() the type MCPToolContext from @tanstack/ai-mcp/server. Then ctx.context.requestInput and ctx.context.sample type-check. On spec 2026, ctx.context.requestInput throws, and the handler returns input_required. The client runs the tool again with the answer. Code before requestInput runs on each call, so it can run more than once. Put work that must run once after requestInput returns. On spec 2026, a tool asks one question per call. A second requestInput throws an Error. If the user declines or cancels, requestInput throws an Error, and the call ends with a tool error. In an execution: 'task' tool, ctx.context.requestInput throws an error. On spec 2025 with sessions: 'memory', requestInput waits on the open session. Without a session, it throws. The same tool call then continues.

typescript
import { toolDefinition } from '@tanstack/ai'
import { createMCPServer } from '@tanstack/ai-mcp/server'
import type { MCPToolContext } from '@tanstack/ai-mcp/server'
import { z } from 'zod'

const askCity = toolDefinition({
  name: 'ask_city',
  description: 'Ask which city to use',
  inputSchema: z.object({}),
}).server<MCPToolContext>(async (_args, ctx) => {
  const city = await ctx.context.requestInput({ message: 'Which city?' })
  return { city }
})

const server = createMCPServer({
  name: 'weather',
  version: '1.0.0',
  tools: [askCity],
})

export function handleMcp(request: Request) {
  return server.fetch(request)
}

Mount handleMcp on GET, POST, and DELETE.

If a tool calls ctx.context.sample on spec 2026, pass sample to createMCPServer. On spec 2026, ctx.context.sample calls the sample function. On spec 2025, ctx.context.sample asks the MCP client. A tool with execution: 'task' returns a spec 2025 task handle. Spec 2026 has no tasks, so that tool runs inline there.

Require a bearer token

The server is an OAuth resource server. Your authorization server issues the token. Pass auth with a verifier. It is the OAuthTokenVerifier type from the MCP SDK. jwtVerifier checks a JWT against the JWKS of the provider. introspectionVerifier checks an opaque token at an RFC 7662 endpoint. A missing or bad token returns 401. A token without a scope in requiredScopes returns 403. A tool reads the token as ctx.context.authInfo. Sessions and tasks belong to the clientId plus the sub claim of the token. Serve the OAuth discovery documents with oauthMetadataResponse at the app root.

typescript
import { createMCPServer, jwtVerifier } from '@tanstack/ai-mcp/server'

const server = createMCPServer({
  name: 'notes',
  version: '1.0.0',
  auth: {
    verifier: jwtVerifier({
      jwksUrl: 'https://auth.example.com/.well-known/jwks.json',
      issuer: 'https://auth.example.com/',
      audience: 'https://mcp.example.com/mcp',
    }),
    requiredScopes: ['notes:read'],
  },
})
Use the auth the app already has

When a middleware already verified the caller, pass the result to server.handle. server.fetch(request) stays a plain Fetch handler. server.handle takes options. options.authInfo is the SDK AuthInfo. The server skips its auth gate for that request. options.context reaches every tool call, resource read, and resource list of that request on ctx.context. Type the values with MCPToolContext<{ db: Db }>. authInfo, requestInput, and sample win over a same-named value in context.

typescript
import { server } from './mcp-server'
import { verifyCaller } from './auth'

export async function handleMcp(request: Request) {
  const caller = await verifyCaller(request)
  if (caller instanceof Response) return caller
  return server.handle(request, {
    authInfo: caller.authInfo,
    context: { db: caller.db },
  })
}
Describe a tool to the host

Set metadata.title and metadata.annotations on the tool definition. The host gets them as the MCP tool title and annotations. Use the MCP names: readOnlyHint, destructiveHint, idempotentHint, openWorldHint. A host skips its confirmation for a tool with readOnlyHint: true. Set metadata._meta to send the MCP tool _meta, for example { ui: { resourceUri: 'ui://view' } } for an MCP Apps view. Pass onerror to createMCPServer to log transport and protocol errors from the SDK. serveMCPStdio also sends its transport errors there.

A tool with no outputSchema can return an MCP CallToolResult. The server sends it as is: its content blocks, its structuredContent, and its isError.

Spec 2025 on a host with many instances

The default is sessions: 'stateless'. It works on a host with many instances, such as Cloudflare Workers. A new server answers each spec 2025 request, and no session is kept. In that mode, ctx.context.requestInput throws for a spec 2025 client. ctx.context.sample calls the sample option, or throws when it is not set. Set sessions: 'reject' to serve spec 2026 only. A spec 2025 request then gets the SDK rejection. Set sessions: 'memory' to keep spec 2025 sessions in the process for 30 idle minutes. Route them with sticky sessions on the mcp-session-id header. serveMCPStdio uses 'memory' when sessions is not set.

Call a createMCPServer server with its types

For a deployed server, pass typeof server and a transport. Import the server with import type, so its code stays out of the app. The client speaks MCP, so the server auth option runs. callTool accepts only the server tool names and their input types. callTool returns the raw MCP result. For a tool with an outputSchema, structuredContent has the tool output type. getPrompt accepts only the server prompt names and their argument types. readResource accepts only the server resource URIs.

typescript
import { createMCPClient } from '@tanstack/ai-mcp'
import type { server } from './mcp-server'

const remote = await createMCPClient<typeof server>({
  transport: { type: 'http', url: 'https://mcp.example.com/api/mcp' },
})
await remote.callTool('get_weather', { city: 'Paris' })

createMCPClient({ server }) is a different client. It calls the tool functions in the same process and returns the tool output, parsed with the outputSchema. readResource(uri, context) puts context on the resource ctx.context. Without it, ctx.context is {}. It opens no connection, and the server auth option does not run. It has no tools(), so do not pass it to chat(). Use it only when the app and the server run in one process.

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. If the connection fails, createMCPClient throws MCPConnectionError. createMCPClient tries spec 2026-07-28 first. If the server does not support that spec, the client uses the 2025 initialize handshake. The client keeps negotiation mode auto. client.instructions holds the server's instructions from the handshake, or undefined when the server sends none. Put them in the system prompt: systemPrompts: client.instructions ? [client.instructions] : [].

Transports
Streamable HTTP (default for internet-facing servers)
typescript
import { createMCPClient } from '@tanstack/ai-mcp'

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

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 Transport from @modelcontextprotocol/client:

typescript
// InMemoryTransport comes from @modelcontextprotocol/client.
// @tanstack/ai-mcp re-exports it. Any Transport from that package works here.
import { createMCPClient, InMemoryTransport } from '@tanstack/ai-mcp'

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 or sse config. The value is an OAuthClientProvider from @modelcontextprotocol/client. The transport attaches tokens, refreshes them, and retries on 401.
typescript
import { createMCPClient } from '@tanstack/ai-mcp'
// An OAuthClientProvider from @modelcontextprotocol/client.
// You persist the tokens on the server.
import { myOAuthProvider } from './oauth-provider'

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. Import StreamableHTTPClientTransport from @modelcontextprotocol/client.

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
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { createMCPClient } from '@tanstack/ai-mcp'

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

const tools = await client.tools()
// tools: McpServerTool[]  (args unknown)

const stream = chat({
  adapter: openaiText('gpt-5.5'),
  messages: [{ role: 'user', content: 'What is the weather in Paris?' }],
  tools,
})

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

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

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

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' names). 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).

See the "Codegen CLI" section below for details.

Tool policy: toolFilter and needsApproval

By default every server tool reaches the model and runs without approval. Set a policy on the client. It applies in tools(), in chat({ mcp }), and per server in createMCPClients. Both callbacks receive the raw MCP tool definition (native unprefixed name, title, annotations).

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

const mcp = await createMCPClient({
  transport: { type: 'http', url: 'https://mcp.example.com/mcp' },
  // Hide tools from the model. Unannotated tools fail this check.
  toolFilter: (tool) => tool.annotations?.readOnlyHint === true,
  // Pause for approval before these tools run (auto-discovery only).
  needsApproval: (tool) => tool.annotations?.destructiveHint !== false,
})
  • toolFilter also applies to tools([defs]): a hidden definition throws MCPToolNotFoundError. MCP Apps widget calls also honor it. It does not apply to callTool().
  • needsApproval does not change tools([defs]): each toolDefinition keeps its own needsApproval.
  • MCP Apps widget calls have no approval step, so the call handler refuses a tool that needsApproval marks ({ ok: false, error: 'Tool needs approval: <name>' }).
  • Annotations are server-declared hints. For an untrusted server, filter by tool.name instead.

Lifecycle

The caller owns the lifecycle. chat() never closes the client.

Tools execute lazily while the response stream is consumed — close only after the stream is drained. In a streaming route handler, try/finally around the return (or await using at function scope) closes the client before the body streams; use a middleware terminal hook there instead (see Common Mistakes below).

typescript
import { chat, toServerSentEventsResponse } from '@tanstack/ai'
import type { ModelMessage } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { createMCPClient } from '@tanstack/ai-mcp'

// Option 1: middleware terminal hooks (streaming route handlers)
export async function POST(request: Request) {
  const { messages } = await request.json()
  const client = await createMCPClient({
    transport: { type: 'http', url: 'https://mcp.example.com/mcp' },
  })
  const stream = chat({
    adapter: openaiText('gpt-5.5'),
    messages,
    tools: await client.tools(),
    middleware: [
      {
        name: 'mcp-close',
        onFinish: () => client.close(),
        onAbort: () => client.close(),
        onError: () => client.close(),
      },
    ],
  })
  return toServerSentEventsResponse(stream)
}

// Option 2: explicit close after in-scope consumption
export async function runToCompletion(messages: Array<ModelMessage>) {
  const client = await createMCPClient({
    transport: { type: 'http', url: 'https://mcp.example.com/mcp' },
  })
  try {
    const stream = chat({
      adapter: openaiText('gpt-5.5'),
      messages,
      tools: await client.tools(),
    })
    for await (const chunk of stream) {
      // stream fully consumed inside this block
    }
  } finally {
    await client.close()
  }
}

// Option 3: await using (TypeScript 5.2+ with Symbol.asyncDispose) —
// same rule: consume the stream before the scope exits.
export async function runWithUsing(messages: Array<ModelMessage>) {
  await using client = await createMCPClient({
    transport: { type: 'http', url: 'https://mcp.example.com/mcp' },
  })
  const stream = chat({
    adapter: openaiText('gpt-5.5'),
    messages,
    tools: await client.tools(),
  })
  for await (const chunk of stream) {
    // ... consume the stream in this scope; close() runs at scope exit
  }
}

chat({ mcp }) — discovery + lifecycle in one prop

Rather than calling client.tools() and client.close() yourself, pass the mcp option to chat() and let it manage the full lifecycle.

typescript
// ChatMCPOptions shape:
// mcp: {
//   clients: Array<MCPClient | MCPClients>,
//   connection?: 'close' | 'keep-alive',  // default: 'close'
//   lazyTools?: boolean,
//   onDiscoveryError?: (error: unknown, source) => void,
// }

Behavior:

  • chat() calls .tools() on every entry in clients at run start and merges all results into the tool list.
  • lazyTools: true is forwarded to tools({ lazy: true }).
  • connection: 'close' (default) — each client is closed when the run ends (after the agent loop completes and the stream is drained). With 'keep-alive', chat() never closes the clients — the caller owns their lifecycle (keep connections warm across requests).
  • onDiscoveryError: throw (or re-throw) to abort the entire call; return normally to skip that source and continue. Omitting the handler re-throws (fail-fast).

When to use mcp vs. the tools spread:

ApproachUse when
chat({ mcp: { clients: [...] } })Convenience: discovery + lifecycle handled for you; untyped args are fine
tools: [...await client.tools([toolDefinition(...)])]Fully-typed args/results via Zod schemas (toolDefinition mode)

Server-side example:

typescript
// Any framework route handler that receives a Request works (TanStack Start,
// Next.js, Hono, ...).
import { chat, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { createMCPClient } from '@tanstack/ai-mcp'

// Created once at module scope; connection: 'keep-alive' below keeps it warm
// across requests.
const mcpClient = await createMCPClient({
  transport: { type: 'http', url: 'https://mcp.example.com/mcp' },
})

export async function POST(request: Request) {
  const { messages } = await request.json()

  const stream = chat({
    adapter: openaiText('gpt-5.5'),
    messages,
    mcp: {
      clients: [mcpClient],
      connection: 'keep-alive', // chat() won't close it — reuse across requests
      onDiscoveryError: (err, source) => {
        console.warn('MCP discovery failed for source, skipping:', err)
        // returning skips this source; throw to fail the whole call fast
      },
    },
  })

  return toServerSentEventsResponse(stream)
  // connection: 'keep-alive' — chat() never closes mcpClient; it stays warm for the next request.
}

You can also pass an MCPClients pool directly:

typescript
import { chat, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { createMCPClients } from '@tanstack/ai-mcp'

const pool = await createMCPClients({
  github: { transport: { type: 'http', url: 'https://mcp.github.com/mcp' } },
  linear: { transport: { type: 'http', url: 'https://mcp.linear.app/mcp' } },
})

export async function POST(request: Request) {
  const { messages } = await request.json()

  const stream = chat({
    adapter: openaiText('gpt-5.5'),
    messages,
    mcp: { clients: [pool], connection: 'keep-alive' },
  })

  return toServerSentEventsResponse(stream)
}
Show full SKILL.md (1,215 more words)Show less

MCP input request

When chat() receives an MCP input request, the run outcome is an interrupt. The stream ends with RUN_FINISHED. The outcome type is interrupt. The stream does not emit RUN_ERROR for this pause.

Read each interrupt whose reason is mcp_input. The payload key is tanstack:interruptPayload. form means the server asks the user for input. sampling means the server asks for a model result. The interrupt id is mcp_input_ plus the tool call id.

typescript
import { chat, INTERRUPT_PAYLOAD_METADATA_KEY } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { createMCPClient } from '@tanstack/ai-mcp'

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

try {
  const stream = chat({
    adapter: openaiText('gpt-5.5'),
    messages: [{ role: 'user', content: 'What is the weather in Paris?' }],
    tools: await client.tools(),
  })

  for await (const chunk of stream) {
    if (chunk.type !== 'RUN_FINISHED') continue
    if (chunk.outcome?.type !== 'interrupt') continue

    for (const item of chunk.outcome.interrupts) {
      if (item.reason !== 'mcp_input') continue
      const payload = item.metadata?.[INTERRUPT_PAYLOAD_METADATA_KEY]
      if (typeof payload !== 'object' || payload === null) continue
      if (!('kind' in payload)) continue
      // payload.kind is 'form' or 'sampling'
      // payload.request is the MCP input body
    }
  }
} finally {
  await client.close()
}

The interrupt has a generic binding. In useChat, the item kind is generic. Call resolveInterrupt(answer) or cancel() on the item. For a form, the answer is an object that matches request.requestedSchema. A createMCPServer server asks for { value: string }. For sampling, the answer is the reply text or a full CreateMessageResult. The route must pass parentRunId and resume to chat(). The next run calls the tool again, and the tool reads ctx.inputResponse. On spec 2026, the MCP client sends that answer with inputResponses and the server requestState. On spec 2025, the tool call fails, because chat() cannot pause that call.

createMCPClients — multiple servers

Connect to many MCP servers in parallel. Each config key becomes the default prefix for that server's tools, preventing name collisions across servers.

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

await using pool = await createMCPClients({
  github: { transport: { type: 'http', url: 'https://mcp.github.com/mcp' } },
  linear: { transport: { type: 'http', url: 'https://mcp.linear.app/mcp' } },
})

// Tool names auto-prefixed: 'github_search_repos', 'linear_create_issue', etc.
const tools = await pool.tools()

// Forward lazy flag to every server:
const lazyTools = await pool.tools({ lazy: true })

// Per-server typed access (keys are typed as string here; generated
// MCPServers types make them literal — see Codegen CLI below):
const githubTools = await pool.clients.github!.tools()

createMCPClients connects in parallel, closes already-connected clients if any connection fails (no leaks), and throws MCPConnectionError naming the failed server(s).

Override or disable prefixing:

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

await using pool = await createMCPClients({
  // 'gh_search_repos'
  github: {
    transport: { type: 'http', url: 'https://mcp.github.com/mcp' },
    prefix: 'gh',
  },
  // 'create_issue' (no prefix)
  linear: {
    transport: { type: 'http', url: 'https://mcp.linear.app/mcp' },
    prefix: '',
  },
})

Abort signal — cancelling in-flight MCP calls

TanStack AI stops waiting for MCP tool calls when the chat run's AbortController fires (e.g. client disconnect, server abort). The abortSignal is threaded through ToolExecutionContext into every tool call with no extra code. For a task-required tool, aborting stops the local task stream and sends a best-effort tasks/cancel for a remote task the MCP server has already created. Cancel is best-effort: a server that ignores tasks/cancel may keep running until TTL.

You can also read it in a hand-written server tool that wraps an MCP call:

typescript
import { toolDefinition } from '@tanstack/ai'
import { z } from 'zod'

const fetchData = toolDefinition({
  name: 'fetch_data',
  description: 'Fetch a record from a slow upstream API',
  inputSchema: z.object({ id: z.string() }),
})

const myTool = fetchData.server(async (args, ctx) => {
  // Forward to any async work that accepts an AbortSignal.
  const result = await fetch(`https://slow.api/data/${args.id}`, {
    signal: ctx?.abortSignal,
  })
  return result.json()
})

Resources

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

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

// List all resources the server exposes.
const resources = await client.resources()

// Read a specific resource by URI.
const resource = await client.readResource(resources[0]!.uri)

// Convert one content block to a TanStack ContentPart.
const part = mcpResourceToContentPart(resource.contents[0]!)
// part: ContentPart  (type: 'text' always for v1)

Inject resources into a chat turn:

typescript
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { createMCPClient, mcpResourceToContentPart } from '@tanstack/ai-mcp'

const client = await createMCPClient({
  transport: { type: 'http', url: 'https://mcp.example.com/mcp' },
})
const resource = await client.readResource('file:///project/README.md')
const parts = resource.contents.map(mcpResourceToContentPart)

const stream = chat({
  adapter: openaiText('gpt-5.5'),
  messages: [
    {
      role: 'user',
      content: [
        ...parts,
        { type: 'text', content: 'Summarize this document.' },
      ],
    },
  ],
})

Prompts

typescript
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { createMCPClient, mcpPromptToMessages } from '@tanstack/ai-mcp'

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

// List prompts the server exposes.
const prompts = await client.prompts()

// Get a prompt (with optional arguments).
const prompt = await client.getPrompt('review_code', { language: 'TypeScript' })

// Convert to TanStack ModelMessage[] for use in chat().
const messages = mcpPromptToMessages(prompt)
// messages: ModelMessage[]  (role: 'user' | 'assistant')

const stream = chat({
  adapter: openaiText('gpt-5.5'),
  messages: [...messages, { role: 'user', content: 'Review src/index.ts.' }],
})

MCP Apps

MCP Apps let an MCP tool surface a UI widget (static or interactive) on the client. Two variants exist. See docs/mcp/apps.md for the full guide.

Static widgets — UIResourcePart

When an MCP tool result carries a ui:// resource, TanStack AI emits a UIResourcePart on the assistant UIMessage, alongside the normal ToolCallPart / ToolResultPart. It is purely presentational — it never enters model input. The resource is read eagerly during the chat() run; if the read fails the tool result still flows to the model and the widget is simply absent (fail-soft). Static widgets require the MCP source to expose readResource — both createMCPClient and a createMCPClients pool do.

typescript
import type { UIResourcePart } from '@tanstack/ai'

// UIResourcePart shape (on the assistant UIMessage):
// {
//   type: 'ui-resource'
//   resource: { uri: string; mimeType: string; text?: string; blob?: string }
//   serverId?: string     // pool prefix / config key — routes interactive calls
//   toolCallId: string    // links to the originating tool call
//   toolName: string      // server-native MCP tool name whose UI this renders
//   meta?: Record<string, unknown>  // reserved — currently always undefined
// }
Interactive apps — createMcpAppCallHandler

For interactive apps (the widget iframe posts tool-call / prompt / link actions back), mount createMcpAppCallHandler from @tanstack/ai-mcp/apps at a POST route. Pass the MCP client(s) you already created — a single MCPClient, an MCPClients pool, or an array of either. The handler reads each client's transport descriptor via client.getInfo() / pool.getServers() (pure config, not a live socket) and reconnects per-call (stateless / serverless-safe). It matches the widget-supplied native (unprefixed) tool name against the server's unprefixed tool names, enforces a same-server allowlist, and returns { ok: true, result } or { ok: false, error }.

For a pool, the serverId on the UIResourcePart is the config key (the tool prefix); for a single client it is the client's prefix (or the sole default when serverId is absent and there is exactly one client).

typescript
import { createMCPClients } from '@tanstack/ai-mcp'
import {
  createMcpAppCallHandler,
  inMemoryMcpSessionStore,
} from '@tanstack/ai-mcp/apps'

// Reuse the same pool you pass to chat({ mcp: { clients: [mcp] } }).
const mcp = await createMCPClients({
  weather: {
    transport: { type: 'http', url: 'https://mcp-app.example.com/mcp' },
  },
})

// Minimal — reconnect-per-call via getServers() descriptor.
const handler = createMcpAppCallHandler({ clients: mcp })

// Options:
// clients   — MCPClient | MCPClients | Array<MCPClient | MCPClients> (required).
//             The handler reads transport descriptors via client.getInfo() /
//             pool.getServers() — the client does not need a live connection.
// store     — optional dynamic/stateful session store (e.g.
//             inMemoryMcpSessionStore()); used alongside clients.
// allowTool — optional authorizer receiving the WHOLE request:
//             (req: McpAppCallRequest) => boolean | Promise<boolean>.
//             The server-exposure check is ALWAYS enforced (the handler
//             rejects any tool the server does not expose). `allowTool`
//             is an ADDITIONAL restriction AND-ed on top: a request must
//             satisfy BOTH the server-exposure check and allowTool.
const handlerWithStore = createMcpAppCallHandler({
  clients: mcp,
  store: inMemoryMcpSessionStore(),
  allowTool: (req) => req.toolName === 'place_order',
})

The handler invokes the server (body: { threadId, serverId?, toolName, args?, messageId? }):

typescript
export async function POST(request: Request) {
  const body = await request.json()
  const result = await handler(body)
  // { ok: true; result: unknown } | { ok: false; error: string }
  return Response.json(result)
}
Client side — useMcpAppBridge + MCPAppResource

In React/Preact, create the bridge with the useMcpAppBridge hook (from @tanstack/ai-react / @tanstack/ai-preact) — it returns a stable bridge per threadId/callEndpoint and always calls your latest sendMessage/onLink, so the bridge isn't recreated on every render (no useMemo / exhaustive-deps by hand). It's a thin wrapper over the framework-agnostic createMcpAppBridge from @tanstack/ai-client (use that directly outside React/Preact). Render resources with MCPAppResource from @tanstack/ai-react/mcp-apps (also @tanstack/ai-preact/mcp-apps, which requires a preact/compat alias). MCPAppResource uses @mcp-ui/client's AppRenderer under the hood — React only. Solid, Vue, Svelte, and Angular renderers are deferred.

The bridge exposes { callTool, sendPrompt, openLink } and routes the iframe's actions: tool → POST to callEndpoint; prompt → chat.sendMessage; link → onLink(url) if provided, otherwise the link is dropped (with a console warning) and openLink returns { isError: true } — it does NOT hang. toolName is read from part.toolName; it is not a prop. Omit bridge for display-only (inert) rendering.

tsx
import { useChat, useMcpAppBridge } from '@tanstack/ai-react'
import { fetchServerSentEvents } from '@tanstack/ai-client'
import { MCPAppResource } from '@tanstack/ai-react/mcp-apps'

function ChatPage() {
  const threadId = 'weather-chat'
  const { messages, sendMessage } = useChat({
    connection: fetchServerSentEvents('/api/chat'),
  })

  const bridge = useMcpAppBridge({
    threadId,
    callEndpoint: '/api/mcp-app/call',
    chat: { sendMessage: async (content) => void sendMessage({ content }) },
    // Opt in to link navigation — absent means links are dropped.
    onLink: (url) => window.open(url, '_blank', 'noopener'),
  })

  return (
    <div>
      {messages.map((msg) =>
        msg.parts.map((part, i) => {
          if (part.type === 'text') return <p key={i}>{part.content}</p>
          if (part.type === 'ui-resource') {
            return (
              <MCPAppResource
                key={i}
                part={part}
                bridge={bridge}
                sandbox={{ url: new URL('https://sandbox.example.com') }}
                // toolInput is optional; toolName comes from part.toolName.
              />
            )
          }
          return null
        }),
      )}
    </div>
  )
}

Codegen CLI

Generate TypeScript types (typed tool names and pool keys) by introspecting live MCP servers.

1. Create mcp.config.ts at your project root:

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

export default defineConfig({
  servers: {
    github: {
      transport: { type: 'http', url: 'https://mcp.github.com/mcp' },
      // prefix must match the runtime createMCPClient({ prefix }) value
    },
  },
  outFile: './src/mcp-types.generated.ts',
})

2. Run the generator:

bash
npx @tanstack/ai-mcp generate

This connects to each server, lists its tools/resources/prompts, converts JSON Schemas to TypeScript, and writes one interface <Name>Server extends ServerDescriptor per server plus a combined interface MCPServers for pool typing.

3. Use the generated types:

typescript
// Single server — narrows tools() return to descriptor-keyed tool names.
import type { GithubServer } from './src/mcp-types.generated'
import { createMCPClient, createMCPClients } from '@tanstack/ai-mcp'

const client = await createMCPClient<GithubServer>({
  transport: { type: 'http', url: 'https://mcp.github.com/mcp' },
})
const tools = await client.tools() // typed to GithubServer's tool names

// Multiple servers via the generated MCPServers map.
import type { MCPServers } from './src/mcp-types.generated'

const pool = await createMCPClients<MCPServers>({
  github: { transport: { type: 'http', url: 'https://mcp.github.com/mcp' } },
})
// pool.clients.github is MCPClient<GithubServer>
// missing/extra keys are a compile error

Codegen deps (json-schema-to-typescript, jiti) are bundled into the CLI bin and do NOT appear in the library's runtime dependency graph.

Error classes

  • MCPConnectionError — thrown when a server connection fails or when calling methods after close().
  • MCPToolNotFoundError — thrown from client.tools([defs]) when a definition's name is not exposed by the server, or the client's toolFilter hides it.
  • MCPTaskRequiredToolError — thrown when a task-required tool is bound via tools([defs]) or called via callTool() and the server does not declare the tasks capability for tools/call. Auto-discovery skips those tools instead of throwing.
  • DuplicateToolNameError — thrown by a single pool's own tools() when two tools within that pool share the same name (same server or pool clients with no prefix). Exported from @tanstack/ai-mcp.
  • MCPDuplicateToolNameError — thrown by chat() when tools from separate mcp.clients entries collide after merging. Exported from @tanstack/ai (not @tanstack/ai-mcp), so users can instanceof it at the chat() call site.
typescript
import {
  MCPConnectionError,
  MCPToolNotFoundError,
  MCPTaskRequiredToolError,
  DuplicateToolNameError,
} from '@tanstack/ai-mcp'

import { MCPDuplicateToolNameError } from '@tanstack/ai'

Complete server-route example

typescript
// src/routes/api.chat.ts — mount POST in your framework's route handler
// (TanStack Start server route, Next.js route handler, Hono, ...).
import { chat, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { createMCPClients } from '@tanstack/ai-mcp'

export async function POST(request: Request) {
  const { messages } = await request.json()

  const pool = await createMCPClients({
    github: {
      transport: { type: 'http', url: 'https://mcp.github.com/mcp' },
    },
    linear: {
      transport: {
        type: 'http',
        url: 'https://mcp.linear.app/mcp',
        headers: {
          Authorization: `Bearer ${process.env.LINEAR_KEY ?? ''}`,
        },
      },
    },
  })

  const stream = chat({
    adapter: openaiText('gpt-5.5'),
    messages,
    tools: await pool.tools(),
    // Close after the run ends — tools execute while the response streams,
    // so `await using` / try-finally would close the pool too early here.
    middleware: [
      {
        name: 'mcp-close',
        onFinish: () => pool.close(),
        onAbort: () => pool.close(),
        onError: () => pool.close(),
      },
    ],
  })

  return toServerSentEventsResponse(stream)
}

Common Mistakes

a. HIGH: closing the client before the stream finishes

chat() executes tools lazily as the model calls them during streaming. If you close the MCP client before the response stream is fully consumed, in-flight tool calls will fail.

Wrong:

typescript
import { chat, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { createMCPClient } from '@tanstack/ai-mcp'

export async function POST(request: Request) {
  const { messages } = await request.json()
  const client = await createMCPClient({
    transport: { type: 'http', url: 'https://mcp.example.com/mcp' },
  })
  const tools = await client.tools()
  const stream = chat({ adapter: openaiText('gpt-5.5'), messages, tools })
  await client.close() // closes before the stream runs tools
  return toServerSentEventsResponse(stream)
}

This includes try/finally around the return, and await using at function scope — both close before the returned Response body streams.

Correct — close in middleware terminal hooks (exactly one of onFinish/onAbort/onError fires per run), or consume the stream in scope before closing:

typescript
import { chat, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { createMCPClient } from '@tanstack/ai-mcp'

export async function POST(request: Request) {
  const { messages } = await request.json()
  const client = await createMCPClient({
    transport: { type: 'http', url: 'https://mcp.example.com/mcp' },
  })

  const stream = chat({
    adapter: openaiText('gpt-5.5'),
    messages,
    tools: await client.tools(),
    middleware: [
      {
        name: 'mcp-close',
        onFinish: () => client.close(),
        onAbort: () => client.close(),
        onError: () => client.close(),
      },
    ],
  })
  return toServerSentEventsResponse(stream)
}
b. HIGH: importing stdioTransport from the main entry point

stdioTransport is only available from @tanstack/ai-mcp/stdio. Importing it from @tanstack/ai-mcp will fail with a module-not-found error and would bundle Node.js child-process code into edge bundles.

Wrong:

typescript
import { stdioTransport } from '@tanstack/ai-mcp' // does not exist here

Correct:

typescript
import { stdioTransport } from '@tanstack/ai-mcp/stdio'
c. MEDIUM: using client.tools([defs]) without matching names

The name field on each toolDefinition must exactly match the tool name the MCP server exposes. Mismatches throw MCPToolNotFoundError at call time, not at type-check time (unless generated types are in use).

d. MEDIUM: not setting a prefix when multiple servers share tool names

Two different errors can arise depending on where the collision is detected:

  • Within a single createMCPClients pool — calling pool.tools() throws DuplicateToolNameError (from @tanstack/ai-mcp) when two servers in that pool expose the same name with no prefix to separate them.
  • Across separate mcp.clients entries in chat() — chat() throws MCPDuplicateToolNameError (from @tanstack/ai) after merging discovered tools from all mcp.clients entries.

In both cases, the fix is the same: use createMCPClients (which auto-prefixes by config key) or set an explicit prefix on each createMCPClient call.

e. HIGH: importing @modelcontextprotocol/sdk

Use @modelcontextprotocol/client for client transports. Use @modelcontextprotocol/server for server helpers. @tanstack/ai-mcp re-exports InMemoryTransport from the client package.

Wrong:

typescript
import { InMemoryTransport } from '@modelcontextprotocol/sdk'

Correct:

typescript
import { InMemoryTransport } from '@modelcontextprotocol/client'

Cross-References

  • See also: ai-core/tool-calling/SKILL.md — MCP tools are ServerTools; all tool patterns (approval, lazy, client-side) apply.
  • See also: ai-core/chat-experience/SKILL.md — wiring tools into chat().

© TanStack, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in packages/ai-mcp/skills/ai-mcp of TanStack/ai.

Open the folder on GitHubat commit 5a41239

Compare with similar skills

AI MCP next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

AI MCP compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
AI MCP this skillTanStack/ai3.2k—~11kAutomated safety check: PassMIT
TS Agent SDKMicrock/ordinary-claude-skills401—~1.3kAutomated safety check: PassCustom licence
holaOS App Builder SDKholaboss-ai/holaOS11k—~11kAutomated safety check: PassCustom licence
Azsdk Common Pipeline FixerAzure/azure-sdk-tools134—~831Automated safety check: PassMIT
MCP Server Builderanthropics/skills180k62 repos~2.3kAutomated safety check: PassApache-2.0
MCP Server BuildershareAI-lab/learn-claude-code78k5 repos~1.2kAutomated safety check: PassMIT

Similar skills

  • TS Agent SDK

    Microck/ordinary-claude-skills

    Generate typed TypeScript SDKs for AI agents to interact with MCP servers.

    401 GitHub stars~1.3k tokensUpdated 1 mo ago
    Agent WorkflowsAuto-check passed
  • holaOS App Builder SDK

    holaboss-ai/holaOS

    Builds new holaOS apps with @holaboss/app-builder-sdk, either as integration-only MCP modules or as dashboard apps with a shadcn UI under src/client/.

    11k GitHub stars~11k tokensUpdated 1 mo ago
    DevelopmentAuto-check passed
  • Azsdk Common Pipeline Fixer

    Azure/azure-sdk-tools

    Official

    Automatically fix Azure SDK CI/CD pipeline failures by applying code changes and verifying locally.

    134 GitHub stars~831 tokensUpdated today
    Backend & APIsAuto-check passed
  • MCP Server Builder

    anthropics/skills

    Official

    Guides the design and implementation of Model Context Protocol servers in TypeScript or Python, from tool naming and error messages to evaluation.

    180k GitHub starsUsed in 62 repos~2.3k tokens
    Agent WorkflowsAuto-check passed
  • MCP Server Builder

    shareAI-lab/learn-claude-code

    Walks through building MCP servers in Python or TypeScript that expose tools, resources and prompts to Claude, with templates, registration and testing.

    78k GitHub starsUsed in 5 repos~1.2k tokens
    Agent WorkflowsAuto-check passed
  • MCP Integration for Plugins

    anthropics/claude-plugins-official

    Official

    Explains how to bundle Model Context Protocol servers in a Claude Code plugin, covering config files, stdio, SSE, HTTP and WebSocket server types, and authentication.

    37k GitHub starsUsed in 11 repos~3.1k tokens
    Agent WorkflowsAuto-check passed

More from TanStack/ai

All 24 skills in this repo
  • I Have Adhd

    TanStack/ai

    A skill your agent uses when the user invokes /i-have-adhd, says they have ADHD, or asks for ADHD-friendly output.

    3.2k GitHub starsUsed in 5 repos~1.8k tokens
    Auto-check passed
  • PR Sweep

    TanStack/ai

    Sweep open (or listed) PRs with up to 100 parallel agents: security-scan outside contributors, rebase onto main when behind (push --force-with-lease), approve pending first-time-contributor CI when…

    3.2k GitHub stars~4.6k tokensUpdated today
    Auto-check passed
  • A skill your agent uses when wiring honcho() from @tanstack/ai-memory/honcho — a hosted memory adapter where recall is a dialectic answer over the user's representation (no discrete fragments).

    3.2k GitHub starsUsed in 1 repo~457 tokens
    Auto-check passed
  • A skill your agent uses when wiring inMemory() from @tanstack/ai-memory/in-memory — explains setup, options (embedder, extract, topK/minScore), when to pick it (dev/tests/single-process demos), and…

    3.2k GitHub starsUsed in 1 repo~448 tokens
    Auto-check passed
  • A skill your agent uses when wiring redis() from @tanstack/ai-memory/redis in production — covers client setup (ioredis or node-redis via fromNodeRedis), the storage model, client-side ranking…

    3.2k GitHub starsUsed in 1 repo~840 tokens
    Auto-check passed
  • A skill your agent uses when adding a public teaching example or a docs tutorial.

    3.2k GitHub stars~1.4k tokensUpdated today
    Auto-check passed

Categories

Questions about AI MCP

What does AI MCP do?

Host-side Model Context Protocol (MCP) for TanStack AI: connect to external MCP servers, host your own tools with createMCPServer, discover and run tools inside any adapter's chat() loop, read…. AI MCP is an agent skill from TanStack/ai. Host-side Model Context Protocol (MCP) for TanStack AI: connect to external MCP servers, host your own tools with createMCPServer, discover and run tools inside any adapter's chat() loop, read resources and prompts, generate TypeScript types (typed tool names/pool keys) with the bundled CLI, and manage lifecycle with close()/await using.

When should I use AI MCP?

AI MCP fits situations like: tasks that involve MCP servers; tasks that involve Type safety.

How do I install AI MCP in Claude Code?

Run `npx skills add TanStack/ai --skill ai-mcp -a claude-code`. Or copy the skill folder (packages/ai-mcp/skills/ai-mcp in TanStack/ai) into .claude/skills/ai-mcp in your project. Claude Code loads it when a task matches its description.

How do I install AI MCP in Codex?

Run `npx skills add TanStack/ai --skill ai-mcp -a codex`. Or copy the skill folder (packages/ai-mcp/skills/ai-mcp in TanStack/ai) into .agents/skills/ai-mcp in your project. Codex loads it when a task matches its description.

Can I use AI MCP in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add TanStack/ai --skill ai-mcp -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/ai-mcp, .gemini/skills/ai-mcp, .github/skills/ai-mcp and .opencode/skills/ai-mcp in your project.

What does AI MCP need to run?

Going by SKILL.md and its folder, AI MCP needs the command-line tools its instructions call (npx and pnpm) and credentials named API_KEY, INTERRUPT_PAYLOAD_METADATA_KEY and LINEAR_KEY.

Does AI MCP access the network?

SKILL.md names 3 domains. In commands or code: mcp.github.com, mcp.linear.app and slow.api; the agent is likely to contact these when it follows the instructions. This is read from the text; nothing was executed.

Is AI MCP safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does AI MCP use?

AI MCP is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does AI MCP use?

About 11k tokens (SKILL.md is roughly 46k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to AI MCP?

Skills that share tags, products or a category with AI MCP: TS Agent SDK (Microck/ordinary-claude-skills, 401 stars), holaOS App Builder SDK (holaboss-ai/holaOS, 11k stars), Azsdk Common Pipeline Fixer (Azure/azure-sdk-tools, 134 stars) and MCP Server Builder (anthropics/skills, 180k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains AI MCP?

TanStack (a GitHub organization) maintains it in TanStack/ai, which has 3,169 GitHub stars. The repository holds 24 skills in this directory. The repository was last updated on October 7, 2026.

Source: TanStack/ai on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.