---
title: "Building an MCP Server for Nuxt"
description: "How we built the Nuxt MCP server to enable AI assistants to access our documentation through structured data and composable tools."
canonical_url: "https://nuxt.com/blog/building-nuxt-mcp"
---
# Building an MCP Server for Nuxt

> How we built the Nuxt MCP server to enable AI assistants to access our documentation through structured data and composable tools.

AI assistants are becoming an increasingly important part of the developer experience. To help them provide accurate, up-to-date information about Nuxt, we built an MCP server that exposes our documentation, blog posts, and deployment guides in a structured way. Here's how we did it with the [Nuxt MCP Toolkit](https://mcp-toolkit.nuxt.dev), and how you can build your own.

::callout{icon="i-lucide-bot"}
Want to test the Nuxt MCP server? Jump to the [Nuxt MCP Server Documentation](https://nuxt.com/docs/4.x/guide/ai/mcp).
::

## What is MCP and why did we build it?

The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) is an open standard that enables AI assistants to securely access data and tools. Think of it as an API specifically designed for AI assistants: rather than returning HTML or generic JSON, it provides structured, semantic data that LLMs can easily understand and use.

MCP defines three main primitives:

- **Resources**: Allow servers to share data that provides context to language models, such as files, database schemas, or application-specific information. Each resource is uniquely identified by a URI.
- **Tools**: Enable AI models to interact with external systems and perform operations (like searching or API calls)
- **Prompts**: Reusable prompt templates with arguments that can be invoked by users

### Why MCP over RAG?

We've observed that AI assistants using MCP servers provide **significantly better responses** than traditional RAG (Retrieval-Augmented Generation) approaches:

- **Structured data in, structured data out**: Tools accept well-defined parameters and return typed data that prevents hallucinations
- **Composable tools**: AI assistants can chain tools together, using the output of one tool as input for another (e.g., search for a topic, then fetch the full content)
- **Faster and more accurate**: No need to process and chunk large documents at query time
- **Always up-to-date**: Direct access to your content layer without reindexing
- **Context-aware navigation**: The AI can intelligently navigate relationships between content

Both [Nuxt](https://nuxt.com/mcp) and [Nuxt UI](https://ui.nuxt.com/mcp) now have MCP servers with similar architectures, making it easier for AI assistants to help developers with these frameworks.

## Technical architecture

Our MCP server is built directly into [nuxt.com](http://nuxt.com) using the [Nuxt MCP Toolkit](https://mcp-toolkit.nuxt.dev) module. The module provides automatic discovery of tools, resources, and prompts from your server directory:

```
nuxt.com/
├── server/
│   └── mcp/
│       ├── tools/
│       │   ├── docs/
│       │   │   ├── list-documentation-pages.ts
│       │   │   ├── get-documentation-page.ts
│       │   │   └── ...
│       │   ├── blog/
│       │   ├── modules/
│       │   └── ...
│       ├── resources/
│       │   ├── docs/
│       │   │   └── nuxt-documentation-pages.ts
│       │   └── ...
│       └── prompts/
│           ├── docs/
│           │   └── find-documentation-for-topic.ts
│           └── ...
└── nuxt.config.ts
```

The architecture is straightforward: you define your tools, resources, and prompts as individual files, and the module automatically registers them and exposes an HTTP endpoint for MCP clients to connect. No manual server setup, no transport configuration, just create files in the right directories and they're ready to use. Subdirectories like `tools/docs/` are optional and only help you organize files: the tool name still comes from the filename.

## Implementation deep dive

### Module setup

Getting started is as simple as adding the module to your Nuxt config:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  modules: ['@nuxtjs/mcp-toolkit'],
  mcp: {
    name: 'Nuxt',
  }
})
```

That's it. The module will automatically scan your `server/mcp/` directory and register everything it finds.

### Tools: Operations for AI models

Tools enable language models to interact with external systems by accepting parameters and performing operations. Here's a simplified version of our `list_documentation_pages` tool:

```ts [server/mcp/tools/docs/list-documentation-pages.ts]
import { z } from 'zod'
import { queryCollection } from '@nuxt/content/server'

