Controllers and Modules

Group features and configure the server

These decorators organize a server: @Controller groups primitives under a prefix, @Module groups a feature's classes, and @McpApp configures the server itself.

@Controller

Marks a class as a controller, auto-registers it as a singleton in the dependency injection container, and optionally prefixes all of its tool, resource, and prompt names.

import { Controller } from '@nitrostack/core';

@Controller(prefixOrOptions?: string | ControllerOptions)

Options:

PropertyTypeRequiredDescription
prefixstringNoOptional prefix applied to every @Tool, @Resource, and @Prompt name defined in this controller (e.g. 'orders' turns 'create' into 'orders_create'). Can also be passed directly as a string argument (@Controller('orders')).

Behavior:

  • Tool Namespacing: Automatically prepends the prefix followed by an underscore _ to all @Tool names in the class.
  • DI Registration: Automatically registers the decorated class in the NitroStack DIContainer. You do not need to add the @Injectable() decorator separately.
  • Lifecycle Support: Because the controller is registered in the DI container, it participates fully in application lifecycle hooks (OnModuleInit, OnApplicationBootstrap, BeforeApplicationShutdown, OnModuleDestroy, OnApplicationShutdown).

Example:

import { Controller, ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core';
import { OrderService } from './order.service.js';

@Controller('orders') // All tools prefixed with 'orders_'
export class OrdersController {
  constructor(private readonly orderService: OrderService) {}

  // Registered as 'orders_create'
  @Tool({
    name: 'create',
    description: 'Create a new customer order',
    inputSchema: z.object({
      customerId: z.string(),
      items: z.array(z.string()),
    }),
  })
  async createOrder(input: { customerId: string; items: string[] }, ctx: ExecutionContext) {
    return await this.orderService.create(input);
  }

  // Registered as 'orders_get_status'
  @Tool({
    name: 'get_status',
    description: 'Retrieve order tracking status',
    inputSchema: z.object({ orderId: z.string() }),
  })
  async getStatus(input: { orderId: string }, ctx: ExecutionContext) {
    return await this.orderService.findStatus(input.orderId);
  }
}

@Module

Defines a module that groups related functionality.

import { Module } from '@nitrostack/core';

@Module(options: ModuleOptions)
@module(name=..., controllers=..., providers=..., imports=..., exports=...)

Options:

PropertyTypeRequiredDescription
namestringYesUnique module identifier
descriptionstringNoModule description
controllersClass[]NoTool, resource, and prompt classes
providersClass[]NoServices for dependency injection
importsModule[]NoModules to import
exportsClass[]NoProviders to export
globalbooleanNoIf true, providers are available globally

Example:

@Module({
  name: 'users',
  description: 'User management module',
  controllers: [UserTools, UserResources],
  providers: [UserService, UserRepository],
  imports: [DatabaseModule],
  exports: [UserService]
})
export class UsersModule {}

@McpApp

Marks the root application module with server configuration.

import { McpApp, Module } from '@nitrostack/core';

@McpApp(options: McpAppOptions)
@mcp_app(module=AppModule, server=ServerConfig(...))

Options:

PropertyTypeRequiredDescription
moduleClassConstructorYesReference to the root module class
serverobjectNoServer metadata (name, version, protocolVersion, extensions)
loggingobjectNoLogging configuration (level: 'debug' | 'info' | 'warn' | 'error')
transportobjectNoTransport configuration (type: 'stdio' | 'http' | 'dual', http: { port, host, basePath })
shutdownHooksbooleanNoRegister OS signal handlers (SIGTERM, SIGINT) for graceful shutdown (default: true)
transformsMcpTransform[]NoCatalog transform pipeline (e.g. BM25SearchTransform, CodeModeTransform)

Example:

import { McpApp, Module, ConfigModule, BM25SearchTransform } from '@nitrostack/core';
import { UsersModule } from './modules/users/users.module.js';

@McpApp({
  module: AppModule,
  server: {
    name: 'my-mcp-server',
    version: '1.0.0',
    protocolVersion: 'auto',
  },
  logging: {
    level: 'info',
  },
  shutdownHooks: true,
  transforms: [
    new BM25SearchTransform({
      defaultLimit: 5,
      alwaysVisible: ['users_get_profile'],
    }),
  ],
})
@Module({
  imports: [ConfigModule.forRoot(), UsersModule],
})
export class AppModule {}

On this page