MCP Server Template
The MCP (Model Context Protocol) Server template provides a simplified framework for building MCP servers, making it easier to create tools that can be used by AI clients like Cursor.
Technical Stack
- Node.js - JavaScript runtime
- TypeScript - Type-safe development
- Model Context Protocol SDK - Official MCP SDK
- Fastify - Fast web framework
- JWT - Authentication support
Quick Start
1. Create Project
# Initialize project
vup init my-mcp-project
cd my-mcp-project
# Add MCP template
vup add my-mcp2. Install Dependencies
# Install dependencies
pnpm install3. Start Development
# STDIO mode (local, for Cursor)
cd apps/my-mcp
pnpm dev
# SSE mode (remote)
pnpm dev:remoteProject Structure
apps/my-mcp/
├── src/
│ ├── framework/ # Framework core
│ │ ├── defineTool.ts # Tool definition helper
│ │ ├── requireAuth.ts # Authentication handling
│ │ ├── toolRegistry.ts # Tool registry
│ │ ├── createServer.ts # Server creation
│ │ ├── types.ts # Type definitions
│ │ └── index.ts # Framework entry
│ ├── tools/ # Tool definitions
│ │ ├── auth.ts # Authentication tools
│ │ ├── public.ts # Public tools (simple example)
│ │ ├── demo.ts # Example tool (document search)
│ │ └── index.ts # Tool exports
│ └── server.ts # Server entry
├── public/
│ └── login.html # Login page
├── data/
│ └── docs.csv # Example data
├── package.json
└── tsconfig.jsonCore Features
Simplified API
Use defineTool() to define tools with a single line of code:
// src/tools/my-tool.ts
import { defineTool } from '../framework';
import type { ToolContext } from '../framework';
export const my_tool = defineTool({
name: 'my_tool',
description: 'My tool description',
inputSchema: {
properties: {
param: { type: 'string', description: 'Parameter description' },
},
required: ['param'],
},
requiresAuth: true, // Enable authentication with one line
handler: async (args, context: ToolContext) => {
// context.userId is automatically injected, no manual check needed
return {
content: [{ type: 'text', text: `Result: ${args.param}` }],
};
},
});Automatic Authentication
Enable authentication with requiresAuth: true:
export const my_tool = defineTool({
name: 'my_tool',
requiresAuth: true, // Automatic authentication check
handler: async (args, context) => {
// context.userId is guaranteed to exist
},
});Tool Registry
Register tools in src/tools/index.ts:
import { my_tool } from './my-tool';
export const TOOLS = [my_tool];Framework API
defineTool()
Helper function to define tools, automatically handling authentication, type conversion, etc.
defineTool({
name: string;
description: string;
inputSchema: {
properties: Record<string, any>;
required?: string[];
};
requiresAuth?: boolean; // Whether authentication is required
handler: (args, context) => Promise<{ content: ... }>;
})requireAuth()
Manually wrap tool handlers to automatically check authentication.
import { requireAuth } from '../framework';
const handler = requireAuth(async (args, context) => {
// context.userId is guaranteed to exist here
});setAuthConfig()
Set authentication configuration.
import { setAuthConfig } from '../framework';
setAuthConfig({
loginUrl: 'http://localhost:9316/login.html',
checkAuth: (context) => !!context.userId,
});createMcpServer()
Create MCP server.
import { createMcpServer, createToolRegistry } from './framework';
import { TOOLS } from './tools';
const registry = createToolRegistry();
TOOLS.forEach((tool) => registry.register(tool));
createMcpServer(
{
name: 'mcp-server',
version: '1.0.0',
mode: 'stdio', // or 'sse'
port: 9316,
auth: {
loginUrl: 'http://localhost:9316/login.html',
checkAuth: (context) => !!context.userId,
},
},
registry
);Development Modes
STDIO Mode (Local)
For direct use by AI clients like Cursor:
pnpm devSSE Mode (Remote)
For remote server deployment:
pnpm dev:remoteAccess:
- MCP endpoint:
http://localhost:9316/mcp - Login page:
http://localhost:9316/login.html
Example Tool
search_docs - Document Search Tool
A complete example tool (src/tools/demo.ts) demonstrating how to implement a practical tool.
Features:
- Read document data from CSV file
- Support search by title, content, author, category
- Return formatted search results
- Limit return count (default 10, max 50)
Data Source:
- CSV file:
data/docs.csv - Contains 50 example document records
- Fields: id, title, content, author, category, created_at
Code Example:
// src/tools/demo.ts
export const search_docs = defineTool({
name: 'search_docs',
description: 'Search document library, support search by title, content, author, category',
inputSchema: {
properties: {
query: { type: 'string', description: 'Search keyword' },
limit: { type: 'number', description: 'Return limit (default 10)' },
},
required: ['query'],
},
handler: async (args, context) => {
// Read data from CSV
const docs = loadDocs();
// Search and return results
// ...
},
});Authentication Mechanism
Automatic Authentication
Use requiresAuth: true to automatically handle authentication:
export const my_tool = defineTool({
name: 'my_tool',
requiresAuth: true, // Automatic authentication check
handler: async (args, context) => {
// context.userId is guaranteed to exist
},
});Manual Authentication
Use requireAuth() to wrap handlers:
import { requireAuth } from '../framework';
const handler = requireAuth(async (args, context) => {
// Authentication passed
});Authentication Flow
- When a tool is called, if
requiresAuth: true, the framework automatically checkscontext.userId - If not authenticated, throws
UrlElicitationRequiredError - Clients like Cursor recognize error code
-32042andelicitationsarray - Client automatically opens login page
- After user login, client retries tool call with JWT token
Available Scripts
{
"scripts": {
"dev": "tsx watch src/server.ts",
"dev:remote": "tsx watch src/server.ts --remote",
"build": "tsc",
"start": "node .output/server.js",
"start:remote": "node .output/server.js --remote"
}
}Best Practices
Error Handling
try {
// Tool logic
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error.message}`,
},
],
};
}Type Safety
Always define proper types for tool arguments:
inputSchema: {
properties: {
query: { type: 'string', description: 'Search keyword' },
limit: { type: 'number', description: 'Result limit' },
},
required: ['query'],
}Tool Organization
Organize tools by functionality:
src/tools/
├── auth.ts # Authentication tools
├── database.ts # Database tools
├── file.ts # File operations
└── index.ts # Export all tools