# Introducing the Nuxt Agent

> Our own AI agent on nuxt.com, grounded in the official docs and the Nuxt ecosystem. We built it internally using the AI SDK, our MCP server, and Nuxt UI components.

<important icon="i-lucide-heart">

First, we want to thank [Kapa.ai](https://kapa.ai), who have powered our AI Chat widget over the past few years through their sponsorship and continue to support our community through our [Discord server](https://go.nuxt.com/discord). <br />

 We decided to build our own to showcase what’s possible with Nuxt and offer a deeper integration with the framework and its ecosystem.

</important>

The **Nuxt Agent** is now in Beta on nuxt.com. We built it ourselves, plugged it into the site, and connected it to our docs, modules catalog, blog, and deployment guides.

<callout icon="i-lucide-sparkles">

Open the agent anywhere on nuxt.com with **⌘I** (or **Ctrl+I**), or jump straight into the full-screen experience at [/chat](/chat).

</callout>

<video :controls="true" className="rounded-lg" poster="https://res.cloudinary.com/nuxt/video/upload/so_0/v1776779145/nuxt/nuxt-agent-built-in-chat_lpaii9.jpg">
<source src="https://res.cloudinary.com/nuxt/video/upload/v1776779145/nuxt/nuxt-agent-built-in-chat_lpaii9.mp4" type="video/mp4" />
</video>

## From a docs widget to a real agent

Kapa AI served us well as a docs Q&A widget for the past couple of years. It searched the docs and summarized an answer, and that was about it. But Nuxt is more than docs. There are modules, templates, deployment providers, a changelog, GitHub issues, playgrounds, and real navigation across the site.

We wanted an agent that could handle all of it, with the same design language as the rest of nuxt.com and the same content pipeline (Nuxt Content) we already run. So we built our own on top of the [Nuxt MCP server](/blog/building-nuxt-mcp) we shipped last November.

Here's what the agent does today:

- **Grounds answers** in the official Nuxt docs and ecosystem data through MCP tools, not retrieved text chunks.
- **Renders rich UI**. Modules, templates, blog posts, hosting providers, and playground links come back as clickable cards instead of plain links.
- **Streams everything** as it runs, including tool call progress.
- **Closes the loop**. Feedback, voting, and issue reports flow into our internal tools so we can keep improving it.

## Meet the agent

### Three ways to talk to it

You can reach the agent three ways:

- A **side panel** pinned to the right on large screens, sliding over on smaller ones. Toggle it from the header or with **⌘I**.
- An **ask bar** at the bottom of `/docs` and `/blog` pages, so you can ask a question without leaving the page you're reading.
- A **full-screen chat** at [/chat](/chat) for longer sessions.

<video :controls="true" className="rounded-lg" poster="https://res.cloudinary.com/nuxt/video/upload/so_0/v1776779145/nuxt/nuxt-agent-fullscreen-chat_gxxzmh.jpg">
<source src="https://res.cloudinary.com/nuxt/video/upload/v1776779145/nuxt/nuxt-agent-fullscreen-chat_gxxzmh.mp4" type="video/mp4" />
</video>

### It knows what page you're on

Ask "how do I customize this for my app?" while reading a doc and the agent automatically pulls that page in as context. A small "Agent is using this page" indicator in the footer makes it explicit, and you can dismiss it whenever you want.

### Rich answers, not just text

Answers come back as more than text. Ask about a module and you get a module card with metadata pulled live from `api.nuxt.com`. Ask for starter templates and you get a row of clickable template cards. Ask about deployment and you get provider cards that link to the right guide. Need to reproduce a bug? The agent can generate a [StackBlitz](https://stackblitz.com) playground link from the conversation itself.

<callout icon="i-lucide-lightbulb">

Try asking: *"Show me official starter templates"* — you'll get the full lineup (`nuxt-ui-dashboard`, `nuxt-ui-saas`, `nuxt-ui-landing`, `nuxt-ui-chat`, `nuxt-ui-docs`, `nuxt-ui-portfolio`) as cards you can open in one click.

</callout>

### Feedback built in

Every assistant message has a thumbs up/down. If you want to share more (a missing piece, a wrong answer, an idea), the **Report issue** action opens a short form and creates a Linear ticket on our side with the conversation attached, so we have everything we need to follow up.

The agent can open that form itself, too. If you ask to "submit feedback" or "report an issue", or if the conversation starts to feel frustrated, the agent calls the `report_issue` tool and the same form opens inline. No button hunting required.

Conversations are saved and resume across reloads, so you can step away and pick up where you left off.

## What the agent can actually do

The agent's grounding comes from the [Nuxt MCP server](https://nuxt.com/mcp), the same one Cursor, Claude Desktop and ChatGPT connect to. That means the Nuxt Agent and your local AI assistant share the same structured data: the official docs, the modules catalog, the blog, deployment guides, and the Nuxt repositories' changelog.

On top of that grounding, the agent has a small set of native tools that render as UI in the chat: module and template cards, hosting providers, blog posts, StackBlitz playground links, and a GitHub issue search across `nuxt`, `nuxt-modules` and `nuxt-content`. The agent reaches for that issue search first whenever you paste an error.

The web is available too, through Anthropic's native `web_search`, but only as a fallback for things the model couldn't reasonably know: a brand new Vue release, a freshly published RFC, recent ecosystem news. It's not a general-purpose search engine, and the system prompt is explicit about that. We never use `web_search` for anything that should be answered from the docs or the rest of the Nuxt content exposed through MCP.

## Under the hood

### The stack

A single Nitro handler at [`server/api/agent.post.ts`](https://github.com/nuxt/nuxt.com/blob/main/server/api/agent.post.ts) drives everything. On the client, an `@ai-sdk/vue` `Chat` instance points at `/api/agent`. On the server, AI SDK v6 `streamText` calls `claude-sonnet-4.6`, with tools merged from our own MCP server (`/mcp`, same origin) and a few native ones (`show_*`, `open_playground`, `report_issue`). Chat state lives in Drizzle ORM, and [`evlog`](https://evlog.dev) wraps the model for structured telemetry on tokens, cost, and tool calls.

### UIMessage streaming with AI SDK v6

The whole pipeline runs on the [AI SDK v6](https://ai-sdk.dev) `UIMessage` streaming model. The server side looks like this:

<code-collapse>

```ts [server/api/agent.post.ts]
const stream = createUIMessageStream({
  execute: async ({ writer }) => {
    const result = streamText({
      model: ai.wrap(MODEL),
      maxOutputTokens: 4000,
      stopWhen: stopWhenResponseComplete,
      system: systemPrompt,
      messages: await convertToModelMessages(messages),
      tools: {
        ...mcpTools as ToolSet,
        web_search: anthropic.tools.webSearch_20250305(),
        search_github_issues: createSearchGitHubIssuesTool(event),
        show_module: showModuleTool,
        show_template: createShowTemplateTool(event),
        show_blog_post: createShowBlogPostTool(event),
        show_hosting: createShowHostingTool(event),
        open_playground: openPlaygroundTool,
        report_issue: reportIssueTool
      },
      experimental_telemetry: {
        isEnabled: true,
        integrations: [createEvlogIntegration(ai)]
      }
    })

    writer.merge(result.toUIMessageStream({
      sendSources: true,
      originalMessages: messages,
      onFinish: ({ messages: finalizedMessages }) => {
        event.waitUntil(saveChat(finalizedMessages))
      }
    }))
  }
})
```

</code-collapse>

Two details are worth pointing out. `stopWhen: stopWhenResponseComplete` is a custom predicate that ends the loop as soon as the model produces text without another tool call, with a hard ceiling at 10 steps. That avoids the classic "model loops forever on tools" failure mode. And `event.waitUntil(saveChat(...))` pushes persistence outside the response lifecycle, so the stream finishes for the user right away while the chat row gets upserted in the background.

### One MCP server, two consumers

The agent and external AI assistants talk to the same MCP server. That's the single most important architectural choice we made. The route handler opens an HTTP MCP client pointed at its own `/mcp` endpoint:

```ts [server/api/agent.post.ts]
const httpClient = await createMCPClient({
  transport: { type: 'http', url: `${getRequestURL(event).origin}${MCP_PATH}` }
})
const mcpTools = await httpClient.tools()
```

Those tools then get merged with the native UI tools into a single `tools` object passed to `streamText`. The payoff: any tool we add to the MCP server is immediately available to the Nuxt Agent and to every external assistant pointed at it, with no extra wiring. We wrote a [separate post](/blog/building-nuxt-mcp) about how the MCP server itself works back in November.

### Persistence, cost, and rate limiting

Chats live in a single `agent_chats` table, keyed by the `x-chat-id` header the client sends on every request. Drizzle's `onConflictDoUpdate` accumulates token usage, estimated cost, duration, and request count across the lifetime of a conversation. That gives us per-chat analytics for free.

Every request also goes through a small `consumeAgentRateLimit` helper before streaming starts. The current cap is 20 messages per day per IP fingerprint, which is enough for real use and low enough to prevent runaway costs if something starts looping.

### A tight system prompt

A lot of agent quality comes from the prompt. A few rules carried most of the weight: reach for `search_github_issues` first whenever the user pastes an error, prefer `show_module` over `get_module` when the answer should render as a card, never call `web_search` unless the question is genuinely outside what the model could know, and never end a turn with only a tool call. Together those rules cut tool-spam and hallucinations enough that the agent stays focused on the task at hand.

## What's next

The agent is launching in Beta. In the short term, we're focused on the basics: better answer quality, richer memory across turns, and cleaner source citations.

Further out, we want nuxt.com to feel more like an application than a static site. The next step there is user accounts. Each logged-in user gets their own session, their chat history saved and resumable across devices, and the Nuxt Agent becomes the first real building block of that more app-like nuxt.com.

We'd love your help shaping where it goes next. If the agent gets something wrong, or misses something you want, use the **Report issue** button inside the chat. It creates a ticket on our side with the full conversation attached, and we read every one.

<callout icon="i-lucide-message-square">

Try the Nuxt Agent now: open it with **⌘I**, use the ask bar on any [docs](/docs) page, or head to [/chat](/chat) for the full-screen experience.

</callout>

The full source for nuxt.com is on [GitHub](https://github.com/nuxt/nuxt.com), including the agent, the MCP server, and every tool covered above. The agent handler is at [`server/api/agent.post.ts`](https://github.com/nuxt/nuxt.com/blob/main/server/api/agent.post.ts), the native tools at [`server/utils/tools/`](https://github.com/nuxt/nuxt.com/tree/main/server/utils/tools), and the UI components at [`app/components/agent/`](https://github.com/nuxt/nuxt.com/tree/main/app/components/agent). Take any of it as inspiration for your own apps. And if you want to build your own MCP server, the [Nuxt MCP Toolkit](https://mcp-toolkit.nuxt.dev) gets you there in a few minutes.
