> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentfront.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Tools

Tools are **typed actions** that execute operations with side effects. They're the primary way to enable an AI model to interact with external systems—calling APIs, modifying data, performing calculations, or triggering workflows.

<Info>
  This feature implements the [MCP Tools specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools). FrontMCP handles all protocol details automatically.
</Info>

## Why Tools?

In the Model Context Protocol, tools serve a distinct purpose from resources and prompts:

| Aspect           | Tool                                | Resource         | Prompt                         |
| ---------------- | ----------------------------------- | ---------------- | ------------------------------ |
| **Purpose**      | Execute actions                     | Provide data     | Provide templated instructions |
| **Direction**    | Model triggers execution            | Model pulls data | Model uses messages            |
| **Side effects** | Yes (mutations, API calls)          | No (read-only)   | No (message generation)        |
| **Use case**     | Actions, calculations, integrations | Context loading  | Conversation templates         |

Tools are ideal for:

* **API integrations** — call external services, webhooks, third-party APIs
* **Data mutations** — create, update, delete records
* **Calculations** — perform computations, transformations
* **System operations** — file operations, process management
* **Workflows** — trigger multi-step processes, orchestration

***

## Creating Tools

### Class Style

Use class decorators for tools that need dependency injection, lifecycle hooks, or complex logic:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { Tool, ToolContext } from '@frontmcp/sdk';
import { z } from 'zod';

@Tool({
  name: 'greet',
  description: 'Greets a user by name',
  inputSchema: { name: z.string() },
})
class GreetTool extends ToolContext {
  async execute({ name }: { name: string }) {
    return `Hello, ${name}!`;
  }
}
```

### Function Style

For simpler tools, use the functional builder:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { tool } from '@frontmcp/sdk';
import { z } from 'zod';

const GreetTool = tool({
  name: 'greet',
  description: 'Greets a user by name',
  inputSchema: { name: z.string() },
})(({ name }) => `Hello, ${name}!`);
```

***

## Registering Tools

Add tools to your app via the `tools` array:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { App } from '@frontmcp/sdk';

@App({
  id: 'my-app',
  name: 'My Application',
  tools: [GreetTool, CalculateTool, SendEmailTool],
})
class MyApp {}
```

Tools can also be generated dynamically by **adapters** (e.g., OpenAPI adapter) or **plugins**.

***

## Input Schemas

Tools use Zod schemas for type-safe input validation. The schema is automatically converted to JSON Schema for MCP protocol compatibility.

### Basic Types

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Tool({
  name: 'user-action',
  inputSchema: {
    userId: z.string(),
    count: z.number(),
    enabled: z.boolean(),
  },
})
```

### With Descriptions

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Tool({
  name: 'send-email',
  inputSchema: {
    to: z.string().email().describe('Recipient email address'),
    subject: z.string().describe('Email subject line'),
    body: z.string().describe('Email body content'),
  },
})
```

### Optional and Default Values

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Tool({
  name: 'search',
  inputSchema: {
    query: z.string(),
    limit: z.number().default(10).describe('Max results to return'),
    offset: z.number().optional().describe('Pagination offset'),
  },
})
```

### Complex Types

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Tool({
  name: 'create-order',
  inputSchema: {
    customerId: z.string(),
    items: z.array(z.object({
      productId: z.string(),
      quantity: z.number().min(1),
    })),
    shipping: z.enum(['standard', 'express', 'overnight']),
  },
})
```

***

## Output Schemas

Optionally define an output schema for response validation:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Tool({
  name: 'calculate-total',
  inputSchema: {
    items: z.array(z.object({
      price: z.number(),
      quantity: z.number(),
    })),
  },
  outputSchema: z.object({
    subtotal: z.number(),
    tax: z.number(),
    total: z.number(),
  }),
})
class CalculateTotalTool extends ToolContext {
  execute({ items }) {
    const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
    const tax = subtotal * 0.1;
    return { subtotal, tax, total: subtotal + tax };
  }
}
```

***

## Return Values

