---
title: "@Tool"
description: "The @Tool decorator defines an MCP tool with validated input/output schemas and full TypeScript type inference."
icon: "wrench"
keywords: ['frontmcp', 'mcp', 'model context protocol', 'sdk', 'reference', 'api', 'decorators', 'typescript', 'tool']
---

## Basic Usage

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

@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

The decorator is generic over the input shape `I` and (optionally) the output
schema `O`. It returns a class decorator that rewraps the decorated class,
inferring the `ToolContext` input/output type parameters from the options — so
you write a plain `class … extends ToolContext` with no explicit generics. The
signature below is illustrative:

```typescript
// outputSchema provided → execute()'s return type is checked against it
function Tool<I extends ZodRawShape, O>(
  opts: ToolMetadataOptions<I, O> & { outputSchema: O },
): <C>(cls: C) => C; // rewrapped with inferred ToolContext<…, In, Out>

// outputSchema omitted → execute() may return anything
function Tool<I extends ZodRawShape>(
  opts: ToolMetadataOptions<I, any> & { outputSchema?: never },
): <C>(cls: C) => C;
```

## Type Safety

The `@Tool` decorator provides compile-time type checking:

- **Input validation**: The `execute()` parameter type must exactly match the `inputSchema`. Mismatches produce a descriptive error at compile time.
- **Output validation**: When `outputSchema` is provided, the `execute()` return type must be assignable to the inferred output type.
- **Context check**: The decorated class must extend `ToolContext`. Using `@Tool` on a plain class produces a compile error.
- **Invalid options**: Typos in decorator options (e.g., `concurrency: { maxConcurrensst: 5 }`) are caught at compile time with specific error messages, without losing autocomplete on other fields.

```typescript
// Compile error: parameter type { query: number } doesn't match inputSchema { query: string }
@Tool({ name: 'search', inputSchema: { query: z.string() } })
class BadTool extends ToolContext {
  async execute(input: { query: number }) { // TS error on this class
    return { result: input.query };
  }
}
```

## Configuration Options

### Required Properties

| Property      | Type       | Description                           |
| ------------- | ---------- | ------------------------------------- |
| `name`        | `string`   | Unique tool identifier                |
| `inputSchema` | `ZodShape` | Zod object shape for input validation |

### Optional Properties