export default defineMcpTool({
  description: `Lists Nuxt documentation pages, optionally filtered by search term.

WHEN TO USE: When you need to find documentation about a topic but don't know the exact page path.
WHEN NOT TO USE: If you already know the page path, use get_documentation_page directly.`,
  inputSchema: {
    version: z.enum(['3.x', '4.x']).optional().default('4.x').describe('Documentation version to fetch'),
    search: z.string().optional().describe('Filter pages by keyword (matches title, path, and description)')
  },
  annotations: {
    readOnlyHint: true,
    openWorldHint: false
  },
  cache: '1h',
  async handler({ version, search }) {
    const event = useEvent()

    let docs = await queryCollection(event, version === '3.x' ? 'docsv3' : 'docsv4')
      .select('title', 'path', 'description')
      .all()

    if (search) {
      const terms = search.toLowerCase().split(/\s+/)
      docs = docs.filter((doc) => {
        const haystack = `${doc.title} ${doc.path} ${doc.description}`.toLowerCase()
        return terms.every(term => haystack.includes(term))
      })
    }

    return docs.map(doc => ({
      title: doc.title,
      path: doc.path,
      description: doc.description,
      url: `https://nuxt.com${doc.path}`
    }))
  }
})
```

Notice a few key things:

- **`defineMcpTool`** is auto-imported, no need to import it manually
- **`inputSchema`** uses Zod for parameter validation
- **`annotations`** tell clients the tool only reads data, so they can call it without asking for confirmation
- **`cache: '1h'`** enables built-in response caching
- **The handler returns plain data**: arrays and objects are serialized to JSON for you, and strings are sent as text. To fail, throw an error (for example with `createError()`) instead of returning one.

The [live tool](https://github.com/nuxt/nuxt.com/blob/main/server/mcp/tools/docs/list-documentation-pages.ts) does more: it also covers Nuxt 5, can search every version at once, includes CLI and example pages, and only returns descriptions when a search term is given, to keep responses small.

The tool name is automatically derived from the filename (`list-documentation-pages.ts` becomes `list_documentation_pages`).

### Resources: Context for language models

Resources allow servers to share data that provides context to language models, such as files, database schemas, or application-specific information. Each resource is uniquely identified by a URI.

The simplest way to expose a file is using the `file` property, which automatically handles URI generation, MIME type detection, and file reading:

```ts [server/mcp/resources/readme.ts]
export default defineMcpResource({
  name: 'readme',
  description: 'Project README file',
  file: 'README.md' // Relative to project root
})
```

For dynamic resources, you can use a custom handler:

```ts [server/mcp/resources/docs/nuxt-documentation-pages.ts]
import { queryCollection } from '@nuxt/content/server'

export default defineMcpResource({
  uri: 'resource://nuxt-com/documentation-pages',
  description: 'Complete list of available Nuxt documentation pages (defaults to v4.x)',
  cache: '1h',
  async handler(uri: URL) {
    const event = useEvent()

    const allDocs = await queryCollection(event, 'docsv4')
      .select('title', 'path', 'description')
      .all()

    const result = allDocs.map(doc => ({
      title: doc.title,
      path: doc.path,
      description: doc.description,
      version: '4.x',
      url: `https://nuxt.com${doc.path}`
    }))

    return {
      contents: [{
        uri: uri.href,
        mimeType: 'application/json',
        text: JSON.stringify(result, null, 2)
      }]
    }
  }
})
```

Unlike tools, which are model-controlled, resources are application-driven, the host application determines how to incorporate them based on user needs, such as through UI elements for explicit selection or automatic context inclusion.

### Prompts: Reusable templates

Prompts are reusable templates with arguments that users can invoke. They return a conversation format that guides the AI through specific workflows:

```ts [server/mcp/prompts/docs/find-documentation-for-topic.ts]
import { z } from 'zod'
import { queryCollection } from '@nuxt/content/server'

export default defineMcpPrompt({
  description: 'Find the best Nuxt documentation for a specific topic or feature',
  inputSchema: {
    topic: z.string().describe('Describe what you want to learn about'),
    version: z.enum(['3.x', '4.x']).optional().describe('Documentation version to search')
  },
  async handler({ topic, version = '4.x' }) {
    const event = useEvent()
    const docsVersion = version === '4.x' ? 'docsv4' : 'docsv3'

    const allDocs = await queryCollection(event, docsVersion)
      .select('title', 'path', 'description')
      .all()

    const allPages = allDocs?.map(doc => ({
      title: doc.title,
      path: doc.path,
      description: doc.description,
      url: `https://nuxt.com${doc.path}`
    })) || []

    return {
      messages: [{
        role: 'user' as const,
        content: {
          type: 'text' as const,
          text: `Help me find the best Nuxt documentation for this topic: "${topic}". Here are all available documentation pages: ${JSON.stringify(allPages, null, 2)}`
        }
      }]
    }
  }
})
```

Prompts differ from tools in that they are user-invoked and return conversation messages, while tools are model-controlled and return structured data.

### Built-in helpers

The module provides several auto-imported helpers to simplify common patterns:

- **`defineMcpTool`**, **`defineMcpResource`**, **`defineMcpPrompt`**: Define your MCP primitives
- **`imageResult(data, mimeType)`** and **`audioResult(data, mimeType)`**: Return base64-encoded media from tools

::note
Earlier versions of the module used `jsonResult()`, `textResult()` and `errorResult()` to shape tool responses. They still work but are deprecated: return the value directly from your handler, and throw errors instead of returning them.
::

### Using Nuxt server utilities

To use Nuxt server utilities like `useEvent()` in your handlers, enable `asyncContext` in your Nuxt config:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  experimental: {
    asyncContext: true
  }
})
```

