> ## 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.

# Dynamic Tools & Resources

> Register MCP tools and resources dynamically from React components

Dynamic tools and resources let React components register MCP capabilities on mount and automatically unregister them on unmount. This enables UI-driven tool availability — tools exist only while the component that defines them is rendered.

## useDynamicTool

Registers an MCP tool for the lifetime of the component. Supports both **zod schemas** (recommended) and raw **JSON Schema**.

### With Zod Schema (Recommended)

```tsx theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { useDynamicTool } from '@frontmcp/react';
import { z } from '@frontmcp/sdk';

function CartControls() {
  const { addToCart } = useCart();

  useDynamicTool({
    name: 'add_to_cart',
    description: 'Add an item to the shopping cart',
    schema: z.object({
      itemId: z.string().describe('Product ID'),
      quantity: z.number().describe('Quantity to add').optional(),
    }),
    execute: async (args) => {
      // args is typed as { itemId: string; quantity?: number }
      await addToCart(args.itemId, args.quantity ?? 1);
      return { content: [{ type: 'text', text: 'Added to cart' }] };
    },
  });

  return <div>Cart controls active</div>;
}
```

When a zod `schema` is provided:

* The schema is converted to JSON Schema automatically via `toJSONSchema` from `zod/v4`
* Input is validated via `safeParse` before reaching your `execute` callback
* Invalid input returns an error `CallToolResult` with issue details
* The `execute` callback receives fully typed, validated args

### With JSON Schema (Backward Compat)

```tsx theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
useDynamicTool({
  name: 'add_to_cart',
  description: 'Add an item to the shopping cart',
  inputSchema: {
    type: 'object',
    properties: {
      itemId: { type: 'string', description: 'Product ID' },
      quantity: { type: 'number', description: 'Quantity to add' },
    },
    required: ['itemId'],
  },
  execute: async (args) => {
    await addToCart(args.itemId as string, (args.quantity as number) ?? 1);
    return { content: [{ type: 'text', text: 'Added to cart' }] };
  },
});
```

### Options

| Option        | Type                                | Default | Description                                        |
| ------------- | ----------------------------------- | ------- | -------------------------------------------------- |
| `name`        | `string`                            | —       | **Required.** MCP tool name                        |
| `description` | `string`                            | —       | **Required.** Description for agents               |
| `schema`      | `z.ZodObject`                       | —       | Zod schema (mutually exclusive with `inputSchema`) |
| `inputSchema` | `Record<string, unknown>`           | —       | JSON Schema (mutually exclusive with `schema`)     |
| `execute`     | `(args) => Promise<CallToolResult>` | —       | **Required.** Tool handler                         |
| `enabled`     | `boolean`                           | `true`  | Conditionally enable/disable                       |
| `server`      | `string`                            | —       | Target a named server                              |

### Conditional Registration

Use `enabled` to conditionally register/unregister tools based on application state:

```tsx theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
function AdminTools({ isAdmin }: { isAdmin: boolean }) {
  useDynamicTool({
    name: 'delete_user',
    description: 'Delete a user account (admin only)',
    schema: z.object({ userId: z.string() }),
    execute: async (args) => {
      await deleteUser(args.userId);
      return { content: [{ type: 'text', text: 'User deleted' }] };
    },
    enabled: isAdmin, // Tool only available when isAdmin is true
  });

  return null;
}
```

### Stale Closure Prevention

The execute function is stored in a ref internally, so it always captures the latest closure values. You don't need to memoize it.

***

## useDynamicResource

Registers an MCP resource for the lifetime of the component.

```tsx theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { useDynamicResource } from '@frontmcp/react';

function UserPreferences({ preferences }: { preferences: UserPrefs }) {
  useDynamicResource({
    uri: 'app://user-preferences',
    name: 'user-preferences',
    description: 'Current user preferences',
    mimeType: 'application/json',
    read: async () => ({
      contents: [
        {
          uri: 'app://user-preferences',
          mimeType: 'application/json',
          text: JSON.stringify(preferences),
        },
      ],
    }),
  });

  return <div>Preferences loaded</div>;
}
```

### Options

| Option        | Type                                | Default | Description                       |
| ------------- | ----------------------------------- | ------- | --------------------------------- |
| `uri`         | `string`                            | —       | **Required.** Resource URI        |
| `name`        | `string`                            | —       | **Required.** Human-readable name |
| `description` | `string`                            | —       | Description for agents            |
| `mimeType`    | `string`                            | —       | Content MIME type                 |
| `read`        | `() => Promise<ReadResourceResult>` | —       | **Required.** Read handler        |
| `enabled`     | `boolean`                           | `true`  | Conditionally enable/disable      |
| `server`      | `string`                            | —       | Target a named server             |

***

## useComponentTree

Exposes the DOM subtree under a ref as a JSON MCP resource. Useful for giving agents visibility into the rendered component hierarchy.

```tsx theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { useRef } from 'react';
import { useComponentTree } from '@frontmcp/react';

function Dashboard() {
  const rootRef = useRef<HTMLDivElement>(null);

  useComponentTree({
    rootRef,
    uri: 'react://component-tree',
    maxDepth: 10,
    includeProps: true,
  });

  return (
    <div ref={rootRef}>
      <div data-component="Sidebar">...</div>
      <div data-component="MainContent">...</div>
    </div>
  );
}
```

### Options

| Option         | Type                             | Default                    | Description                          |
| -------------- | -------------------------------- | -------------------------- | ------------------------------------ |
| `rootRef`      | `RefObject<HTMLElement \| null>` | —                          | **Required.** Root element ref       |
| `uri`          | `string`                         | `'react://component-tree'` | Resource URI                         |
| `maxDepth`     | `number`                         | `10`                       | Maximum traversal depth              |
| `includeProps` | `boolean`                        | `false`                    | Include `data-*` attributes as props |
| `server`       | `string`                         | —                          | Target a named server                |

### Output Format

The resource returns a JSON tree with `component`, `tag`, `children`, and optional `props`:

```json theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
{
  "component": "Dashboard",
  "tag": "div",
  "children": [
    { "component": "Sidebar", "tag": "div", "children": [] },
    { "component": "MainContent", "tag": "div", "children": [] }
  ]
}
```

Elements with `data-component` attributes use that value as `component`. Others fall back to the tag name.

***

## Mount/Unmount Lifecycle

Dynamic tools and resources follow React's effect lifecycle:

1. **Mount**: The tool/resource is registered with the `DynamicRegistry`
2. **Update**: If dependencies change, the old registration is cleaned up and a new one is created
3. **Unmount**: The tool/resource is automatically unregistered

This means agents only see tools that correspond to currently rendered UI. When a user navigates away from a page, its tools disappear; when they navigate back, the tools reappear.

```tsx theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
function App() {
  const [page, setPage] = useState<'home' | 'settings'>('home');

  return (
    <FrontMcpProvider server={server}>
      {page === 'home' && <HomePage />}     {/* home tools registered */}
      {page === 'settings' && <Settings />} {/* settings tools registered */}
    </FrontMcpProvider>
  );
}
```
