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
- What's Included
- Quick Start
- Project Architecture
- Controller Namespacing
- BM25 Search Transform Configuration
- Interactive Testing
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:
- Group related operations into feature classes using
@Controller('namespace'). - Collapse hundreds of tools into two synthetic meta-tools:
search_toolsandcall_tool. - Keep essential bootstrap tools directly visible using
alwaysVisible. - 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, andinventory_reportasalwaysVisible.
Quick Start
Create Project
# 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
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:
// 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) };
}
}
// 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:
// 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:
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:
{
"name": "search_tools",
"arguments": {
"query": "calculate customer sales tax"
}
}
Response:
{
"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:
{
"name": "call_tool",
"arguments": {
"name": "finance_calculate_tax",
"arguments": {
"amount": 1000,
"region": "US-CA"
}
}
}
Result:
{
"gross": 1000,
"tax": 82.5,
"net": 1082.5
}