npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@leanmcp/core

v0.3.3

Published

Core library implementing decorators, reflection, and MCP runtime server

Readme

@leanmcp/core

Core library for building Model Context Protocol (MCP) servers with TypeScript decorators and declarative schema definition.

Features

  • Type-safe decorators - @Tool, @Prompt, @Resource with full TypeScript support
  • Schema generation - Define JSON Schema declaratively using @SchemaConstraint decorators on class properties
  • Streamable HTTP transport - Production-ready HTTP server with session management
  • Input validation - Built-in AJV validation for all inputs
  • Clean API - Function names automatically become tool/prompt/resource names
  • MCP compliant - Built on official @modelcontextprotocol/sdk

Installation

npm install @leanmcp/core

Peer Dependencies

For HTTP server support:

npm install express cors

Quick Start

1. Define Your Service with Class-Based Schema

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

// Define input schema as a class
class AnalyzeSentimentInput {
  @SchemaConstraint({
    description: 'Text to analyze',
    minLength: 1
  })
  text!: string;

  @Optional()
  @SchemaConstraint({
    description: 'Language code',
    enum: ['en', 'es', 'fr'],
    default: 'en'
  })
  language?: string;
}

// Define output schema
class AnalyzeSentimentOutput {
  @SchemaConstraint({ enum: ['positive', 'negative', 'neutral'] })
  sentiment!: string;

  @SchemaConstraint({ minimum: -1, maximum: 1 })
  score!: number;
}

export class SentimentService {
  @Tool({ 
    description: 'Analyze sentiment of text',
    inputClass: AnalyzeSentimentInput
  })
  async analyzeSentiment(input: AnalyzeSentimentInput): Promise<AnalyzeSentimentOutput> {
    // Your implementation
    return {
      sentiment: 'positive',
      score: 0.8,
      confidence: 0.95
    };
  }
}

2. Create and Start Server

Option A: Zero-Config Auto-Discovery (Recommended)

The simplest way to create an HTTP server with auto-discovery:

import { createHTTPServer } from "@leanmcp/core";

// Create and start HTTP server with auto-discovery
await createHTTPServer({
  name: "my-mcp-server",
  version: "1.0.0",
  port: 3000,
  cors: true,
  logging: true
});

console.log('\nMCP Server running');
console.log('HTTP endpoint: http://localhost:3000/mcp');
console.log('Health check: http://localhost:3000/health');

What happens automatically:

  • Services are discovered from ./mcp directory
  • HTTP server is created and started
  • Session management is configured
  • CORS is enabled (if specified)

Directory Structure:

your-project/
├── main.ts
└── mcp/
    ├── sentiment/
    │   └── index.ts    # export class SentimentService
    ├── weather/
    │   └── index.ts    # export class WeatherService
    └── database/
        └── index.ts    # export class DatabaseService

Option B: Factory Pattern (Advanced)

For advanced use cases requiring manual service registration or custom configuration:

import { createHTTPServer, MCPServer } from "@leanmcp/core";
import { SentimentService } from "./services/sentiment";

// Create MCP server with factory function
const serverFactory = async () => {
  const server = new MCPServer({
    name: "my-mcp-server",
    version: "1.0.0",
    logging: true,
    autoDiscover: false  // Disable auto-discovery for manual registration
  });

  // Register services manually
  server.registerService(new SentimentService());

  return server.getServer();
};

// Start HTTP server with factory
await createHTTPServer(serverFactory, {
  port: 3000,
  cors: true,
  logging: true
});

Decorators

@Tool

Marks a method as an MCP tool (callable function). Use inputClass to specify the input schema class.

class CalculateInput {
  @SchemaConstraint({ description: 'First number' })
  a!: number;
  
  @SchemaConstraint({ description: 'Second number' })
  b!: number;
}

@Tool({ 
  description: 'Calculate sum of two numbers',
  inputClass: CalculateInput
})
async calculate(input: CalculateInput) {
  return { result: input.a + input.b };
}

@Prompt

Marks a method as an MCP prompt template. Input schema is automatically inferred from parameter type.

class CodeReviewInput {
  @SchemaConstraint({ description: 'Code to review' })
  code!: string;
  
  @SchemaConstraint({ description: 'Programming language' })
  language!: string;
}

@Prompt({ description: 'Generate code review prompt' })
codeReview(input: CodeReviewInput) {
  return {
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: `Review this ${input.language} code:\n\n${input.code}`
      }
    }]
  };
}

@Resource

Marks a method as an MCP resource (data source).

@Resource({ description: 'Get system configuration', mimeType: 'application/json' })
async getConfig() {
  return {
    version: "1.0.0",
    environment: process.env.NODE_ENV
  };
}

@SchemaConstraint

Add validation constraints to class properties for automatic schema generation.

class UserInput {
  @SchemaConstraint({
    description: 'User email',
    format: 'email',
    minLength: 5,
    maxLength: 100
  })
  email!: string;

  @SchemaConstraint({
    description: 'User age',
    minimum: 18,
    maximum: 120
  })
  age!: number;

  @Optional()
  @SchemaConstraint({
    description: 'User role',
    enum: ['admin', 'user', 'guest'],
    default: 'user'
  })
  role?: string;
}

@Optional

Marks a property as optional in the schema.

class SearchInput {
  @SchemaConstraint({ description: 'Search query' })
  query!: string;

  @Optional()
  @SchemaConstraint({ description: 'Max results', default: 10 })
  limit?: number;
}

API Reference

MCPServer

Main server class for registering services.

const server = new MCPServer({
  name: string;           // Server name
  version: string;        // Server version
  logging?: boolean;      // Enable logging (default: false)
  debug?: boolean;        // Enable verbose debug logs (default: false)
  autoDiscover?: boolean; // Enable auto-discovery (default: true)
  mcpDir?: string;        // Custom mcp directory path (optional)
});

