Build and Deploy Scalable MCP Servers with Full Backend Capabilities
Production-grade support:
✅ Authentication ✅ Multi-tenant isolation ✅ Request logging ✅ Observability & Monitoring ✅ Private Cloud deployment
Ideal for teams building:
- Agent platforms
- Customer-facing intelligent workflows
- Multi-tenant SaaS AI systems
Links
- Docs: https://docs.leanmcp.com
- Build & Deploy: https://ship.leanmcp.com
- Observability Platform: https://app.leanmcp.com
- npm packages: https://www.npmjs.com/search?q=%40leanmcp
- GitHub org: https://github.com/LeanMCP
If you're:
- An Infra / AI / DevTool developer — you're warmly welcome to contribute ideas or code.
- Building your own Agent platform — reach out for enterprise-grade deployment support.
For partnerships & business inquiries: founders@leanmcp.com
If you find this project valuable, please consider giving us a GitHub star 🌟 !
- Quick Start
- Installation & Packages
- Core Concepts
- Common Patterns
- Detailed Reference
- Contributing
- Business Collaboration
npx @leanmcp/cli create my-mcp-server
cd my-mcp-server
npm installThis generates a clean project structure:
my-mcp-server/
├── main.ts # Entry point with HTTP server
├── package.json # Dependencies
├── tsconfig.json # TypeScript config
└── mcp/ # Services directory
└── example/
└── index.ts # Example service
The generated mcp/example/index.ts shows class-based schema validation:
import { Tool, Optional, SchemaConstraint } from "@leanmcp/core";
// Define input schema as a TypeScript class
class AnalyzeSentimentInput {
@SchemaConstraint({
description: 'Text to analyze',
minLength: 1
})
text!: string;
@Optional()
@SchemaConstraint({
description: 'Language code',
enum: ['en', 'es', 'fr', 'de'],
default: 'en'
})
language?: string;
}
// Define output schema
class AnalyzeSentimentOutput {
@SchemaConstraint({ enum: ['positive', 'negative', 'neutral'] })
sentiment!: string;
@SchemaConstraint({ minimum: -1, maximum: 1 })
score!: number;
@SchemaConstraint({ minimum: 0, maximum: 1 })
confidence!: number;
}
export class SentimentService {
@Tool({
description: 'Analyze sentiment of text',
inputClass: AnalyzeSentimentInput
})
async analyzeSentiment(args: AnalyzeSentimentInput): Promise<AnalyzeSentimentOutput> {
const sentiment = this.detectSentiment(args.text);
return {
sentiment: sentiment > 0 ? 'positive' : sentiment < 0 ? 'negative' : 'neutral',
score: sentiment,
confidence: Math.abs(sentiment)
};
}
private detectSentiment(text: string): number {
// Simple keyword-based sentiment analysis
const positiveWords = ['good', 'great', 'excellent', 'amazing', 'love'];
const negativeWords = ['bad', 'terrible', 'awful', 'horrible', 'hate'];
let score = 0;
const words = text.toLowerCase().split(/\s+/);
words.forEach(word => {
if (positiveWords.includes(word)) score += 0.3;
if (negativeWords.includes(word)) score -= 0.3;
});
return Math.max(-1, Math.min(1, score));
}
}npm startYour MCP server starts on http://localhost:8080 with:
- HTTP endpoint:
http://localhost:8080/mcp - Health check:
http://localhost:8080/health
LeanMCP is modular. Start with the core packages, then add capabilities as needed.
| Package | Purpose | Install |
|---|---|---|
| @leanmcp/cli | Project scaffolding and local dev / deploy workflow | npm install -g @leanmcp/cli or npx @leanmcp/cli |
| @leanmcp/core | MCP server runtime, decorators, schema validation | npm install @leanmcp/core |
| Package | Purpose | When to use | Install |
|---|---|---|---|
| @leanmcp/auth | Authentication and access control | Real users, permissions, multi-user MCP servers | npm install @leanmcp/auth |
| @leanmcp/elicitation | Structured user input during execution | Tools need guided or multi-step input | npm install @leanmcp/elicitation |
| @leanmcp/ui | UI components for MCP Apps | Interactive MCP experiences (advanced) | npm install @leanmcp/ui |
| @leanmcp/env-injection | Request-scoped environment / secret injection | Multi-tenant secrets, per-request config | npm install @leanmcp/env-injection |
| @leanmcp/utils | Shared utilities | Extending or building on LeanMCP internals | npm install @leanmcp/utils |
npm install -g @leanmcp/clinpm install @leanmcp/core
npm install --save-dev @leanmcp/cliCore Concepts (Click to expand)
Callable functions that perform actions (like API endpoints).
class AddInput {
@SchemaConstraint({ description: 'First number' })
a!: number;
@SchemaConstraint({ description: 'Second number' })
b!: number;
}
@Tool({
description: 'Calculate sum of two numbers',
inputClass: AddInput
})
async add(input: AddInput): Promise<{ result: number }> {
return { result: input.a + input.b };
}
// Tool name: "add" (from function name)Reusable prompt templates for LLM interactions.
@Prompt({ description: 'Generate a greeting prompt' })
greetingPrompt(args: { name?: string }) {
return {
messages: [{
role: 'user',
content: { type: 'text', text: `Say hello to ${args.name || 'there'}!` }
}]
};
}
// Prompt name: "greetingPrompt" (from function name)Data endpoints that provide information (like REST GET endpoints).
@Resource({ description: 'Service statistics' })
getStats() {
return {
uptime: process.uptime(),
requestCount: 1523
};
}
// Resource URI: "servicename://getStats" (auto-generated)Common Patterns (Click to expand)
@tool("search_docs")
async searchDocs(query: string) {
return await this.vectorStore.search(query)
}@requireAuth()
@tool("get_user_data")
async getUserData() {
...
}const input = await elicit({
type: "form",
fields: [...]
})These snippets show common patterns only. Full API details live in the documentation.
CLI Commands (Click to expand)
The LeanMCP CLI provides an interactive experience for creating and managing MCP projects.
Creates a new MCP server project with interactive setup:
leanmcp create my-mcp-serverInteractive prompts:
- Auto-install dependencies (optional)
- Start dev server after creation (optional)
Generated structure:
my-mcp-server/
├── main.ts # Entry point with HTTP server
├── package.json # Project dependencies
├── tsconfig.json # TypeScript configuration
├── .gitignore # Git ignore rules
├── .dockerignore # Docker ignore rules
├── .env # Environment variables
├── .env.local # Local overrides
└── mcp/ # Services directory
└── example/
└── index.ts # Example service
Adds a new service to an existing project with auto-registration:
leanmcp add weatherWhat it does:
- Creates
mcp/weather/index.tswith boilerplate (Tool, Prompt, Resource examples) - Auto-registers the service in
main.ts - Ready to customize and use immediately
For complete CLI documentation including all commands, options, and advanced usage, see @leanmcp/cli README.
Decorators (Click to expand)
| Decorator | Purpose | Usage |
|---|---|---|
@Tool |
Callable function | @Tool({ description?: string, inputClass?: Class }) |
@Prompt |
Prompt template | @Prompt({ description?: string }) |
@Resource |
Data endpoint | @Resource({ description?: string }) |
| Decorator | Purpose | Usage |
|---|---|---|
@Optional |
Mark property as optional | Property decorator |
@SchemaConstraint |
Add validation rules | Property decorator with constraints |
Available Constraints:
- String:
minLength,maxLength,pattern,enum,format,description,default - Number:
minimum,maximum,description,default - Array:
minItems,maxItems,description - Common:
description,default
Example:
class UserInput {
@SchemaConstraint({
description: 'User email address',
format: 'email'
})
email!: string;
@Optional()
@SchemaConstraint({
description: 'User age',
minimum: 18,
maximum: 120
})
age?: number;
@SchemaConstraint({
description: 'User roles',
enum: ['admin', 'user', 'guest'],
default: 'user'
})
role!: string;
}Project Structure (Click to expand)
Simplified API (Recommended):
import { createHTTPServer } from "@leanmcp/core";
// Services are automatically discovered from ./mcp directory
await createHTTPServer({
name: "my-mcp-server",
version: "1.0.0",
port: 8080,
cors: true,
logging: true
});Factory Pattern (Advanced):
import { createHTTPServer, MCPServer } from "@leanmcp/core";
import { ExampleService } from "./mcp/example/index.js";
const serverFactory = async () => {
const server = new MCPServer({
name: "my-mcp-server",
version: "1.0.0",
autoDiscover: false
});
server.registerService(new ExampleService());
return server.getServer();
};
await createHTTPServer(serverFactory, {
port: 8080,
cors: true
});import { Tool, Prompt, Resource } from "@leanmcp/core";
class ToolInput {
@SchemaConstraint({ description: 'Input parameter' })
param!: string;
}
export class ServiceName {
@Tool({
description: 'Tool description',
inputClass: ToolInput
})
async toolMethod(args: ToolInput) {
// Tool implementation
return { result: 'success' };
}
@Prompt({ description: 'Prompt description' })
promptMethod(args: { param?: string }) {
// Prompt implementation
return {
messages: [{
role: 'user',
content: { type: 'text', text: 'Prompt text' }
}]
};
}
@Resource({ description: 'Resource description' })
resourceMethod() {
// Resource implementation
return { data: 'value' };
}
}API Reference (Click to expand)
Creates and starts an HTTP server with MCP support.
Simplified API (Recommended):
await createHTTPServer({
name: string; // Server name (required)
version: string; // Server version (required)
port?: number; // Port number (default: 3001)
cors?: boolean | object; // Enable CORS (default: false)
logging?: boolean; // Enable logging (default: false)
debug?: boolean; // Enable debug logs (default: false)
autoDiscover?: boolean; // Auto-discover services (default: true)
mcpDir?: string; // Custom mcp directory path (optional)
sessionTimeout?: number; // Session timeout in ms (optional)
});Example:
import { createHTTPServer } from "@leanmcp/core";
// Services automatically discovered from ./mcp directory
await createHTTPServer({
name: "my-mcp-server",
version: "1.0.0",
port: 3000,
cors: true,
logging: true
});Factory Pattern (Advanced):
import { createHTTPServer, MCPServer } from "@leanmcp/core";
import { MyService } from "./mcp/myservice/index.js";
const serverFactory = async () => {
const server = new MCPServer({
name: "my-mcp-server",
version: "1.0.0",
autoDiscover: false
});
server.registerService(new MyService());
return server.getServer();
};
await createHTTPServer(serverFactory, {
port: 3000,
cors: true
});Main server class for manual service registration.
Constructor Options:
const server = new MCPServer({
name: string; // Server name (required)
version: string; // Server version (required)
logging?: boolean; // Enable logging (default: false)
debug?: boolean; // Enable debug logs (default: false)
autoDiscover?: boolean; // Auto-discover services (default: true)
mcpDir?: string; // Custom mcp directory path (optional)
});Methods:
registerService(instance)- Manually register a service instancegetServer()- Get the underlying MCP SDK server
Example:
import { MCPServer } from "@leanmcp/core";
const server = new MCPServer({
name: "my-server",
version: "1.0.0",
autoDiscover: false
});
server.registerService(new WeatherService());
server.registerService(new PaymentService());Examples (Click to expand)
import { Tool, Prompt, Resource, SchemaConstraint, Optional } from "@leanmcp/core";
class WeatherInput {
@SchemaConstraint({
description: 'City name',
minLength: 1
})
city!: string;
@Optional()
@SchemaConstraint({
description: 'Units',
enum: ['metric', 'imperial'],
default: 'metric'
})
units?: string;
}
class WeatherOutput {
@SchemaConstraint({ description: 'Temperature value' })
temperature!: number;
@SchemaConstraint({
description: 'Weather conditions',
enum: ['sunny', 'cloudy', 'rainy', 'snowy']
})
conditions!: string;
@SchemaConstraint({
description: 'Humidity percentage',
minimum: 0,
maximum: 100
})
humidity!: number;
}
export class WeatherService {
@Tool({
description: 'Get current weather for a city',
inputClass: WeatherInput
})
async getCurrentWeather(args: WeatherInput): Promise<WeatherOutput> {
// Simulate API call
return {
temperature: 72,
conditions: 'sunny',
humidity: 65
};
}
@Prompt({ description: 'Generate weather query prompt' })
weatherPrompt(args: { city?: string }) {
return {
messages: [{
role: 'user',
content: {
type: 'text',
text: `What's the weather forecast for ${args.city || 'the city'}?`
}
}]
};
}
@Resource({ description: 'Supported cities list' })
getSupportedCities() {
return {
cities: ['New York', 'London', 'Tokyo', 'Paris', 'Sydney'],
count: 5
};
}
}import { Tool, SchemaConstraint } from "@leanmcp/core";
class CalculatorInput {
@SchemaConstraint({
description: 'First number',
minimum: -1000000,
maximum: 1000000
})
a!: number;
@SchemaConstraint({
description: 'Second number',
minimum: -1000000,
maximum: 1000000
})
b!: number;
}
class CalculatorOutput {
@SchemaConstraint({ description: 'Calculation result' })
result!: number;
}
export class CalculatorService {
@Tool({
description: 'Add two numbers',
inputClass: CalculatorInput
})
async add(args: CalculatorInput): Promise<CalculatorOutput> {
return { result: args.a + args.b };
}
@Tool({
description: 'Subtract two numbers',
inputClass: CalculatorInput
})
async subtract(args: CalculatorInput): Promise<CalculatorOutput> {
return { result: args.a - args.b };
}
@Tool({
description: 'Multiply two numbers',
inputClass: CalculatorInput
})
async multiply(args: CalculatorInput): Promise<CalculatorOutput> {
return { result: args.a * args.b };
}
@Tool({
description: 'Divide two numbers',
inputClass: CalculatorInput
})
async divide(args: CalculatorInput): Promise<CalculatorOutput> {
if (args.b === 0) {
throw new Error('Division by zero');
}
return { result: args.a / args.b };
}
}import { Tool, SchemaConstraint } from "@leanmcp/core";
import { AuthProvider, Authenticated } 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 class - no token field needed
class SendMessageInput {
@SchemaConstraint({
description: 'Channel to send message to',
minLength: 1
})
channel!: string;
@SchemaConstraint({
description: 'Message text',
minLength: 1
})
text!: string;
}
// Protect entire service with authentication
@Authenticated(authProvider)
export class SlackService {
@Tool({
description: 'Send message to Slack channel',
inputClass: SendMessageInput
})
async sendMessage(args: SendMessageInput) {
// Token automatically validated from _meta.authorization.token
// Only business arguments are passed here
return {
success: true,
channel: args.channel,
timestamp: Date.now().toString()
};
}
}Client Usage:
// Call with authentication
await mcpClient.callTool({
name: "sendMessage",
arguments: {
channel: "#general",
text: "Hello world"
},
_meta: {
authorization: {
type: "bearer",
token: "your-jwt-token"
}
}
});See examples/slack-with-auth for a complete working example.
Development (Click to expand)
# Clone the repository
git clone https://github.com/leanmcp/leanmcp-sdk.git
cd leanmcp-sdk
# Install dependencies
npm install
# Build all packages
npm run buildleanmcp-sdk/
├── package.json # Root workspace config
├── tsconfig.base.json # Shared TypeScript config
├── turbo.json # Turborepo configuration
└── packages/
├── cli/ # @leanmcp/cli - CLI binary
├── core/ # @leanmcp/core - Core decorators & runtime
├── auth/ # @leanmcp/auth - Authentication with @Authenticated decorator
└── utils/ # @leanmcp/utils - Utilities (planned)
# Build core package
cd packages/core
npm run build
# Build CLI package
cd packages/cli
npm run build# Create a test project
npx @leanmcp/cli create test-project
cd test-project
# Link local development version
npm link ../../packages/core
npm link ../../packages/cli
# Run the test project
npm start- Compile-time validation - Catch errors before runtime
- Autocomplete - Full IntelliSense support in VS Code
- Refactoring - Safe renames and changes across your codebase
- No duplication - Define schemas once using TypeScript types
- Type inference - Automatic schema generation from decorators
We Actively Welcome Contributors!
New to open source? Perfect! We have plenty of good first issues waiting for you.
|
Fork & Contribute
|
Good First Issues
|
Join Community Chat with maintainers and contributors |
- Documentation: Help make our guides clearer
- Examples: Add new service examples (weather, payments, etc.)
- Auth Integrations: Add support for new auth providers
- Bug Fixes: Fix reported issues
- Tests: Improve test coverage
- Features: Propose and implement new capabilities
See our Contributing Guide for detailed instructions.
# Run tests
npm test
# Run linter
npm run lint
# Build all packages
npm run buildThe LeanMCP founding team provides enterprise service support:
- MCP Agent deployment support (SDK + CLI + deployment environment configuration)
- Observability runtime platform (view agent call traces / usage behavior)
- Private deployment solutions (on-demand integration with your private cloud / intranet)
Contact us: founders@leanmcp.com
MIT License - see LICENSE file for details
- Built on top of Model Context Protocol (MCP)
- Uses reflect-metadata for decorator support
- Inspired by the amazing MCP community