| Property         | Type                        | Description                                                                       |
| ---------------- | --------------------------- | --------------------------------------------------------------------------------- |
| `description`    | `string`                    | Tool description for AI                                                            |
| `outputSchema`   | `ZodType`                   | Output validation schema                                                           |
| `id`             | `string`                    | Stable identifier for tracking                                                     |
| `tags`           | `string[]`                  | Categorization tags                                                                |
| `visibility`     | `'public' \| 'hidden' \| 'internal'` | Discovery visibility. `hidden` hides from public lists; `internal` keeps it for in-process callers only |
| `rateLimit`      | `RateLimitConfig`           | Per-tool rate limit (`maxRequests`, `windowMs`, `partitionBy`)                     |
| `concurrency`    | `ConcurrencyConfig`         | Per-tool concurrency cap (`maxConcurrent`, optional queue config)                  |
| `timeout`        | `TimeoutConfig`             | Per-tool execution timeout (`executeMs`)                                           |
| `availableWhen`  | `EntryAvailability`         | Restrict discovery to specific platforms/runtimes/deployments. See [Environment Awareness](/frontmcp/features/environment-awareness) |
| `execution`      | `{ taskSupport?: 'required' \| 'optional' \| 'forbidden' }` | MCP 2025-11-25 task-execution mode for `tools/list` items |
| `authProviders`  | `ToolAuthProviderRef[]`     | Required auth providers for this tool (string name or `{ name, required?, scopes?, alias? }`) |
| `annotations`    | `ToolAnnotations`           | MCP hints (`title`, `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`) |
| `examples`       | `ToolExample[]`             | Usage examples (`description`, `input`, optional `output`)                          |
| `ui`             | `ToolUIConfig`              | UI widget configuration (`template`, `csp`, sanitization options)                   |
| `output`         | `OutputPolicy`              | Output-validation + output-schema exposure policy (`allowNonFinite`, `schemaMode`, `schemaDescriptionFormat`). Resolved Tool > App > server > default. See [Output schema exposure](/frontmcp/servers/tools#output-schema-exposure) |
| `hideFromDiscovery` | `boolean`                | Deprecated alias for `visibility: 'hidden'`                                         |

### Output Types

```typescript
// 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

```typescript
@Tool({
  name: 'sensitive_operation',
  inputSchema: { data: z.string() },
  authProviders: [
    { name: 'github', scopes: ['repo', 'user'] }, // required by default
  ],
})
class SensitiveTool extends ToolContext {
  async execute(input) {
    // The credential gate runs before execute(): a required provider with no
    // connected credential aborts the call with a JSON-RPC -32001 error
    // (data: { tool, providers, authUrl }) — execute() never runs in that case.
    // `scopes` are advertised via the server's PRM `scopes_supported`.
    const userId = this.auth.user.sub;
    const token = this.context.authInfo?.token; // raw bearer for outbound calls
    return await callApi(token, input.data, userId);
  }
}
```

`authProviders` entries default to `required: true`. A `required: true` provider
**gates the call before `execute()`** when its credential isn't connected for the
session (`-32001` / `data.authUrl`); set `required: false` for optional providers,
which never gate. The gate is a no-op for tools with no `authProviders` and for
unauthenticated / public requests.

### Annotations

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

### Examples

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

### UI Configuration

The `ui.template` field accepts a builder function, an HTML/MDX string, a React component, or a `FileSource` (for client-side transpilation):

```typescript
@Tool({
  name: 'user_profile',
  inputSchema: { userId: z.string() },
  ui: {
    template: (ctx) => `<div>User ${ctx.input.userId}</div>`,
  },
})
```

See the [Tool UI guide](/frontmcp/servers/tools#tool-ui) for the full `ToolUIConfig` shape (CSP, sanitization, helpers, etc.).

## Function-Based Alternative

For simpler tools, use the `tool()` function:

```typescript
import { tool } from '@frontmcp/sdk';
import { z } from '@frontmcp/sdk';

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

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

### Notifications

```typescript
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)

```typescript
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

```typescript
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

```typescript
import { UnauthorizedError } from '@frontmcp/sdk';

async execute(input) {
  // Rich, typed identity (preferred)
  const userId = this.auth.user.sub;          // subject (e.g. JWT `sub`)
  const email = this.auth.user.email;
  const tenantId = this.auth.claims['tenantId']; // raw JWT claim
  if (!this.auth.hasRole('admin')) {
    // Use the SDK error flow, not `throw new Error(...)` — a raw Error is
    // redacted to "Internal error" before it reaches the client, whereas a
    // PublicMcpError's message is surfaced. `this.fail` never returns.
    this.fail(new UnauthorizedError('Admin role required'));
  }
}
```

### HTTP Requests

```typescript
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

```typescript
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:

```typescript
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

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

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: [
    {
      description: 'Add two numbers',
      input: { operation: 'add', a: 2, b: 3 },
      output: { result: 5, operation: 'add' },
    },
  ],
})
class CalculateTool extends ToolContext {
  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 {}
```

## Related

<CardGroup cols={2}>
  <Card title="ToolContext" icon="code" href="/frontmcp/sdk-reference/contexts/tool-context">
    Context class details
  </Card>
  <Card title="ToolRegistry" icon="database" href="/frontmcp/sdk-reference/registries/tool-registry">
    Tool registry API
  </Card>
  <Card title="Tool Errors" icon="triangle-exclamation" href="/frontmcp/sdk-reference/errors/tool-errors">
    Tool-related errors
  </Card>
  <Card title="@Resource" icon="file" href="/frontmcp/sdk-reference/decorators/resource">
    Define resources
  </Card>
</CardGroup>
