Vercel AI SDK 6: Building Production Agents with ToolLoopAgent and MCP

You built the agent loop yourself—again

You copy-pasted the same while loop for the third time this month. Call the model, parse tool calls, run execute(), push results back into messages, repeat until something breaks at step nine. Vercel AI SDK 6 shipped in May 2026 with a fix that feels almost unfair: ToolLoopAgent handles that entire cycle as a first-class TypeScript abstraction, and MCP tool discovery is stable enough to treat like any other npm dependency.

If you have been wiring agents in Next.js route handlers with generateText and manual state, you are not behind. Most teams were. The shift in Vercel AI SDK 6 is not another chat wrapper—it is production agent infrastructure with human approval gates, local DevTools, and HTTP-based MCP clients you can actually deploy.

By the end of this walkthrough, you will know how to define a reusable ToolLoopAgent, attach MCP servers without custom glue code, debug multi-step runs locally, and migrate from SDK 5 without rewriting your React UI from scratch.

What Vercel AI SDK 6 actually shipped

Vercel released AI SDK 6 as a major version built on the v3 Language Model Specification. That spec unlocks agents, structured outputs combined with tool calling, and tighter provider consistency across OpenAI, Anthropic, and Google models.

The headline features for backend and full-stack TypeScript teams are ToolLoopAgent, stable MCP in @ai-sdk/mcp, DevTools via @ai-sdk/devtools, and needsApproval for human-in-the-loop tool execution. Streaming latency also improved roughly 15–25% from wire format changes—nice on dashboards, essential when agents fan out across ten tool calls.

Clay and Thomson Reuters were already running serious workloads on earlier SDK versions. SDK 6 formalizes patterns those teams hacked together: reusable agent objects, typed UI streaming, and MCP servers as ordinary tool sources.

Why ToolLoopAgent replaces hand-rolled loops

Before SDK 6, generateText and streamText gave you full control—but every route handler reimplemented the same loop. Pass tools, wait for tool-call parts, execute, append tool results, call again. Miss one edge case and your agent either loops forever or silently stops early.

ToolLoopAgent wraps that loop with sensible defaults: stopWhen stepCountIs(20), shared configuration for model and instructions, and the same generate() and stream() entry points you already know. Define the agent once in agents/support-agent.ts, then call it from API routes, background jobs, and tests.

Agent vs inline generateText

Inline calls still make sense for one-shot completions. Agents win when the same configuration crosses layers—your Express or Next.js API, a BullMQ worker, and a Vitest suite should not each maintain duplicate tool maps.

ApproachBest forTrade-off
generateText / streamTextSingle-step prompts, tight custom controlYou own the full tool loop
ToolLoopAgentMulti-step agents reused across app layersOpinionated stop conditions
Custom Agent interfaceDurable workflows, exotic orchestrationMore code to maintain

The Agent type in SDK 6 is an interface, not a sealed class. ToolLoopAgent is the default production implementation, but you can swap in something like DurableAgent from Workflow DevKit when every tool step must be retryable infrastructure.

Building your first ToolLoopAgent in TypeScript

Start with a narrow agent scope. Weather lookups, internal admin queries, or ticket triage—pick one domain so your tool schemas stay small and testable.

// agents/weather-agent.ts
import { ToolLoopAgent, stepCountIs } from 'ai';
import { weatherTool } from '@/tools/weather-tool';

export const weatherAgent = new ToolLoopAgent({
  model: 'anthropic/claude-sonnet-4.5',
  instructions: 'You are a concise weather assistant. Ask for a city if missing.',
  tools: { weather: weatherTool },
  stopWhen: stepCountIs(10),
});

export async function runWeatherQuery(prompt: string) {
  const result = await weatherAgent.generate({ prompt });
  return result.text;
}

That is the entire loop. No manual message array juggling. The agent calls the model, executes weatherTool when requested, feeds structured results back, and stops when the model answers or hits your step limit.

Call options for per-request context

Production agents rarely run with static instructions. Account tier, user ID, and retrieved RAG chunks change every request. SDK 6 adds callOptionsSchema and prepareCall so you inject context type-safely at generate() time.

import { ToolLoopAgent } from 'ai';
import { z } from 'zod';

export const supportAgent = new ToolLoopAgent({
  model: 'anthropic/claude-sonnet-4.5',
  callOptionsSchema: z.object({
    userId: z.string(),
    plan: z.enum(['free', 'pro', 'enterprise']),
  }),
  prepareCall: ({ options, ...settings }) => ({
    ...settings,
    instructions: `Support agent for user ${options.userId} on ${options.plan} plan.`,
  }),
  tools: { /* billing, docs, ticket tools */ },
});

await supportAgent.generate({
  prompt: 'Why was I charged twice?',
  options: { userId: 'user_42', plan: 'pro' },
});

Keep prepareCall pure. Heavy RAG retrieval belongs in your route handler; pass retrieved doc IDs through options instead of hiding database calls inside agent configuration.

Streaming to React with type safety

Define once, use everywhere applies to the UI layer too. InferAgentUIMessage exports a message type from your agent file, then useChat on the client renders typed tool parts without stringly-typed switches.

