with one click
manage-mcp
// Manage MCP servers in Nuxt with @nuxtjs/mcp-toolkit — setup, create tools/resources/prompts, organize with handlers, build interactive MCP Apps, add elicitation/logging/sessions, review, and troubleshoot.
// Manage MCP servers in Nuxt with @nuxtjs/mcp-toolkit — setup, create tools/resources/prompts, organize with handlers, build interactive MCP Apps, add elicitation/logging/sessions, review, and troubleshoot.
| name | manage-mcp |
| description | Manage MCP servers in Nuxt with @nuxtjs/mcp-toolkit — setup, create tools/resources/prompts, organize with handlers, build interactive MCP Apps, add elicitation/logging/sessions, review, and troubleshoot. |
Complete skill for managing Model Context Protocol (MCP) servers in Nuxt with @nuxtjs/mcp-toolkit. Setup, create tools/resources/prompts, organize with multi-handler folder convention, build interactive UI widgets (MCP Apps), wire up sessions and observability, review, and troubleshoot.
::code-group
pnpm add @nuxtjs/mcp-toolkit zod
npm install @nuxtjs/mcp-toolkit zod
yarn add @nuxtjs/mcp-toolkit zod
bun add @nuxtjs/mcp-toolkit zod
::
Or in one go via the Nuxt CLI:
npx nuxt module add mcp-toolkit
Add to nuxt.config.ts:
export default defineNuxtConfig({
modules: ['@nuxtjs/mcp-toolkit'],
mcp: {
name: 'My MCP Server',
description: 'Read and update todos for the current user.',
},
nitro: {
experimental: { asyncContext: true }, // required for useEvent / useMcpServer / useMcpLogger
},
})
server/mcp/
├── tools/ # Actions the AI can perform
│ └── admin/ # Subdirectory → group: 'admin'
├── resources/ # Data the AI can read
├── prompts/ # Reusable message templates
└── handlers/ # Optional: named handlers mounted at /mcp/<name>
└── admin/
├── index.ts # defineMcpHandler({ middleware: requireAdmin })
├── tools/ # auto-attached to /mcp/admin
└── resources/
app/mcp/ # Optional: MCP Apps (interactive Vue widgets)
└── palette.vue # → app `palette`, callable from ChatGPT / Cursor
pnpm devcurl http://localhost:3000/mcp (responds to MCP JSON-RPC)Tools are functions AI assistants can call. Auto-discovered from any .ts/.js file under server/mcp/tools/.
import { z } from 'zod'
export default defineMcpTool({
description: 'Echo a message back to the user',
inputSchema: {
message: z.string().describe('Text to echo'),
},
handler: async ({ message }) => {
return `Echo: ${message}` // string is auto-wrapped into a text content item
},
})
name and title are auto-derived from the filename (echo.ts → name: 'echo', title: 'Echo'). Override either by setting them explicitly.
Handlers can return any of these — the toolkit normalizes them:
| Return | Wrapped as |
|---|---|
string / number / boolean | { content: [{ type: 'text', text: String(v) }] } |
| Plain object / array | JSON-stringified into a text content item |
imageResult(base64, mime) | { content: [{ type: 'image', data, mimeType }] } |
audioResult(base64, mime) | { content: [{ type: 'audio', data, mimeType }] } |
Full CallToolResult | Passed through (use for structuredContent, embedded resources, multi-content) |
| Thrown error | Caught and converted to isError: true. createError({ statusCode, message }) from h3 includes the status code in the response. |
import { z } from 'zod'
export default defineMcpTool({
description: 'Get a user by ID',
inputSchema: { id: z.string().describe('User ID') },
handler: async ({ id }) => {
const user = await getUser(id)
if (!user) throw createError({ statusCode: 404, message: 'User not found' })
return user // plain object — auto-stringified
},
})
For typed structured output, pair outputSchema with a structuredContent return:
import { z } from 'zod'
export default defineMcpTool({
description: 'Calculate Body Mass Index',
inputSchema: {
height: z.number().describe('Height in meters'),
weight: z.number().describe('Weight in kilograms'),
},
outputSchema: {
bmi: z.number(),
category: z.string(),
},
handler: async ({ height, weight }) => {
const bmi = weight / (height * height)
const category = bmi < 18.5 ? 'underweight' : bmi < 25 ? 'normal' : bmi < 30 ? 'overweight' : 'obese'
return { structuredContent: { bmi, category } }
},
})
Behavioral hints that help clients decide whether to prompt for confirmation:
export default defineMcpTool({
description: 'Delete a user account',
annotations: {
readOnlyHint: false,
destructiveHint: true,
idempotentHint: true,
openWorldHint: false,
},
inputSchema: { id: z.string() },
inputExamples: [{ id: 'usr_42' }, { id: 'admin' }],
handler: async ({ id }) => {
await deleteUser(id)
return `Deleted ${id}.`
},
})
Common patterns: read-only → readOnlyHint: true; create → idempotentHint: false; update → idempotentHint: true; delete → destructiveHint: true, idempotentHint: true.
group (single) and tags (free-form) help organize tools and feed listMcpTools({ group, tags }) filters. group is auto-inferred from the parent folder — server/mcp/tools/admin/delete-user.ts → group: 'admin'. Explicit group wins.
export default defineMcpTool({
group: 'admin',
tags: ['destructive', 'user-management'],
description: 'Delete a user account',
// ...
})
export default defineMcpTool({
cache: '5m', // also accepts `Number` of ms or full Nitro cache options
description: 'Fetch weather (cached)',
inputSchema: { city: z.string() },
handler: async ({ city }) => $fetch(`/api/weather?city=${city}`),
})
enabled)Hide a tool per request — runs after middleware, so event.context is populated:
export default defineMcpTool({
enabled: event => Boolean(event.context.user),
description: 'List the current user’s todos',
handler: async () => listTodos(useEvent().context.user.id),
})
See tool examples →.
Resources expose read-only data addressable by URI. Auto-discovered from server/mcp/resources/.
export default defineMcpResource({
description: 'Project README file',
file: 'README.md', // URI, MIME type, and handler auto-generated
})
export default defineMcpResource({
description: 'Application config',
uri: 'config:///app',
metadata: { mimeType: 'application/json' },
handler: async (uri) => ({
contents: [{
uri: uri.toString(),
mimeType: 'application/json',
text: JSON.stringify({ env: process.env.NODE_ENV }, null, 2),
}],
}),
})
Pass a ResourceTemplate from the SDK as uri:
import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'
export default defineMcpResource({
description: 'Fetch a user by ID',
uri: new ResourceTemplate('user:///{id}', { list: undefined }),
handler: async (uri, { id }) => {
const user = await getUser(String(id))
return {
contents: [{
uri: uri.toString(),
mimeType: 'application/json',
text: JSON.stringify(user, null, 2),
}],
}
},
})
Use the top-level metadata field for MIME type and annotations (audience, priority, lastModified):
export default defineMcpResource({
description: 'Project README',
file: 'README.md',
metadata: {
mimeType: 'text/markdown',
annotations: {
audience: ['user', 'assistant'],
priority: 0.8,
lastModified: new Date().toISOString(),
},
},
})
See resource examples →.
Prompts are reusable message templates. Auto-discovered from server/mcp/prompts/.
export default defineMcpPrompt({
description: 'Code-review assistant',
handler: async () => 'You are a senior reviewer. Be concise and actionable.',
})
The string is wrapped into a single user message. Set role: 'assistant' to wrap as an assistant message instead.
import { z } from 'zod'
export default defineMcpPrompt({
description: 'Generate a focused code review',
inputSchema: {
language: z.string().describe('Programming language'),
focus: z.enum(['performance', 'security', 'maintainability']).describe('Review focus'),
},
handler: async ({ language, focus }) =>
`Review my ${language} code. Focus on ${focus}.`,
})
completable()Surface dynamic suggestions in clients that support prompts/complete:
import { z } from 'zod'
export default defineMcpPrompt({
description: 'Open an issue for a project',
inputSchema: {
project: completable(z.string(), async (value) => {
const projects = await listProjects()
return projects.filter(p => p.startsWith(value)).slice(0, 5)
}).describe('Project name'),
},
handler: async ({ project }) => `Open an issue for ${project}.`,
})
GetPromptResult (multi-message)export default defineMcpPrompt({
description: 'Structured debugging session',
inputSchema: { issue: z.string(), env: z.enum(['dev', 'staging', 'prod']) },
handler: async ({ issue, env }) => ({
messages: [
{ role: 'user', content: { type: 'text', text: `Debug in ${env}:\n${issue}` } },
{ role: 'assistant', content: { type: 'text', text: 'Let me analyze step by step:' } },
],
}),
})
::callout{icon="i-lucide-info" color="info"}
The MCP spec only allows user and assistant roles. Put system instructions inside the user message text.
::
See prompt examples →.
By default everything under server/mcp/{tools,resources,prompts}/ is exposed at /mcp. To split into multiple endpoints with different middleware, use the folder convention:
server/mcp/
├── tools/ # → /mcp (default handler)
└── handlers/
├── admin/
│ ├── index.ts # defineMcpHandler({ middleware: requireAdmin })
│ ├── tools/ # → /mcp/admin (auto)
│ └── prompts/
└── public/
├── index.ts
└── tools/ # → /mcp/public (auto)
export default defineMcpHandler({
description: 'Admin tools — destructive operations gated by Bearer auth.',
middleware: async (event) => {
const apiKey = getHeader(event, 'authorization')?.replace('Bearer ', '')
const user = apiKey ? await verifyAdmin(apiKey) : null
if (user) event.context.user = user
// No throw — let `enabled` guards on individual tools hide them when there's no user
},
// tools / resources / prompts omitted → folder convention auto-attaches them
})
mcp.defaultHandlerStrategy controls what /mcp exposes when named handlers exist:
'orphans' (default) — only definitions not attached to a named handler.'all' — every discovered definition (the kitchen-sink route).Cross-cutting filters use the function form:
import { defineMcpHandler, getMcpTools } from '@nuxtjs/mcp-toolkit/server'
export default defineMcpHandler({
// Every tool tagged 'searchable', regardless of folder
tools: event => getMcpTools({ event, tags: ['searchable'] }),
})
See handlers reference →.
Middleware lives on defineMcpHandler (no separate defineMcpMiddleware exists). It runs before/after the MCP request, can populate event.context, and supports auto-next():
// Override the default `/mcp` handler with custom middleware
export default defineMcpHandler({
middleware: async (event) => {
// Soft auth: set context on success, never throw 401 from middleware
// (throwing 401 triggers OAuth discovery in MCP clients)
const apiKey = getHeader(event, 'authorization')?.replace('Bearer ', '')
if (apiKey) {
const user = await verifyApiKey(apiKey).catch(() => null)
if (user) event.context.user = user
}
},
})
::callout{icon="i-lucide-triangle-alert" color="warning"}
Don't throw createError({ statusCode: 401 }) from middleware — most clients interpret a 401 on the MCP route as "this server requires OAuth" and stop the regular flow. Either return 403 for hard rejections, or keep auth soft and let per-tool enabled guards hide what the user can't access.
::
next() for timing)import { extractToolNames } from '@nuxtjs/mcp-toolkit/server'
export default defineMcpHandler({
middleware: async (event, next) => {
const start = Date.now()
const response = await next()
const tools = await extractToolNames(event)
console.log(`[mcp] ${tools.join(',') || 'rpc'} took ${Date.now() - start}ms`)
return response
},
})
For structured wide events on every MCP request, use useMcpLogger() instead of console.log — it tags mcp.tool, mcp.session_id, user.id, etc. automatically.
Two per-request Nitro hooks fire during the MCP request lifecycle. Subscribe from a server/plugins/*.ts plugin to mutate the resolved config or reach the SDK McpServer instance from anywhere — no need to own a defineMcpHandler. Listeners that throw are logged and the request continues.
defineMcpHandler middleware → mcp:config:resolved → createMcpServer → mcp:server:created → transport
mcp:config:resolved — mutate tools/resources/prompts per requestFires after dynamic resolvers and enabled(event) guards, before the per-request McpServer is built. Mutate ctx.config in place.
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('mcp:config:resolved', ({ config, event }) => {
if (!event.context.user) {
config.tools = config.tools.filter(t => !t.tags?.includes('admin'))
}
})
})
mcp:server:created — reach the SDK serverFires after every tool/resource/prompt has been registered, before the server is connected to the transport. Use the SDK API to register definitions late or call getSdkServer(server) for low-level handlers.
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('mcp:server:created', ({ server, event }) => {
server.registerTool(
'whoami',
{ description: 'Return the current user id' },
async () => ({
content: [{ type: 'text', text: String(event.context.userId ?? 'anonymous') }],
}),
)
})
})
See the hooks reference →.
Stateful MCP — server assigns an Mcp-Session-Id and remembers data across tool calls in the same session.
export default defineNuxtConfig({
mcp: {
sessions: true, // or { maxDuration: 30 * 60_000, maxSessions: 1000 }
},
})
import { z } from 'zod'
export default defineMcpTool({
description: 'Remember a fact for this session',
inputSchema: { key: z.string(), value: z.string() },
handler: async ({ key, value }) => {
const session = useMcpSession<{ facts: Record<string, string> }>()
const facts = (await session.get('facts')) ?? {}
facts[key] = value
await session.set('facts', facts)
return `Remembered ${key}.`
},
})
Sessions enable SSE streaming, server-to-client notifications, and elicitation. See sessions reference →.
Ask the connected client for structured input mid-request, or send the user to a URL (MCP spec 2025-11-25).
import { z } from 'zod'
export default defineMcpTool({
description: 'Create a release after asking for the channel',
inputSchema: { name: z.string() },
handler: async ({ name }) => {
const elicit = useMcpElicitation()
if (!elicit.supports('form')) {
return `Pass --channel via your client; "${name}" needs a release channel.`
}
const result = await elicit.form({
message: `Pick a channel for "${name}"`,
schema: {
channel: z.enum(['stable', 'beta']).describe('Release channel'),
notify: z.boolean().default(true),
},
})
if (result.action !== 'accept') return `Cancelled (${result.action}).`
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 boolean.McpElicitationError (code: 'unsupported' | 'invalid-schema' | 'invalid-response') to fall back gracefully.Requires nitro.experimental.asyncContext: true and a client that declared the elicitation capability.
See elicitation docs →.
useMcpLogger()Split-channel logger. notify goes to the connected client; set / event / evlog feed the request's evlog wide event when observability is on.
import { z } from 'zod'
export default defineMcpTool({
description: 'Charge a payment method',
inputSchema: { userId: z.string(), amount: z.number().int().positive() },
annotations: { destructiveHint: true, idempotentHint: false },
handler: async ({ userId, amount }) => {
const log = useMcpLogger('billing')
log.set({ billing: { amount } })
await log.notify.info({ msg: 'starting charge', amount })
try {
const receipt = await chargeCard(userId, amount)
log.event('charge_completed', { receiptId: receipt.id })
return `Charged ${amount}. Receipt: ${receipt.id}`
}
catch (err) {
log.evlog.error('charge failed', err as Error)
throw err
}
},
})
log.notify): notify(level, data, logger?) + .debug / .info / .warning / .error. Always resolves, never throws.set(fields) / event(name, fields?) / setUser({ id, email, name }) / setSession({ id }) / evlog. Throws McpObservabilityNotEnabledError when off.mcp.method, mcp.tool, mcp.session_id, mcp.request_id, service: '<env.service>/mcp'. user.* / session.* flow through automatically when middleware sets event.context.user / event.context.session.::code-group
pnpm add evlog
npm install evlog
::
export default defineNuxtConfig({
modules: ['evlog/nuxt', '@nuxtjs/mcp-toolkit'],
evlog: { env: { service: 'my-app' } },
})
mcp.logging: omit (auto-detect), true (assert evlog/nuxt is registered), false (opt out).
Ship every MCP wide event to Axiom, Sentry, OTLP, HyperDX, Datadog, Better Stack, or PostHog with one Nitro plugin:
import { createAxiomDrain } from 'evlog/adapters/axiom'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
})
See logging docs → and evlog.dev →.
Author Vue Single-File Components in app/mcp/ — they ship to MCP-Apps-compatible hosts (ChatGPT, Cursor) as interactive widgets backed by your MCP tool handler.
<script setup lang="ts">
import { z } from 'zod'
defineMcpApp({
description: 'Pick a colour and preview a palette.',
inputSchema: { base: z.string().describe('Hex colour, e.g. #2563eb') },
handler: async ({ base }) => ({
structuredContent: await $fetch('/api/palette', { query: { base } }),
}),
})
const { data, sendPrompt } = useMcpApp<{ swatches: { name: string, hex: string }[] }>()
</script>
<template>
<div class="grid grid-cols-3 gap-2">
<button
v-for="s in data?.swatches"
:key="s.hex"
class="rounded-md p-3 text-white"
:style="{ background: s.hex }"
@click="sendPrompt(`Use ${s.name}`)"
>
{{ s.name }}
</button>
</div>
</template>
Each SFC becomes a tool, a UI resource at ui://mcp-app/<name>, and a single-file HTML bundle. The handler runs server-side; structuredContent is inlined into the HTML so the iframe boots with full data on the first paint.
useMcpApp<T>() exposes data, loading, error, hostContext, callTool(name, params), sendPrompt(prompt), and openLink(url).
CSP is strict by default — opt extra origins in:
defineMcpApp({
csp: {
resourceDomains: ['https://images.example.com'], // <img>, <style>, fonts
connectDomains: ['https://api.example.com'], // fetch / XHR / WebSocket
},
// ...
})
See MCP Apps reference →.
Identify the server in client UIs and steer the LLM with operational instructions:
export default defineNuxtConfig({
mcp: {
name: 'Todos MCP',
description: 'Read and update todos for the current user.', // shown in client UIs
instructions: 'Always call list-todos before create-todo. Group results by status.', // injected into the LLM system prompt
icons: [
{ src: 'https://example.com/icon.png', mimeType: 'image/png', sizes: ['64x64'] },
],
},
})
Override per-handler when an endpoint needs a different identity (e.g. /mcp/admin with its own description and icons).
Use listMcp* to expose summaries (catalog endpoints) and getMcp* for raw definitions to feed into a handler:
import { listMcpDefinitions } from '@nuxtjs/mcp-toolkit/server'
export default defineEventHandler(async (event) => {
const { tools, resources, prompts } = await listMcpDefinitions({ event })
return {
name: 'My MCP Server',
tools: tools.map(t => ({ name: t.name, description: t.description })),
resources: resources.map(r => ({ name: r.name, uri: r.uri })),
prompts: prompts.map(p => ({ name: p.name, description: p.description })),
}
})
Filters: event (apply enabled guards), group, tags, handler, orphansOnly. Pass event to match exactly what the request would see.
Wrap every tool exposed by a handler into a single code tool. The LLM writes JavaScript that calls tools via codemode.*, executed in a secure V8 isolate via secure-exec.
export default defineMcpHandler({
experimental_codeMode: {
progressive: true,
memoryLimit: 128, // MB
cpuTimeLimitMs: 5000,
maxToolCalls: 20,
},
})
npm install secure-exec
Useful for letting the LLM orchestrate many tool calls in one round-trip. Not supported on Cloudflare Workers (returns a clear runtime error).
See Code Mode docs →.
When reviewing or modernizing server/mcp/**, walk this checklist:
✅ Direct returns (string, object, array) instead of full CallToolResult
✅ throw createError({ statusCode, message }) for failures (caught and returned as isError)
✅ .describe() on every Zod field
✅ Honest annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint)
✅ inputExamples for non-trivial schemas
✅ enabled: event => Boolean(event.context.user) to hide tools per request
✅ cache: '5m' (or full Nitro options) for expensive idempotent ops
✅ nitro.experimental.asyncContext: true when calling useEvent() / useMcpServer() / useMcpSession() / useMcpLogger() / useMcpElicitation()
❌ textResult / jsonResult / errorResult (deprecated — use direct returns or throw)
❌ defineMcpMiddleware (does not exist — middleware is a field on defineMcpHandler)
❌ Throwing 401 from middleware (triggers OAuth discovery in clients — return 403, or use soft auth + enabled guards)
❌ Generic descriptions, missing await, unvalidated input, exposed secrets
✅ mimeType under metadata (not at top level)
✅ file: 'README.md' shorthand for static files (URI, MIME and handler auto-generated)
✅ ResourceTemplate for parameterized URIs
✅ metadata.annotations for audience / priority / lastModified
❌ Returning huge payloads — paginate via templates ❌ Skipping the MIME type — clients use it to render correctly
✅ Plain string returns when one user message is enough (uses role field, default 'user')
✅ completable(z.string(), async value => suggestions) for argument autocomplete
✅ Single, focused purpose per prompt
❌ Mixing system instructions outside the user message (the spec rejects system role)
✅ Folder convention (server/mcp/handlers/<name>/) over manual tools: [...] arrays
✅ One responsibility per handler (admin / public / apps), pick the right defaultHandlerStrategy
✅ Per-handler description / instructions / icons when an endpoint has its own identity
server/mcp/{tools,resources,prompts}/ (or under a handlers/<name>/ subfolder)?export default (not a named export)?pnpm nuxt prepare.'@nuxtjs/mcp-toolkit' listed in modules in nuxt.config.ts?pnpm dev:prepare (or pnpm nuxt prepare) to regenerate type stubs.mcp.autoImports: false, import explicitly from @nuxtjs/mcp-toolkit/server.curl -X POST http://localhost:3000/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Should return a JSON-RPC response. If 404, check mcp.enabled !== false and mcp.route.
useEvent() / useMcpServer() / useMcpSession() throws "no async context"Add nitro.experimental.asyncContext: true to nuxt.config.ts. These composables read the current event from Nitro's async-local storage, which only works when asyncContext is on (default since Nuxt 3.8+).
If your IDE / vue-tsc infers Promise<…> for one of these (a known auto-import quirk), await the call defensively — runtime works either way: const mcp = await useMcpServer().
You're throwing 401 from middleware. Switch to soft auth: set event.context.user on success, return 403 for hard rejections, and gate per-tool with enabled: event => Boolean(event.context.user).
Set mcp.security.allowedOrigins:
mcp: {
security: {
allowedOrigins: ['https://my-app.vercel.app'],
// allowedOrigins: '*' // explicit opt-out — use with care
},
}
See troubleshooting reference →.
Eval the right-tool-for-the-prompt selection with Evalite:
pnpm add -D evalite vitest @ai-sdk/mcp ai
{
"scripts": {
"eval": "evalite",
"eval:ui": "evalite watch"
}
}
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 }),
],
})
Run it:
pnpm dev # in one terminal
pnpm eval # in another, or `pnpm eval:ui` for the watcher UI
See testing reference →.
export default defineNuxtConfig({
modules: ['@nuxtjs/mcp-toolkit'],
mcp: {
name: 'My Server',
description: 'What this server does',
instructions: 'How the LLM should use it',
route: '/mcp',
sessions: true,
defaultHandlerStrategy: 'orphans', // or 'all'
security: { allowedOrigins: ['https://my-app.vercel.app'] },
logging: true, // requires evlog/nuxt
},
nitro: { experimental: { asyncContext: true } },
})
| Helper | Purpose |
|---|---|
defineMcpTool / defineMcpResource / defineMcpPrompt | Declare a definition (auto-discovered). |
defineMcpHandler | Custom handler with middleware / dynamic tools. |
defineMcpApp (in app/mcp/*.vue) | Interactive Vue widget. |
imageResult / audioResult | Wrap binary content in a tool response. |
completable | Argument autocomplete on prompts. |
extractToolNames | Parse tool names from the JSON-RPC body in middleware. |
useMcpServer() | Mid-session register/unregister tools. |
useMcpSession<T>() | Per-session storage (sessions must be enabled). |
useMcpLogger(name?) | Client notifications + server-side wide events. |
useMcpElicitation() | Form / URL / confirm prompts to the client. |
useMcpApp<T>() (in MCP App SFCs) | Reactive data + callTool / sendPrompt bridge. |
listMcpTools / listMcpResources / listMcpPrompts / listMcpDefinitions | JSON-friendly summaries (catalog endpoints). |
getMcpTools / getMcpResources / getMcpPrompts | Raw definitions (feed back into a handler). |
getSdkServer | Reach the low-level SDK Server from an McpServer (advanced). |
| Hook | Fires |
|---|---|
mcp:config:resolved | Per request, after dynamic resolvers — mutate config.tools / resources / prompts / instructions / icons / name. |
mcp:server:created | Per request, after every definition is registered — call server.registerTool(...), getSdkServer(server).setRequestHandler(...), etc. |
npx @modelcontextprotocol/inspector http://localhost:3000/mcpcurl -X POST … -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'[HINT] Download the complete skill directory including SKILL.md and all related files