ワンクリックで
manage-mcp
Manage MCP servers in Nuxt - setup, create, customize with middleware, review, and troubleshoot
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Manage MCP servers in Nuxt - setup, create, customize with middleware, review, and troubleshoot
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Implement or resume tasks from a Spectra change
Archive a completed change
Have a focused discussion about a topic and reach a conclusion
Implement or resume tasks from a Spectra change
Archive a completed change
Have a focused discussion about a topic and reach a conclusion
| name | manage-mcp |
| description | Manage MCP servers in Nuxt - setup, create, customize with middleware, review, and troubleshoot |
Complete skill for managing Model Context Protocol (MCP) servers in Nuxt applications. Setup, create, customize with middleware and handlers, review, and troubleshoot.
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',
},
})
server/mcp/
├── tools/ # Actions AI can perform
│ ├── admin/ # Subdirectory → group: 'admin'
│ └── content/ # Subdirectory → group: 'content'
├── resources/ # Data AI can read
└── prompts/ # Message templates
pnpm devhttp://localhost:3000/mcp (should redirect)Tools are functions AI assistants can call.
import { z } from 'zod'
export default defineMcpTool({
description: 'What the tool does',
inputSchema: {
param: z.string().describe('Parameter description'),
},
handler: async ({ param }) => {
return 'Result' // or return { foo: 'bar' } for JSON; full CallToolResult still supported
},
})
// 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')
if (!param) {
throw createError({ statusCode: 400, message: 'Error: param required' })
}
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.
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' },
],
// ...
})
Organize tools with group and tags for filtering and progressive discovery:
export default defineMcpTool({
group: 'admin',
tags: ['destructive', 'user-management'],
description: 'Delete a user account',
// ...
})
Groups are auto-inferred from subdirectories: server/mcp/tools/admin/delete-user.ts → group: 'admin'. Explicit group takes precedence.
export default defineMcpTool({
cache: '5m', // 5 minutes
// ...
})
Resources expose read-only data.
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',
}],
}
},
})
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',
}],
}
},
})
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',
}],
}
},
})
Prompts are reusable message templates.
export default defineMcpPrompt({
description: 'Code review',
handler: async () => {
return {
messages: [{
role: 'user',
content: {
type: 'text',
text: 'Review this code for best practices.',
},
}],
}
},
})
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(', ')}`,
},
}],
}
},
})
Customize MCP behavior with middleware and handlers for authentication, logging, rate limiting, and more.
// 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()
},
})
// server/mcp/handlers/custom.ts
export default defineMcpHandler({
name: 'custom-mcp',
route: '/mcp/custom',
handler: async (event) => {
return {
tools: await loadCustomTools(),
resources: [],
prompts: [],
}
},
})
See detailed middleware guide →
useMcpElicitation()Ask the connected client for structured input mid-request, or send the user to a URL.
import { z } from 'zod'
export default defineMcpTool({
name: 'create_release',
inputSchema: { name: z.string() },
handler: async ({ name }) => {
const elicit = useMcpElicitation()
const result = await elicit.form({
message: `Pick a channel for "${name}"`,
schema: {
channel: z.enum(['stable', 'beta']).describe('Release channel'),
},
})
if (result.action !== 'accept') return 'Cancelled.'
return `Released "${name}" on ${result.content.channel}.`
},
})
elicit.url({ message, url }) — opt-in per spec, gate with elicit.supports('url').await elicit.confirm('Continue?') returns a boolean.elicit.supports('form' | 'url') — always false before init completes.McpElicitationError (code: 'unsupported' | 'invalid-schema' | 'invalid-response') to fall back when the client doesn't support elicitation.useMcpLogger()Split-channel logger: notify the connected client and capture structured wide events (powered by evlog, an optional peer dependency — install with pnpm add evlog to enable wide events).
export default defineMcpTool({
name: 'charge_card',
inputSchema: { userId: z.string(), amount: z.number().int() },
handler: async ({ userId, amount }) => {
const log = useMcpLogger('billing')
// → server-side wide event (dev terminal + drains)
log.set({ user: { id: userId }, billing: { amount } })
log.event('charge_started', { amount })
// → MCP client (Inspector / Cursor / Claude)
await log.notify.info({ msg: 'starting charge', amount })
const receipt = await charge(userId, amount)
return `Charged ${amount}.`
},
})
log.notify): notify(level, data, logger?) plus notify.debug / notify.info / notify.warning / notify.error shortcuts. Always resolves, never throws — respects the client's logging/setLevel per session. Works with or without evlog.evlog installed): set(fields) accumulates context onto the request's wide event; event(name, fields?) captures a discrete event; evlog exposes the full request logger. These throw McpObservabilityNotEnabledError when observability is off.mcp.transport, mcp.route, mcp.session_id, mcp.method, mcp.request_id, and mcp.tool / mcp.resource / mcp.prompt based on the JSON-RPC payload (no user code required).mcp.logging modes: omit (auto-detect — on if evlog installed), true / object (force on, throws at build if missing), false (force off — notify keeps working).Ship every MCP wide event to Axiom, Sentry, OTLP, HyperDX, Datadog, Better Stack, or PostHog with a single Nitro plugin. Each adapter lives under evlog/adapters/* and is registered on the evlog:drain hook:
import { createAxiomDrain } from 'evlog/adapters/axiom'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
})
The hook is additive — register multiple drains in parallel. Custom drains are just (ctx) => Promise<void> registered on the same hook.
See evlog.dev → for the full list of adapters, env-var conventions, sampling, and redaction.
See logging docs →
When reviewing or modernizing server/mcp/**, walk through this list so implementations stay aligned with current toolkit behavior and Nuxt server typing.
Tool return values
string, number, boolean, plain objects, or arrays. The module wraps them into CallToolResult (JSON is pretty-printed for objects).textResult, jsonResult, errorResult — migrate to direct values and throw createError({ statusCode, message }) (or throw new Error(...)) for failures.CallToolResult (content, structuredContent, embedded resources, isError) for cases that need explicit MCP shapes.Async context & server composables
useMcpServer() needs nitro.experimental.asyncContext: true in nuxt.config. If TypeScript reports Promise<McpServerHelper> (common with server auto-imports), use const mcp = await useMcpServer() before registerTool / removeTool / etc.useMcpSession() / useEvent(): await if the IDE or vue-tsc indicates a Promise; keep session and event usage inside tool, resource, or prompt handlers.Hygiene
await on a Promise-backed call in handlers (DB, fetch, composables that return promises)..describe() on schema fields for good model UX; use inputExamples for non-trivial shapes.readOnlyHint, destructiveHint, idempotentHint, openWorldHint honestly.pnpm eslint / nuxi typecheck on the app after refactors (catch deprecated APIs and missing await early).✅ Use kebab-case filenames
✅ Add .describe() to all Zod fields
✅ Return plain values or throw createError for failures (not deprecated errorResult)
✅ Add caching for expensive ops
✅ Clear, actionable descriptions
✅ Validate all inputs
✅ Add annotations (readOnlyHint, destructiveHint, etc.)
✅ Add inputExamples for tools with optional/complex params
✅ nitro.experimental.asyncContext: true when using useMcpServer()
❌ Generic descriptions
❌ Skip error handling
❌ Expose sensitive data
❌ No input validation
❌ textResult / jsonResult / errorResult in new code (deprecated)
✅ 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
✅ Clear descriptions ✅ Meaningful parameters ✅ Default values where appropriate ✅ Single, focused purpose ✅ Reusable design
❌ Overly complex ❌ Skip descriptions ❌ Mix multiple concerns
Fix:
modules: ['@nuxtjs/mcp-toolkit'] in configserver/mcp/ directory?pnpm nuxt prepareFix:
curl http://localhost:3000/mcpenabled: true in configFix:
.optional() for optional fieldsFix:
.ts or .js?export default?See detailed troubleshooting →
pnpm add -D evalite vitest @ai-sdk/mcp ai
Add to package.json:
{
"scripts": {
"eval": "evalite",
"eval:ui": "evalite watch"
}
}
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,
}),
],
})
# Start server
pnpm dev
# Run tests (in another terminal)
pnpm eval
# Or with UI
pnpm eval:ui # http://localhost:3006
# 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
// nuxt.config.ts
export default defineNuxtConfig({
mcp: {
name: 'My Server',
route: '/mcp',
enabled: true,
dir: 'mcp',
},
})