// app/api/chat/route.ts
import { createAgentUIStreamResponse } from 'ai';
import { weatherAgent } from '@/agents/weather-agent';

export async function POST(req: Request) {
  const { messages } = await req.json();
  return createAgentUIStreamResponse({
    agent: weatherAgent,
    uiMessages: messages,
  });
}

Your React component imports WeatherAgentUIMessage and renders tool-weather parts with full TypeScript coverage. That end-to-end typing is the difference between demo-grade chat UIs and agents your frontend team will actually maintain.

MCP integration with @ai-sdk/mcp

Model Context Protocol went from experimental SDK 5 glue to stable infrastructure in SDK 6. The @ai-sdk/mcp package connects HTTP, SSE, or local stdio transports and adapts remote MCP tools into AI SDK tool objects your agent already understands.

This matters because MCP is becoming the USB-C of AI tools. GitHub, Stripe, Postgres wrappers, and internal microservices expose capabilities through the same protocol. Your TypeScript agent should consume them without bespoke REST adapters per vendor.

Connecting an HTTP MCP server

Use HTTP transport in production. SSE works for some hosts; stdio is for local dev only—it cannot ship to Vercel or Kubernetes the way HTTP clients can.

import { createMCPClient } from '@ai-sdk/mcp';
import { ToolLoopAgent } from 'ai';

const mcpClient = await createMCPClient({
  transport: {
    type: 'http',
    url: 'https://mcp.yourcompany.com/mcp',
    headers: { Authorization: `Bearer ${process.env.MCP_TOKEN}` },
  },
});

const mcpTools = await mcpClient.tools({
  schemas: {
    'search-docs': {
      inputSchema: z.object({ query: z.string() }),
    },
  },
});

const researchAgent = new ToolLoopAgent({
  model: 'anthropic/claude-sonnet-4.5',
  instructions: 'Use search-docs for factual answers. Cite sources.',
  tools: mcpTools,
});

Schema discovery via mcpClient.tools() without schemas is faster to prototype. Explicit schemas give you IDE autocomplete and let you load only the tools you need—a security win when a server exposes dozens of capabilities.

OAuth, resources, and elicitation

Remote MCP servers often need OAuth. SDK 6 ships auth helpers and OAuthClientProvider so you are not hand-rolling PKCE and token refresh. Wire redirect URLs once, store tokens in your session layer, and reuse the authenticated client across agent runs.

Resources expose read-only context—files, database rows, API payloads—your app fetches and injects. Prompts are reusable server-side templates. Elicitation lets the server ask your app for mid-flight user input, which pairs naturally with needsApproval for sensitive writes.

  • HTTP transport for production MCP connections
  • OAuth via authProvider on the MCP client transport
  • resources and experimental_listPrompts for context templates
  • onElicitationRequest when the server needs user input mid-tool

Always close MCP clients when runs finish. For streamText, use onFinish to call mcpClient.close(). Leaked connections show up as ghost sessions on hosted MCP gateways—not fun at 2 a.m.

Human-in-the-loop tool approval

Agents that delete rows, charge cards, or run shell commands cannot fire tools blindly. SDK 5 teams built custom pause/resume state machines. SDK 6 adds needsApproval on individual tools—one flag, no bespoke orchestration.

import { tool } from 'ai';
import { z } from 'zod';

export const runCommand = tool({
  description: 'Execute a shell command on the build runner',
  inputSchema: z.object({ command: z.string() }),
  needsApproval: async ({ command }) => command.includes('rm') || command.includes('sudo'),
  execute: async ({ command }) => runOnRunner(command),
});

Static true requires approval for every invocation. A function lets you auto-approve harmless ls calls while blocking destructive patterns. Store user preferences in your database if repeat approvals annoy power users.

On the React side, check invocation.state === 'approval-requested' and call addToolApprovalResponse with the approval id. Denied tools return structured errors the model can reason about instead of hanging the loop.

Combine approval with MCP carefully. A remote MCP tool might trigger side effects you cannot see locally. Treat third-party MCP tools like external APIs: read-only by default, explicit approval for mutations, and audit logs on every approved call.

Debugging agent runs with AI SDK DevTools

Multi-step agents fail in nonlinear ways. Step three gets a slightly different tool result, step four chooses a different tool, and by step eight you are debugging a trajectory that never happened in staging. Console.log per step does not scale.

AI SDK DevTools wraps your language model with devToolsMiddleware and records every call: prompts, tool inputs, token usage, timing, and raw provider payloads. Launch the viewer with npx @ai-sdk/devtools and open localhost:4983 while reproducing the bug locally.

import { wrapLanguageModel, gateway } from 'ai';
import { devToolsMiddleware } from '@ai-sdk/devtools';

const devModel = wrapLanguageModel({
  model: gateway('anthropic/claude-sonnet-4.5'),
  middleware: devToolsMiddleware(),
});

// Pass devModel to ToolLoopAgent during local debugging
const debugAgent = new ToolLoopAgent({
  model: devModel,
  instructions: weatherAgent.instructions,
  tools: weatherAgent.tools,
});

