NitroStack Logo
/templates
/enterprise search

Enterprise Search Template

The Enterprise Search Template (typescript-enterprise-search) demonstrates progressive tool discovery using NitroStack's BM25SearchTransform and @Controller namespacing. It is designed for enterprise systems with dozens or hundreds of specialized tools, collapsing large tool catalogs to reduce LLM context token consumption by up to 98%.

Table of Contents


Overview

In enterprise deployments, exposing complete tool schemas for every domain (finance, inventory, customer support) can bloat prompts with tens of thousands of tokens before an agent even starts reasoning.

This template demonstrates how to:

  1. Group related operations into feature classes using @Controller('namespace').
  2. Collapse hundreds of tools into two synthetic meta-tools: search_tools and call_tool.
  3. Keep essential bootstrap tools directly visible using alwaysVisible.
  4. Dynamically validate input parameters using Zod schemas when dispatched via call_tool.

What's Included

  • Finance Controller: Invoicing, ledger auditing, tax calculation, and financial summaries.
  • Inventory Controller: Stock checking, warehouse reordering, and inventory reporting.
  • Support Controller: User login authentication, ticket lookups, and system status.
  • BM25 Search Engine: In-memory BM25 information retrieval indexing names, descriptions, and parameter definitions.
  • Bypass Configuration: Designates support_auth_login, support_get_system_status, finance_get_financial_summary, and inventory_report as alwaysVisible.

Quick Start

Create Project

Bash
# Using the preset alias:
npx @nitrostack/cli init enterprise-tools --preset enterprise-search

# Or using full template name:
npx @nitrostack/cli init enterprise-tools --template typescript-enterprise-search

cd enterprise-tools
npm run dev

Project Structure

Text
enterprise-tools/
├── src/
│   ├── controllers/
│   │   ├── finance.controller.ts     # @Controller('finance')
│   │   ├── inventory.controller.ts   # @Controller('inventory')
│   │   └── support.controller.ts     # @Controller('support')
│   ├── app.module.ts                 # @McpApp with BM25SearchTransform
│   └── index.ts                      # Application bootstrap
├── tsconfig.json
└── package.json

Controller Namespacing

Rather than manually naming tools finance_calculate_tax or inventory_check_stock, each controller declares a domain prefix via @Controller:

Typescript
// src/controllers/finance.controller.ts
import { Controller, ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core';

@Controller('finance') // All tools automatically prefixed with 'finance_'
export class FinanceController {
  @Tool({
    name: 'calculate_tax', // Exposed in MCP catalog as 'finance_calculate_tax'
    description: 'Calculate sales and corporate tax for a transaction',
    inputSchema: z.object({
      amount: z.number().positive().describe('Gross transaction amount'),
      region: z.enum(['US-CA', 'US-NY', 'EU-DE', 'UK']).describe('Tax jurisdiction'),
    }),
  })
  async calculateTax(input: { amount: number; region: string }, ctx: ExecutionContext) {
    ctx.logger.info('Calculating tax', { region: input.region, amount: input.amount });
    const rates: Record<string, number> = { 'US-CA': 0.0825, 'US-NY': 0.08875, 'EU-DE': 0.19, UK: 0.20 };
    const rate = rates[input.region] ?? 0.10;
    return { gross: input.amount, tax: input.amount * rate, net: input.amount * (1 + rate) };
  }
}
Typescript
// src/controllers/inventory.controller.ts
import { Controller, ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core';

@Controller('inventory') // Exposed as 'inventory_check_stock'
export class InventoryController {
  @Tool({
    name: 'check_stock',
    description: 'Check available warehouse inventory levels for an SKU',
    inputSchema: z.object({
      sku: z.string().describe('Product SKU identifier'),
    }),
  })
  async checkStock(input: { sku: string }, ctx: ExecutionContext) {
    return { sku: input.sku, inStock: true, quantityAvailable: 420 };
  }
}

BM25 Search Transform Configuration

In src/app.module.ts, the root application module attaches BM25SearchTransform to the server:

Typescript
// src/app.module.ts
import { McpApp, Module, BM25SearchTransform } from '@nitrostack/core';
import { FinanceController } from './controllers/finance.controller.js';
import { InventoryController } from './controllers/inventory.controller.js';
import { SupportController } from './controllers/support.controller.js';

@McpApp({
  module: AppModule,
  server: {
    name: 'enterprise-search-service',
    version: '1.0.0',
  },
  transforms: [
    new BM25SearchTransform({
      defaultLimit: 5,
      alwaysVisible: [
        'support_auth_login',
        'support_get_system_status',
        'finance_get_financial_summary',
        'inventory_report',
      ],
      searchToolDescription:
        'Searches available tools by keywords or a natural language description of the task. ' +
        'For a specific task (for example finance, tax, payroll, inventory, stock, or customer support), ' +
        'call it with task keywords to get the matching tool and its parameters before answering. ' +
        'If the user asks what you can do or wants to see the tools, call it with no query to get a brief index of every available tool. ' +
        'Never decline a request without searching first.',
    }),
  ],
})
@Module({
  name: 'app',
  description: 'Enterprise MCP service optimized with BM25 progressive tool discovery',
  controllers: [FinanceController, InventoryController, SupportController],
})
export class AppModule {}

Interactive Testing

Start the development server and open NitroStudio:

Bash
npm run dev

1. Catalog Inspection (tools/list)

When your AI model requests tools/list, it receives only 6 tools instead of dozens:

  • search_tools (synthetic discovery tool)
  • call_tool (synthetic proxy dispatcher)
  • support_auth_login (alwaysVisible)
  • support_get_system_status (alwaysVisible)
  • finance_get_financial_summary (alwaysVisible)
  • inventory_report (alwaysVisible)

2. Searching for Capabilities

The AI model queries search_tools:

JSON
{
  "name": "search_tools",
  "arguments": {
    "query": "calculate customer sales tax"
  }
}

Response:

JSON
{
  "tools": [
    {
      "name": "finance_calculate_tax",
      "description": "Calculate sales and corporate tax for a transaction",
      "inputSchema": {
        "type": "object",
        "properties": {
          "amount": { "type": "number", "description": "Gross transaction amount" },
          "region": { "type": "string", "enum": ["US-CA", "US-NY", "EU-DE", "UK"] }
        },
        "required": ["amount", "region"]
      }
    }
  ]
}

3. Calling the Discovered Tool

The model dispatches the tool invocation via call_tool:

JSON
{
  "name": "call_tool",
  "arguments": {
    "name": "finance_calculate_tax",
    "arguments": {
      "amount": 1000,
      "region": "US-CA"
    }
  }
}

Result:

JSON
{
  "gross": 1000,
  "tax": 82.5,
  "net": 1082.5
}