Widgets and Observability

Attach UI to tools, report health, and react to events

@Widget attaches UI to a tool, @HealthCheck reports on a dependency, and @OnEvent runs a method when an event is emitted.

@Widget

Attaches an interactive UI widget to an MCP tool. The widget is a Next.js page component that renders the tool's output.

import { Widget, WidgetRouteMetadata } from '@nitrostack/core';

// Signature 1: String route shorthand
@Widget(routePath: string)

// Signature 2: Object metadata configuration
@Widget(metadata: WidgetRouteMetadata)
@widget("route")  # or WidgetOptions(...)

Parameters & Options:

ParameterTypeRequiredDescription
routestringYes (in object form)Route path matching src/widgets/app/{route}/page.tsx
cspWidgetCspOptionsNoContent Security Policy allowlist for external network domains and frames
domainstringNoWidget origin domain (HTTPS) for OpenAI ChatGPT sandbox mapping
prefersBorderbooleanNoWhether the client UI should render a decorative container border

1. Basic Route Example:

@Tool({ name: 'get_chart_data' })
@Widget('chart-visualization')
async getChartData(input: ChartInput, ctx: ExecutionContext) {
  return this.chartService.getData(input);
  // Widget at src/widgets/app/chart-visualization/page.tsx renders result
}

2. Object Metadata Example with CSP:

@Tool({ name: 'interactive_map' })
@Widget({
  route: 'map-view',
  csp: {
    connectDomains: ['https://api.mapbox.com'],
    resourceDomains: ['https://tiles.mapbox.com'],
    frameDomains: [],
  },
  domain: 'https://myapp.nitrostack.ai',
  prefersBorder: true,
})
async getMap(input: { location: string }, ctx: ExecutionContext) {
  return { coordinates: [40.7128, -74.006] };
}

Complete Example with Invocation Messages:

@Tool({
  name: 'get_dashboard',
  title: 'User Dashboard',
  description: 'Get user dashboard with stats',
  inputSchema: z.object({ userId: z.string() }),
  // Status messages shown during execution
  invocation: {
    invoking: 'Loading dashboard...',
    invoked: 'Dashboard ready'
  },
  // Example data for widget preview (IMPORTANT!)
  examples: {
    request: { userId: 'user-123' },
    response: {
      user: { name: 'John', email: 'john@example.com' },
      stats: { orders: 42, totalSpent: 1234.56 }
    }
  }
})
@Widget('user-dashboard')
async getDashboard(input: { userId: string }, ctx: ExecutionContext) {
  return { user: {...}, stats: {...} };
}

Note

The examples.response data is used by clients to render widget previews. Always provide realistic example data matching your response structure.

@HealthCheck

Defines a health check for system monitoring.

import { HealthCheck, HealthCheckInterface } from '@nitrostack/core';

@HealthCheck(options: HealthCheckOptions)
@health_check(name="system")

Options:

PropertyTypeRequiredDescription
namestringYesHealth check identifier
descriptionstringNoDescription of what is checked
intervalnumberNoCheck interval in seconds

Example:

@HealthCheck({
  name: 'database',
  description: 'Database connectivity check',
  interval: 30
})
export class DatabaseHealthCheck implements HealthCheckInterface {
  async check(): Promise<HealthCheckResult> {
    const isHealthy = await this.db.ping();
    return {
      status: isHealthy ? 'up' : 'down',
      message: isHealthy ? 'Database connected' : 'Database unreachable'
    };
  }
}

@OnEvent

Subscribes to events.

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

@OnEvent(eventName: string)
@on_event("name")

Example:

@Injectable()
export class NotificationService {
  @OnEvent('order.created')
  async handleOrderCreated(data: OrderCreatedEvent): Promise<void> {
    await this.emailService.sendOrderConfirmation(data.userId, data.orderId);
  }
}

On this page