// Manual registration
server.registerService(instance: any): void;

// Get underlying MCP SDK server
server.getServer(): Server;

Options:

  • logging: Enable basic logging for server operations
  • debug: Enable verbose debug logs showing detailed service registration (requires logging: true)
  • autoDiscover: Automatically discover and register services from ./mcp directory (default: true)
  • mcpDir: Custom path to the mcp directory (default: auto-detected ./mcp)

Zero-Config Auto-Discovery

Services are automatically discovered and registered from the ./mcp directory when the server is created:

Basic Usage (Simplified API):

import { createHTTPServer } from "@leanmcp/core";

await createHTTPServer({
  name: "my-server",
  version: "1.0.0",
  port: 3000,
  logging: true  // Enable logging
});

With Debug Logging:

await createHTTPServer({
  name: "my-server",
  version: "1.0.0",
  port: 3000,
  logging: true,
  debug: true  // Show detailed service registration logs
});

With Shared Dependencies:

For services that need shared dependencies, create a config.ts (example) file in your mcp directory:

// mcp/config.ts
import { AuthProvider } from "@leanmcp/auth";

if (!process.env.COGNITO_USER_POOL_ID || !process.env.COGNITO_CLIENT_ID) {
  throw new Error('Missing required Cognito configuration');
}

export const authProvider = new AuthProvider('cognito', {
  region: process.env.AWS_REGION || 'us-east-1',
  userPoolId: process.env.COGNITO_USER_POOL_ID,
  clientId: process.env.COGNITO_CLIENT_ID,
  clientSecret: process.env.COGNITO_CLIENT_SECRET
});

await authProvider.init();

Then import in your services:

// mcp/slack/index.ts
import { Tool } from "@leanmcp/core";
import { Authenticated } from "@leanmcp/auth";
import { authProvider } from "../config.js";

@Authenticated(authProvider)
export class SlackService {
  constructor() {
    // No parameters needed - use environment or imported config
  }

  @Tool({ description: 'Send a message' })
  async sendMessage(args: any) {
    // Implementation
  }
}

Your main file stays clean:

import { createHTTPServer } from "@leanmcp/core";

await createHTTPServer({
  name: "my-server",
  version: "1.0.0",
  port: 3000,
  logging: true
});

// Services are automatically discovered and registered

How It Works:

  • Automatically discovers and registers services from the ./mcp directory during server initialization
  • Recursively scans for index.ts or index.js files
  • Dynamically imports each file and looks for exported classes
  • Instantiates services with no-args constructors
  • Registers all discovered services with their decorated methods

Directory Structure:

your-project/
├── main.ts
└── mcp/
    ├── config.ts      # Optional: shared dependencies
    ├── slack/
    │   └── index.ts   # export class SlackService
    ├── database/
    │   └── index.ts   # export class DatabaseService
    └── auth/
        └── index.ts   # export class AuthService

createHTTPServer

Create and start an HTTP server with streamable transport.

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

Factory Pattern (Advanced):

await createHTTPServer(
  serverFactory: () => Server | Promise<Server>,
  options: {
    port?: number;             // Port number (default: 3001)
    cors?: boolean | object;   // Enable CORS (default: false)
    logging?: boolean;         // Enable HTTP request logging (default: false)
    sessionTimeout?: number;   // Session timeout in ms (optional)
  }
);

CORS Configuration:

// Simple CORS (allow all origins - not recommended for production)
await createHTTPServer({
  name: "my-server",
  version: "1.0.0",
  cors: true
});

// Advanced CORS configuration
await createHTTPServer({
  name: "my-server",
  version: "1.0.0",
  cors: {
    origin: 'https://example.com',  // Specific origin
    credentials: true                // Allow credentials
  }
});

Schema Generation

Generate JSON Schema from TypeScript classes:

import { classToJsonSchemaWithConstraints } from "@leanmcp/core";

const schema = classToJsonSchemaWithConstraints(MyInputClass);

HTTP Endpoints

When using createHTTPServer, the following endpoints are available:

  • POST /mcp - MCP protocol endpoint (accepts JSON-RPC 2.0 messages)
  • GET /health - Health check endpoint
  • GET / - Welcome message

Environment Variables

PORT=3000              # Server port (optional)
NODE_ENV=production    # Environment (optional)

Error Handling

All tools automatically handle errors and return them in MCP format:

class DivideInput {
  @SchemaConstraint({ description: 'Numerator' })
  a!: number;
  
  @SchemaConstraint({ description: 'Denominator' })
  b!: number;
}

@Tool({ 
  description: 'Divide numbers',
  inputClass: DivideInput
})
async divide(input: DivideInput) {
  if (input.b === 0) {
    throw new Error("Division by zero");
  }
  return { result: input.a / input.b };
}

Errors are returned as:

{
  "content": [{"type": "text", "text": "Error: Division by zero"}],
  "isError": true
}

TypeScript Support

Full TypeScript support with type inference:

class MyInput {
  @SchemaConstraint({ description: 'Input field' })
  field!: string;
}

class MyOutput {
  result!: string;
}

// Input schema defined via inputClass, output type inferred from return type
@Tool({ 
  description: 'My tool',
  inputClass: MyInput
})
async myTool(input: MyInput): Promise<MyOutput> {
  // TypeScript knows the exact types
  const result: MyOutput = {
    result: input.field.toUpperCase()
    // Full autocomplete and type checking
  };
  return result;
}

Key Points:

  • Input schema is defined using inputClass in the @Tool decorator
  • Output schema is inferred from the return type
  • For tools with no input parameters, omit the inputClass option
  • Use @SchemaConstraint decorators to add validation and documentation to your input classes

License

MIT

Related Packages

Links