Skip to content

Production-ready TypeScript SDK for MCP servers: auth, multi-tenant, observability. Build enterprise AI agents fast.

License

Notifications You must be signed in to change notification settings

LeanMCP/leanmcp-sdk

Repository files navigation

LeanMCP Logo

MIT License Documentation Website Follow on X Discord Community
Python repo PyPI version PyPI downloads Python docs
TypeScript repo npm version npm downloads

LeanMCP SDK: Production-Ready Infra for MCP Servers.

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


Thanks for your Interest !

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 🌟 !


Table of Contents


Quick Start

1. Create a new project

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

2. Define your 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));
  }
}

3. Run your server

npm start

Your MCP server starts on http://localhost:8080 with:

  • HTTP endpoint: http://localhost:8080/mcp
  • Health check: http://localhost:8080/health

Installation & Packages

LeanMCP is modular. Start with the core packages, then add capabilities as needed.

Required for every MCP server

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

Optional capability packages

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

Global CLI Installation

npm install -g @leanmcp/cli

Project-Level Installation

npm install @leanmcp/core
npm install --save-dev @leanmcp/cli

Core Concepts (Click to expand)

Tools

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)

Prompts

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)

Resources

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)

Define a tool

@tool("search_docs")
async searchDocs(query: string) {
  return await this.vectorStore.search(query)
}

Require authentication

@requireAuth()
@tool("get_user_data")
async getUserData() {
  ...
}

Ask for structured input

const input = await elicit({
  type: "form",
  fields: [...]
})

These snippets show common patterns only. Full API details live in the documentation.


Detailed Reference

CLI Commands (Click to expand)

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:

  • 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

leanmcp add <service-name>

Adds a new service to an existing project with auto-registration:

leanmcp add weather

What it does:

  • Creates mcp/weather/index.ts with boilerplate (Tool, Prompt, Resource examples)
  • Auto-registers the service in main.ts
  • Ready to customize and use immediately

More CLI Features

For complete CLI documentation including all commands, options, and advanced usage, see @leanmcp/cli README.

Decorators (Click to expand)

Core Decorators

Decorator Purpose Usage
@Tool Callable function @Tool({ description?: string, inputClass?: Class })
@Prompt Prompt template @Prompt({ description?: string })
@Resource Data endpoint @Resource({ description?: string })

Schema Decorators

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)

Main Entry Point (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
});

Service Structure (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' };
  }
}
API Reference (Click to expand)

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
});

MCPServer

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 instance
  • getServer() - 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)

Complete Weather Service

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
    };
  }
}

Calculator Service with Validation

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 };
  }
}

Authenticated Service with AWS Cognito

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)

Setting Up the Monorepo

# 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

Monorepo Structure

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)

Building Individual Packages

# Build core package
cd packages/core
npm run build

# Build CLI package
cd packages/cli
npm run build

Testing Your Changes

# 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

Type Safety Benefits

  • 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

Contributing

We Actively Welcome Contributors!

If LeanMCP is useful to you, please give us a star!

GitHub stars GitHub forks

Contributing is Easy

New to open source? Perfect! We have plenty of good first issues waiting for you.

Fork & Contribute

  1. Fork the repo
  2. Create a branch
  3. Make changes
  4. Submit PR

Fork Now →

Good First Issues

  • Documentation improvements
  • Example additions
  • Auth provider integrations
  • Test coverage

Browse Issues →

Join Community

Chat with maintainers and contributors

Join Discord →

What You Can Contribute

  • 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.

Development Workflow

# Run tests
npm test

# Run linter
npm run lint

# Build all packages
npm run build

Business Collaboration: What Can We Do for You?

The 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


License

MIT License - see LICENSE file for details

Links

Acknowledgments