Leanmcp Builder logo

Leanmcp Builder

OrganizationPopular
Leanmcp
leanmcp-builder

Build MCP servers using the Leanmcp SDK with decorator-based TypeScript. Use this skill when users ask for "leanmcp", "MCP with decorators", "MCP with authentication", "MCP with elicitation", "MCP with environment injection", or want a simpler, more elegant way to build MCP servers. Leanmcp provides automatic schema generation, dependency injection, authentication, and user input collection.

Overview

PublisherLeanmcp
Repositorygateway-skills
Skill nameleanmcp-builder
Stars
2.1K
Forks
2.1K
Bundled files
5
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 5 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by Leanmcp on GitHub. Read the source before you install it.

Installation

Install the Leanmcp Builder AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/Leanmcp/gateway-skills/tree/main/skills/leanmcp-builder
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/Leanmcp/gateway-skills.git /tmp/gateway-skills
mkdir -p .claude/skills
cp -r /tmp/gateway-skills/skills/leanmcp-builder .claude/skills/leanmcp-builder
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Leanmcp Builder in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Leanmcp Builder on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Leanmcp Builder is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Leanmcp Builder Skill

This skill guides you in building MCP servers using the Leanmcp SDK - a decorator-based TypeScript framework for elegant MCP development.

When to Use This Skill

  • User asks for "leanmcp server"
  • User wants "MCP with decorators"
  • User needs "authenticated MCP server"
  • User wants "simpler MCP development"
  • User mentions "@leanmcp/core"
  • User needs "user input collection" or "elicitation"
  • User wants "environment injection" for multi-tenant secrets

Leanmcp vs Vanilla MCP

FeatureVanilla MCPLeanmcp SDK
Tool definitionManual schema@Tool decorator with auto-schema
Input validationManualAutomatic with @SchemaConstraint
Service discoveryManual registrationAuto-discovery from ./mcp directory
AuthenticationDIY@Authenticated decorator
User inputNot built-in@Elicitation decorator
SecretsDIY@RequireEnv + getEnv()

Core Packages

bash
# Minimal setup
npm install @leanmcp/core

# With authentication
npm install @leanmcp/auth

# With user input forms
npm install @leanmcp/elicitation

# With user secrets
npm install @leanmcp/env-injection

# CLI (global or dev)
npm install -g @leanmcp/cli

Project Structure

my-leanmcp-server/
├── main.ts                 # Entry point (minimal)
├── mcp/                    # Auto-discovered services
│   ├── example/
│   │   └── index.ts        # Exports ExampleService
│   └── myservice/
│       └── index.ts        # Exports MyService
├── package.json
├── tsconfig.json
└── .env

Required package.json

json
{
  "name": "my-leanmcp-server",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "leanmcp dev",
    "start": "leanmcp start",
    "build": "leanmcp build"
  },
  "dependencies": {
    "@leanmcp/core": "latest",
    "dotenv": "^16.5.0"
  },
  "devDependencies": {
    "@leanmcp/cli": "latest",
    "@types/node": "^20.0.0",
    "typescript": "^5.6.3"
  }
}

TypeScript Configuration

json
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "node",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["**/*.ts"]
}

Entry Point (main.ts)

typescript
import dotenv from 'dotenv';
import { createHTTPServer } from '@leanmcp/core';

dotenv.config();

// Services are automatically discovered from ./mcp directory
await createHTTPServer({
  name: 'my-leanmcp-server',
  version: '1.0.0',
  port: 3001,
  cors: true,
  logging: true,
});

@Tool Decorator

Marks a method as an MCP tool with automatic schema generation.

Basic Tool

typescript
import { Tool } from '@leanmcp/core';

export class MyService {
  @Tool({ description: 'Echo a message back' })
  async echo(args: { message: string }) {
    return { echoed: args.message };
  }
}

Tool with Input Class (Recommended)

typescript
import { Tool, SchemaConstraint, Optional } from '@leanmcp/core';

class CalculateInput {
  @SchemaConstraint({ description: 'First number', minimum: -1000000 })
  a!: number;

  @SchemaConstraint({ description: 'Second number', minimum: -1000000 })
  b!: number;