Tools support multiple return formats. The SDK automatically converts your return value to the MCP `CallToolResult` format.

### Simple Returns

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
// String -> text content
execute() {
  return 'Operation completed successfully';
}

// Object -> auto-serialized to JSON
execute() {
  return { id: '123', status: 'created' };
}

// Number/Boolean -> converted to text
execute() {
  return 42;
}
```

### Full MCP Format

For complete control over the response, return the full `CallToolResult` structure:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
execute() {
  return {
    content: [
      {
        type: 'text',
        text: 'Operation completed',
      },
    ],
    isError: false,
  };
}
```

### Multiple Content Items

Return an array to include multiple content blocks:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
execute() {
  return {
    content: [
      { type: 'text', text: 'Summary of results' },
      { type: 'text', text: JSON.stringify(details) },
    ],
  };
}
```

***

## Tool Metadata

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Tool({
  name: string,              // Required: unique identifier
  description?: string,      // Optional: hint for the LLM
  inputSchema: ZodSchema,    // Required: Zod schema for input validation
  outputSchema?: ZodSchema,  // Optional: Zod schema for output validation
  examples?: Array<{         // Optional: usage examples for discovery
    description: string;     //   - what this example demonstrates
    input: Record<string, unknown>; //   - example input parameters
    output?: unknown;        //   - optional expected output
  }>,
  title?: string,            // Optional: human-readable display name
  icons?: Icon[],            // Optional: UI icons
  tags?: string[],           // Optional: categorization tags
  annotations?: {            // Optional: MCP tool annotations
    title?: string;          //   - display title
    readOnlyHint?: boolean;  //   - hint that tool is read-only
    destructiveHint?: boolean; // - hint that tool is destructive
    idempotentHint?: boolean;  // - hint that tool is idempotent
    openWorldHint?: boolean;   // - hint for open-world assumption
  },
  ui?: ToolUIConfig,         // Optional: visual widget configuration
  hideFromDiscovery?: boolean, // Optional: hide from tools/list (default: false)
})
```

**Field descriptions:**

| Field               | Description                                                  |
| ------------------- | ------------------------------------------------------------ |
| `name`              | Programmatic identifier used internally and in MCP responses |
| `description`       | Helps the model understand when and how to use this tool     |
| `inputSchema`       | Zod schema defining expected input parameters                |
| `outputSchema`      | Zod schema for validating and documenting output             |
| `title`             | Human-friendly name for UI display                           |
| `icons`             | Array of icons for visual representation in clients          |
| `tags`              | Categorization for organization and filtering                |
| `annotations`       | MCP-defined hints about tool behavior                        |
| `examples`          | Usage examples for discovery and LLM understanding           |
| `ui`                | Visual widget configuration (template, display mode, etc.)   |
| `hideFromDiscovery` | When `true`, tool is callable but not listed in `tools/list` |

***

## Tool Examples

Provide examples to improve discoverability and help LLMs understand how to use your tools effectively.

<CodeGroup>
  ```ts Class Style theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  @Tool({
    name: 'users:create',
    description: 'Create a new user account',
    inputSchema: {
      email: z.string().email(),
      role: z.enum(['admin', 'user']),
    },
    examples: [
      {
        description: 'Create an admin user',
        input: { email: 'admin@example.com', role: 'admin' },
      },
      {
        description: 'Create a regular user',
        input: { email: 'user@example.com', role: 'user' },
      },
    ],
  })
  class CreateUserTool extends ToolContext {
    async execute({ email, role }) {
      // Implementation
    }
  }
  ```

  ```ts Function Style theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  const CreateUserTool = tool({
    name: 'users:create',
    description: 'Create a new user account',
    inputSchema: {
      email: z.string().email(),
      role: z.enum(['admin', 'user']),
    },
    examples: [
      {
        description: 'Create an admin user',
        input: { email: 'admin@example.com', role: 'admin' },
      },
      {
        description: 'Create a regular user',
        input: { email: 'user@example.com', role: 'user' },
      },
    ],
  })(({ email, role }) => {
    // Implementation
  });
  ```
</CodeGroup>

