Guards

Guard interface and usage for TypeScript and Python

Guards control access to tools, resources, and prompts by determining whether a request should proceed. They implement authorization logic, verify authentication tokens, check permissions, and enforce access policies.

Guards execute before the handler and can deny access by returning false or throwing an exception.

Guard Interface

A guard has one method. Return true to let the call continue; return false or throw to stop it.

interface Guard {
  canActivate(context: ExecutionContext): Promise<boolean> | boolean;
}
async def can_activate(self, context: ExecutionContext) -> bool

Stack with @use_guards. Built-ins: ApiKeyGuard, JwtGuard, OAuthGuard.

from nitrostack import use_guards, OAuthGuard, ApiKeyGuard, JwtGuard

@use_guards(OAuthGuard)
async def search_flights(...):
    ...

Scaffold: nitrostack-py generate guard MyGuard.

Read tokens from context.metadata (authorization, headers, _oauth). OAuthGuard allows no-token when OAUTH_REQUIRED is unset.

Parameters:

  • context: The execution context containing auth data, logger, and metadata

Returns:

  • true: Allow the request to proceed
  • false: Deny access (throws ForbiddenException)
  • throw Error: Deny access with custom error

Using Guards

Attach guards with @UseGuards. When you list several, they run in order and all of them must pass.

On Individual Methods

Put @UseGuards on the handler it protects.

import { ToolDecorator as Tool, UseGuards, ExecutionContext } from '@nitrostack/core';
import { AuthGuard } from './guards/auth.guard.js';

export class UserTools {
  @Tool({ name: 'get_profile' })
  @UseGuards(AuthGuard)
  async getProfile(input: {}, ctx: ExecutionContext) {
    return this.userService.findById(ctx.auth!.subject);
  }
}

Multiple Guards

Guards execute in order. All must pass for the request to proceed:

@Tool({ name: 'delete_user' })
@UseGuards(AuthGuard, AdminGuard, RateLimitGuard)
async deleteUser(input: { userId: string }, ctx: ExecutionContext) {
  // Only authenticated admins within rate limit can access
  return this.userService.delete(input.userId);
}

On Resources and Prompts

Guards work on all MCP primitives:

@Resource({
  uri: 'admin://dashboard',
  name: 'Admin Dashboard'
})
@UseGuards(AdminGuard)
async getAdminDashboard(uri: string, ctx: ExecutionContext) {
  // Admin-only resource
}

@Prompt({
  name: 'confidential_analysis',
  title: 'Confidential Analysis',
  description: 'Analyze confidential data'
})
@UseGuards(OfficerClearanceGuard)
async getConfidentialPrompt(args: any, ctx: ExecutionContext) {
  // Officer-level prompt
}

Dependency Injection

Guards can inject services:

@Injectable({ deps: [UserService, ConfigService] })
export class SubscriptionGuard implements Guard {
  constructor(
    private userService: UserService,
    private configService: ConfigService
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const userId = context.auth?.subject;
    if (!userId) return false;

    const user = await this.userService.findById(userId);
    if (!user) return false;

    const requiredPlan = this.configService.get('REQUIRED_PLAN', 'pro');
    
    if (user.subscriptionPlan !== requiredPlan && user.subscriptionPlan !== 'enterprise') {
      context.logger.info('Subscription check failed', {
        userId,
        userPlan: user.subscriptionPlan,
        requiredPlan
      });
      return false;
    }

    return true;
  }
}

Guard patterns

Worked examples are split by what the guard checks.

On this page