Decorators Overview
LeanMCP uses TypeScript decorators to define MCP capabilities with full type safety and automatic metadata generation.
Core MCP Decorators
These decorators define the primary MCP protocol features:
Marks a method as a callable MCP tool (function).
@Tool({
description: 'Calculate sum of two numbers',
inputClass: AddInput
})
async add(args: AddInput): Promise<{ result: number }> {
return { result: args.a + args.b };
}
Options:
description?: string - Tool description
inputClass?: Class - Input schema class
securitySchemes?: SecurityScheme[] - Auth requirements
Learn more about Tools →
@Prompt
Marks a method as a reusable prompt template.
@Prompt({ description: 'Generate greeting message' })
greetingPrompt(args: { name?: string }) {
return {
messages: [{
role: 'user',
content: { type: 'text', text: `Hello ${args.name}!` }
}]
};
}
Options:
description?: string - Prompt description
inputClass?: Class - Input schema class (optional, can be inferred)
Learn more about Prompts →
@Resource
Marks a method as a data endpoint (resource).
@Resource({ description: 'Service statistics' })
getStats() {
return { uptime: process.uptime(), requestCount: 1523 };
}
Options:
description?: string - Resource description
mimeType?: string - Content type (default: 'application/json')
inputClass?: Class - Input schema class
uri?: string - Custom URI (default: auto-generated)
Learn more about Resources →
Schema Decorators
These decorators define input/output validation schemas:
@SchemaConstraint
Adds JSON Schema validation constraints to a property.
class UserInput {
@SchemaConstraint({
description: 'User email address',
format: 'email',
minLength: 5
})
email!: string;
@SchemaConstraint({
description: 'User age',
minimum: 18,
maximum: 120
})
age!: number;
}
Available Constraints:
String
Number
Array
Common
minLength?: number - Minimum string length
maxLength?: number - Maximum string length
pattern?: string - Regex pattern (e.g., '^[a-z]+$')
format?: string - Format hint (e.g., 'email', 'uri', 'date-time')
enum?: string[] - Allowed values
description?: string - Field description
default?: string - Default value
minimum?: number - Minimum value (inclusive)
maximum?: number - Maximum value (inclusive)
exclusiveMinimum?: number - Minimum value (exclusive)
exclusiveMaximum?: number - Maximum value (exclusive)
multipleOf?: number - Value must be multiple of this
description?: string - Field description
default?: number - Default value
minItems?: number - Minimum array length
maxItems?: number - Maximum array length
uniqueItems?: boolean - Items must be unique
items?: object - Schema for array items
description?: string - Field description
description?: string - Human-readable description
default?: any - Default value if not provided
type?: string - Explicit type override
enum?: any[] - List of allowed values
Learn more about Schema Validation →
@Optional
Marks a property as optional in the JSON Schema.
class SearchInput {
@SchemaConstraint({ description: 'Search query' })
query!: string;
@Optional()
@SchemaConstraint({ description: 'Result limit', default: 10 })
limit?: number;
}
Without @Optional(), properties are marked as required in the generated JSON Schema.
Authentication Decorators
These decorators add authentication and authorization:
@Auth
Adds authentication requirements to a class or method.
// Class-level (all methods require auth)
@Auth({ provider: 'clerk' })
export class SecureService {
@Tool({ description: 'Protected operation' })
async protectedTool() { /* ... */ }
}
// Method-level (specific method requires auth)
export class MixedService {
@Tool({ description: 'Public operation' })
async publicTool() { /* ... */ }
@Tool({ description: 'Premium feature' })
@Auth({ provider: 'stripe' })
async premiumTool() { /* ... */ }
}
Options:
provider: string - Auth provider name (e.g., 'clerk', 'auth0', 'cognito')
@Authenticated
From @leanmcp/auth - enforces authentication via middleware:
import { Authenticated } from '@leanmcp/auth';
@Authenticated(authProvider)
export class SlackService {
@Tool({ description: 'Send message', inputClass: SendMessageInput })
async sendMessage(args: SendMessageInput) {
// Automatically authenticated
}
}
See Authentication Guide for setup details.
UI & Rendering Decorators
These decorators control UI presentation:
@UI
Links a UI component to a tool or resource.
@UI('DashboardComponent')
export class DashboardService {
@Tool({ description: 'Open dashboard' })
async openDashboard() { /* ... */ }
}
@Render
Specifies output rendering format.
@Tool({ description: 'Generate report' })
@Render('markdown')
async generateReport() {
return '# Report\n\nData: ...';
}
Supported Formats:
'markdown' - Markdown text
'html' - HTML content
'json' - JSON data
'chart' - Chart visualization
'table' - Tabular data
@GPTApp
From @leanmcp/ui - creates interactive UI experiences:
import { GPTApp } from '@leanmcp/ui/server';
@Tool({ description: 'Open interactive dashboard' })
@GPTApp({
component: './DashboardComponent',
title: 'Social Monitor'
})
async openDashboard(input: DashboardInput) {
return { status: 'ready' };
}
See UI Components Guide for details.
Environment & Configuration Decorators
@UserEnvs
Injects environment variables or user-level configuration.
export class APIService {
@UserEnvs()
private userConfig!: Record<string, string>;
@Tool({ description: 'Call API' })
async callAPI() {
const apiKey = this.userConfig['API_KEY'];
// Use per-user configuration
}
}
See Environment Injection Guide for usage.
Workflow Decorators
@Deprecated
Marks a tool, prompt, or resource as deprecated.
@Tool({ description: 'Old API method' })
@Deprecated('Use newMethod() instead')
async oldMethod() {
// Logs warning when called
}
Features:
- Automatically logs deprecation warning
- Can be applied to classes or methods
- Optional custom message
Security Decorators
From MCP authorization specification:
Security Schemes
Define authentication requirements in @Tool decorator:
@Tool({
description: 'Fetch private data',
securitySchemes: [{ type: 'oauth2', scopes: ['read:user'] }]
})
async fetchPrivateData() { /* ... */ }
@Tool({
description: 'Public endpoint',
securitySchemes: [{ type: 'noauth' }]
})
async publicEndpoint() { /* ... */ }
Security Scheme Types:
From packages/core/src/decorators.ts:19-24:
export interface SecurityScheme {
type: 'noauth' | 'oauth2';
scopes?: string[]; // OAuth scopes (for oauth2 type)
}
LeanMCP provides helpers to read decorator metadata:
import { getMethodMetadata, getDecoratedMethods } from '@leanmcp/core';
// Get metadata for a specific method
const metadata = getMethodMetadata(MyService.prototype.myTool);
console.log(metadata.toolName); // 'myTool'
console.log(metadata.toolDescription); // Tool description
// Get all tools in a class
const tools = getDecoratedMethods(MyService, 'tool:name');
tools.forEach(({ method, propertyKey, metadata }) => {
console.log(`Tool: ${propertyKey}`);
});
From packages/core/src/decorators.ts:325-350:
Tool Metadata:
'tool:name' - Tool name
'tool:description' - Tool description
'tool:inputClass' - Input schema class
'tool:securitySchemes' - Security requirements
Prompt Metadata:
'prompt:name' - Prompt name
'prompt:description' - Prompt description
'prompt:inputClass' - Input schema class
Resource Metadata:
'resource:uri' - Resource URI
'resource:name' - Resource name
'resource:description' - Resource description
'resource:mimeType' - MIME type
Complete Example
Here’s a service using multiple decorators:
import {
Tool,
Prompt,
Resource,
SchemaConstraint,
Optional,
Auth,
Render
} from '@leanmcp/core';
// Input schemas
class AnalyzeInput {
@SchemaConstraint({ description: 'Text to analyze', minLength: 1 })
text!: string;
@Optional()
@SchemaConstraint({
description: 'Language',
enum: ['en', 'es', 'fr'],
default: 'en'
})
language?: string;
}
// Service with mixed auth
@Auth({ provider: 'clerk' })
export class AnalysisService {
// Tool with schema validation
@Tool({
description: 'Analyze text sentiment',
inputClass: AnalyzeInput
})
@Render('json')
async analyze(args: AnalyzeInput) {
return {
sentiment: 'positive',
score: 0.85
};
}
// Prompt template
@Prompt({ description: 'Generate analysis prompt' })
analysisPrompt(args: { text: string }) {
return {
messages: [{
role: 'user',
content: { type: 'text', text: `Analyze: ${args.text}` }
}]
};
}
// Resource endpoint
@Resource({ description: 'Supported languages' })
getLanguages() {
return {
languages: ['en', 'es', 'fr'],
default: 'en'
};
}
// Deprecated method
@Tool({ description: 'Old analysis method' })
@Deprecated('Use analyze() instead')
async oldAnalyze() {
// Legacy implementation
}
}
Best Practices
Decorator Guidelines:
- Use clear, descriptive method names (they become tool/prompt/resource names)
- Always include
description in decorator options
- Define explicit
inputClass for complex schemas
- Apply
@Optional() to all optional properties
- Use
@Auth at class level when all methods need authentication
- Combine decorators logically (
@Tool + @Render, @Tool + @Auth)
Decorators are evaluated at module load time. Don’t use runtime-dependent logic in decorator arguments.
Decorator Ordering
When using multiple decorators, order matters:
// Correct order (specific to general)
@Tool({ description: 'Process data' })
@Auth({ provider: 'clerk' })
@Render('json')
@Deprecated('Use v2 API')
async processData() { /* ... */ }
General Rule:
- Primary decorator (
@Tool, @Prompt, @Resource)
- Authentication (
@Auth, @Authenticated)
- UI/Rendering (
@Render, @UI, @GPTApp)
- Lifecycle (
@Deprecated)
Next Steps