NitroStack SDK Reference - For AI Code Editors
Comprehensive SDK reference for AI agents editing NitroStack v3.0 code
Table of Contents
- Architecture Overview
- Application Bootstrap
- Modules
- Tools
- Resources
- Prompts
- Guards
- Middleware
- Interceptors
- Pipes
- Services & DI
- Decorators Reference
- Widgets
- Health Checks
- Caching
- Rate Limiting
- Error Handling
- File Structure
- Import Rules
- Common Patterns
Architecture Overview
NitroStack v3.0 uses decorator-based architecture inspired by NestJS:
- Declarative - Use decorators instead of factory functions
- Modular - Organize code into feature modules
- DI-First - Dependency injection for testability
- Type-Safe - Zod schemas for runtime validation
- Protocol-Native - Built for MCP protocol, not HTTP
Key Principles
- ā
Use decorators (
@Tool,@Module,@Injectable) - ā
No manual registration (
server.tool(),server.resource()) - ā Constructor injection for dependencies
- ā Services contain business logic, tools are thin
- ā
ES modules with
.jsextensions in imports
Application Bootstrap
Root Module (app.module.ts)
import { McpApp, Module } from 'nitrostack';
import { ConfigModule } from 'nitrostack/config';
import { JWTModule } from 'nitrostack/jwt';
import { ProductsModule } from './modules/products/products.module.js';
import { DatabaseService } from './services/database.service.js';
@McpApp({
server: {
name: 'my-ecommerce-server',
version: '1.0.0',
description: 'E-commerce MCP server'
},
logging: {
level: 'info' // debug | info | warn | error
}
})
@Module({
imports: [
ConfigModule.forRoot(), // Environment variables
JWTModule.forRoot(), // JWT authentication
ProductsModule, // Feature modules
OrdersModule
],
providers: [DatabaseService], // Global services
controllers: [] // Global tools/resources
})
export class AppModule {}
Entry Point (index.ts)
import { McpApplicationFactory } from 'nitrostack';
import { AppModule } from './app.module.js';
// Bootstrap application
McpApplicationFactory.create(AppModule);
That's it! No manual server setup needed.
Modules
Modules organize related features into cohesive units.
Basic Module
import { Module } from 'nitrostack';
import { ProductsTools } from './products.tools.js';
import { ProductsResources } from './products.resources.js';
import { ProductsPrompts } from './products.prompts.js';
import { ProductService } from './products.service.js';
@Module({
name: 'products',
description: 'Product catalog management',
controllers: [ProductsTools, ProductsResources, ProductsPrompts],
providers: [ProductService],
imports: [], // Other modules this depends on
exports: [] // Services to expose to other modules
})
export class ProductsModule {}
Module with Dependencies
@Module({
name: 'orders',
controllers: [OrdersTools],
providers: [OrderService],
imports: [ProductsModule, UserModule], // Import other modules
exports: [OrderService] // Make OrderService available to others
})
export class OrdersModule {}
Dynamic Modules (Advanced)
// Config module with options
@Module({})
export class ConfigModule {
static forRoot(options?: ConfigOptions) {
return {
module: ConfigModule,
providers: [
{
provide: 'CONFIG_OPTIONS',
useValue: options || {}
},
ConfigService
],
exports: [ConfigService]
};
}
}
Tools
Tools are functions that AI models can call.
Basic Tool
import { Tool, ExecutionContext } from 'nitrostack';
import { z } from 'zod';
export class ProductsTools {
@Tool({
name: 'get_product',
description: 'Get product details by ID',
inputSchema: z.object({
product_id: z.string().describe('Unique product identifier')
})
})
async getProduct(input: any, ctx: ExecutionContext) {
ctx.logger.info('Fetching product', { id: input.product_id });
return {
id: input.product_id,
name: 'Example Product',
price: 99.99
};
}
}
Tool with Examples
@Tool({
name: 'browse_products',
description: 'Browse products with filters',
inputSchema: z.object({
category: z.string().optional().describe('Product category'),
page: z.number().default(1).describe('Page number'),
limit: z.number().default(10).describe('Items per page')
}),
examples: {
request: { category: 'electronics', page: 1, limit: 10 },
response: {
products: [
{ id: '1', name: 'Laptop', price: 999 },
{ id: '2', name: 'Mouse', price: 29 }
],
total: 2,
page: 1
}
}
})
async browseProducts(input: any, ctx: ExecutionContext) {
return {
products: [],
total: 0,
page: input.page
};
}
Tool with Widget
import { Tool, Widget, ExecutionContext } from 'nitrostack';
@Tool({
name: 'get_product',
description: 'Get product details',
inputSchema: z.object({ product_id: z.string() }),
examples: {
request: { product_id: 'prod-1' },
response: { id: 'prod-1', name: 'Laptop', price: 999, image_url: '/laptop.jpg' }
}
})
@Widget('product-card') // Link to src/widgets/app/product-card/page.tsx
async getProduct(input: any, ctx: ExecutionContext) {
return await this.productService.findById(input.product_id);
}
Tool with Guards
import { Tool, UseGuards, ExecutionContext } from 'nitrostack';
import { JWTGuard } from '../../guards/jwt.guard.js';
@Tool({
name: 'create_order',
description: 'Create a new order',
inputSchema: z.object({
items: z.array(z.object({
product_id: z.string(),
quantity: z.number()
}))
})
})
@UseGuards(JWTGuard) // Require authentication
async createOrder(input: any, ctx: ExecutionContext) {
const userId = ctx.auth?.subject; // Set by guard
ctx.logger.info('Creating order', { userId, items: input.items });
return await this.orderService.create(userId, input.items);
}
Tool with Caching
import { Tool, Cache } from 'nitrostack';
@Tool({
name: 'get_categories',
description: 'Get all product categories',
inputSchema: z.object({})
})
@Cache({ ttl: 300 }) // Cache for 5 minutes
async getCategories() {
return await this.productService.getCategories();
}
Tool with Rate Limiting
import { Tool, RateLimit } from 'nitrostack';
@Tool({
name: 'send_email',
description: 'Send email notification',
inputSchema: z.object({
to: z.string().email(),
subject: z.string(),
body: z.string()
})
})
@RateLimit({
requests: 10, // Max requests
window: '1m', // Time window (1m, 1h, 1d)
key: (ctx) => ctx.auth?.subject || 'anonymous'
})
async sendEmail(input: any, ctx: ExecutionContext) {
await this.emailService.send(input.to, input.subject, input.body);
return { success: true };
}
Tool with DI
import { Injectable } from 'nitrostack';
@Injectable()
export class ProductService {
constructor(private db: DatabaseService) {}
async findById(id: string) {
return this.db.queryOne('SELECT * FROM products WHERE id = ?', [id]);
}
}
export class ProductsTools {
constructor(private productService: ProductService) {} // Auto-injected
@Tool({ name: 'get_product' })
async getProduct(input: any) {
return await this.productService.findById(input.product_id);
}
}
Resources
Resources are data schemas AI can read.
Basic Resource
import { Resource, ExecutionContext } from 'nitrostack';
@Resource({
uri: 'product://{id}',
name: 'Product Data',
description: 'Detailed product information',
mimeType: 'application/json'
})
async getProductResource(uri: string, ctx: ExecutionContext) {
const id = uri.split('://')[1]; // Extract ID from URI
const product = await this.productService.findById(id);
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify(product, null, 2)
}]
};
}
Resource with Widget
@Resource({
uri: 'catalog://categories',
name: 'Product Categories',
description: 'All available product categories',
mimeType: 'application/json',
examples: {
response: {
categories: ['Electronics', 'Fashion', 'Home']
}
}
})
@Widget('categories-list')
async getCategoriesResource(uri: string, ctx: ExecutionContext) {
const categories = await this.productService.getCategories();
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify({ categories })
}]
};
}
Prompts
Prompts are conversation templates for AI.
Basic Prompt
import { Prompt, ExecutionContext } from 'nitrostack';
@Prompt({
name: 'product_review',
description: 'Generate product review template',
arguments: [
{
name: 'product_id',
description: 'ID of product to review',
required: true
},
{
name: 'rating',
description: 'Rating (1-5)',
required: false
}
]
})
async getReviewPrompt(args: any, ctx: ExecutionContext) {
const product = await this.productService.findById(args.product_id);
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Write a ${args.rating || 5}-star review for: ${product.name}`
}
}
]
};
}
Prompt with Context
@Prompt({
name: 'order_summary',
description: 'Generate order summary for customer',
arguments: [
{ name: 'order_id', description: 'Order ID', required: true }
]
})
async getOrderSummaryPrompt(args: any, ctx: ExecutionContext) {
const order = await this.orderService.findById(args.order_id);
return {
messages: [
{
role: 'system',
content: {
type: 'text',
text: 'You are a helpful customer service assistant.'
}
},
{
role: 'user',
content: {
type: 'text',
text: `Summarize order #${order.id} with ${order.items.length} items, total $${order.total}`
}
}
]
};
}
Guards
Guards implement authentication/authorization.
JWT Guard
import { Guard, ExecutionContext, Injectable } from 'nitrostack';
import jwt from 'jsonwebtoken';
@Injectable()
export class JWTGuard implements Guard {
async canActivate(context: ExecutionContext): Promise<boolean> {
const authHeader = context.metadata?.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return false;
}
const token = authHeader.substring(7);
try {
const payload = jwt.verify(token, process.env.JWT_SECRET!);
// Attach auth info to context
context.auth = {
subject: payload.sub,
email: payload.email,
...payload
};
return true;
} catch (error) {
context.logger.warn('JWT verification failed', { error });
return false;
}
}
}
Admin Guard
@Injectable()
export class AdminGuard implements Guard {
async canActivate(context: ExecutionContext): Promise<boolean> {
// Assumes JWTGuard ran first
const user = context.auth;
if (!user) {
return false;
}
// Check if user is admin
return user.role === 'admin';
}
}
// Usage: Multiple guards (all must pass)
@Tool({ name: 'delete_user' })
@UseGuards(JWTGuard, AdminGuard)
async deleteUser(input: any, ctx: ExecutionContext) {
// Only admins with valid JWT can call this
}
Middleware
Middleware runs before/after tool execution.
Logging Middleware
import { Middleware, MiddlewareInterface, ExecutionContext } from 'nitrostack';
@Middleware()
export class LoggingMiddleware implements MiddlewareInterface {
async use(context: ExecutionContext, next: () => Promise<any>): Promise<any> {
const start = Date.now();
context.logger.info('Tool starting', {
tool: context.toolName,
input: context.input
});
try {
const result = await next();
const duration = Date.now() - start;
context.logger.info('Tool completed', {
tool: context.toolName,
duration: `${duration}ms`
});
return result;
} catch (error) {
const duration = Date.now() - start;
context.logger.error('Tool failed', {
tool: context.toolName,
duration: `${duration}ms`,
error
});
throw error;
}
}
}
// Usage
@Tool({ name: 'my_tool' })
@UseMiddleware(LoggingMiddleware)
async myTool(input: any, ctx: ExecutionContext) {}
Auth Middleware
@Middleware()
export class AuthMiddleware implements MiddlewareInterface {
async use(context: ExecutionContext, next: () => Promise<any>) {
if (!context.auth) {
throw new Error('Unauthorized');
}
return next();
}
}
Interceptors
Interceptors transform requests/responses.
Transform Interceptor
import { Interceptor, InterceptorInterface, ExecutionContext } from 'nitrostack';
@Interceptor()
export class TransformInterceptor implements InterceptorInterface {
async intercept(context: ExecutionContext, next: () => Promise<any>): Promise<any> {
const result = await next();
// Wrap all responses in standard format
return {
success: true,
data: result,
timestamp: new Date().toISOString(),
tool: context.toolName
};
}
}
// Usage
@Tool({ name: 'get_product' })
@UseInterceptors(TransformInterceptor)
async getProduct(input: any) {
return { id: '1', name: 'Product' };
}
// Returns:
// {
// success: true,
// data: { id: '1', name: 'Product' },
// timestamp: '2025-10-24T12:00:00Z',
// tool: 'get_product'
// }
Pipes
Pipes transform/validate input before handler execution.
Validation Pipe
import { Pipe, PipeInterface } from 'nitrostack';
import { z } from 'zod';
@Pipe()
export class ValidationPipe implements PipeInterface {
async transform(value: any, metadata: any): Promise<any> {
if (metadata.schema) {
// Validate with Zod
return metadata.schema.parse(value);
}
return value;
}
}
Services & Dependency Injection
Injectable Service
import { Injectable } from 'nitrostack';
@Injectable()
export class ProductService {
constructor(private db: DatabaseService) {} // DI
async findById(id: string) {
return this.db.queryOne('SELECT * FROM products WHERE id = ?', [id]);
}
async search(query: string) {
return this.db.query(
'SELECT * FROM products WHERE name LIKE ?',
[`%${query}%`]
);
}
async getCategories() {
return this.db.query('SELECT DISTINCT category FROM products');
}
}
Using Services in Tools
export class ProductsTools {
constructor(
private productService: ProductService,
private cacheService: CacheService
) {} // Both auto-injected
@Tool({ name: 'search_products' })
async searchProducts(input: any, ctx: ExecutionContext) {
const results = await this.productService.search(input.query);
await this.cacheService.set(`search:${input.query}`, results);
return results;
}
}
Decorators Reference
Core Decorators
| Decorator | Purpose | Example |
|---|---|---|
@McpApp(options) | Root application module | @McpApp({ server: { name: 'my-server' } }) |
@Module(options) | Feature module | @Module({ name: 'products', controllers: [...] }) |
@Injectable() | Mark for DI | @Injectable() class ProductService {} |
Tool Decorators
| Decorator | Purpose | Example |
|---|---|---|
@Tool(options) | Define tool | @Tool({ name: 'get_product', inputSchema: z.object({}) }) |
@Resource(options) | Define resource | @Resource({ uri: 'product://{id}' }) |
@Prompt(options) | Define prompt | @Prompt({ name: 'review_prompt' }) |
Feature Decorators
| Decorator | Purpose | Example |
|---|---|---|
@Widget(name) | Link UI widget | @Widget('product-card') |
@UseGuards(...guards) | Apply guards | @UseGuards(JWTGuard, AdminGuard) |
@UseMiddleware(...mw) | Apply middleware | @UseMiddleware(LoggingMiddleware) |
@UseInterceptors(...int) | Apply interceptors | @UseInterceptors(TransformInterceptor) |
@Cache(options) | Cache responses | @Cache({ ttl: 300 }) |
@RateLimit(options) | Rate limiting | @RateLimit({ requests: 10, window: '1m' }) |
@HealthCheck(name) | Define health check | @HealthCheck('database') |
Widgets
UI components rendered alongside tool responses.
Widget File Structure
src/widgets/
āāā app/
ā āāā product-card/
ā ā āāā page.tsx # Widget component
ā āāā products-grid/
ā ā āāā page.tsx
ā āāā ...
āāā types/
ā āāā tool-data.ts # Generated types
āāā styles/
ā āāā ecommerce.ts # Shared inline styles
āāā widget-manifest.json # Widget metadata
āāā package.json
āāā next.config.js
Basic Widget
'use client';
import { useEffect, useState } from 'react';
export default function ProductCard() {
const [data, setData] = useState<any>(null);
useEffect(() => {
// Listen for data from Studio/MCP
const handleMessage = (event: MessageEvent) => {
if (event.data.type === 'toolOutput') {
setData(event.data.data);
}
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, []);
if (!data) {
return <div>Loading...</div>;
}
return (
<div style={{ padding: '20px', border: '1px solid #ddd' }}>
<h2>{data.name}</h2>
<p>Price: ${data.price}</p>
<img src={data.image_url} alt={data.name} style={{ maxWidth: '200px' }} />
</div>
);
}
Widget with Types
'use client';
import { useEffect, useState } from 'react';
import { GetProductOutput } from '../../types/tool-data';
export default function ProductCard() {
const [data, setData] = useState<GetProductOutput | null>(null);
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (event.data.type === 'toolOutput') {
setData(event.data.data as GetProductOutput);
}
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, []);
if (!data) return <div>Loading...</div>;
// TypeScript knows data.name, data.price, etc.
return (
<div>
<h2>{data.name}</h2>
<p>${data.price}</p>
</div>
);
}
Widget Manifest
{
"widgets": [
{
"uri": "product-card",
"name": "Product Card",
"description": "Display product details",
"toolName": "get_product",
"examples": {
"request": { "product_id": "1" },
"response": { "id": "1", "name": "Laptop", "price": 999 }
}
}
]
}
Linking Widget to Tool
@Tool({
name: 'get_product',
inputSchema: z.object({ product_id: z.string() })
})
@Widget('product-card') // Links to src/widgets/app/product-card/page.tsx
async getProduct(input: any) {
return { id: input.product_id, name: 'Laptop', price: 999 };
}
Health Checks
Monitor system health.
Basic Health Check
import { HealthCheck, Injectable } from 'nitrostack';
@Injectable()
export class SystemHealthCheck {
@HealthCheck('system')
async checkSystem() {
const uptime = process.uptime();
const memory = process.memoryUsage();
return {
status: uptime > 0 ? 'up' : 'down',
message: 'System is operational',
details: {
uptime: `${Math.floor(uptime)}s`,
memory: `${Math.round(memory.heapUsed / 1024 / 1024)}MB`
}
};
}
}
Database Health Check
@Injectable()
export class DatabaseHealthCheck {
constructor(private db: DatabaseService) {}
@HealthCheck('database')
async checkDatabase() {
try {
await this.db.query('SELECT 1');
return {
status: 'up',
message: 'Database is responsive',
details: { connection: 'active' }
};
} catch (error) {
return {
status: 'down',
message: 'Database connection failed',
details: { error: error.message }
};
}
}
}
Caching
Cache tool responses for performance.
import { Tool, Cache } from 'nitrostack';
// Simple TTL caching
@Tool({ name: 'get_categories' })
@Cache({ ttl: 300 }) // 5 minutes
async getCategories() {
return await this.productService.getCategories();
}
// Custom cache key
@Tool({ name: 'get_product' })
@Cache({
ttl: 60,
key: (input) => `product:${input.product_id}`
})
async getProduct(input: any) {
return await this.productService.findById(input.product_id);
}
Rate Limiting
Limit tool execution frequency.
import { Tool, RateLimit } from 'nitrostack';
// Per-user rate limiting
@Tool({ name: 'send_email' })
@RateLimit({
requests: 10, // Max 10 requests
window: '1m', // Per 1 minute
key: (ctx) => ctx.auth?.subject || 'anon' // Key by user ID
})
async sendEmail(input: any) {
await this.emailService.send(input.to, input.subject, input.body);
return { success: true };
}
// Global rate limiting
@Tool({ name: 'expensive_operation' })
@RateLimit({
requests: 100,
window: '1h',
key: () => 'global'
})
async expensiveOperation(input: any) {
// ...
}
Error Handling
@Tool({ name: 'get_user' })
async getUser(input: any, ctx: ExecutionContext) {
const user = await this.userService.findById(input.user_id);
if (!user) {
throw new Error('User not found');
}
return user;
}
// Custom error class
export class NotFoundError extends Error {
constructor(resource: string, id: string) {
super(`${resource} with ID ${id} not found`);
this.name = 'NotFoundError';
}
}
@Tool({ name: 'get_product' })
async getProduct(input: any) {
const product = await this.productService.findById(input.product_id);
if (!product) {
throw new NotFoundError('Product', input.product_id);
}
return product;
}
File Structure
src/
āāā modules/
ā āāā auth/
ā ā āāā auth.module.ts
ā ā āāā auth.tools.ts
ā ā āāā auth.resources.ts
ā ā āāā auth.prompts.ts
ā ā āāā auth.service.ts
ā ā āāā guards/
ā ā āāā jwt.guard.ts
ā āāā products/
ā ā āāā products.module.ts
ā ā āāā products.tools.ts
ā ā āāā products.resources.ts
ā ā āāā products.prompts.ts
ā ā āāā products.service.ts
ā āāā ...
āāā services/
ā āāā database.service.ts
ā āāā cache.service.ts
āāā guards/
ā āāā jwt.guard.ts
ā āāā admin.guard.ts
āāā middleware/
ā āāā logging.middleware.ts
āāā interceptors/
ā āāā transform.interceptor.ts
āāā pipes/
ā āāā validation.pipe.ts
āāā health/
ā āāā system.health.ts
ā āāā database.health.ts
āāā widgets/
ā āāā app/
ā ā āāā product-card/
ā ā āāā products-grid/
ā ā āāā ...
ā āāā types/
ā ā āāā tool-data.ts
ā āāā styles/
ā āāā shared.ts
āāā app.module.ts
āāā index.ts
Import Rules
Always Use .js Extensions
// ā
Correct
import { ProductService } from './products.service.js';
import { JWTGuard } from '../auth/jwt.guard.js';
import { DatabaseService } from '../../services/database.service.js';
// ā Wrong
import { ProductService } from './products.service';
Core Imports
// Core decorators
import {
Tool,
Resource,
Prompt,
Module,
McpApp,
Injectable,
UseGuards,
Cache,
RateLimit,
HealthCheck,
ExecutionContext
} from 'nitrostack';
// Config
import { ConfigModule, ConfigService } from 'nitrostack/config';
// JWT
import { JWTModule } from 'nitrostack/jwt';
// Validation
import { z } from 'zod';
// Widgets (in widget files)
import { withToolData } from 'nitrostack/widgets';
Common Patterns
CRUD Operations
export class ProductsTools {
constructor(private productService: ProductService) {}
// Create
@Tool({
name: 'create_product',
inputSchema: z.object({
name: z.string(),
price: z.number(),
category: z.string()
})
})
@UseGuards(JWTGuard, AdminGuard)
async create(input: any) {
return await this.productService.create(input);
}
// Read
@Tool({
name: 'get_product',
inputSchema: z.object({ product_id: z.string() })
})
@Cache({ ttl: 60 })
async get(input: any) {
return await this.productService.findById(input.product_id);
}
// Update
@Tool({
name: 'update_product',
inputSchema: z.object({
product_id: z.string(),
name: z.string().optional(),
price: z.number().optional()
})
})
@UseGuards(JWTGuard, AdminGuard)
async update(input: any) {
return await this.productService.update(input.product_id, input);
}
// Delete
@Tool({
name: 'delete_product',
inputSchema: z.object({ product_id: z.string() })
})
@UseGuards(JWTGuard, AdminGuard)
async delete(input: any) {
await this.productService.delete(input.product_id);
return { success: true };
}
}
Pagination Pattern
@Tool({
name: 'list_products',
inputSchema: z.object({
page: z.number().default(1),
limit: z.number().default(20).max(100),
category: z.string().optional()
})
})
async listProducts(input: any) {
const offset = (input.page - 1) * input.limit;
const products = await this.productService.list({
limit: input.limit,
offset,
category: input.category
});
const total = await this.productService.count({ category: input.category });
return {
products,
pagination: {
page: input.page,
limit: input.limit,
total,
pages: Math.ceil(total / input.limit)
}
};
}
Search Pattern
@Tool({
name: 'search_products',
inputSchema: z.object({
query: z.string().min(1),
filters: z.object({
category: z.string().optional(),
min_price: z.number().optional(),
max_price: z.number().optional()
}).optional()
})
})
@Cache({ ttl: 30, key: (input) => `search:${input.query}:${JSON.stringify(input.filters)}` })
async search(input: any) {
return await this.productService.search(input.query, input.filters);
}
Key Rules for AI Agents
- ā Always use decorators - No factory functions
- ā
Constructor injection - Never use
new ClassName() - ā Services for logic - Tools should be thin wrappers
- ā Zod for schemas - All tool inputs must have inputSchema
- ā Return JSON - All tool outputs must be JSON-serializable
- ā
ES modules - Use
.jsin imports, not.ts - ā ExecutionContext - Second parameter to all handlers
- ā Examples - Always include request/response examples
That's the complete NitroStack SDK reference!
For more details, check:
/docs/sdk/typescript/- Full documentation/templates/typescript-starter/- Simple example/templates/typescript-auth/- Full-featured example