by LeanMCP
A TypeScript toolkit for building Model Context Protocol servers with built‑in authentication, multi‑tenancy, payment integration, monitoring, and UI components for ChatGPT apps.
LeanMCP SDK enables developers to create enterprise‑grade MCP servers quickly. It supplies decorators for tools, prompts, and resources, automatic schema validation, and out‑of‑the‑box support for authentication providers (Auth0, Cognito, Supabase, Firebase), multi‑tenant API keys, Stripe billing, and UI libraries for interactive ChatGPT applications.
npx @leanmcp/cli create my-server
cd my-server
npm install @leanmcp/core @leanmcp/cli
mcp/ directory using @Tool, @Prompt, and @Resource decorators.npm start # runs the generated HTTP server on localhost:8080
POST /mcp with the tool name and arguments, optionally adding _meta.authorization for protected endpoints.@Tool, @Prompt, @Resource with TypeScript‑first schema validation.@Authenticated guard for services.Q: Do I need to write OpenAPI specs manually?
A: No. Input and output schemas are defined via TypeScript classes and @SchemaConstraint decorators; the SDK generates the necessary validation automatically.
Q: Can I run the server in a Docker container?
A: Yes. The generated project includes a Dockerfile template; build it with docker build -t my-mcp . and run docker run -p 8080:8080 my-mcp.
Q: How is authentication configured?
A: Install @leanmcp/auth, create an AuthProvider instance (e.g., Cognito), call await authProvider.init(), and apply @Authenticated(authProvider) to services or individual tools.
Q: What runtime does the server use?
A: A lightweight Express‑based HTTP server created by createHTTPServer from @leanmcp/core.
Q: Is there support for other transport protocols? A: The core library also offers gRPC and WebSocket adapters; see the API reference for configuration details.
Q: How do I add a new service after the project is created?
A: Use the CLI command npx @leanmcp/cli add <service-name> which scaffolds the service file and registers it automatically.
npx @leanmcp/cli create my-server
cd my-server
npm run dev
Your MCP server is now running with schema validation, resources, and prompt capabilities.
• Start with basic tools
• Add authentication
• Schema validation
• User-level API keys
• Permission management
• Session handling
• HTTP transport
• Monitoring & observability
• Production scaling
• Build ChatGPT Apps
• UI components library
• Interactive interfaces
Other MCP SDKs: Just connect tools to AI agents - LeanMCP: Enterprise features out of the box
| Feature | Basic MCP | LeanMCP | Time Saved |
|---|---|---|---|
| Basic Tools | ✅ | ✅ | - |
| Authentication | ❌ | ✅ | 2-3 weeks |
| Payment Integration | ❌ | ✅ | 1-2 weeks |
| UI Components | ❌ | ✅ | 1-2 weeks |
| Enterprise Deploy | ❌ | ✅ | 1 week |
| Monitoring & Logs | ❌ | ✅ | 1 week |
npm install -g @leanmcp/cli
npm install @leanmcp/core
npm install --save-dev @leanmcp/cli
npx @leanmcp/cli create my-mcp-server
cd my-mcp-server
npm install
This 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 start
Your MCP server starts on http://localhost:8080 with:
http://localhost:8080/mcphttp://localhost:8080/healthCallable 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)
The LeanMCP CLI provides an interactive experience for creating and managing MCP projects.
leanmcp create <project-name>Creates a new MCP server project with interactive setup:
leanmcp create my-mcp-server
Interactive prompts:
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
leanmcp add <service-name>Adds a new service to an existing project with auto-registration:
leanmcp add weather
What it does:
mcp/weather/index.ts with boilerplate (Tool, Prompt, Resource examples)main.tsFor complete CLI documentation including all commands, options, and advanced usage, see @leanmcp/cli README.
| 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:
minLength, maxLength, pattern, enum, format, description, defaultminimum, maximum, description, defaultminItems, maxItems, descriptiondescription, defaultExample:
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;
}
main.ts)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
});
mcp/service-name/index.ts)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' };
}
}
createHTTPServer(options | serverFactory, options?)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
});
MCPServerMain 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 serverExample:
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());
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.
# Clone the repository
git clone https://github.com/leanmcp/leanmcp-sdk.git
cd leanmcp-sdk
# Install dependencies
npm install
# Build all packages
npm run build
leanmcp-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
Contributions are welcome! Please follow these guidelines:
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)# Run tests
npm test
# Run linter
npm run lint
# Build all packages
npm run build
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
See our Contributing Guide for detailed instructions.
MIT License - see LICENSE file for details
Please log in to share your review and rating for this MCP.
Explore related MCPs that share similar capabilities and solve comparable challenges
by modelcontextprotocol
A Model Context Protocol server for Git repository interaction and automation.
by zed-industries
A high‑performance, multiplayer code editor designed for speed and collaboration.
by modelcontextprotocol
Model Context Protocol Servers
by modelcontextprotocol
A Model Context Protocol server that provides time and timezone conversion capabilities.
by cline
An autonomous coding assistant that can create and edit files, execute terminal commands, and interact with a browser directly from your IDE, operating step‑by‑step with explicit user permission.
by upstash
Provides up-to-date, version‑specific library documentation and code examples directly inside LLM prompts, eliminating outdated information and hallucinated APIs.
by daytonaio
Provides a secure, elastic infrastructure that creates isolated sandboxes for running AI‑generated code with sub‑90 ms startup, unlimited persistence, and OCI/Docker compatibility.
by continuedev
Enables faster shipping of code by integrating continuous AI agents across IDEs, terminals, and CI pipelines, offering chat, edit, autocomplete, and customizable agent workflows.
by github
Connects AI tools directly to GitHub, enabling natural‑language interactions for repository browsing, issue and pull‑request management, CI/CD monitoring, code‑security analysis, and team collaboration.
{
"mcpServers": {
"leanmcp-sdk": {
"command": "npx",
"args": [
"@leanmcp/cli",
"create",
"my-server"
],
"env": {}
}
}
}claude mcp add leanmcp-sdk npx @leanmcp/cli create my-server