Skip to main content
Build a simple calculator tool to learn the fundamentals of FrontMCP tool development. By the end of this guide, you’ll understand how to create, validate, and test MCP tools.
Prerequisites: A FrontMCP project initialized (see Installation)

What You’ll Build

A simple add tool that adds two numbers together with full type safety and input validation.

Step 1: Create the Tool File

Create a new file src/apps/calculator/tools/add.tool.ts:
import { Tool, ToolContext } from '@frontmcp/sdk';
import { z } from 'zod';

@Tool({
  name: 'add',
  description: 'Add two numbers together',
  inputSchema: {
    a: z.number().describe('First number'),
    b: z.number().describe('Second number'),
  },
  outputSchema: 'number',
})
export default class AddTool extends ToolContext {
  async execute(input: { a: number; b: number }) {
    return input.a + input.b;
  }
}
Let’s break this down:
PropertyPurpose
nameUnique identifier for the tool
descriptionHelps LLMs understand when to use this tool
inputSchemaZod schema for input validation
outputSchemaCan be a Zod schema or shorthand like 'number'
execute()The logic that runs when the tool is called

Step 2: Create the App

Create src/apps/calculator/index.ts to group your tools:
import { App } from '@frontmcp/sdk';
import AddTool from './tools/add.tool';

@App({
  id: 'calculator',
  name: 'Calculator App',
  tools: [AddTool],
})
export default class CalculatorApp {}

Step 3: Register with the Server

Update your main server file to include the app:
import { FrontMcp, LogLevel } from '@frontmcp/sdk';
import CalculatorApp from './apps/calculator';

@FrontMcp({
  info: { name: 'My MCP Server', version: '1.0.0' },
  apps: [CalculatorApp],
  http: { port: 3000 },
  logging: { level: LogLevel.INFO },
})
export default class Server {}

Step 4: Test Your Tool

1

Start the server

npm run dev
2

Open MCP Inspector

npx @modelcontextprotocol/inspector
Connect to http://localhost:3000 and you’ll see your calculator:add tool listed.
3

Call the tool

In the Inspector, call the tool with:
{ "a": 5, "b": 3 }
You should get back 8.

Adding More Tools

Let’s add a multiply tool. Create src/apps/calculator/tools/multiply.tool.ts:
import { Tool, ToolContext } from '@frontmcp/sdk';
import { z } from 'zod';

@Tool({
  name: 'multiply',
  description: 'Multiply two numbers together',
  inputSchema: {
    a: z.number().describe('First number'),
    b: z.number().describe('Second number'),
  },
  outputSchema: 'number',
})
export default class MultiplyTool extends ToolContext {
  async execute(input: { a: number; b: number }) {
    return input.a * input.b;
  }
}
Then add it to your app:
import { App } from '@frontmcp/sdk';
import AddTool from './tools/add.tool';
import MultiplyTool from './tools/multiply.tool';

@App({
  id: 'calculator',
  name: 'Calculator App',
  tools: [AddTool, MultiplyTool],
})
export default class CalculatorApp {}

Input Validation

Zod automatically validates inputs. Try calling add with invalid input:
{ "a": "not a number", "b": 3 }
You’ll get a validation error because "not a number" isn’t a valid number.

Common Validation Patterns

inputSchema: {
  // Required string
  name: z.string().min(1),

  // Optional with default
  limit: z.number().default(10),

  // Enum values
  status: z.enum(['active', 'inactive']),

  // Array of strings
  tags: z.array(z.string()),

  // Nested object
  config: z.object({
    enabled: z.boolean(),
    threshold: z.number().positive(),
  }),
}

Best Practices

Good descriptions help LLMs choose the right tool:
// Good - specific and actionable
description: 'Add two numbers together and return their sum'

// Bad - too vague
description: 'Math operation'
Add context for each input field:
inputSchema: {
  amount: z.number().positive().describe('Amount in USD'),
  category: z.string().describe('Expense category like "travel" or "meals"'),
}
Each tool should do one thing well. Instead of a calculate tool that handles add/subtract/multiply/divide, create separate tools for each operation.
async execute(input: { a: number; b: number }) {
  if (input.b === 0) {
    throw new Error('Cannot divide by zero');
  }
  return input.a / input.b;
}

Next Steps

Add Caching

Cache tool results for better performance

Create Prompts

Build prompts and resources alongside tools

Add UI Templates

Render rich HTML widgets for tool outputs

Tool Reference

Full tool decorator documentation