<CardGroup cols={2}>
  <Card title="CodeCall Discovery" icon="magnifying-glass">
    Examples are indexed for semantic search with 2x weight, helping users find the right tools faster.
  </Card>

  <Card title="LLM Understanding" icon="brain">
    The `codecall:describe` tool returns up to 5 examples per tool to help LLMs understand usage patterns.
  </Card>
</CardGroup>

<Tip>
  If you don't provide examples, FrontMCP auto-generates smart examples based on tool intent (create, list, get, update, delete, search). User-provided examples always take priority.
</Tip>

***

## Tool Annotations

Annotations provide hints to clients about tool behavior:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Tool({
  name: 'delete-record',
  description: 'Permanently delete a record',
  inputSchema: { id: z.string() },
  annotations: {
    destructiveHint: true,  // Warns clients this operation is destructive
  },
})
```

| Annotation        | Description                                                            |
| ----------------- | ---------------------------------------------------------------------- |
| `title`           | Display title for the tool                                             |
| `readOnlyHint`    | Tool doesn't modify state (like a resource, but returns computed data) |
| `destructiveHint` | Tool performs irreversible operations (delete, overwrite)              |
| `idempotentHint`  | Multiple identical calls produce the same result                       |
| `openWorldHint`   | Tool interacts with external systems (APIs, services)                  |

***

## Tool Context

Class-based tools have access to a rich execution context via `this`:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Tool({
  name: 'context-example',
  inputSchema: { query: z.string() },
})
class ContextExampleTool extends ToolContext {
  async execute({ query }: { query: string }) {
    // Input received by the tool
    this.input;              // { query: 'value' }
    this.metadata;           // Tool metadata (name, description, etc.)

    // Authentication (prefer this.requestContext.authInfo)
    this.authInfo;           // Auth context from MCP session (deprecated)

    // Dependency injection
    this.get(ConfigService); // Resolve a provider
    this.tryGet(Cache);      // Resolve or return undefined

    // Scope access
    this.scope;              // Access the current scope

    // Utilities
    this.fetch(url);         // Built-in fetch for HTTP requests

    // Notifications (MCP 2025-11-25)
    this.notify(message);           // Send notifications/message to client
    this.progress(current, total);  // Send notifications/progress to client

    // Flow control
    this.respond(value);     // End execution with a response

    return `Processed: ${query}`;
  }
}
```

### Using Providers

Inject services via the `get()` method:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Tool({
  name: 'create-user',
  inputSchema: {
    email: z.string().email(),
    name: z.string(),
  },
})
class CreateUserTool extends ToolContext {
  async execute({ email, name }) {
    const db = this.get(DatabaseProvider);
    const user = await db.users.create({ email, name });
    return { id: user.id, email: user.email };
  }
}
```

### Request Context

Class-based tools have access to the full request context including tracing, timing, and authentication:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Tool({
  name: 'traced-tool',
  inputSchema: { query: z.string() },
})
class TracedTool extends ToolContext {
  async execute({ query }: { query: string }) {
    // Full request context
    const ctx = this.requestContext;

    // Tracing (W3C Trace Context)
    console.log(ctx.requestId);                    // Unique request ID
    console.log(ctx.traceContext.traceId);         // W3C Trace ID
    console.log(ctx.traceContext.parentId);        // Parent span ID (if present)

    // Authentication (prefer over this.authInfo)
    console.log(ctx.authInfo.token);               // JWT token
    console.log(ctx.authInfo.user);                // User info

    // Session
    console.log(ctx.sessionId);                    // MCP session ID

    // Timing marks for performance tracking
    ctx.mark('db-query-start');
    await this.queryDatabase();
    ctx.mark('db-query-end');
    console.log(ctx.elapsed('db-query-start', 'db-query-end')); // ms

    // Request metadata
    console.log(ctx.metadata.userAgent);
    console.log(ctx.metadata.clientIp);

    return `Processed: ${query}`;
  }
}
```

<Tip>
  Use `this.requestContext.authInfo` instead of the deprecated `this.authInfo` for accessing authentication data.
  See [Request Context](/frontmcp/extensibility/request-context) for complete API reference.
