> ## Documentation Index
> Fetch the complete documentation index at: https://docs.leanmcp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# @leanmcp/core

> Core library for building MCP servers with TypeScript decorators

# @leanmcp/core

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

## Features

<CardGroup cols={2}>
  <Card title="Type-Safe Decorators" icon="code">
    `@Tool`, `@Prompt`, `@Resource` with full TypeScript support
  </Card>

  <Card title="Auto-Discovery" icon="magnifying-glass">
    Zero-config service discovery from `./mcp` directory
  </Card>

  <Card title="Schema Generation" icon="diagram-project">
    Declarative JSON Schema with `@SchemaConstraint` decorators
  </Card>

  <Card title="HTTP Transport" icon="server">
    Production-ready HTTP server with session management
  </Card>
</CardGroup>

## Installation

```bash theme={null}
npm install @leanmcp/core
```

For HTTP server support:

```bash theme={null}
npm install express cors
```

## Quick Start

### Zero-Config (Recommended)

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

```typescript theme={null}
import { createHTTPServer } from "@leanmcp/core";

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

// Services are automatically discovered from ./mcp directory
```

**Directory Structure:**

```
your-project/
├── main.ts
└── mcp/
    ├── sentiment/
    │   └── index.ts   # export class SentimentService
    ├── weather/
    │   └── index.ts   # export class WeatherService
    └── config.ts      # Optional: shared dependencies
```

### Define a Service

```typescript theme={null}
// mcp/sentiment/index.ts
import { Tool, SchemaConstraint, Optional } from "@leanmcp/core";

class AnalyzeSentimentInput {
  @SchemaConstraint({
    description: 'Text to analyze',
    minLength: 1
  })
  text!: string;

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

export class SentimentService {
  @Tool({ 
    description: 'Analyze sentiment of text',
    inputClass: AnalyzeSentimentInput
  })
  async analyzeSentiment(input: AnalyzeSentimentInput) {
    return {
      sentiment: 'positive',
      score: 0.8
    };
  }
}
```

***

## Decorators

### @Tool

Marks a method as a callable MCP tool.

```typescript theme={null}
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 };
}
```

**Options:**

| Option        | Type     | Description                 |
| ------------- | -------- | --------------------------- |
| `description` | `string` | Tool description for the AI |
| `inputClass`  | `Class`  | Class defining input schema |

### @Prompt

Marks a method as a reusable prompt template.

```typescript theme={null}
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).

```typescript theme={null}
@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.

```typescript theme={null}
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;
}
```

**Common constraints:**

* `description`, `default` - Documentation
* `minLength`, `maxLength` - String length
* `minimum`, `maximum` - Number range
* `enum` - Allowed values
* `format` - String format (`email`, `uri`, `date`, etc.)
* `pattern` - Regex pattern

### @Optional

Marks a property as optional in the schema.

```typescript theme={null}
class SearchInput {
  @SchemaConstraint({ description: 'Search query' })
  query!: string;

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

***

## API Reference

### createHTTPServer

Create and start an HTTP server with auto-discovery.

**Simplified API (Recommended):**

```typescript theme={null}
await createHTTPServer({
  name: string;              // Server name (required)
  version: string;           // Server version (required)
  port?: number;             // Port (default: 3001)
  cors?: boolean | object;   // Enable CORS (default: false)
  logging?: boolean;         // Enable logging (default: false)
  debug?: boolean;           // Verbose debug logs (default: false)
  autoDiscover?: boolean;    // Auto-discover services (default: true)
  mcpDir?: string;           // Custom mcp directory path
  sessionTimeout?: number;   // Session timeout in ms
  stateless?: boolean;       // Stateless mode for Lambda/serverless (default: true)
  dashboard?: boolean;       // Serve dashboard UI at / (default: true)
});
```

**Factory Pattern (Advanced):**

```typescript theme={null}
const serverFactory = async () => {
  const server = new MCPServer({
    name: "my-server",
    version: "1.0.0",
    autoDiscover: false  // Disable for manual registration
  });
  
  server.registerService(new MyService());
  return server.getServer();
};

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

### MCPServer

Main server class for registering services.

```typescript theme={null}
const server = new MCPServer({
  name: string;           // Server name
  version: string;        // Server version
  logging?: boolean;      // Enable logging (default: false)
  debug?: boolean;        // Verbose debug logs (default: false)
  autoDiscover?: boolean; // Auto-discover from ./mcp (default: true)
  mcpDir?: string;        // Custom mcp directory path
});

server.registerService(instance);  // Manual registration
server.getServer();                // Get underlying MCP SDK server
```

***

## Auto-Discovery

Services are automatically discovered from the `./mcp` directory:

1. Recursively scans for `index.ts` or `index.js` files
2. Dynamically imports each file
3. Looks for exported classes
4. Instantiates with no-args constructors
5. Registers all decorated methods

### Shared Dependencies

For services needing shared configuration (auth, database, etc.), create a `config.ts`:

```typescript theme={null}
// mcp/config.ts
import { AuthProvider } from "@leanmcp/auth";

export 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();
```

Then import in your services:

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

@Authenticated(authProvider)
export class SlackService {
  @Tool({ description: 'Send a message' })
  async sendMessage(args: { channel: string; message: string }) {
    // Implementation
  }
}
```

***

## HTTP Endpoints

| Endpoint  | Method | Description                          |
| --------- | ------ | ------------------------------------ |
| `/mcp`    | POST   | MCP protocol endpoint (JSON-RPC 2.0) |
| `/health` | GET    | Health check                         |
| `/`       | GET    | Welcome message                      |

## Error Handling

Errors are automatically caught and returned in MCP format:

```typescript theme={null}
@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 };
}
```

Returns:

```json theme={null}
{
  "content": [{"type": "text", "text": "Error: Division by zero"}],
  "isError": true
}
```

## Environment Variables

```bash theme={null}
PORT=3001              # Server port
NODE_ENV=production    # Environment
```

## TypeScript Support

<Note>
  **Key Points:**

  * Input schema is defined via `inputClass` in the decorator
  * Output type is inferred from the return type
  * For tools with no input, omit `inputClass`
  * Use `@SchemaConstraint` for validation and documentation
</Note>

```typescript theme={null}
class MyInput {
  @SchemaConstraint({ description: 'Input field' })
  field!: string;
}

@Tool({ description: 'My tool', inputClass: MyInput })
async myTool(input: MyInput): Promise<{ result: string }> {
  return { result: input.field.toUpperCase() };
}
```

## Related Packages

* [@leanmcp/cli](/sdk/cli) - CLI tool for project creation
* [@leanmcp/auth](/sdk/auth) - Authentication decorators
* [@leanmcp/ui](/sdk/ui) - MCP App UI components
* [@leanmcp/elicitation](/sdk/elicitation) - Structured user input

## Links

* [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk)
* [NPM Package](https://www.npmjs.com/package/@leanmcp/core)
* [MCP Specification](https://modelcontextprotocol.io/specification/2025-11-25)
