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, and how you can build your own.
What is MCP and why did we build it?
The Model Context Protocol (MCP) 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: Data that provides context to language models, uniquely identified by URIs (like documentation pages or blog posts)
- 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 and Nuxt UI 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 as a server route, leveraging Nuxt's full-stack capabilities:
nuxt.com/
├── server/
│ ├── routes/
│ │ └── mcp.ts # Main MCP server
│ └── api/
│ └── mcp/
│ ├── list-documentation-pages.get.ts
│ ├── get-documentation-page.get.ts
│ ├── search-content.get.ts
│ └── ... (other endpoints)
└── content/
├── blog/
├── deploy/
└── ... (Nuxt Content files)
The architecture is straightforward. The MCP Server Route (server/routes/mcp.ts
) handles the MCP protocol, the API Endpoints (server/api/mcp/*.ts
) query Nuxt Content, and the HTTP Transport enables real-time communication with AI assistants.
Implementation deep dive
Server setup
The foundation is the @modelcontextprotocol/sdk
package. We create an MCP server instance and configure it:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'
function createServer() {
const server = new McpServer({
name: 'nuxt-com',
version: '1.0.0'
})
// Register resources, tools, and prompts here...
return server
}
Resources: Context for language models
Resources provide data and context that language models can use. Each resource is identified by a URI. Here's how we expose all documentation pages:
server.registerResource(
'nuxt-documentation-pages',
'resource://nuxt-com/documentation-pages',
{
title: 'Nuxt Documentation Pages',
description: 'Complete list of available Nuxt documentation pages'
},
async (uri) => {
const result = await $fetch('/api/mcp/list-documentation-pages', {
query: { version: '4.x' }
})
return {
contents: [{
uri: uri.href,
mimeType: 'application/json',
text: JSON.stringify(result, null, 2)
}]
}
}
)
The API endpoint uses Nuxt Content's queryCollection
to fetch data efficiently:
import { queryCollection } from '@nuxt/content/server'
import { z } from 'zod'
const querySchema = z.object({
version: z.enum(['3.x', '4.x', 'all']).optional().default('4.x')
})
export default defineCachedEventHandler(async (event) => {
const { version } = await getValidatedQuery(event, querySchema.parse)
const allDocs = await queryCollection(event, 'docsv4')
.select('title', 'path', 'description')
.all()
return allDocs.map(doc => ({
title: doc.title,
path: doc.path,
description: doc.description,
version: doc.path.includes('/docs/4.x') ? '4.x' : '3.x',
url: `https://nuxt.com${doc.path}`
}))
}, {
maxAge: 60 * 60, // Cache for 1 hour
getKey: (event) => `mcp-documentation-pages-${getQuery(event).version || '4.x'}`
})
Notice the caching strategy, this ensures fast responses while keeping content fresh.
Tools: Operations for AI models
Tools enable language models to interact with external systems by accepting parameters and performing operations. Our search_content
tool demonstrates parameter validation with Zod:
server.tool(
'search_content',
'Searches across all Nuxt content including documentation, blog posts, and deployment guides',
{
query: z.string().describe('Search query'),
type: z.enum(['docs', 'blog', 'deploy', 'all']).optional(),
version: z.enum(['3.x', '4.x', 'all']).optional()
},
async (params) => {
const result = await $fetch('/api/mcp/search-content', { query: params })
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }
}
)
The search implementation uses Nuxt Content's query capabilities:
export default defineEventHandler(async (event) => {
const { query: searchQuery, type, version } = await getValidatedQuery(event, querySchema.parse)
const results = []
if (type === 'docs' || type === 'all') {
const docs = await queryCollection(event, 'docsv4')
.where('title', 'LIKE', `%${searchQuery}%`)
.select('title', 'path', 'description')
.limit(20)
.all()
results.push(...docs.map(doc => ({
type: 'documentation',
title: doc.title,
path: doc.path,
description: doc.description,
url: `https://nuxt.com${doc.path}`
})))
}
// Similar logic for blog and deploy content...
return results
})
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:
server.registerPrompt(
'find_documentation_for_topic',
{
title: 'Find Documentation for Topic',
description: 'Find the best Nuxt documentation for a specific topic',
argsSchema: {
topic: z.string().describe('What you want to learn about')
}
},
async ({ topic }) => {
const searchResults = await $fetch('/api/mcp/search-content', {
query: { query: topic, type: 'docs' }
})
return {
messages: [{
role: 'user',
content: {
type: 'text',
text: `Help me find documentation for: "${topic}". Results: ${JSON.stringify(searchResults, 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.
HTTP transport layer
The final piece is handling the HTTP communication using Streamable HTTP transport:
export default defineEventHandler(async (event) => {
// Redirect browsers to documentation
if (getHeader(event, 'accept')?.includes('text/html')) {
return sendRedirect(event, '/docs/guide/ai/mcp')
}
const server = createServer()
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined
})
// Clean up on connection close
event.node.res.on('close', () => {
transport.close()
server.close()
})
await server.connect(transport)
const body = await readBody(event)
await transport.handleRequest(event.node.req, event.node.res, body)
})
This handler does three important things:
- Redirects browser traffic to our documentation
- Manages server and transport lifecycle
- Processes MCP protocol requests using HTTP POST/GET
The StreamableHTTPServerTransport
can optionally use Server-Sent Events (SSE) for streaming responses when multiple messages need to be sent back to the client, but it also supports simple JSON responses for basic request-response patterns.
Building your own MCP server
Ready to build an MCP server for your own application? Here's a simplified guide:
1. Install dependencies
npm install @modelcontextprotocol/sdk zod
2. Create a server route
Create server/routes/mcp.ts
in your Nuxt project:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'
function createServer() {
const server = new McpServer({
name: 'my-app',
version: '1.0.0'
})
// Add your resources, tools, and prompts
return server
}
export default defineEventHandler(async (event) => {
const server = createServer()
const transport = new StreamableHTTPServerTransport()
await server.connect(transport)
const body = await readBody(event)
await transport.handleRequest(event.node.req, event.node.res, body)
})
3. Register your first resource
server.registerResource(
'my-content',
'resource://my-app/content',
{
title: 'My Content',
description: 'All my app content'
},
async (uri) => {
const data = await $fetch('/api/my-content')
return {
contents: [{
uri: uri.href,
mimeType: 'application/json',
text: JSON.stringify(data, null, 2)
}]
}
}
)
4. Add a search tool
server.tool(
'search',
'Search through my content',
{
query: z.string().describe('Search query')
},
async ({ query }) => {
const results = await $fetch('/api/search', { query: { q: query } })
return { content: [{ type: 'text', text: JSON.stringify(results) }] }
}
)
5. Create supporting API endpoints
Use Nuxt Content, a database, or any data source to power your API endpoints:
import { queryCollection } from '@nuxt/content/server'
export default defineEventHandler(async (event) => {
const content = await queryCollection(event, 'my-collection').all()
return content
})
That's it! Your MCP server is now accessible at https://your-domain.com/mcp
.
Get started with the Nuxt MCP server
Ready to experience the power of MCP with Nuxt? Our server is already live and provides access to all Nuxt documentation, blog posts, and deployment guides.
Quick install for Cursor
The easiest way to get started is with Cursor's one-click installation:
Install Nuxt MCP Server in CursorOther 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.
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 in the server/routes/mcp.ts
file and server/api/mcp/
directory. Feel free to use it as inspiration for your own implementation!