Skip to main content

mcp-server-skills

Pattern for building MCP servers in Next.js with mcp-handler, shared Zod schemas, and reusable server actions.

Jump to install

Source facts

Repository
gocallum/nextjs16-agent-skills
Last source activity
January 19, 2026 at 10:17
Detected SKILL.md language
English
Stars
23
Forks
7

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions ยท Read-only preview
name
mcp-server-skills
description
Pattern for building MCP servers in Next.js with mcp-handler, shared Zod schemas, and reusable server actions.
## Links - Model Context Protocol: https://modelcontextprotocol.io/ - mcp-handler (HTTP): https://www.npmjs.com/package/mcp-handler - Reference implementation (Roll Dice): https://github.com/gocallum/rolldice-mcpserver - Claude Desktop + mcp-remote bridge: https://www.npmjs.com/package/mcp-remote ## Folder Structure (Next.js App Router) ``` app/ api/[transport]/route.ts # One handler for all transports (e.g., /api/mcp) actions/mcp-actions.ts # Server actions reusing the same logic/schemas lib/ dice.ts | tools.ts # Zod schemas, tool definitions, pure logic components/ # UI that calls server actions for web testing ``` **Goal:** Keep `route.ts` minimal. Put logic + Zod schemas in `lib/*` so both the MCP handler and server actions share a single source of truth. ## Shared Zod Schema + Tool Definition ```ts // lib/dice.ts import { z } from "zod"; export const diceSchema = z.number().int().min(2); export function rollDice(sides: number) { const validated = diceSchema.parse(sides); const value = 1 + Math.floor(Math.random() * validated); return { type: "text" as const, text: `๐ŸŽฒ You rolled a ${value}!` }; } export const rollDiceTool = { name: "roll_dice", description: "Rolls an N-sided die", schema: { sides: diceSchema }, } as const; ``` ## Reusable Server Actions (Web UI + Tests) ```ts // app/actions/mcp-actions.ts "use server"; import { rollDice as rollDiceCore, rollDiceTool } from "@/lib/dice"; export async function rollDice(sides: number) { try { const result = rollDiceCore(sides); return { success: true, result: { content: [result] } }; } catch { return { success: false, error: { code: -32602, message: "Invalid parameters: sides must be >= 2" }, }; } } export async function listTools() { return { success: true, result: { tools: [ { name: rollDiceTool.name, description: rollDiceTool.description, inputSchema: { type: "object", properties: { sides: { type: "number", minimum: 2 } }, required: ["sides"], }, }, ], }, }; } ``` Server actions call the same logic as the MCP handler and power the web UI, keeping responses aligned. ## Lightweight MCP Route ```ts // app/api/[transport]/route.ts import { createMcpHandler } from "mcp-handler"; import { rollDice, rollDiceTool } from "@/lib/dice"; const handler = createMcpHandler( (server) => { server.tool( rollDiceTool.name, rollDiceTool.description, rollDiceTool.schema, async ({ sides }) => ({ content: [rollDice(sides)] }), ); }, {}, // server options { basePath: "/api", // must match folder path maxDuration: 60, verboseLogs: true, }, ); export { handler as GET, handler as POST }; ``` **Pattern highlights** - Route only wires `createMcpHandler`; no business logic inline. - `server.tool` consumes the shared tool schema/description and calls shared logic. - `basePath` should align with the folder (e.g., `/api/[transport]`). - Works for SSE/HTTP transports; stdio can be added separately if needed. ## Claude Desktop Config (mcp-remote) ```jsonc { "mcpServers": { "rolldice": { "command": "npx", "args": ["-y", "mcp-remote", "http://localhost:3000/api/mcp"] } } } ``` ## Best Practices 1) **Single source of truth** โ€” schemas + logic in `lib/*`; both MCP tools and server actions import them. 2) **Validation first** โ€” use Zod for inputs and reuse the same schema for UI + MCP. 3) **Keep route.ts light** โ€” only handler wiring, logging, and transport config. 4) **Shared responses** โ€” standardize `{ success, result | error }` shapes for tools and UI. 5) **Vercel-friendly** โ€” avoid stateful globals; configure `maxDuration` and `runtime` if needed. 6) **Multiple transports** โ€” expose `/api/[transport]` for HTTP/SSE; add stdio entrypoint when required. 7) **Local testing** โ€” hit server actions from the web UI to ensure MCP responses stay in sync.
View on GitHub