  @Optional()
  @SchemaConstraint({ 
    description: 'Operation to perform',
    enum: ['add', 'subtract', 'multiply', 'divide'],
    default: 'add'
  })
  operation?: string;
}

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

Tool Naming

Tool name is derived from the method name:

  • async calculate(...) -> tool name: calculate
  • async sendMessage(...) -> tool name: sendMessage

@Resource Decorator

typescript
import { Resource } from '@leanmcp/core';

export class InfoService {
  @Resource({ description: 'Get server information' })
  async serverInfo() {
    return {
      contents: [{
        uri: 'server://info',
        mimeType: 'application/json',
        text: JSON.stringify({
          name: 'my-server',
          version: '1.0.0',
          uptime: process.uptime()
        })
      }]
    };
  }
}

@Prompt Decorator

typescript
import { Prompt } from '@leanmcp/core';

export class PromptService {
  @Prompt({ description: 'Generate a greeting prompt' })
  async greeting(args: { name?: string }) {
    return {
      messages: [{
        role: 'user' as const,
        content: {
          type: 'text' as const,
          text: `Hello ${args.name || 'there'}! Welcome to my server.`
        }
      }]
    };
  }
}

@Authenticated Decorator

Add authentication to your service:

typescript
import { Tool } from '@leanmcp/core';
import { Authenticated, AuthProvider, authUser } from '@leanmcp/auth';

const authProvider = new AuthProvider('cognito', {
  region: 'us-east-1',
  userPoolId: 'us-east-1_XXXXXXXXX',
  clientId: 'your-client-id'
});
await authProvider.init();

@Authenticated(authProvider)
export class SecureService {
  @Tool({ description: 'Get user data' })
  async getUserData() {
    // authUser is automatically available
    return { 
      userId: authUser.sub,
      email: authUser.email 
    };
  }
}

Supported providers: AWS Cognito, Clerk, Auth0, Leanmcp

@Elicitation Decorator

Collect structured user input:

typescript
import { Tool } from '@leanmcp/core';
import { Elicitation } from '@leanmcp/elicitation';

export class ChannelService {
  @Tool({ description: 'Create a new channel' })
  @Elicitation({
    title: 'Channel Details',
    fields: [
      { name: 'channelName', label: 'Channel Name', type: 'text', required: true },
      { name: 'isPrivate', label: 'Private Channel', type: 'boolean', defaultValue: false },
      { name: 'topic', label: 'Topic', type: 'textarea' }
    ]
  })
  async createChannel(args: { channelName: string; isPrivate: boolean; topic?: string }) {
    return { success: true, channel: args.channelName };
  }
}

Field types: text, textarea, number, boolean, select, multiselect, email, url, date

@RequireEnv Decorator

Inject user-specific secrets:

typescript
import { Tool } from '@leanmcp/core';
import { Authenticated } from '@leanmcp/auth';
import { RequireEnv, getEnv } from '@leanmcp/env-injection';

@Authenticated(authProvider, { projectId: 'my-project' })
export class SlackService {
  @Tool({ description: 'Send Slack message' })
  @RequireEnv(['SLACK_TOKEN', 'SLACK_CHANNEL'])
  async sendMessage(args: { message: string }) {
    const token = getEnv('SLACK_TOKEN')!;
    const channel = getEnv('SLACK_CHANNEL')!;
    
    await fetch('https://slack.com/api/chat.postMessage', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ channel, text: args.message })
    });
    
    return { success: true };
  }
}

Complete Service Example

typescript
// mcp/products/index.ts
import { Tool, Resource, SchemaConstraint, Optional } from '@leanmcp/core';

class CreateProductInput {
  @SchemaConstraint({ description: 'Product name', minLength: 1 })
  name!: string;

  @SchemaConstraint({ description: 'Price in dollars', minimum: 0 })
  price!: number;

  @Optional()
  @SchemaConstraint({ description: 'Product description' })
  description?: string;
}

class UpdateProductInput {
  @SchemaConstraint({ description: 'Product ID' })
  id!: string;

  @Optional()
  @SchemaConstraint({ description: 'Product name' })
  name?: string;

