Server Concepts
Overview
NitroStack v3.0 introduces a NestJS-inspired architecture built around decorators, modules, and dependency injection. This guide covers the core concepts.
Application Bootstrap
@McpApp Decorator
The @McpApp decorator marks your root module and configures the application:
import { McpApp, Module } from 'nitrostack';
@McpApp({
server: {
name: 'my-mcp-server',
version: '1.0.0',
description: 'My awesome MCP server'
},
logging: {
level: 'info',
file: 'logs/server.log'
}
})
@Module({
imports: [
ConfigModule.forRoot(),
JWTModule.forRoot({ secret: process.env.JWT_SECRET! }),
ProductsModule,
UsersModule
]
})
export class AppModule {}
McpApplicationFactory
Bootstrap your application:
import { McpApplicationFactory } from 'nitrostack';
import { AppModule } from './app.module.js';
// This is all you need!
McpApplicationFactory.create(AppModule);
The factory:
- Reads @McpApp metadata
- Initializes logger
- Sets up DI container
- Registers all modules
- Builds and registers tools/resources/prompts
- Starts the MCP server
Modules
What are Modules?
Modules organize your application into logical units. Each module groups related functionality.
@Module({
name: 'products',
description: 'Product catalog management',
controllers: [ProductsTools, ProductsResources, ProductsPrompts],
providers: [ProductService, DatabaseService],
imports: [HttpModule],
exports: [ProductService]
})
export class ProductsModule {}
Module Properties
| Property | Description | Required |
|---|---|---|
name | Module identifier | Yes |
description | Module description | No |
controllers | Tool/resource/prompt classes | No |
providers | Services for DI | No |
imports | Other modules to import | No |
exports | Providers to export | No |
Controllers
Controllers contain your tools, resources, and prompts:
// products.tools.ts
export class ProductsTools {
@Tool({ name: 'get_product' })
async getProduct(input: any, ctx: ExecutionContext) {
// Tool logic
}
}
// products.resources.ts
export class ProductsResources {
@Resource({ uri: 'product://{id}' })
async getProductResource(uri: string, ctx: ExecutionContext) {
// Resource logic
}
}
// products.prompts.ts
export class ProductsPrompts {
@Prompt({ name: 'review_product' })
async getReviewPrompt(args: any, ctx: ExecutionContext) {
// Prompt logic
}
}
Providers (Services)
Providers contain business logic:
@Injectable()
export class ProductService {
constructor(private db: DatabaseService) {}
async findById(id: string) {
return this.db.query('SELECT * FROM products WHERE id = ?', [id]);
}
async search(query: string) {
return this.db.query('SELECT * FROM products WHERE name LIKE ?', [`%${query}%`]);
}
}
Imports
Import other modules to use their exports:
@Module({
name: 'orders',
imports: [
ProductsModule, // ā Use ProductService
PaymentsModule
],
controllers: [OrdersTools],
providers: [OrderService]
})
export class OrdersModule {}
Exports
Export providers for other modules:
@Module({
name: 'products',
providers: [ProductService],
exports: [ProductService] // ā Other modules can use this
})
export class ProductsModule {}
Dependency Injection
@Injectable Decorator
Mark classes for DI:
@Injectable()
export class EmailService {
async send(to: string, subject: string, body: string) {
// Send email
}
}
Constructor Injection
Inject dependencies via constructor:
export class UserTools {
constructor(
private userService: UserService, // ā Auto-injected
private emailService: EmailService, // ā Auto-injected
private db: DatabaseService // ā Auto-injected
) {}
@Tool({ name: 'create_user' })
async createUser(input: any) {
const user = await this.userService.create(input);
await this.emailService.send(user.email, 'Welcome!', '...');
return user;
}
}
DI Container
The DI container automatically:
- Resolves dependencies
- Creates instances
- Manages lifecycle
- Handles circular dependencies
Scopes
Services are singleton by default:
@Injectable()
export class DatabaseService {
// One instance for entire application
}
Configuration
ConfigModule
Manage environment variables:
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: '.env',
isGlobal: true,
validate: (config) => {
if (!config.JWT_SECRET) {
throw new Error('JWT_SECRET is required');
}
return config;
}
})
]
})
export class AppModule {}
ConfigService
Access configuration:
@Injectable()
export class MyService {
constructor(private config: ConfigService) {}
doSomething() {
const secret = this.config.get('JWT_SECRET');
const port = this.config.get('PORT', 3000); // with default
}
}
Execution Context
Every tool, resource, and prompt receives an ExecutionContext:
interface ExecutionContext {
// Authentication
auth?: {
subject?: string;
token?: string;
[key: string]: any;
};
// Logging
logger: Logger;
// Tool information
toolName?: string;
// Emit events
emit(event: string, data: any): void;
// Request metadata
metadata: Record<string, any>;
}
Usage:
@Tool({ name: 'create_order' })
@UseGuards(JWTGuard)
async createOrder(input: any, ctx: ExecutionContext) {
const userId = ctx.auth?.subject; // From guard
ctx.logger.info('Creating order...');
const order = await this.orderService.create(input, userId);
ctx.emit('order.created', order); // Event
return order;
}
Lifecycle
Application Lifecycle
-
Bootstrap
McpApplicationFactory.create(AppModule); -
Module Registration
- Resolve imports
- Register providers
- Register controllers
-
DI Container Setup
- Build dependency graph
- Create instances
-
Server Initialization
- Register tools
- Register resources
- Register prompts
-
Server Start
- Listen for MCP requests
- Process via stdio
Request Lifecycle
- Request Arrives (via stdio)
- Route to Handler (tool/resource/prompt)
- Execute Pipeline:
- Middleware (before)
- Guards (authentication)
- Pipes (validation)
- Handler execution
- Interceptors (transform)
- Exception filters (errors)
- Middleware (after)
- Send Response
Module Organization
Feature Modules
Organize by feature:
src/
āāā modules/
ā āāā auth/
ā ā āāā auth.module.ts
ā ā āāā auth.tools.ts
ā ā āāā auth.service.ts
ā ā āāā guards/
ā ā āāā jwt.guard.ts
ā āāā products/
ā ā āāā products.module.ts
ā ā āāā products.tools.ts
ā ā āāā products.resources.ts
ā ā āāā products.prompts.ts
ā ā āāā products.service.ts
ā āāā orders/
ā āāā orders.module.ts
ā āāā orders.tools.ts
ā āāā orders.service.ts
āāā app.module.ts
āāā index.ts
Shared Modules
Create reusable modules:
@Module({
name: 'database',
providers: [DatabaseService],
exports: [DatabaseService],
global: true // Available everywhere
})
export class DatabaseModule {}
Core Modules
Essential functionality:
@Module({
name: 'core',
providers: [
Logger,
ConfigService,
CacheService
],
exports: [
Logger,
ConfigService,
CacheService
],
global: true
})
export class CoreModule {}
Best Practices
1. One Module Per Feature
// ā
Good
ProductsModule // All product logic
OrdersModule // All order logic
UsersModule // All user logic
// ā Avoid
ToolsModule // Too generic
2. Use Services for Logic
// ā
Good
export class ProductsTools {
constructor(private productService: ProductService) {}
@Tool({ name: 'get_product' })
async getProduct(input: any) {
return this.productService.findById(input.id);
}
}
// ā Avoid - Logic in tool
export class ProductsTools {
@Tool({ name: 'get_product' })
async getProduct(input: any) {
const db = getDatabase();
return db.query('SELECT * FROM products WHERE id = ?', [input.id]);
}
}
3. Export What You Share
@Module({
providers: [ProductService, InternalService],
exports: [ProductService] // Only export what others need
})
4. Keep Modules Focused
// ā
Good - Focused
@Module({
name: 'products',
controllers: [ProductsTools],
providers: [ProductService]
})
// ā Avoid - Too much
@Module({
name: 'everything',
controllers: [Products, Orders, Users, Payments],
providers: [Many, Services, Here]
})
5. Use ConfigModule
// ā
Good
@Injectable()
export class MyService {
constructor(private config: ConfigService) {}
getSecret() {
return this.config.get('SECRET');
}
}
// ā Avoid
const SECRET = process.env.SECRET;
Examples
Simple Application
// app.module.ts
@McpApp({
server: { name: 'simple-server', version: '1.0.0' }
})
@Module({
imports: [ConfigModule.forRoot()],
controllers: [SimpleTools]
})
export class AppModule {}
// index.ts
McpApplicationFactory.create(AppModule);
Complex Application
// app.module.ts
@McpApp({
server: { name: 'complex-server', version: '1.0.0' },
logging: { level: 'info' }
})
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
DatabaseModule,
JWTModule.forRoot({ secret: process.env.JWT_SECRET! }),
AuthModule,
ProductsModule,
OrdersModule,
PaymentsModule,
NotificationsModule
]
})
export class AppModule {}
Next Steps
Remember: Modules keep your code organized, DI makes it testable, and decorators keep it clean!