Then you can access the H3 event and use Nuxt server composables like `queryCollection` from [Nuxt Content](https://content.nuxt.com).

## Building your own MCP server

Ready to build an MCP server for your own application? With the Nuxt MCP Toolkit, it takes just a few minutes.

### 1. Install the module

```bash [Terminal]
npx nuxt add mcp
```

Or install it manually along with Zod, which the module uses for input validation:

```bash [Terminal]
npm install -D @nuxtjs/mcp-toolkit zod
```

### 2. Configure the module

Add basic configuration to your Nuxt config:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  modules: ['@nuxtjs/mcp-toolkit'],
  mcp: {
    name: 'my-app',
    version: '1.0.0'
  }
})
```

### 3. Create your first tool

Create a file in `server/mcp/tools/`:

```ts [server/mcp/tools/search.ts]
import { z } from 'zod'

export default defineMcpTool({
  description: 'Search through my content',
  inputSchema: {
    query: z.string().describe('Search query')
  },
  async handler({ query }) {
    // Your search logic here
    return await searchContent(query)
  }
})
```

That's it! Your MCP server is now accessible at `https://your-domain.com/mcp`.

### 4. Add resources and prompts (optional)

You can also add resources and prompts following the same pattern:

```ts [server/mcp/resources/readme.ts]
export default defineMcpResource({
  name: 'readme',
  description: 'Project README file',
  file: 'README.md'
})
```

For more advanced configuration options, check out the [Nuxt MCP Toolkit documentation](https://mcp-toolkit.nuxt.dev).

## Get started with the Nuxt MCP server

Ready to experience the power of MCP with Nuxt? Our server is already live at `https://nuxt.com/mcp`. It gives AI assistants access to the Nuxt documentation (v3, v4 and v5), blog posts, the changelog, deployment providers, modules, and Nuxt contributors.

### Quick install for Cursor

The easiest way to get started is with Cursor's one-click installation:

::u-button
```yaml [props]
to: cursor://anysphere.cursor-deeplink/mcp/install?name=nuxt&config=eyJ0eXBlIjoiaHR0cCIsInVybCI6Imh0dHBzOi8vbnV4dC5jb20vbWNwIn0%3D
label: Install Nuxt MCP Server in Cursor
color: neutral
icon: i-custom-cursor
```
::

### Other AI assistants

The Nuxt MCP server works with Claude Desktop, Windsurf, Visual Studio Code, ChatGPT, and many other MCP-compatible AI assistants. For complete setup instructions for all platforms, check out our [MCP documentation](https://nuxt.com/docs/4.x/guide/ai/mcp).

### Expose only the tools you need

Every tool an assistant can see takes up room in its context. If you only need part of the server, send an `X-MCP-Tools` header with a comma-separated list of tool names, and the server will only expose those tools:

```json [.cursor/mcp.json]
{
  "mcpServers": {
    "nuxt": {
      "type": "http",
      "url": "https://nuxt.com/mcp",
      "headers": {
        "X-MCP-Tools": "list_documentation_pages,get_documentation_page"
      }
    }
  }
}
```

The server rejects unknown tool names, so a typo fails loudly instead of silently hiding a tool. The [MCP documentation](https://nuxt.com/docs/4.x/guide/ai/mcp) lists every available tool.

This header is a feature of the Nuxt MCP Toolkit, so any server built with the module supports it too.

We encourage you to build MCP servers for your own applications. Whether it's documentation, API references, or domain-specific knowledge, MCP makes it easy for AI assistants to provide accurate, helpful information to your users.

The complete source code for our MCP server is available on [GitHub](https://github.com/nuxt/nuxt.com) in the [`server/mcp/`](https://github.com/nuxt/nuxt.com/tree/main/server/mcp) directory. Feel free to use it as inspiration for your own implementation!


## Sitemap

See the full [sitemap](https://nuxt.com/sitemap.md) for all pages.
