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!