Install to Claude Code
npx -y skills add https://github.com/gocallum/nextjs16-agent-skills --skill mcp-server-skillsDescription
mcp server skills
SKILL.md
---
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.