DevTools is a development dependency. Strip or gate the middleware in production builds—the overhead and data sensitivity are not worth shipping to end users. Keep it on developer machines and CI replay jobs where you reproduce customer traces.

When a user report says the agent did the wrong thing, compare step timelines in DevTools against your approval logs. You will often find the model never saw the document you thought RAG injected—fix the pipeline, not the prompt first.

Migrating from AI SDK 5 to 6

Despite the major version bump, Vercel designed SDK 6 as an incremental upgrade for most apps. The v3 Language Model Specification enables new features; it does not throw away your generateText knowledge.

Run the automated codemod first:

npx @ai-sdk/codemod upgrade v6

Then upgrade packages: ai, @ai-sdk/react, provider packages, and add @ai-sdk/mcp if you use MCP. The migration guide covers manual fixes the codemod cannot infer—usually custom middleware and deprecated experimental imports.

SDK 5 patterns and their SDK 6 equivalents

SDK 5 habitSDK 6 direction
Manual tool loop in route handlerExtract ToolLoopAgent module
Experimental MCP SSE clientStable createMCPClient with HTTP transport
generateText then generateObject chainToolLoopAgent with Output.object schema
console.log debuggingdevToolsMiddleware + local viewer
Custom approval UI state machineneedsApproval + addToolApprovalResponse

Migrate one route at a time. Keep a parallel SDK 5 endpoint behind a feature flag until DevTools confirms identical tool trajectories on recorded fixtures. Jumping every agent at once makes regressions hard to bisect.

Strict tool schemas changed semantics slightly—strict mode is now opt-in per tool instead of all-or-nothing. Audit tools using exotic JSON Schema features; mark compatible ones with strict: true and leave the rest in regular mode within the same agent.

Production patterns for Next.js and Node

ToolLoopAgent belongs in a dedicated module layer, not inlined in app/api/route.ts. Co-locate Zod schemas with tool execute functions, export the agent, and keep route handlers thin—parse auth, call agent.stream or createAgentUIStreamResponse, return.

  • Cap steps aggressively in user-facing chat; use higher limits only in batch workers.
  • Pass correlation IDs through call options for OpenTelemetry spans per tool.
  • Rate-limit MCP servers separately from LLM providers—they have their own quotas.
  • Cache MCP tool lists at startup for long-lived Node workers; refresh on interval in serverless only if needed.

For serverless Next.js, prefer short MCP sessions: create client, fetch tools, run agent, close in finally. Long-lived MCP over stdio does not survive cold starts. HTTP MCP with bearer tokens fits Vercel's execution model cleanly.

Structured output plus tools landed in the same release—use Output.object on ToolLoopAgent when you need JSON for downstream React components or webhooks after the agent finishes reasoning. One loop, one typed result object, no second model call to format JSON.

Summary

Vercel AI SDK 6, released in May 2026, turns TypeScript agents from hand-rolled loops into maintainable infrastructure. ToolLoopAgent owns the call-execute-repeat cycle with configurable stop conditions and reusable configuration across API routes, workers, and tests.

Stable @ai-sdk/mcp support connects your agents to the broader MCP ecosystem over HTTP with OAuth, resources, and elicitation. needsApproval adds human-in-the-loop safety without custom pause state, and DevTools finally gives you step-by-step visibility into multi-call agent runs during local development.

Migrate with npx @ai-sdk/codemod upgrade v6, extract one agent module as a pilot, and prove trajectories in DevTools before flipping production traffic. That sequence gets you from SDK 5 chat endpoints to production agents faster than rewriting another while loop.

FAQ

When did Vercel AI SDK 6 release?

Vercel shipped AI SDK 6 in May 2026 alongside stable MCP support, ToolLoopAgent, DevTools, and human-in-the-loop tool approval. It is available on npm as the ai package and updated @ai-sdk provider modules.

What is ToolLoopAgent?

ToolLoopAgent is the default production Agent implementation in SDK 6. It repeatedly calls the model, executes requested tools, injects results into the conversation, and stops when the model finishes or hits your stopWhen condition—stepCountIs(20) by default.

How do I connect MCP tools in SDK 6?

Install @ai-sdk/mcp, create an MCP client with createMCPClient and HTTP transport, call mcpClient.tools(), and pass the returned tools object into ToolLoopAgent. Close the client when the agent run completes.

Does SDK 6 require a full rewrite from SDK 5?

No for most apps. Run npx @ai-sdk/codemod upgrade v6 and follow the migration guide for edge cases. Incrementally replace manual tool loops with ToolLoopAgent modules rather than big-bang refactors.

How does tool approval work with React?

Mark tools with needsApproval, detect approval-requested state in your UI component, and call addToolApprovalResponse from useChat with approved true or false. The agent loop resumes automatically after the user decides.

Can I use DevTools in production?

DevTools targets local debugging via devToolsMiddleware and the localhost viewer. Do not wrap production models with DevTools middleware—use OpenTelemetry and structured logging in deployed environments instead.

Is MCP stable in AI SDK 6?

Yes. MCP moved from experimental SDK 5 status to stable @ai-sdk/mcp with HTTP transport, OAuth helpers, resources, prompts, and elicitation support suitable for production Node and Next.js deployments.