  @Optional()
  @SchemaConstraint({ description: 'Price in dollars', minimum: 0 })
  price?: number;
}

export class ProductsService {
  private products: Map<string, any> = new Map();

  @Tool({ description: 'Create a new product', inputClass: CreateProductInput })
  async createProduct(input: CreateProductInput) {
    const id = crypto.randomUUID();
    const product = { id, ...input, createdAt: new Date().toISOString() };
    this.products.set(id, product);
    return { success: true, product };
  }

  @Tool({ description: 'List all products' })
  async listProducts() {
    return { products: Array.from(this.products.values()) };
  }

  @Tool({ description: 'Update a product', inputClass: UpdateProductInput })
  async updateProduct(input: UpdateProductInput) {
    const product = this.products.get(input.id);
    if (!product) throw new Error('Product not found');
    
    if (input.name) product.name = input.name;
    if (input.price) product.price = input.price;
    product.updatedAt = new Date().toISOString();
    
    return { success: true, product };
  }

  @Tool({ description: 'Delete a product' })
  async deleteProduct(args: { id: string }) {
    if (!this.products.has(args.id)) throw new Error('Product not found');
    this.products.delete(args.id);
    return { success: true };
  }

  @Resource({ description: 'Get product statistics' })
  async productStats() {
    const products = Array.from(this.products.values());
    return {
      contents: [{
        uri: 'products://stats',
        mimeType: 'application/json',
        text: JSON.stringify({
          total: products.length,
          totalValue: products.reduce((sum, p) => sum + p.price, 0)
        })
      }]
    };
  }
}

Server Configuration Options

typescript
import { MCPServer, createHTTPServer } from '@leanmcp/core';

// Simple API (recommended)
await createHTTPServer({
  name: 'my-server',
  version: '1.0.0',
  port: 3001,
  cors: true,
  logging: true
});

// Advanced: Factory pattern with manual registration
const serverFactory = async () => {
  const server = new MCPServer({
    name: 'my-server',
    version: '1.0.0',
    autoDiscover: false,  // Disable auto-discovery
    logging: true
  });
  
  server.registerService(new MyService());
  return server.getServer();
};

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

Editing Guidelines

DO

  • Add new services in mcp/servicename/index.ts
  • Use @Tool, @Resource, @Prompt decorators
  • Use @SchemaConstraint for input validation
  • Use @Optional() for optional fields
  • Export service classes from index.ts
  • Follow decorator patterns consistently

DON'T

  • Wrap services in AppProvider (CLI handles this)
  • Use direct imports for components in @UIApp
  • Completely rewrite existing files
  • Add terminal/command instructions
  • Remove existing working code

Error Handling

typescript
@Tool({ description: 'Divide two numbers' })
async divide(input: { a: number; b: number }) {
  if (input.b === 0) {
    throw new Error('Division by zero is not allowed');
  }
  return { result: input.a / input.b };
}

Errors are automatically caught and formatted as MCP error responses.

Best Practices

  1. Always provide descriptions - Helps LLMs understand tool purpose
  2. Use inputClass for complex inputs - Automatic schema generation
  3. Return structured data - Objects with clear field names
  4. Handle errors gracefully - Throw descriptive errors
  5. Keep tools focused - One tool = one clear action
  6. Organize by domain - One service per business domain
  7. Use schema constraints - Validate inputs with min/max/enum

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Leanmcp Builder AI skill do?

Build MCP servers using the Leanmcp SDK with decorator-based TypeScript. Use this skill when users ask for "leanmcp", "MCP with decorators", "MCP with authentication", "MCP with elicitation", "MCP with environment injection", or want a simpler, more elegant way to build MCP servers. Leanmcp provides automatic schema generation, dependency injection, authentication, and user input collection.

Why use Leanmcp Builder on TypingMind?

Because you install it once and use it with any model. Leanmcp Builder is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Leanmcp Builder in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/Leanmcp/gateway-skills/tree/main/skills/leanmcp-builder. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Leanmcp Builder?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Leanmcp Builder?

As many as you like. As long as a model supports skills, you can use Leanmcp Builder with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Leanmcp Builder AI skill free?

Yes. It is published on GitHub by Leanmcp under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