A production-ready template for building Model Context Protocol (MCP) servers using Next.js and the mcp-handler adapter.
This template provides a streamlined way to create MCP servers that can be consumed by AI agents like ChatGPT, Claude, Cursor, and other MCP-compatible clients. Deploy once, connect everywhere.
- 🚀 One-click deployment to Vercel
- 🔧 Easy tool creation using Zod schemas for type-safe inputs
- 🌐 Streamable HTTP transport for efficient real-time communication
- 📦 Pre-configured with TypeScript, Tailwind CSS, and Next.js 16
- 🎨 Built-in landing page with setup instructions
┌─────────────────────────────────────────────────────────────────┐
│ MCP Clients │
│ (ChatGPT, Claude, Cursor, Custom Agents) │
└──────────────────────────┬──────────────────────────────────────┘
│
│ HTTP (Streamable HTTP Transport)
▼
┌─────────────────────────────────────────────────────────────────┐
│ Next.js Application │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ /mcp endpoint │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ mcp-handler adapter │ │ │
│ │ │ • Tool registration │ │ │
│ │ │ • Request/response handling │ │ │
│ │ │ • Schema validation (Zod) │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌──────────────┴──────────────┐ │ │
│ │ ▼ ▼ │ │
│ │ ┌────────────┐ ┌────────────────┐ │ │
│ │ │ echo │ │ get_server_time│ │ │
│ │ │ tool │ │ tool │ │ │
│ │ └────────────┘ └────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ / (Landing Page) │ │
│ │ Setup guide & MCP URL display │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
mcp-server-template/
├── app/
│ ├── globals.css # Global styles
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Landing page with setup instructions
│ └── mcp/
│ └── route.ts # ⭐ MCP server endpoint (add tools here)
├── components/
│ ├── theme-provider.tsx # Dark/light theme support
│ └── ui/ # Reusable UI components (shadcn/ui)
├── scripts/
│ └── test-streamable-http-client.ts # Test client for local development
├── public/
│ └── images/ # Setup guide screenshots
├── package.json
└── README.md
git clone https://github.com/RitualChain/mcp-server-template.git
cd mcp-server-template
bun installbun devVisit http://localhost:3030 to see the landing page.
Or deploy manually:
npx vercelTools are defined in app/mcp/route.ts. The createMcpHandler function takes three arguments:
- Tool Registration Function - Async function where you register tools using
server.tool() - Capabilities - Server capabilities configuration
- Options - Runtime options like
maxDurationandbasePath
Each tool requires four parameters:
| Parameter | Type | Description |
|---|---|---|
name |
string | Unique identifier for the tool |
description |
string | What the tool does (shown to AI agents) |
schema |
object | Zod schema defining input parameters |
handler |
function | Async function that executes the tool |
Here's the complete app/mcp/route.ts structure:
import { createMcpHandler } from "mcp-handler";
import { z } from "zod";
// StreamableHttp server
const handler = createMcpHandler(
// 1. Tool Registration Function
async (server) => {
server.tool(
"echo",
"Echo back a message to verify input/output communication",
{
message: z.string(),
},
async ({ message }) => ({
content: [{ type: "text", text: `Tool echo: ${message}` }],
})
);
server.tool(
"get_server_time",
"Returns the current server time and timezone information",
{},
async () => ({
content: [
{
type: "text",
text: JSON.stringify(
{
serverTime: new Date().toISOString(),
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
timestamp: Date.now(),
},
null,
2
),
},
],
})
);
},
// 2. Capabilities — declare server supports tools
{
capabilities: {
tools: {},
},
},
// 3. Options — runtime configuration
{
basePath: "",
verboseLogs: true,
maxDuration: 60,
disableSse: true,
}
);
export { handler as GET, handler as POST, handler as DELETE };To add a new tool, insert it in the tool registration function:
server.tool(
"get_pokemon",
"Fetches Pokemon data from the PokeAPI",
{
name: z.string().describe("Name of the Pokemon"),
},
async ({ name }) => {
const response = await fetch(
`https://pokeapi.co/api/v2/pokemon/${name}`
);
const data = await response.json();
return {
content: [
{
type: "text",
text: JSON.stringify(
{
name: data.name,
types: data.types.map((t: { type: { name: string } }) => t.type.name),
height: data.height,
weight: data.weight,
},
null,
2
),
},
],
};
}
);Use Zod to define type-safe input schemas for your tools:
import { z } from "zod";
// Required string
{ message: z.string() }
// String with description (shown to AI)
{ name: z.string().describe("The user's full name") }
// Optional parameter
{ limit: z.number().optional() }
// With default value
{ count: z.number().default(10) }
// Enum choices
{ format: z.enum(["json", "text", "markdown"]) }
// No parameters
{}Tools must return an object with a content array. Each content item has a type and corresponding data:
// Text response
return {
content: [{ type: "text", text: "Your response here" }],
};
// JSON response (stringify for readability)
return {
content: [
{
type: "text",
text: JSON.stringify({ key: "value" }, null, 2),
},
],
};
// Multiple content items
return {
content: [
{ type: "text", text: "First part" },
{ type: "text", text: "Second part" },
],
};| Tool | Description |
|---|---|
echo |
Echoes back a message (useful for testing connectivity) |
get_server_time |
Returns current server time and timezone |
Visiting /mcp in a browser (GET request) returns server info as JSON:
{
"name": "MCP Server Template",
"version": "0.1.0",
"protocol": "Model Context Protocol (MCP)",
"transport": "Streamable HTTP",
"status": "running",
"tools": [...],
"usage": {
"note": "This endpoint uses POST for MCP communication",
"test": "npx tsx scripts/test-streamable-http-client.ts <origin>",
"docs": "https://modelcontextprotocol.io"
}
}MCP clients communicate via POST requests to this endpoint.
- Go to Settings → Apps & Connectors
- Click Create to add a new connector
- Enter your MCP URL:
https://your-app.vercel.app/mcp - Select "No authentication" and confirm
See the landing page for detailed step-by-step instructions with screenshots.
Add to your .cursor/mcp.json:
{
"mcpServers": {
"my-mcp-server": {
"url": "https://your-app.vercel.app/mcp"
}
}
}Add to your Claude config file:
{
"mcpServers": {
"my-mcp-server": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://your-app.vercel.app/mcp"]
}
}
}npx tsx scripts/test-streamable-http-client.ts https://your-app.vercel.appThis will connect to your MCP server and list available tools.
Add --verbose or -v for detailed output including tool schemas:
npx tsx scripts/test-streamable-http-client.ts https://your-app.vercel.app --verbose# Terminal 1: Start the dev server
bun dev
# Terminal 2: Test the MCP endpoint
npx tsx scripts/test-streamable-http-client.ts http://localhost:3030The third argument to createMcpHandler configures runtime behavior:
{
basePath: "", // Base path for MCP routes (empty = /mcp)
verboseLogs: true, // Enable detailed logging for debugging
maxDuration: 60, // Max execution time in seconds
disableSse: true, // Use Streamable HTTP instead of SSE
}| Option | Type | Default | Description |
|---|---|---|---|
basePath |
string | "" |
Base path prefix for the MCP endpoint |
verboseLogs |
boolean | false |
Enable verbose logging for debugging |
maxDuration |
number | 60 |
Maximum function execution time (seconds) |
disableSse |
boolean | false |
Use Streamable HTTP transport instead of SSE |
For optimal performance on Vercel:
- Enable Fluid Compute in your project settings for efficient execution
- Adjust
maxDurationbased on your plan:
| Plan | Max Duration |
|---|---|
| Hobby | 60 seconds |
| Pro | 800 seconds |
| Enterprise | 800 seconds |
// For Pro/Enterprise accounts
{
basePath: "",
verboseLogs: true,
maxDuration: 800, // Increase from default 60
disableSse: true,
}| Variable | Description | Required |
|---|---|---|
NEXT_PUBLIC_APP_URL |
Your production URL (for landing page display) | Optional |
- Framework: Next.js 16
- MCP Adapter: mcp-handler
- MCP SDK: @modelcontextprotocol/sdk
- Validation: Zod
- Styling: Tailwind CSS 4
- Components: shadcn/ui
- MCP TypeScript SDK Documentation
- Model Context Protocol Specification
- Vercel MCP Template
- mcp-handler npm package
MIT © Ritual Chain