Interceptors Guide
Overview
Interceptors bind extra logic before or after tool execution. They can transform requests, modify responses, or add metadata.
Creating an Interceptor
import { Interceptor, InterceptorInterface, ExecutionContext } from 'nitrostack';
@Interceptor()
export class TransformInterceptor implements InterceptorInterface {
  async intercept(context: ExecutionContext, next: () => Promise<any>): Promise<any> {
    // Execute tool
    const result = await next();
    
    // Transform response
    return {
      success: true,
      data: result,
      metadata: {
        timestamp: new Date().toISOString(),
        tool: context.toolName
      }
    };
  }
}
Using Interceptors
On a Tool
@Tool({ name: 'get_product' })
@UseInterceptors(TransformInterceptor)
async getProduct(input: any, ctx: ExecutionContext) {
  return { id: 1, name: 'Product' };
  // Returns: { success: true, data: { id: 1, name: 'Product' }, metadata: {...} }
}
Multiple Interceptors
@Tool({ name: 'create_user' })
@UseInterceptors(TransformInterceptor, LoggingInterceptor, CacheInterceptor)
async createUser(input: any, ctx: ExecutionContext) {
  // All interceptors run in order
}
Interceptor Examples
Response Wrapper
@Interceptor()
export class ResponseWrapperInterceptor implements InterceptorInterface {
  async intercept(context: ExecutionContext, next: () => Promise<any>): Promise<any> {
    const start = Date.now();
    
    try {
      const result = await next();
      
      return {
        success: true,
        data: result,
        meta: {
          duration: Date.now() - start,
          timestamp: new Date().toISOString()
        }
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
        meta: {
          duration: Date.now() - start,
          timestamp: new Date().toISOString()
        }
      };
    }
  }
}
Data Transformation
@Interceptor()
export class SnakeToCamelInterceptor implements InterceptorInterface {
  async intercept(context: ExecutionContext, next: () => Promise<any>): Promise<any> {
    const result = await next();
    return this.transformKeys(result);
  }
  
  private transformKeys(obj: any): any {
    if (Array.isArray(obj)) {
      return obj.map(item => this.transformKeys(item));
    }
    
    if (obj && typeof obj === 'object') {
      return Object.keys(obj).reduce((acc, key) => {
        const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
        acc[camelKey] = this.transformKeys(obj[key]);
        return acc;
      }, {} as any);
    }
    
    return obj;
  }
}
Sensitive Data Masking
@Interceptor()
export class MaskingInterceptor implements InterceptorInterface {
  private sensitiveFields = ['password', 'ssn', 'creditCard'];
  
  async intercept(context: ExecutionContext, next: () => Promise<any>): Promise<any> {
    const result = await next();
    return this.maskSensitiveData(result);
  }
  
  private maskSensitiveData(obj: any): any {
    if (Array.isArray(obj)) {
      return obj.map(item => this.maskSensitiveData(item));
    }
    
    if (obj && typeof obj === 'object') {
      const masked: any = {};
      for (const [key, value] of Object.entries(obj)) {
        if (this.sensitiveFields.includes(key)) {
          masked[key] = '***MASKED***';
        } else {
          masked[key] = this.maskSensitiveData(value);
        }
      }
      return masked;
    }
    
    return obj;
  }
}
Best Practices
- Don't mutate original data - Return new objects
 - Handle errors gracefully - Catch and transform
 - Keep interceptors simple - Single responsibility
 - Use DI for services - Injectable interceptors
 - Document transformations - Clear JSDoc