Skip to main content

Basic Usage

import { Tool, ToolContext } from '@frontmcp/sdk';
import { z } from 'zod';

@Tool({
  name: 'get_weather',
  description: 'Get current weather for a location',
  inputSchema: {
    city: z.string().describe('City name'),
    units: z.enum(['celsius', 'fahrenheit']).default('celsius'),
  },
})
class GetWeatherTool extends ToolContext {
  async execute(input: { city: string; units: 'celsius' | 'fahrenheit' }) {
    const weather = await fetchWeather(input.city, input.units);
    return { temperature: weather.temp, conditions: weather.conditions };
  }
}

Signature

function Tool<I extends Shape, O extends OutputSchema>(
  opts: ToolMetadataOptions<I, O>
): ClassDecorator

Configuration Options

Required Properties

PropertyTypeDescription
namestringUnique tool identifier
inputSchemaZodShapeZod object shape for input validation

Optional Properties

PropertyTypeDescription
descriptionstringTool description for AI
outputSchemaZodTypeOutput validation schema
idstringStable identifier for tracking
tagsstring[]Categorization tags

Output Types

// Object output
@Tool({
  name: 'create_user',
  inputSchema: { name: z.string(), email: z.string().email() },
  outputSchema: z.object({
    id: z.string(),
    name: z.string(),
    email: z.string(),
  }),
})

// Primitive outputs
@Tool({
  name: 'calculate',
  inputSchema: { expression: z.string() },
  outputSchema: 'number', // Returns a number
})

// MCP content types
@Tool({
  name: 'generate_image',
  inputSchema: { prompt: z.string() },
  outputSchema: 'image', // Returns base64 image
})

@Tool({
  name: 'text_to_speech',
  inputSchema: { text: z.string() },
  outputSchema: 'audio', // Returns base64 audio
})

@Tool({
  name: 'fetch_document',
  inputSchema: { uri: z.string() },
  outputSchema: 'resource', // Returns MCP resource
})

Authorization

@Tool({
  name: 'sensitive_operation',
  inputSchema: { data: z.string() },
  authProviders: [
    { ref: 'github', scopes: ['repo', 'user'] },
  ],
})
class SensitiveTool extends ToolContext {
  async execute(input) {
    // Access is validated before execute() is called
    const token = this.getAuthInfo().accessToken;
    return await callApi(token, input.data);
  }
}

Annotations

@Tool({
  name: 'delete_file',
  inputSchema: { path: z.string() },
  annotations: {
    readOnlyHint: false,
    destructiveHint: true,
    idempotentHint: false,
    openWorldHint: false,
  },
})

Examples

@Tool({
  name: 'search_users',
  inputSchema: { query: z.string(), limit: z.number().optional() },
  examples: [
    {
      input: { query: 'john', limit: 10 },
      output: { users: [{ id: '1', name: 'John Doe' }], total: 1 },
    },
  ],
})

UI Configuration

@Tool({
  name: 'user_profile',
  inputSchema: { userId: z.string() },
  ui: {
    widget: 'user-card',
    template: 'react',
  },
})

Function-Based Alternative

For simpler tools, use the tool() function:
import { tool } from '@frontmcp/sdk';
import { z } from 'zod';

const getWeather = tool({
  name: 'get_weather',
  description: 'Get current weather',
  inputSchema: { city: z.string() },
})((input, ctx) => {
  // ctx provides access to context methods
  const config = ctx.get(ConfigToken);
  return { temperature: 72, city: input.city };
});

Context Methods

The ToolContext base class provides:

Dependency Injection

async execute(input) {
  const service = this.get(ServiceToken);      // Required dependency
  const optional = this.tryGet(OptionalToken); // Optional dependency
}

Notifications

async execute(input) {
  // Log message (MCP 2025-11-25 spec)
  await this.notify('Processing started', 'info');

  // Progress notification
  await this.progress(50, 100, 'Halfway done...');
}

Elicitation (Interactive Input)

async execute(input) {
  const result = await this.elicit(
    'Please confirm this action',
    z.object({ confirmed: z.boolean() }),
    { mode: 'form', ttl: 300000 }
  );

  if (result.status === 'accept' && result.content.confirmed) {
    // Proceed with action
  }
}

Platform Detection

async execute(input) {
  // Detect AI platform
  if (this.platform === 'claude') {
    // Claude-specific formatting
  }

  // Get client info
  const client = this.clientInfo; // { name: 'claude', version: '1.0' }
}

Authentication

async execute(input) {
  const auth = this.getAuthInfo();
  const token = auth.accessToken;
  const user = auth.user;
}

HTTP Requests

async execute(input) {
  // Context-aware fetch with auto-injected headers
  const response = await this.fetch('https://api.example.com/data', {
    method: 'POST',
    body: JSON.stringify(input),
  });
}

Error Handling

async execute(input) {
  if (!input.valid) {
    this.fail(new Error('Invalid input'));
  }

  // Early return with output
  if (cached) {
    this.respond(cached); // Ends execution
  }
}

Type Inference

FrontMCP provides helper types for extracting input/output types:
import { ToolInputOf, ToolOutputOf } from '@frontmcp/sdk';

const metadata = {
  name: 'my_tool',
  inputSchema: { x: z.number(), y: z.number() },
  outputSchema: z.object({ result: z.number() }),
} as const;

type Input = ToolInputOf<typeof metadata>;   // { x: number; y: number }
type Output = ToolOutputOf<typeof metadata>; // { result: number }

Full Example

import { Tool, ToolContext, App, FrontMcp } from '@frontmcp/sdk';
import { z } from 'zod';

const inputSchema = {
  operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
  a: z.number(),
  b: z.number(),
};

const outputSchema = z.object({
  result: z.number(),
  operation: z.string(),
});

@Tool({
  name: 'calculate',
  description: 'Perform arithmetic operations',
  inputSchema,
  outputSchema,
  annotations: {
    readOnlyHint: true,
    idempotentHint: true,
  },
  examples: [
    { input: { operation: 'add', a: 2, b: 3 }, output: { result: 5, operation: 'add' } },
  ],
})
class CalculateTool extends ToolContext<typeof inputSchema, typeof outputSchema> {
  async execute(input) {
    await this.notify(`Calculating ${input.operation}(${input.a}, ${input.b})`, 'debug');

    let result: number;
    switch (input.operation) {
      case 'add': result = input.a + input.b; break;
      case 'subtract': result = input.a - input.b; break;
      case 'multiply': result = input.a * input.b; break;
      case 'divide':
        if (input.b === 0) this.fail(new Error('Division by zero'));
        result = input.a / input.b;
        break;
    }

    return { result, operation: input.operation };
  }
}

@App({ name: 'calculator', tools: [CalculateTool] })
class CalculatorApp {}

@FrontMcp({
  info: { name: 'Calculator', version: '1.0.0' },
  apps: [CalculatorApp],
})
export default class CalculatorServer {}

ToolContext

Context class details

ToolRegistry

Tool registry API

Tool Errors

Tool-related errors

@Resource

Define resources