#!/usr/bin/env node
/**
* MCP Learning Server - A comprehensive tutorial implementation
*
* This server demonstrates MCP concepts from basic to advanced:
* 1. Basic server setup and structure
* 2. Tool registration and handling
* 3. Resource management
* 4. Prompt templates
* 5. Error handling and validation
* 6. State management
* 7. Advanced features
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
ListPromptsRequestSchema,
GetPromptRequestSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
// ============================================================================
// LESSON 1: Understanding MCP Server Structure
// ============================================================================
/**
* MCP servers have three main components:
* 1. Tools - Functions the AI can call
* 2. Resources - Data the AI can read
* 3. Prompts - Templates for AI interactions
*/
class MCPLearningServer {
private server: Server;
private serverState: Map<string, any> = new Map(); // Simple state storage
constructor() {
// LESSON 2: Server Configuration
this.server = new Server(
{
name: 'mcp-learning-server',
version: '1.0.0',
},
{
capabilities: {
tools: {}, // We can provide tools
resources: {}, // We can provide resources
prompts: {}, // We can provide prompt templates
},
}
);
this.setupHandlers();
this.setupErrorHandling();
this.initializeServerState();
}
// ============================================================================
// LESSON 3: Setting Up Request Handlers
// ============================================================================
private setupHandlers(): void {
// Tools are functions the AI can call
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools: this.getAvailableTools() };
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
return await this.handleToolCall(request);
});
// Resources are data the AI can read
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
return { resources: this.getAvailableResources() };
});
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
return await this.handleResourceRead(request);
});
// Prompts are templates for AI interactions
this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
return { prompts: this.getAvailablePrompts() };
});
this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
return await this.handlePromptRequest(request);
});
}
// ============================================================================
// LESSON 4: Tool Definitions and Implementation
// ============================================================================
private getAvailableTools() {
return [
// BEGINNER: Simple tools with basic input/output
{
name: 'hello_world',
description: 'A simple greeting tool to understand MCP basics',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name to greet',
},
},
required: ['name'],
},
},
// BEGINNER: Math operations to understand parameter handling
{
name: 'calculator',
description: 'Perform basic math operations',
inputSchema: {
type: 'object',
properties: {
operation: {
type: 'string',
enum: ['add', 'subtract', 'multiply', 'divide'],
description: 'Math operation to perform',
},
a: {
type: 'number',
description: 'First number',
},
b: {
type: 'number',
description: 'Second number',
},
},
required: ['operation', 'a', 'b'],
},
},
// INTERMEDIATE: State management
{
name: 'counter',
description: 'Manage a counter with state persistence',
inputSchema: {
type: 'object',
properties: {
action: {
type: 'string',
enum: ['increment', 'decrement', 'reset', 'get'],
description: 'Counter action',
},
amount: {
type: 'number',
description: 'Amount to increment/decrement (default: 1)',
default: 1,
},
},
required: ['action'],
},
},
// INTERMEDIATE: Data processing with validation
{
name: 'data_processor',
description: 'Process arrays of data with various operations',
inputSchema: {
type: 'object',
properties: {
data: {
type: 'array',
items: { type: 'number' },
description: 'Array of numbers to process',
},
operation: {
type: 'string',
enum: ['sum', 'average', 'max', 'min', 'sort'],
description: 'Operation to perform on data',
},
},
required: ['data', 'operation'],
},
},
// ADVANCED: Complex object manipulation
{
name: 'task_manager',
description: 'Manage a list of tasks with CRUD operations',
inputSchema: {
type: 'object',
properties: {
action: {
type: 'string',
enum: ['create', 'read', 'update', 'delete', 'list'],
description: 'Task management action',
},
task: {
type: 'object',
properties: {
id: { type: 'string' },
title: { type: 'string' },
description: { type: 'string' },
completed: { type: 'boolean' },
priority: { type: 'string', enum: ['low', 'medium', 'high'] },
},
description: 'Task object for create/update operations',
},
id: {
type: 'string',
description: 'Task ID for read/update/delete operations',
},
},
required: ['action'],
},
},
// ADVANCED: Error handling demonstration
{
name: 'error_demo',
description: 'Demonstrate different types of error handling',
inputSchema: {
type: 'object',
properties: {
error_type: {
type: 'string',
enum: ['validation', 'not_found', 'internal', 'timeout', 'none'],
description: 'Type of error to demonstrate',
},
},
required: ['error_type'],
},
},
];
}
// ============================================================================
// LESSON 5: Tool Call Handler Implementation
// ============================================================================
private async handleToolCall(request: any) {
const { name, arguments: args } = request.params;
try {
console.error(`[MCP] Tool called: ${name} with args:`, JSON.stringify(args));
switch (name) {
case 'hello_world':
return this.handleHelloWorld(args);
case 'calculator':
return this.handleCalculator(args);
case 'counter':
return this.handleCounter(args);
case 'data_processor':
return this.handleDataProcessor(args);
case 'task_manager':
return this.handleTaskManager(args);
case 'error_demo':
return this.handleErrorDemo(args);
default:
throw new McpError(ErrorCode.MethodNotFound, `Tool ${name} not found`);
}
} catch (error) {
console.error(`[MCP] Tool ${name} error:`, error);
if (error instanceof McpError) {
throw error;
}
const errorMessage = error instanceof Error ? error.message : String(error);
throw new McpError(ErrorCode.InternalError, `Tool ${name} failed: ${errorMessage}`);
}
}
// ============================================================================
// LESSON 6: Individual Tool Implementations
// ============================================================================
// BEGINNER: Simple string manipulation
private handleHelloWorld(args: any) {
const { name } = args;
const greeting = `Hello, ${name}! Welcome to MCP learning.`;
const explanation = `
This is your first MCP tool call! Here's what happened:
1. You called the 'hello_world' tool
2. You passed the parameter: name = "${name}"
3. The server processed your request
4. This response was generated and sent back
MCP tools always return content in this format with a 'text' type.
`;
return {
content: [
{
type: 'text',
text: `${greeting}\n${explanation}`,
},
],
};
}
// BEGINNER: Parameter validation and basic operations
private handleCalculator(args: any) {
const { operation, a, b } = args;
// Input validation example
if (typeof a !== 'number' || typeof b !== 'number') {
throw new McpError(ErrorCode.InvalidParams, 'Both a and b must be numbers');
}
let result: number;
let explanation: string;
switch (operation) {
case 'add':
result = a + b;
explanation = `Added ${a} + ${b} = ${result}`;
break;
case 'subtract':
result = a - b;
explanation = `Subtracted ${a} - ${b} = ${result}`;
break;
case 'multiply':
result = a * b;
explanation = `Multiplied ${a} Γ ${b} = ${result}`;
break;
case 'divide':
if (b === 0) {
throw new McpError(ErrorCode.InvalidParams, 'Cannot divide by zero');
}
result = a / b;
explanation = `Divided ${a} Γ· ${b} = ${result}`;
break;
default:
throw new McpError(ErrorCode.InvalidParams, `Unknown operation: ${operation}`);
}
return {
content: [
{
type: 'text',
text: JSON.stringify({
operation,
inputs: { a, b },
result,
explanation,
lesson: 'This demonstrates parameter validation and error handling in MCP tools',
}, null, 2),
},
],
};
}
// INTERMEDIATE: State management
private handleCounter(args: any) {
const { action, amount = 1 } = args;
const currentValue = this.serverState.get('counter') || 0;
let newValue = currentValue;
let message = '';
switch (action) {
case 'increment':
newValue = currentValue + amount;
this.serverState.set('counter', newValue);
message = `Incremented by ${amount}`;
break;
case 'decrement':
newValue = currentValue - amount;
this.serverState.set('counter', newValue);
message = `Decremented by ${amount}`;
break;
case 'reset':
newValue = 0;
this.serverState.set('counter', newValue);
message = 'Reset to 0';
break;
case 'get':
newValue = currentValue;
message = 'Retrieved current value';
break;
default:
throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${action}`);
}
return {
content: [
{
type: 'text',
text: JSON.stringify({
action,
previous_value: currentValue,
current_value: newValue,
message,
lesson: 'This demonstrates state management in MCP servers - data persists between calls',
}, null, 2),
},
],
};
}
// INTERMEDIATE: Array processing
private handleDataProcessor(args: any) {
const { data, operation } = args;
if (!Array.isArray(data)) {
throw new McpError(ErrorCode.InvalidParams, 'Data must be an array');
}
if (data.some(item => typeof item !== 'number')) {
throw new McpError(ErrorCode.InvalidParams, 'All data items must be numbers');
}
let result: any;
let explanation: string;
switch (operation) {
case 'sum':
result = data.reduce((acc, val) => acc + val, 0);
explanation = `Sum of ${data.length} numbers`;
break;
case 'average':
result = data.length > 0 ? data.reduce((acc, val) => acc + val, 0) / data.length : 0;
explanation = `Average of ${data.length} numbers`;
break;
case 'max':
result = Math.max(...data);
explanation = `Maximum value from ${data.length} numbers`;
break;
case 'min':
result = Math.min(...data);
explanation = `Minimum value from ${data.length} numbers`;
break;
case 'sort':
result = [...data].sort((a, b) => a - b);
explanation = `Sorted ${data.length} numbers in ascending order`;
break;
default:
throw new McpError(ErrorCode.InvalidParams, `Unknown operation: ${operation}`);
}
return {
content: [
{
type: 'text',
text: JSON.stringify({
input_data: data,
operation,
result,
explanation,
lesson: 'This demonstrates complex data processing and validation in MCP tools',
}, null, 2),
},
],
};
}
// ADVANCED: CRUD operations with complex state
private handleTaskManager(args: any) {
const { action, task, id } = args;
// Initialize tasks if not exists
if (!this.serverState.has('tasks')) {
this.serverState.set('tasks', new Map());
}
const tasks = this.serverState.get('tasks');
switch (action) {
case 'create':
if (!task || !task.title) {
throw new McpError(ErrorCode.InvalidParams, 'Task with title is required for create');
}
const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const newTask = {
id: taskId,
title: task.title,
description: task.description || '',
completed: task.completed || false,
priority: task.priority || 'medium',
created_at: new Date().toISOString(),
};
tasks.set(taskId, newTask);
return {
content: [
{
type: 'text',
text: JSON.stringify({
action: 'create',
task: newTask,
message: 'Task created successfully',
lesson: 'This demonstrates creating resources with generated IDs',
}, null, 2),
},
],
};
case 'read':
if (!id) {
throw new McpError(ErrorCode.InvalidParams, 'Task ID is required for read');
}
const foundTask = tasks.get(id);
if (!foundTask) {
throw new McpError(ErrorCode.InvalidParams, `Task with ID ${id} not found`);
}
return {
content: [
{
type: 'text',
text: JSON.stringify({
action: 'read',
task: foundTask,
lesson: 'This demonstrates reading specific resources by ID',
}, null, 2),
},
],
};
case 'update':
if (!id) {
throw new McpError(ErrorCode.InvalidParams, 'Task ID is required for update');
}
const existingTask = tasks.get(id);
if (!existingTask) {
throw new McpError(ErrorCode.InvalidParams, `Task with ID ${id} not found`);
}
const updatedTask = {
...existingTask,
...task,
id, // Ensure ID doesn't change
updated_at: new Date().toISOString(),
};
tasks.set(id, updatedTask);
return {
content: [
{
type: 'text',
text: JSON.stringify({
action: 'update',
task: updatedTask,
message: 'Task updated successfully',
lesson: 'This demonstrates updating resources with partial data',
}, null, 2),
},
],
};
case 'delete':
if (!id) {
throw new McpError(ErrorCode.InvalidParams, 'Task ID is required for delete');
}
if (!tasks.has(id)) {
throw new McpError(ErrorCode.InvalidParams, `Task with ID ${id} not found`);
}
const deletedTask = tasks.get(id);
tasks.delete(id);
return {
content: [
{
type: 'text',
text: JSON.stringify({
action: 'delete',
deleted_task: deletedTask,
message: 'Task deleted successfully',
lesson: 'This demonstrates resource deletion',
}, null, 2),
},
],
};
case 'list':
const allTasks = Array.from(tasks.values());
return {
content: [
{
type: 'text',
text: JSON.stringify({
action: 'list',
tasks: allTasks,
count: allTasks.length,
lesson: 'This demonstrates listing all resources',
}, null, 2),
},
],
};
default:
throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${action}`);
}
}
// ADVANCED: Error handling demonstration
private async handleErrorDemo(args: any) {
const { error_type } = args;
switch (error_type) {
case 'validation':
throw new McpError(ErrorCode.InvalidParams, 'This is a validation error example - invalid parameters were provided');
case 'not_found':
throw new McpError(ErrorCode.InvalidParams, 'This is a not found error example - requested resource does not exist');
case 'internal':
throw new McpError(ErrorCode.InternalError, 'This is an internal error example - something went wrong in the server');
case 'timeout':
// Simulate a timeout
await new Promise(resolve => setTimeout(resolve, 100));
throw new McpError(ErrorCode.InternalError, 'This is a timeout error example - operation took too long');
case 'none':
return {
content: [
{
type: 'text',
text: JSON.stringify({
message: 'No error occurred - this demonstrates successful error handling',
error_types: {
validation: 'InvalidParams - for bad input data',
not_found: 'InvalidParams - for missing resources',
internal: 'InternalError - for server-side problems',
timeout: 'InternalError - for operations that take too long',
},
lesson: 'Proper error handling is crucial for robust MCP servers',
}, null, 2),
},
],
};
default:
throw new McpError(ErrorCode.InvalidParams, `Unknown error type: ${error_type}`);
}
}
// ============================================================================
// LESSON 7: Resources - Providing Data to AI
// ============================================================================
private getAvailableResources() {
return [
{
uri: 'learning://mcp-concepts',
name: 'MCP Core Concepts',
description: 'Fundamental concepts of Model Context Protocol',
mimeType: 'text/plain',
},
{
uri: 'learning://server-state',
name: 'Current Server State',
description: 'Current state of the MCP learning server',
mimeType: 'application/json',
},
{
uri: 'learning://best-practices',
name: 'MCP Best Practices',
description: 'Best practices for building MCP servers',
mimeType: 'text/markdown',
},
];
}
private async handleResourceRead(request: any) {
const { uri } = request.params;
switch (uri) {
case 'learning://mcp-concepts':
return {
contents: [
{
uri,
mimeType: 'text/plain',
text: `
MCP (Model Context Protocol) Core Concepts:
1. SERVERS: Programs that provide capabilities to AI models
- Run as separate processes
- Communicate via stdio, HTTP, or other transports
- Register tools, resources, and prompts
2. TOOLS: Functions the AI can call
- Have defined input schemas (JSON Schema)
- Perform actions and return results
- Can maintain state between calls
3. RESOURCES: Data the AI can read
- Files, databases, APIs, etc.
- Identified by URIs
- Can be dynamic or static
4. PROMPTS: Templates for AI interactions
- Pre-defined conversation starters
- Can include context and instructions
- Help standardize AI interactions
5. TRANSPORTS: Communication methods
- stdio (most common for development)
- HTTP (for web services)
- Custom protocols
This learning server demonstrates all these concepts!
`,
},
],
};
case 'learning://server-state':
const state = Object.fromEntries(this.serverState);
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify({
current_state: state,
explanation: 'This shows the current internal state of the MCP server',
note: 'State persists between tool calls but is lost when server restarts',
}, null, 2),
},
],
};
case 'learning://best-practices':
return {
contents: [
{
uri,
mimeType: 'text/markdown',
text: `
# MCP Server Best Practices
## 1. Error Handling
- Always validate input parameters
- Use appropriate error codes (InvalidParams, InternalError, etc.)
- Provide clear, helpful error messages
- Handle edge cases gracefully
## 2. Schema Design
- Define clear, specific input schemas
- Use enums for limited value sets
- Include helpful descriptions
- Mark required vs optional parameters
## 3. State Management
- Keep state simple and predictable
- Consider persistence needs
- Clean up resources appropriately
- Handle concurrent access if needed
## 4. Performance
- Implement timeouts for long operations
- Limit resource usage (memory, CPU)
- Cache expensive operations when possible
- Stream large responses if supported
## 5. Security
- Validate all inputs thoroughly
- Sanitize file paths and user data
- Implement appropriate access controls
- Log security-relevant events
## 6. Documentation
- Document all tools, resources, and prompts
- Provide usage examples
- Explain complex behaviors
- Keep documentation up to date
## 7. Testing
- Test all tool combinations
- Verify error conditions
- Test with invalid inputs
- Performance test under load
`,
},
],
};
default:
throw new McpError(ErrorCode.InvalidParams, `Resource not found: ${uri}`);
}
}
// ============================================================================
// LESSON 8: Prompts - AI Interaction Templates
// ============================================================================
private getAvailablePrompts() {
return [
{
name: 'mcp-tutorial',
description: 'Interactive MCP learning tutorial',
arguments: [
{
name: 'level',
description: 'Learning level: beginner, intermediate, or advanced',
required: false,
},
],
},
{
name: 'debug-session',
description: 'Start a debugging session for MCP development',
arguments: [
{
name: 'issue',
description: 'Description of the issue you are facing',
required: true,
},
],
},
];
}
private async handlePromptRequest(request: any) {
const { name, arguments: args } = request.params;
switch (name) {
case 'mcp-tutorial':
const level = args?.level || 'beginner';
return {
description: `MCP Tutorial - ${level} level`,
messages: [
{
role: 'user',
content: {
type: 'text',
text: `I want to learn about MCP (Model Context Protocol) at the ${level} level. Please guide me through the concepts and help me understand how to build MCP servers.`,
},
},
{
role: 'assistant',
content: {
type: 'text',
text: `Great! I'll help you learn MCP at the ${level} level. Let me start by showing you what this learning server can do.
First, let me check the current server state and then demonstrate some tools based on your level:
${level === 'beginner'
? 'We\'ll start with basic concepts like simple tools and parameter handling.'
: level === 'intermediate'
? 'We\'ll cover state management, data processing, and resource handling.'
: 'We\'ll explore advanced topics like CRUD operations, error handling, and best practices.'
}
Let's begin by exploring the available tools and resources!`,
},
},
],
};
case 'debug-session':
const issue = args?.issue || 'general debugging';
return {
description: 'MCP Debugging Session',
messages: [
{
role: 'user',
content: {
type: 'text',
text: `I'm having an issue with MCP development: ${issue}. Can you help me debug this?`,
},
},
{
role: 'assistant',
content: {
type: 'text',
text: `I'll help you debug your MCP issue: "${issue}".
Let me start by gathering some information about the current server state and then we can work through the problem systematically.
Common MCP issues include:
1. Tool registration problems
2. Schema validation errors
3. Transport communication issues
4. State management bugs
5. Error handling problems
Let's investigate your specific issue step by step.`,
},
},
],
};
default:
throw new McpError(ErrorCode.InvalidParams, `Prompt not found: ${name}`);
}
}
// ============================================================================
// LESSON 9: Server Lifecycle Management
// ============================================================================
private setupErrorHandling(): void {
this.server.onerror = (error) => {
console.error('[MCP Learning Server Error]', error);
};
process.on('SIGINT', async () => {
console.error('[MCP] Shutting down gracefully...');
await this.server.close();
process.exit(0);
});
process.on('SIGTERM', async () => {
console.error('[MCP] Received SIGTERM, shutting down...');
await this.server.close();
process.exit(0);
});
}
private initializeServerState(): void {
// Initialize with some demo data
this.serverState.set('counter', 0);
this.serverState.set('server_start_time', new Date().toISOString());
this.serverState.set('tasks', new Map());
console.error('[MCP] Server state initialized');
}
// ============================================================================
// LESSON 10: Starting the Server
// ============================================================================
async run(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('π MCP Learning Server is running!');
console.error('π This server teaches MCP concepts from beginner to advanced');
console.error('π§ Available tools:', this.getAvailableTools().length);
console.error('π Available resources:', this.getAvailableResources().length);
console.error('π¬ Available prompts:', this.getAvailablePrompts().length);
console.error('');
console.error('Try these learning exercises:');
console.error('1. Call "hello_world" to understand basic tool calls');
console.error('2. Use "calculator" to learn parameter validation');
console.error('3. Try "counter" to see state management');
console.error('4. Explore "task_manager" for CRUD operations');
console.error('5. Read resources to understand MCP concepts');
console.error('6. Use prompts for guided learning sessions');
}
}
// Start the learning server
const server = new MCPLearningServer();
server.run().catch((error) => {
console.error('Failed to start MCP Learning Server:', error);
process.exit(1);
});