</Tip>

***

## Progress Notifications

Send real-time updates to clients during long-running operations using MCP notifications.

### Sending Messages

Use `this.notify()` to send log messages to the client:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Tool({
  name: 'long-task',
  inputSchema: { items: z.array(z.string()) },
})
class LongTaskTool extends ToolContext {
  async execute({ items }) {
    await this.notify('Starting processing...', 'info');

    for (const item of items) {
      await this.notify(`Processing: ${item}`, 'debug');
      await this.processItem(item);
    }

    await this.notify('All items processed!', 'info');
    return { processed: items.length };
  }
}
```

Log levels: `'debug'`, `'info'`, `'warning'`, `'error'`

### Sending Progress

Use `this.progress()` for progress bar updates. This only works when the client includes a `progressToken` in the request:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Tool({
  name: 'file-processor',
  inputSchema: { files: z.array(z.string()) },
})
class FileProcessorTool extends ToolContext {
  async execute({ files }) {
    for (let i = 0; i < files.length; i++) {
      // progress(current, total, message?)
      await this.progress(i + 1, files.length, `Processing ${files[i]}`);
      await this.processFile(files[i]);
    }
    return { processed: files.length };
  }
}
```

<Info>
  Progress notifications are only sent if the client includes a `progressToken` in the request's `_meta` field.
  If no token is provided, `this.progress()` returns `false` and silently succeeds.
</Info>

### Progress Token Access

The `progressToken` is automatically extracted from the request's `_meta` field and made available via the `ToolCallExtra` type for advanced use cases:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import type { ToolCallExtra } from '@frontmcp/sdk';

// In custom tool handlers or plugins, you can access the progressToken directly
const extra: ToolCallExtra = {
  authInfo: { ... },
  progressToken: 'token-123',  // From request._meta.progressToken
};
```

For most use cases, simply use `this.progress()` which handles the token automatically.

***

## Real-World Examples

### Calculator Tool

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Tool({
  name: 'calculate',
  description: 'Perform mathematical calculations',
  inputSchema: {
    expression: z.string().describe('Mathematical expression to evaluate'),
  },
  annotations: {
    readOnlyHint: true,
    idempotentHint: true,
  },
})
class CalculateTool extends ToolContext {
  execute({ expression }) {
    // Using a safe math parser (not eval!)
    const result = safeEvaluate(expression);
    return { expression, result };
  }
}
```

### API Integration Tool

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Tool({
  name: 'create-github-issue',
  description: 'Create a new issue in a GitHub repository',
  inputSchema: {
    owner: z.string().describe('Repository owner'),
    repo: z.string().describe('Repository name'),
    title: z.string().describe('Issue title'),
    body: z.string().optional().describe('Issue body'),
    labels: z.array(z.string()).optional().describe('Labels to apply'),
  },
  annotations: {
    openWorldHint: true,
  },
})
class CreateGitHubIssueTool extends ToolContext {
  async execute({ owner, repo, title, body, labels }) {
    const config = this.get(ConfigProvider);

    const response = await this.fetch(
      `https://api.github.com/repos/${owner}/${repo}/issues`,
      {
        method: 'POST',
        headers: {
          'Authorization': `token ${config.githubToken}`,
          'Accept': 'application/vnd.github.v3+json',
        },
        body: JSON.stringify({ title, body, labels }),
      }
    );

    if (!response.ok) {
      throw new Error(`GitHub API error: ${response.status}`);
    }

    const issue = await response.json();
    return {
      id: issue.id,
      number: issue.number,
      url: issue.html_url,
    };
  }
}
```

### Database Mutation Tool

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Tool({
  name: 'update-user-status',
  description: 'Update a user\'s account status',
  inputSchema: {
    userId: z.string().describe('User ID'),
    status: z.enum(['active', 'suspended', 'deleted']).describe('New status'),
    reason: z.string().optional().describe('Reason for status change'),
  },
  annotations: {
    destructiveHint: true,
  },
})
class UpdateUserStatusTool extends ToolContext {
  async execute({ userId, status, reason }) {
    const db = this.get(DatabaseProvider);
    const audit = this.get(AuditLogProvider);

    const user = await db.users.findById(userId);
    if (!user) {
      throw new Error(`User ${userId} not found`);
    }

    await db.users.update(userId, { status });
    await audit.log({
      action: 'user_status_change',
      userId,
      previousStatus: user.status,
      newStatus: status,
      reason,
      performedBy: this.authInfo?.userId,
    });

    return {
      userId,
      previousStatus: user.status,
      newStatus: status,
      updatedAt: new Date().toISOString(),
    };
  }
}
```

