Skip to main content

Examples

Complete, production-ready examples demonstrating various features of the LeanMCP SDK.

Basic MCP Server

A minimal MCP server with a simple tool.
import { createHTTPServer, MCPServer, Tool, SchemaConstraint } from "@leanmcp/core";

// Define input schema
class CalculateInput {
  @SchemaConstraint({ description: 'First number' })
  a!: number;
  
  @SchemaConstraint({ description: 'Second number' })
  b!: number;
  
  @SchemaConstraint({ 
    description: 'Operation',
    enum: ['add', 'subtract', 'multiply', 'divide']
  })
  operation!: string;
}

// Service with tools
export class CalculatorService {
  @Tool({ 
    description: 'Perform arithmetic operations',
    inputClass: CalculateInput
  })
  async calculate(input: CalculateInput) {
    let result: number;
    
    switch (input.operation) {
      case 'add':
        result = input.a + input.b;
        break;
      case 'subtract':
        result = input.a - input.b;
        break;
      case 'multiply':
        result = input.a * input.b;
        break;
      case 'divide':
        if (input.b === 0) throw new Error('Division by zero');
        result = input.a / input.b;
        break;
      default:
        throw new Error('Invalid operation');
    }
    
    return { result };
  }
}

// Create and start server
const serverFactory = () => {
  const server = new MCPServer({
    name: "calculator-server",
    version: "1.0.0",
    logging: true
  });
  
  server.registerService(new CalculatorService());
  return server.getServer();
};

await createHTTPServer(serverFactory, { port: 3000, cors: true });

Weather Service

MCP server that fetches weather data from an external API.
import { Tool, SchemaConstraint, Optional } from "@leanmcp/core";
import { retry, formatResponse } from "@leanmcp/utils";

class GetWeatherInput {
  @SchemaConstraint({ 
    description: 'City name',
    minLength: 1
  })
  city!: string;
  
  @Optional()
  @SchemaConstraint({ 
    description: 'Country code (ISO 3166)',
    pattern: '^[A-Z]{2}$'
  })
  country?: string;
  
  @Optional()
  @SchemaConstraint({ 
    description: 'Temperature unit',
    enum: ['celsius', 'fahrenheit'],
    default: 'celsius'
  })
  unit?: string;
}

export class WeatherService {
  private apiKey = process.env.WEATHER_API_KEY!;
  
  @Tool({ 
    description: 'Get current weather for a city',
    inputClass: GetWeatherInput
  })
  async getWeather(input: GetWeatherInput) {
    try {
      const location = input.country 
        ? `${input.city},${input.country}`
        : input.city;
      
      const units = input.unit === 'fahrenheit' ? 'imperial' : 'metric';
      
      // Fetch with retry logic
      const data = await retry(
        async () => {
          const response = await fetch(
            `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=${units}&appid=${this.apiKey}`
          );
          
          if (!response.ok) {
            throw new Error(`Weather API error: ${response.statusText}`);
          }
          
          return response.json();
        },
        { maxRetries: 3, delayMs: 1000 }
      );
      
      const temp = Math.round(data.main.temp);
      const unit = input.unit === 'fahrenheit' ? '°F' : '°C';
      
      return {
        content: [{
          type: "text",
          text: `Weather in ${data.name}: ${data.weather[0].description}, ${temp}${unit}`
        }]
      };
      
    } catch (error) {
      return {
        content: [{
          type: "text",
          text: `Failed to fetch weather: ${error.message}`
        }],
        isError: true
      };
    }
  }
}

Authenticated Service with AWS Cognito

MCP server with AWS Cognito authentication for protected operations.
import { Tool, SchemaConstraint, Optional } from "@leanmcp/core";
import { Authenticated, AuthProvider } from "@leanmcp/auth";

// Initialize auth provider
const authProvider = new AuthProvider('cognito', {
  region: process.env.AWS_REGION,
  userPoolId: process.env.COGNITO_USER_POOL_ID,
  clientId: process.env.COGNITO_CLIENT_ID
});
await authProvider.init();

// Input schemas
class CreatePostInput {
  @SchemaConstraint({ description: 'Post title', minLength: 1, maxLength: 200 })
  title!: string;
  
  @SchemaConstraint({ description: 'Post content', minLength: 1 })
  content!: string;
  
  @Optional()
  @SchemaConstraint({ description: 'Post tags' })
  tags?: string[];
}

class GetPostsInput {
  @Optional()
  @SchemaConstraint({ description: 'Author user ID' })
  authorId?: string;
  
  @Optional()
  @SchemaConstraint({ description: 'Maximum number of posts', minimum: 1, maximum: 100, default: 10 })
  limit?: number;
}

// Blog service with authentication
export class BlogService {
  private posts: any[] = [];
  
  @Tool({ description: 'Create a new blog post (requires authentication)' })
  @Authenticated(authProvider)
  async createPost(input: CreatePostInput) {
    // Token is validated via _meta.authorization.token
    const post = {
      id: `post-${Date.now()}`,
      title: input.title,
      content: input.content,
      tags: input.tags || [],
      createdAt: new Date().toISOString()
    };
    
    this.posts.push(post);
    
    return {
      success: true,
      post
    };
  }
  
  @Tool({ description: 'Get blog posts (public)' })
  async getPosts(input: GetPostsInput) {
    let filtered = this.posts;
    
    if (input.authorId) {
      filtered = filtered.filter(p => p.authorId === input.authorId);
    }
    
    const limit = input.limit || 10;
    const posts = filtered.slice(0, limit);
    
    return {
      posts,
      total: filtered.length
    };
  }
  
