Manage MCP
Complete skill for managing Model Context Protocol (MCP) servers in Nuxt applications. Setup, create, customize with middleware and handlers, review, and troubleshoot.
When to Use
- Setup: "Setup an MCP server in my Nuxt app"
- Create: "Create a tool to calculate BMI" / "Add a resource to read the README"
- Customize: "Add authentication to my MCP server" / "Create middleware for rate limiting"
- Review: "Review my MCP implementation" / "Check for best practices"
- Troubleshoot: "My auto-imports aren't working" / "Cannot connect to endpoint"
- Test: "Create tests for my MCP tools"
Setup MCP Server
Installation
Automatic (recommended):
npx nuxt module add mcp-toolkit
Manual:
pnpm add -D @nuxtjs/mcp-toolkit zod
Add to nuxt.config.ts:
export default defineNuxtConfig({
modules: ['@nuxtjs/mcp-toolkit'],
mcp: {
name: 'My MCP Server',
},
})
Directory Structure
server/mcp/
├── tools/ # Actions AI can perform
├── resources/ # Data AI can read
└── prompts/ # Message templates
Verification
- Start:
pnpm dev - Check:
http://localhost:3000/mcp(should redirect) - Open DevTools (Shift+Alt+D) → MCP tab
Create Tools
Tools are functions AI assistants can call.
Basic Structure
import { z } from 'zod'
export default defineMcpTool({
description: 'What the tool does',
inputSchema: {
param: z.string().describe('Parameter description'),
},
handler: async ({ param }) => {
return {
content: [{
type: 'text',
text: 'Result',
}],
}
},
})
Input Patterns
// Required
name: z.string().describe('User name')
// Optional with default
limit: z.number().default(10).describe('Max results')
// Enum
format: z.enum(['json', 'xml']).describe('Format')
// Array
tags: z.array(z.string()).describe('Tags')
Error Handling
if (!param) {
return {
content: [{ type: 'text', text: 'Error: param required' }],
isError: true,
}
}
Annotations
Behavioral hints that help MCP clients decide when to prompt for confirmation:
export default defineMcpTool({
annotations: {
readOnlyHint: true, // Only reads data, no side effects
destructiveHint: false, // Does not delete or destroy data
idempotentHint: false, // Multiple calls may have different effects
openWorldHint: false, // No external API calls
},
// ...
})
Common patterns: read-only tools → readOnlyHint: true, create → idempotentHint: false, update → idempotentHint: true, delete → destructiveHint: true, idempotentHint: true.
Input Examples
Type-safe usage examples that help AI models fill in parameters correctly:
export default defineMcpTool({
inputSchema: {
title: z.string().describe('Todo title'),
content: z.string().optional().describe('Description'),
},
inputExamples: [
{ title: 'Buy groceries', content: 'Milk, eggs, bread' },
{ title: 'Fix login bug' },
],
// ...
})
Caching
export default defineMcpTool({
cache: '5m', // 5 minutes
// ...
})
Create Resources
Resources expose read-only data.
File Resource
import { readFile } from 'node:fs/promises'
export default defineMcpResource({
description: 'Read a file',
uri: 'file:///README.md',
mimeType: 'text/markdown',
handler: async (uri: URL) => {
const content = await readFile('README.md', 'utf-8')
return {
contents: [{
uri: uri.toString(),
text: content,
mimeType: 'text/markdown',
}],
}
},
})
API Resource
export default defineMcpResource({
description: 'Fetch API data',
uri: 'api:///users',
mimeType: 'application/json',
cache: '5m',
handler: async (uri: URL) => {
const data = await $fetch('https://api.example.com/users')
return {
contents: [{
uri: uri.toString(),
text: JSON.stringify(data, null, 2),
mimeType: 'application/json',
}],
}
},
})
Dynamic Resource
import { z } from 'zod'
export default defineMcpResource({
description: 'Fetch by ID',
uriTemplate: {
uriTemplate: 'user:///{id}',
arguments: {
id: z.string().describe('User ID'),
},
},
handler: async (uri: URL, args) => {
const user = await fetchUser(args.id)
return {
contents: [{
uri: uri.toString(),
text: JSON.stringify(user),
mimeType: 'application/json',
}],
}
},
})
Create Prompts
Prompts are reusable message templates.
Static Prompt
export default defineMcpPrompt({
description: 'Code review',
handler: async () => {
return {
messages: [{
role: 'user',
content: {
type: 'text',
text: 'Review this code for best practices.',
},
}],
}
},
})
Dynamic Prompt
import { z } from 'zod'
export default defineMcpPrompt({
description: 'Custom review',
inputSchema: {
language: z.string().describe('Language'),
focus: z.array(z.string()).describe('Focus areas'),
},
handler: async ({ language, focus }) => {
return {
messages: [{
role: 'user',
content: {
type: 'text',
text: `Review my ${language} code: ${focus.join(', ')}`,
},
}],
}
},
})
Middleware & Handlers
Customize MCP behavior with middleware and handlers for authentication, logging, rate limiting, and more.
Basic Middleware
// server/mcp/middleware.ts
export default defineMcpMiddleware({
handler: async (event, next) => {
console.log('MCP Request:', event.path)
// Check auth
const token = event.headers.get('authorization')
if (!token) {
return createError({ statusCode: 401, message: 'Unauthorized' })
}
return next()
},
})
Custom Handler
// server/mcp/handlers/custom.ts
export default defineMcpHandler({
name: 'custom-mcp',
route: '/mcp/custom',
handler: async (event) => {
return {
tools: await loadCustomTools(),
resources: [],
prompts: [],
}
},
})
Common Use Cases
- Authentication: API keys, JWT tokens
- Rate limiting: Per IP or per user
- Logging: Request/response tracking
- CORS: Cross-origin configuration
- Multiple endpoints: Public/admin separation
See detailed middleware guide →
Review & Best Practices
Tool Checklist
✅ Use kebab-case filenames
✅ Add .describe() to all Zod fields
✅ Return isError: true for errors
✅ Add caching for expensive ops
✅ Clear, actionable descriptions
✅ Validate all inputs
✅ Add annotations (readOnlyHint, destructiveHint, etc.)
✅ Add inputExamples for tools with optional/complex params
❌ Generic descriptions ❌ Skip error handling ❌ Expose sensitive data ❌ No input validation
Resource Checklist
✅ Descriptive URIs (config:///app)
✅ Set appropriate MIME types
✅ Enable caching when needed
✅ Handle errors gracefully
✅ Use URI templates for collections
❌ Unclear URI schemes ❌ Skip MIME types ❌ Expose sensitive data ❌ Return huge datasets without pagination
Prompt Checklist
✅ Clear descriptions ✅ Meaningful parameters ✅ Default values where appropriate ✅ Single, focused purpose ✅ Reusable design
❌ Overly complex ❌ Skip descriptions ❌ Mix multiple concerns
Troubleshooting
Auto-imports Not Working
Fix:
- Check
modules: ['@nuxtjs/mcp-toolkit']in config - Restart dev server
- Files in
server/mcp/directory? - Run
pnpm nuxt prepare
Endpoint Not Accessible
Fix:
- Dev server running?
- Test:
curl http://localhost:3000/mcp - Check
enabled: truein config - Review server logs
Validation Errors
Fix:
- Required fields provided?
- Types match schema?
- Use
.optional()for optional fields - Enum values exact match?
Tool Not Discovered
Fix:
- File extension
.tsor.js? - Using
export default? - File in correct directory?
- Restart dev server
See detailed troubleshooting →
Testing with Evals
Setup
pnpm add -D evalite vitest @ai-sdk/mcp ai
Add to package.json:
{
"scripts": {
"eval": "evalite",
"eval:ui": "evalite watch"
}
}
Basic Test
Create test/mcp.eval.ts:
import { experimental_createMCPClient as createMCPClient } from '@ai-sdk/mcp'
import { generateText } from 'ai'
import { evalite } from 'evalite'
import { toolCallAccuracy } from 'evalite/scorers'
evalite('MCP Tool Selection', {
data: async () => [
{
input: 'Calculate BMI for 70kg 1.75m',
expected: [{
toolName: 'bmi-calculator',
input: { weight: 70, height: 1.75 },
}],
},
],
task: async (input) => {
const mcp = await createMCPClient({
transport: { type: 'http', url: 'http://localhost:3000/mcp' },
})
try {
const result = await generateText({
model: 'openai/gpt-4o',
prompt: input,
tools: await mcp.tools(),
})
return result.toolCalls ?? []
}
finally {
await mcp.close()
}
},
scorers: [
({ output, expected }) => toolCallAccuracy({
actualCalls: output,
expectedCalls: expected,
}),
],
})
Running
# Start server
pnpm dev
# Run tests (in another terminal)
pnpm eval
# Or with UI
pnpm eval:ui # http://localhost:3006
Quick Reference
Common Commands
# Setup
npx nuxt module add mcp-toolkit
# Dev
pnpm dev
# Test endpoint
curl http://localhost:3000/mcp
# Regenerate types
pnpm nuxt prepare
# Run evals
pnpm eval
Configuration
// nuxt.config.ts
export default defineNuxtConfig({
mcp: {
name: 'My Server',
route: '/mcp',
enabled: true,
dir: 'mcp',
},
})
Debug Tools
- DevTools: Shift+Alt+D → MCP tab
- Logs: Check terminal
- curl: Test endpoint