Examples
Complete, production-ready examples demonstrating various features of the LeanMCP SDK.Basic MCP Server
A minimal MCP server with a simple tool.Copy
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.Copy
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.Copy
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.Copy
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.Copy
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.Copy
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
Copy
# 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
Copy
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);