  @Tool({ description: 'Delete a blog post (requires authentication)' })
  @Authenticated(authProvider)
  async deletePost(input: { postId: string }) {
    const postIndex = this.posts.findIndex(p => p.id === input.postId);
    
    if (postIndex === -1) {
      throw new Error('Post not found');
    }
    
    this.posts.splice(postIndex, 1);
    
    return {
      success: true,
      message: 'Post deleted successfully'
    };
  }
}

Service with Elicitation

MCP server using elicitation to collect input from users.
import { Tool, SchemaConstraint, Optional } from "@leanmcp/core";
import { Elicitation } from "@leanmcp/elicitation";

class SlackService {
  @Tool({ description: "Create a new Slack channel" })
  @Elicitation({
    title: "Create Channel",
    description: "Please provide channel details",
    fields: [
      {
        name: "channelName",
        label: "Channel Name",
        type: "text",
        required: true,
        validation: {
          pattern: "^[a-z0-9-]+$",
          errorMessage: "Must be lowercase alphanumeric with hyphens"
        }
      },
      {
        name: "isPrivate",
        label: "Private Channel",
        type: "boolean",
        defaultValue: false
      }
    ]
  })
  async createChannel(args: { channelName: string; isPrivate: boolean }) {
    return {
      success: true,
      channelId: `C${Date.now()}`,
      channelName: args.channelName
    };
  }
}

Database Service with Resources

MCP server providing both tools and resources.
import { Tool, Resource, Prompt, SchemaConstraint } from "@leanmcp/core";

interface User {
  id: string;
  name: string;
  email: string;
  createdAt: string;
}

export class DatabaseService {
  private users: User[] = [
    { id: '1', name: 'John Doe', email: '[email protected]', createdAt: '2024-01-01' },
    { id: '2', name: 'Jane Smith', email: '[email protected]', createdAt: '2024-01-02' }
  ];
  
  // Tool: Query users
  @Tool({ 
    description: 'Search users by name or email',
    inputClass: class {
      @SchemaConstraint({ description: 'Search query' })
      query!: string;
    }
  })
  async searchUsers(input: { query: string }) {
    const query = input.query.toLowerCase();
    const results = this.users.filter(u => 
      u.name.toLowerCase().includes(query) || 
      u.email.toLowerCase().includes(query)
    );
    
    return { users: results, count: results.length };
  }
  
  // Resource: Get all users
  @Resource({ 
    description: 'Get all users in the database',
    mimeType: 'application/json'
  })
  async getAllUsers() {
    return {
      users: this.users,
      total: this.users.length,
      lastUpdated: new Date().toISOString()
    };
  }
  
  // Resource: Get database stats
  @Resource({ 
    description: 'Get database statistics',
    mimeType: 'application/json'
  })
  async getDatabaseStats() {
    return {
      totalUsers: this.users.length,
      oldestUser: this.users[0]?.createdAt,
      newestUser: this.users[this.users.length - 1]?.createdAt
    };
  }
  
  // Prompt: Generate user report
  @Prompt({ description: 'Generate a user report prompt' })
  userReport(input: { userId: string }) {
    const user = this.users.find(u => u.id === input.userId);
    
    if (!user) {
      throw new Error('User not found');
    }
    
    return {
      messages: [{
        role: "user",
        content: {
          type: "text",
          text: `Generate a detailed report for user: ${user.name} (${user.email}). Include account age and activity summary.`
        }
      }]
    };
  }
}

Multi-Service Server

Complete server with multiple services.
import { createHTTPServer, MCPServer } from "@leanmcp/core";
import { CalculatorService } from "./services/calculator";
import { WeatherService } from "./services/weather";
import { BlogService } from "./services/blog";
import { DatabaseService } from "./services/database";

const serverFactory = () => {
  const server = new MCPServer({
    name: "multi-service-mcp",
    version: "1.0.0",
    logging: true
  });
  
  // Register all services
  server.registerService(new CalculatorService());
  server.registerService(new WeatherService());
  server.registerService(new BlogService());
  server.registerService(new DatabaseService());
  
  return server.getServer();
};

// Start server
const PORT = process.env.PORT || 3000;

await createHTTPServer(serverFactory, {
  port: PORT,
  cors: true,
  logging: true
});

console.log(`🚀 MCP Server running on http://localhost:${PORT}`);

Testing Your Server

Using cURL

# List all tools
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/list",
    "id": 1
  }'

# Call a tool
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
      "name": "calculate",
      "arguments": {
        "a": 10,
        "b": 5,
        "operation": "add"
      }
    },
    "id": 2
  }'

# Call authenticated tool (with _meta)
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
      "name": "createPost",
      "arguments": {
        "title": "My First Post",
        "content": "Hello, World!"
      },
      "_meta": {
        "authorization": {
          "type": "bearer",
          "token": "YOUR_COGNITO_TOKEN"
        }
      }
    },
    "id": 3
  }'

Using TypeScript Client

async function callMCPTool(toolName: string, args: any, token?: string) {
  const params: any = {
    name: toolName,
    arguments: args
  };
  
  // Add authentication if token provided
  if (token) {
    params._meta = {
      authorization: {
        type: "bearer",
        token: token
      }
    };
  }
  
  const response = await fetch('http://localhost:3000/mcp', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      jsonrpc: '2.0',
      method: 'tools/call',
      params,
      id: Date.now()
    })
  });
  
  return response.json();
}

// Use it
const result = await callMCPTool('calculate', {
  a: 10,
  b: 5,
  operation: 'multiply'
});

console.log(result);

Next Steps