### File Operation Tool

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Tool({
  name: 'write-file',
  description: 'Write content to a file',
  inputSchema: {
    path: z.string().describe('File path'),
    content: z.string().describe('File content'),
    encoding: z.enum(['utf-8', 'base64']).default('utf-8'),
  },
})
class WriteFileTool extends ToolContext {
  async execute({ path, content, encoding }) {
    const fs = await import('fs/promises');
    const pathModule = await import('path');

    // Security: validate path is within allowed directory
    const safePath = pathModule.resolve(process.cwd(), 'workspace', path);
    if (!safePath.startsWith(pathModule.resolve(process.cwd(), 'workspace'))) {
      throw new Error('Path traversal not allowed');
    }

    await fs.writeFile(safePath, content, encoding);

    return {
      path: safePath,
      size: Buffer.byteLength(content, encoding),
      written: true,
    };
  }
}
```

***

## MCP Protocol Integration

Tools integrate with the MCP protocol via two flows:

| Flow         | Description                                                       |
| ------------ | ----------------------------------------------------------------- |
| `tools/list` | Returns all available tools with their metadata and input schemas |
| `tools/call` | Executes a specific tool with provided arguments                  |

When a client requests `tools/call` with a name and arguments:

1. The SDK locates the tool by name
2. Arguments are validated against the tool's `inputSchema`
3. The `execute()` method is called with the validated arguments
4. The return value is validated against `outputSchema` (if provided) and converted to MCP `CallToolResult` format

### Capabilities

FrontMCP automatically advertises tool capabilities during MCP initialization:

```json theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
{
  "capabilities": {
    "tools": {
      "listChanged": true
    }
  }
}
```

| Capability    | Description                                                                                          |
| ------------- | ---------------------------------------------------------------------------------------------------- |
| `listChanged` | When `true`, the server will send `notifications/tools/list_changed` when tools are added or removed |

The SDK sets `listChanged: true` when you have any tools registered, enabling clients to receive real-time notifications when tools are dynamically added or removed.

### Change Notifications

When tools change dynamically (e.g., via adapters or plugins), FrontMCP automatically sends `notifications/tools/list_changed` to connected clients. Clients that support this notification will refresh their tool list.

<Tip>
  For the full protocol specification, see [MCP Tools](https://modelcontextprotocol.io/specification/2025-11-25/server/tools).
</Tip>

***

## Tool UI

Tools can render visual widgets alongside their responses. This enables rich, interactive presentations of tool outputs—weather cards, order summaries, data tables, and more.

### Basic UI Configuration

Add a `ui` property to attach a visual template:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Tool({
  name: 'get_weather',
  description: 'Get current weather for a location',
  inputSchema: {
    location: z.string(),
  },
  ui: {
    template: (ctx) => `
      <div class="weather-card">
        <h2>${ctx.helpers.escapeHtml(ctx.output.location)}</h2>
        <p>${ctx.output.temperature}°C - ${ctx.output.conditions}</p>
      </div>
    `,
    widgetDescription: 'Displays current weather conditions',
  },
})
class GetWeatherTool extends ToolContext {
  async execute({ location }) {
    return { location, temperature: 22, conditions: 'Sunny' };
  }
}
```

### Template Types

FrontMCP auto-detects your template type:

<CodeGroup>
  ```ts HTML Template theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  ui: {
    template: (ctx) => `<p>${ctx.helpers.escapeHtml(ctx.output.message)}</p>`,
  }
  ```

  ```tsx React Component theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  import WeatherCard from './components/WeatherCard';

  ui: {
    template: WeatherCard, // React component
  }
  ```

  ```ts MDX Template theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  ui: {
    template: `
  # Weather Report

  **Location:** {output.location}
  **Temperature:** {output.temperature}°C

  <Alert type="info">Data updated every 15 minutes</Alert>
    `,
    mdxComponents: { Alert },
  }
  ```
</CodeGroup>

### UI Configuration Options

| Option               | Description                                                                             |
| -------------------- | --------------------------------------------------------------------------------------- |
| `template`           | HTML function, React component, or MDX string                                           |
| `displayMode`        | `'inline'` (default), `'fullscreen'`, or `'pip'`                                        |
| `widgetDescription`  | Human-readable description shown to users                                               |
| `widgetAccessible`   | Allow widget to call tools via MCP Bridge                                               |
| `csp`                | Content Security Policy (allowed domains)                                               |
| `servingMode`        | How HTML is delivered: `'auto'` (default), `'inline'`, `'static'`, `'hybrid'`, etc.     |
| `htmlResponsePrefix` | Text prefix for Claude dual-payload HTML block (default: `'Here is the visual result'`) |
| `mdxComponents`      | Custom components for MDX templates                                                     |
| `hydrate`            | Enable client-side React hydration for interactivity                                    |

### Using @frontmcp/ui Components

Combine Tool UI with the `@frontmcp/ui` component library:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { card, badge, button, descriptionList } from '@frontmcp/ui';

@Tool({
  name: 'get_order',
  inputSchema: { orderId: z.string() },
  ui: {
    template: (ctx) => {
      const { output, helpers } = ctx;
      return card(`
        <div class="flex justify-between">
          <span>${helpers.escapeHtml(output.id)}</span>
          ${badge(output.status, { variant: 'success' })}
        </div>
        ${descriptionList([
          { term: 'Customer', description: output.customer },
          { term: 'Total', description: helpers.formatCurrency(output.total) },
        ])}
      `, { title: 'Order Details' });
    },
  },
})
class GetOrderTool extends ToolContext { /* ... */ }
```

### Testing Tool UI

Use `@frontmcp/testing` for E2E validation of rendered widgets:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { test, expect, UIAssertions } from '@frontmcp/testing';

test('renders weather UI correctly', async ({ mcp }) => {
  const result = await mcp.tools.call('get_weather', { location: 'London' });

  expect(result).toHaveRenderedHtml();
  expect(result).toBeXssSafe();
  expect(result).toContainBoundValue('London');

  const html = UIAssertions.assertValidUI(result, ['location', 'temperature']);
});
```

<CardGroup cols={2}>
  <Card title="Tool UI Guide" icon="palette" href="/frontmcp/ui/integration/tools">
    Complete configuration options and examples
  </Card>

  <Card title="UI Components" icon="puzzle-piece" href="/frontmcp/ui/components/overview">
    Pre-built components from @frontmcp/ui
  </Card>

  <Card title="Templates" icon="code" href="/frontmcp/ui/templates/overview">
    HTML, React, and MDX template patterns
  </Card>

  <Card title="Testing UI" icon="flask-vial" href="/frontmcp/ui/integration/testing">
    E2E testing with @frontmcp/testing
  </Card>
</CardGroup>

***

## Best Practices

**Do:**

* Use descriptive `name` and `description` fields to help models understand tool purpose
* Define clear input schemas with `.describe()` on each field
* Use appropriate annotations (`destructiveHint`, `idempotentHint`, etc.) to guide client behavior
* Validate inputs thoroughly and return meaningful error messages
* Keep tools focused on a single action or operation

**Don't:**

* Create tools for read-only data retrieval (use resources instead)
* Skip input validation—always define a proper `inputSchema`
* Ignore error handling—wrap external calls in try/catch
* Create overly complex tools—split into multiple tools if needed
* Expose sensitive operations without proper authentication checks
