> ## Documentation Index
> Fetch the complete documentation index at: https://mcp-b-sync-npm-packages-docs-bf03420.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Best Practices for Creating Tools

> Learn best practices for designing and implementing WebMCP tools on websites you control. Tool design principles, naming conventions, security, and optimal AI integration.

This guide covers the complete tool development lifecycle: from naming and schema design, through implementation and error handling, to testing and monitoring. Unlike userscripts where you work around existing structures, controlling your entire website lets you design tools from the ground up for optimal AI integration.

<Info>
  These practices apply when you own the website and can integrate WebMCP tools directly into your codebase. For userscript development, see [Managing Userscripts](/extension/managing-userscripts).
</Info>

## Tool Design Principles

### Use Descriptive, Consistent Names

Follow a clear naming convention for all your tools:

<CardGroup cols={2}>
  <Card title="Good Names" icon="check">
    * `products_search`
    * `cart_add_item`
    * `user_get_profile`
    * `orders_list_recent`
  </Card>

  <Card title="Avoid" icon="x">
    * `doStuff`
    * `action1`
    * `helper`
    * `processData`
  </Card>
</CardGroup>

**Naming pattern:** Use `domain_verb_noun` or `verb_noun` format:

```javascript "Tool naming patterns" lines icon="square-js" theme={null}
navigator.modelContext.registerTool({ // [!code --]
  name: 'search',      // Too generic // [!code --]
  name: 'doAction',    // Unclear purpose // [!code --]
  name: 'helper1'      // Meaningless // [!code --]
}); // [!code --]

navigator.modelContext.registerTool({ // [!code ++]
  name: 'products_search',  // Clear domain + action // [!code ++]
  name: 'cart_add_item',    // Verb + noun pattern // [!code ++]
  name: 'orders_get_status' // Descriptive and specific // [!code ++]
}); // [!code ++]
```

### Provide Detailed Descriptions

<Warning>
  Tool names, descriptions, and input schemas are sent directly to the AI model's context. This is the ONLY information the model has about your tools. Make these as detailed and informative as possible.
</Warning>

Help AI agents understand **when** and **how** to use your tools by including everything they need to know in the description:

```javascript "Tool descriptions" lines icon="square-js" expandable theme={null}
navigator.modelContext.registerTool({ // [!code --]
  name: 'products_search', // [!code --]
  description: 'Searches for products', // Too vague! // [!code --]
  // Model doesn't know when to use this or what it returns // [!code --]
}); // [!code --]

navigator.modelContext.registerTool({ // [!code ++]
  name: 'products_search', // [!code ++]
  description: `Search products by name, category, or SKU. // [!code ++]
 // [!code ++]
Returns paginated results with title, price, stock status, and product URLs. // [!code ++]
Use this when users ask about product availability or pricing. // [!code ++]
 // [!code ++]
If searching by price range, use the minPrice and maxPrice parameters. // [!code ++]
Results are sorted by relevance by default.`, // [!code ++]
  inputSchema: { // [!code ++]
    query: z.string().min(1).describe('Product name, category, or SKU to search for'), // [!code ++]
    minPrice: z.number().positive().optional().describe('Minimum price filter in USD'), // [!code ++]
    maxPrice: z.number().positive().optional().describe('Maximum price filter in USD'), // [!code ++]
    limit: z.number().int().min(1).max(100).default(10).describe('Number of results (max 100)') // [!code ++]
  } // [!code ++]
  // ... // [!code ++]
}); // [!code ++]
```

**Include in descriptions:**

* What the tool does and when to use it
* What data it returns and in what format
* When to use it vs similar tools
* Any important limitations or constraints
* Prerequisites or dependencies on other tools
* If this tool will modify the available tool list

### Design Powerful, Consolidated Tools

Create consolidated tools that handle related operations rather than many single-purpose tools:

<Tabs>
  <Tab title="Good: Consolidated Tools">
    ```javascript "Consolidated tool example" lines icon="check" theme={null}
    // ✅ Powerful tool handling related operations
    navigator.modelContext.registerTool({
      name: 'cart_manager',
      description: 'Manage all cart operations including add, remove, view, clear, and update quantity. Use action parameter to specify the operation.',
      inputSchema: {
        action: z.enum(['add', 'remove', 'view', 'clear', 'update']).describe('The cart operation to perform'),
        productId: z.string().optional().describe('Product ID (required for add, remove, update)'),
        quantity: z.number().positive().optional().describe('Quantity (required for add and update)')
      },
      // ...
    });
    ```
  </Tab>

  <Tab title="Avoid: Too Many Single-Purpose Tools">
    ```javascript "Multiple single-purpose tools" lines icon="x" theme={null}
    // ❌ Multiple tools consuming unnecessary context
    navigator.modelContext.registerTool({
      name: 'cart_add_item',
      description: 'Add a single product to cart',
      // ...
    });

    navigator.modelContext.registerTool({
      name: 'cart_remove_item',
      description: 'Remove a product from cart',
      // ...
    });

    navigator.modelContext.registerTool({
      name: 'cart_get_contents',
      description: 'View current cart contents',
      // ...
    });
    ```
  </Tab>
</Tabs>

**Benefits of consolidated tools:**

* Reduces context consumption (fewer tool definitions)
* Modern AI models handle complex tools effectively
* Fewer tools to maintain and document
* Simpler tool discovery for AI agents
* More efficient use of available context window

## Input Validation

### Use Zod for Type-Safe Validation

<Info>
  Parameter descriptions (via `.describe()`) are sent to the AI model's context. Be detailed and specific!
</Info>

Zod provides excellent TypeScript integration and runtime validation. Use `.describe()` on every parameter to tell the model exactly what it needs to know:

```typescript "Zod validation with descriptions" twoslash lines icon="check" expandable theme={null}
import { z } from 'zod';

navigator.modelContext.registerTool({
  name: 'products_search',
  description: 'Search products by various criteria',
  inputSchema: {
    query: z.string()
      .min(1, 'Search query cannot be empty')
      .max(100, 'Search query too long')
      .describe('Product name, category, or SKU to search for. Examples: "laptop", "running shoes", "SKU-12345"'),

    minPrice: z.number()
      .positive()
      .optional()
      .describe('Minimum price filter in USD. Use with maxPrice to filter by price range.'),

    maxPrice: z.number()
      .positive()
      .optional()
      .describe('Maximum price filter in USD. Use with minPrice to filter by price range.'),

    category: z.enum(['electronics', 'clothing', 'home', 'sports'])
      .optional()
      .describe('Product category filter. Choose from: electronics, clothing, home, or sports.'),

    limit: z.number()
      .int()
      .min(1)
      .max(100)
      .default(10)
      .describe('Number of results to return (1-100, default: 10). Use higher values for broader searches.')
  },
  async execute({ query, minPrice, maxPrice, category, limit }) {
    // TypeScript knows the exact types here
    // ...
  }
});
```

### Validate Business Logic Constraints

Go beyond type checking to enforce business rules:

```typescript theme={null}
inputSchema: {
  productId: z.string().uuid().describe('Product UUID'),
  quantity: z.number()
    .int()
    .positive()
    .max(99, 'Cannot order more than 99 items at once')
    .describe('Quantity to add to cart'),

  promoCode: z.string()
    .regex(/^[A-Z0-9]{6,12}$/, 'Invalid promo code format')
    .optional()
    .describe('Optional promotional code')
}
```

### Provide Helpful Error Messages

```javascript theme={null}
async execute({ productId, quantity }) {
  // Validate availability
  const product = await getProduct(productId);

  if (!product) {
    return {
      content: [{
        type: "text",
        text: `Product ${productId} not found. Please verify the product ID.`
      }],
      isError: true
    };
  }

  if (product.stock < quantity) {
    return {
      content: [{
        type: "text",
        text: `Only ${product.stock} units available. Requested: ${quantity}.`
      }],
      isError: true
    };
  }

  // Proceed with adding to cart
  // ...
}
```

## Response Format

### Use Markdown Instead of JSON

<Info>
  AI models work better with markdown-formatted responses than JSON. Markdown is more readable and easier for models to incorporate into natural language responses.
</Info>

Return data as markdown strings rather than JSON objects:

<Tabs>
  <Tab title="Good: Markdown">
    ```javascript "Markdown response format" lines icon="check" expandable theme={null}
    // ✅ Markdown formatted response
    async execute({ query }) {
      try {
        const results = await searchProducts(query);

        const markdown = `# Search Results for "${query}"

    Found ${results.length} products:

    ${results.map((p, i) => `
    ${i + 1}. **${p.name}** - $${p.price}
    - SKU: ${p.sku}
    - Stock: ${p.inStock ? '✓ In Stock' : '✗ Out of Stock'}
    - [View Product](${p.url})
    `).join('\n')}

    ---
    *Showing ${results.length} of ${results.total} total results*`;

        return {
          content: [{
            type: "text",
            text: markdown
          }]
        };
      } catch (error) {
        return {
          content: [{
            type: "text",
            text: `**Error searching products:** ${error.message}`
          }],
          isError: true
        };
      }
    }
    ```
  </Tab>

  <Tab title="Avoid: JSON">
    ```javascript "JSON response format" lines icon="x" theme={null}
    // ❌ JSON is harder for models to parse and present
    async execute({ query }) {
      const results = await searchProducts(query);

      return {
        content: [{
          type: "text",
          text: JSON.stringify({
            success: true,
            data: results,
            meta: {
              total: results.length,
              query: query
            }
          }, null, 2)
        }]
      };
    }
    ```
  </Tab>
</Tabs>

**Benefits of markdown responses:**

* More natural for AI to read and present to users
* Better formatting in chat interfaces
* Easier for models to extract specific information
* More human-readable in logs and debugging

### Include Helpful Context in Responses

Provide information that helps the AI understand and present the results:

````javascript theme={null}
async execute({ orderId }) {
  const order = await getOrder(orderId);

  // ✅ Rich markdown with context
  return {
    content: [{
      type: "text",
      text: `# Order #${order.id}

**Status:** ${order.status}
**Placed:** ${new Date(order.createdAt).toLocaleDateString()}
**Total:** $${order.total.toFixed(2)}

## Items
${order.items.map(item => `
- **${item.name}** x${item.quantity} - $${item.price * item.quantity}
`).join('\n')}

## Shipping
${order.shippingAddress.street}
${order.shippingAddress.city}, ${order.shippingAddress.state} ${order.shippingAddress.zip}

${order.trackingNumber ? `**Tracking:** ${order.trackingNumber}` : '*Tracking number not yet available*'}

---
*Order placed on ${new Date(order.createdAt).toLocaleString()}*`
    }]
  };
}

## Error Handling

### Handle Errors Gracefully

Always anticipate and handle potential failures:

```javascript
async execute({ userId }) {
  try {
    // Check authentication
    const currentUser = await getCurrentUser();
    if (!currentUser) {
      return {
        content: [{
          type: "text",
          text: "User not authenticated. Please log in first."
        }],
        isError: true
      };
    }

    // Check authorization
    if (currentUser.id !== userId && !currentUser.isAdmin) {
      return {
        content: [{
          type: "text",
          text: "Unauthorized. You can only access your own profile."
        }],
        isError: true
      };
    }

    // Fetch data with timeout
    const profile = await fetchUserProfile(userId, { timeout: 5000 });

    if (!profile) {
      return {
        content: [{
          type: "text",
          text: `User profile ${userId} not found.`
        }],
        isError: true
      };
    }

    // Return as markdown for better readability
    return {
      content: [{
        type: "text",
        text: `# User Profile

**Name:** ${profile.name}
**Email:** ${profile.email}
**Member since:** ${new Date(profile.createdAt).toLocaleDateString()}
**Account type:** ${profile.accountType}`
      }]
    };

  } catch (error) {
    console.error('Error fetching user profile:', error);

    return {
      content: [{
        type: "text",
        text: `Failed to fetch user profile: ${error.message}`
      }],
      isError: true
    };
  }
}
````

### Use Specific Error Messages

Help AI agents understand what went wrong with clear, formatted error messages:

```typescript theme={null}
enum ErrorCode {
  UNAUTHORIZED = 'UNAUTHORIZED',
  NOT_FOUND = 'NOT_FOUND',
  VALIDATION_ERROR = 'VALIDATION_ERROR',
  RATE_LIMIT = 'RATE_LIMIT',
  SERVER_ERROR = 'SERVER_ERROR'
}

function formatError(code: ErrorCode, message: string, details?: string) {
  return {
    content: [{
      type: "text",
      text: `**Error (${code}):** ${message}${details ? `\n\n${details}` : ''}`
    }],
    isError: true
  };
}

// Usage
if (rateLimitExceeded) {
  return formatError(
    ErrorCode.RATE_LIMIT,
    'Too many requests.',
    'Please try again in 60 seconds.'
  );
}
```

## Security Best Practices

<Note>
  For comprehensive security guidance including authentication, authorization, input validation, prompt injection protection, and multi-website threat models, see the dedicated [Security Guide](/security).
</Note>

## Performance Optimization

<Note>
  For comprehensive performance guidelines including tool registration patterns, tool limits, lazy registration, timeouts, and memory management, see the [Performance Guidelines](/concepts/performance).
</Note>

### Optimistic Updates for Voice Models

Voice models and real-time interactions work best with instant tool responses. Implement optimistic updates by operating on in-app state rather than waiting for async API calls:

<Tabs>
  <Tab title="Good: Optimistic Update">
    ```javascript "Optimistic update pattern" lines icon="bolt" expandable highlight={9-11,15-17} theme={null}
    // ✅ Update local state immediately, sync in background
    navigator.modelContext.registerTool({
      name: 'cart_add_item',
      description: 'Add product to cart',
      inputSchema: {
        productId: z.string(),
        quantity: z.number().positive()
      },
      async execute({ productId, quantity }) {
        // Update in-app state immediately
        const cartState = getCartState();
        const product = cartState.addItem({ productId, quantity });

        // Return success instantly as markdown
        const cartItems = cartState.getItems();
        const markdown = `**Added to cart!**

    ${product.name} x${quantity}

    ## Current Cart (${cartItems.length} items)
    ${cartItems.map(item => `- ${item.name} x${item.quantity} - $${item.price * item.quantity}`).join('\n')}

    **Total:** $${cartState.getTotal()}`;

        // Sync to backend in background (don't await)
        syncCartToBackend(cartState).catch(err => {
          console.error('Background sync failed:', err);
          // Handle sync errors appropriately
        });

        return {
          content: [{ type: "text", text: markdown }]
        };
      }
    });
    ```
  </Tab>

  <Tab title="Avoid: Blocking on API">
    ```javascript "Blocking API call pattern" lines icon="x" highlight={6-10} theme={null}
    // ❌ Slow - waits for API before responding
    navigator.modelContext.registerTool({
      name: 'cart_add_item',
      description: 'Add product to cart',
      async execute({ productId, quantity }) {
        // Blocks until API responds (could take seconds)
        const result = await fetch('/api/cart/add', {
          method: 'POST',
          body: JSON.stringify({ productId, quantity })
        });

        const data = await result.json();

        // Even with markdown, waiting for API is still slow
        return {
          content: [{ type: "text", text: `Added ${data.productName} to cart` }]
        };
      }
    });
    ```
  </Tab>
</Tabs>

**Benefits of optimistic updates:**

* Instant tool responses for voice and real-time interactions
* Better user experience with immediate feedback
* Voice models can chain multiple operations smoothly
* Reduced latency in multi-step workflows

**Implementation tips:**

* Maintain local application state (Redux, Zustand, React Context)
* Update state synchronously before returning from tool
* Queue background sync operations
* Handle sync failures gracefully with retry logic

## Testing & Quality Assurance

### Test Tool Registration

Verify tools are properly registered:

```typescript theme={null}
import { describe, it, expect, beforeEach } from 'vitest';

describe('Product Search Tool', () => {
  beforeEach(() => {
    // Mock navigator.modelContext
    global.navigator = {
      modelContext: {
        registerTool: vi.fn()
      }
    } as any;
  });

  it('should register with correct schema', () => {
    registerProductSearchTool();

    expect(navigator.modelContext.registerTool).toHaveBeenCalledWith(
      expect.objectContaining({
        name: 'products_search',
        description: expect.any(String),
        inputSchema: expect.any(Object)
      })
    );
  });
});
```

### Test Tool Execution

Verify tool handlers work correctly:

```typescript theme={null}
describe('Product Search Execution', () => {
  it('should return products for valid query', async () => {
    const result = await executeProductSearch({ query: 'laptop' });

    expect(result.content[0].text).toBeTruthy();
    const data = JSON.parse(result.content[0].text);
    expect(data.success).toBe(true);
    expect(data.products).toBeInstanceOf(Array);
  });

  it('should handle empty query gracefully', async () => {
    const result = await executeProductSearch({ query: '' });

    expect(result.isError).toBe(true);
    const data = JSON.parse(result.content[0].text);
    expect(data.errorCode).toBe('VALIDATION_ERROR');
  });

  it('should handle database errors', async () => {
    // Mock database failure
    vi.spyOn(db, 'searchProducts').mockRejectedValue(
      new Error('Database connection failed')
    );

    const result = await executeProductSearch({ query: 'laptop' });

    expect(result.isError).toBe(true);
  });
});
```

### Test with Real AI Agents

Use the MCP-B Extension to test with actual AI:

<Steps>
  <Step title="Install MCP-B Extension">
    Get it from the [Chrome Web Store](https://chromewebstore.google.com/detail/mcp-b-extension/daohopfhkdelnpemnhlekblhnikhdhfa)
  </Step>

  <Step title="Load your website">
    Navigate to your development site where tools are registered
  </Step>

  <Step title="Verify tool discovery">
    Open the extension and confirm your tools appear in the available tools list
  </Step>

  <Step title="Test with natural language">
    Ask the AI agent to use your tools: "Search for laptops under \$1000"
  </Step>

  <Step title="Verify results">
    Check that the AI correctly interprets tool responses and presents them to the user
  </Step>
</Steps>

## Tool Organization

### Group Related Tools

Organize tools logically in your codebase:

```typescript theme={null}
// tools/products.ts
export function registerProductTools() {
  navigator.modelContext.registerTool({
    name: 'products_search',
    // ...
  });

  navigator.modelContext.registerTool({
    name: 'products_get_details',
    // ...
  });
}

// tools/cart.ts
export function registerCartTools() {
  navigator.modelContext.registerTool({
    name: 'cart_add_item',
    // ...
  });

  navigator.modelContext.registerTool({
    name: 'cart_get_contents',
    // ...
  });
}

// tools/index.ts
export function registerAllTools() {
  registerProductTools();
  registerCartTools();
  registerOrderTools();
}
```

### Use Consistent Prefixes

Group tools by domain using name prefixes:

```javascript theme={null}
// Product domain
products_search
products_get_details
products_get_reviews

// Cart domain
cart_add_item
cart_remove_item
cart_update_quantity
cart_clear

// Order domain
orders_create
orders_get_status
orders_list_recent
```

### Reference Related Tools in Descriptions

<Info>
  Remember: Tool descriptions go into the model's context. Reference other tools by name to help the model understand workflows and dependencies.
</Info>

Make it clear when tools should be called in sequence or when one tool depends on another:

```typescript theme={null}
// ✅ References other tools and explains workflow
navigator.modelContext.registerTool({
  name: 'cart_checkout',
  description: `Proceed to checkout with current cart contents.

Prerequisites:
- User must be authenticated
- Cart must contain at least one item (call cart_get_contents to verify)
- Shipping address must be set (call user_set_shipping_address if needed)

Returns a checkout URL where user can complete payment.`,
  // ...
});

// ✅ Mentions tool list changes
navigator.modelContext.registerTool({
  name: 'user_login',
  description: `Authenticate user with email and password.

After successful login, additional tools will become available:
- orders_list_recent
- user_get_profile
- user_update_settings

Call this tool first if user wants to access account features.`,
  // ...
});

// ✅ References prerequisite tool
navigator.modelContext.registerTool({
  name: 'order_track_shipment',
  description: `Get real-time tracking information for an order shipment.

Prerequisite: Call orders_get_details first to get the tracking number.
Requires the trackingNumber from that response.`,
  inputSchema: {
    trackingNumber: z.string().describe('Tracking number from orders_get_details')
  }
  // ...
});
```

**When to reference other tools:**

* A tool should be called before this one
* This tool's execution will register/unregister other tools
* Data from another tool is needed as input
* Multiple tools work together in a common workflow

## Framework Integration

### React with Hooks

Use `useWebMCP` for component-scoped tools:

```typescript "React integration with useWebMCP" twoslash lines icon="react" expandable highlight={8-31} theme={null}
import { useWebMCP } from '@mcp-b/react-webmcp';
import { z } from 'zod';

function ProductSearch() {
  const [results, setResults] = useState([]);

  useWebMCP({
    name: 'products_search',
    description: 'Search products',
    inputSchema: {
      query: z.string().min(1)
    },
    async execute({ query }) {
      const data = await searchProducts(query);
      setResults(data); // Update UI

      // Return markdown formatted results
      return {
        content: [{
          type: "text",
          text: `# Search Results

Found ${data.length} products matching "${query}":

${data.map(p => `- **${p.name}** - $${p.price}`).join('\n')}`
        }]
      };
    }
  });

  return (
    <div>
      {/* Component renders search results */}
      {results.map(product => <ProductCard key={product.id} {...product} />)}
    </div>
  );
}
```

### Vue with Composition API

```typescript theme={null}
import { onMounted, onUnmounted } from 'vue';

export function useProductSearch() {
  const results = ref([]);
  let registration: ToolRegistration | null = null;

  onMounted(() => {
    registration = navigator.modelContext.registerTool({
      name: 'products_search',
      description: 'Search products',
      inputSchema: {
        query: z.string().min(1)
      },
      async execute({ query }) {
        const data = await searchProducts(query);
        results.value = data;

        // Return markdown formatted results
        return {
          content: [{
            type: "text",
            text: `# Search Results

Found ${data.length} products:

${data.map(p => `- **${p.name}** - $${p.price}`).join('\n')}`
          }]
        };
      }
    });
  });

  onUnmounted(() => {
    registration?.unregister();
  });

  return { results };
}
```

### Vanilla JavaScript

```javascript theme={null}
// Register on page load
document.addEventListener('DOMContentLoaded', () => {
  const registration = navigator.modelContext.registerTool({
    name: 'products_search',
    description: 'Search products',
    inputSchema: {
      type: 'object',
      properties: {
        query: { type: 'string' }
      }
    },
    async execute({ query }) {
      const results = await searchProducts(query);

      // Update DOM
      document.getElementById('results').innerHTML =
        results.map(p => `<div>${p.name} - $${p.price}</div>`).join('');

      // Return markdown formatted results
      return {
        content: [{
          type: "text",
          text: `# Search Results

Found ${results.length} products:

${results.map(p => `- **${p.name}** - $${p.price}`).join('\n')}`
        }]
      };
    }
  });

  // Cleanup on page unload
  window.addEventListener('beforeunload', () => {
    registration.unregister();
  });
});
```

## Documentation

### Write for the Model, Not Developers

<Warning>
  JSDoc comments and code documentation do NOT go into the model's context. Only the tool name, description, and input schema are sent to the AI.
</Warning>

Put all important information in the tool description and parameter descriptions:

```typescript theme={null}
// ❌ JSDoc won't help the model
/**
 * @param query - Search query
 * @param maxPrice - Maximum price
 */
navigator.modelContext.registerTool({
  name: 'products_search',
  description: 'Search products',
  // ...
});

// ✅ Everything in the description and schema
navigator.modelContext.registerTool({
  name: 'products_search',
  description: `Search the product catalog using full-text search across names, descriptions, and SKUs.

Returns paginated array of products with name, price, stock status, and URLs.
Useful when users ask "what products do you have" or "find me a laptop under $1000".`,
  inputSchema: {
    query: z.string()
      .min(1)
      .max(100)
      .describe('Search query (1-100 characters). Can be product name, category, or SKU.'),
    category: z.enum(['electronics', 'clothing', 'home'])
      .optional()
      .describe('Optional category filter'),
    maxPrice: z.number()
      .positive()
      .optional()
      .describe('Optional maximum price filter in USD'),
    limit: z.number()
      .int()
      .min(1)
      .max(100)
      .default(10)
      .describe('Results per page (1-100, default: 10)')
  }
});
```

### Create Tool Catalog for Developers

Maintain a reference document for your development team:

```markdown theme={null}
# WebMCP Tools Catalog

## Product Tools

### products_search
**Description:** Search products by name, category, or SKU
**Inputs:**
- `query` (string, required): Search terms
- `category` (string, optional): Filter by category
- `limit` (number, optional): Results per page (default: 10)

**Returns:** Array of products with pricing and availability

**Example:** Find laptops under $1000
```

### Use OpenAPI/JSON Schema

Export tool schemas for documentation:

```typescript theme={null}
export const toolSchemas = {
  products_search: {
    name: 'products_search',
    description: 'Search products',
    parameters: {
      type: 'object',
      properties: {
        query: {
          type: 'string',
          description: 'Search query'
        }
      },
      required: ['query']
    }
  }
};
```

## Monitoring & Analytics

### Log Tool Usage

Track which tools are being called:

```javascript theme={null}
async execute(params) {
  const startTime = Date.now();

  try {
    const results = await performSearch(params.query);

    // Log successful execution
    analytics.track('tool_executed', {
      toolName: 'products_search',
      duration: Date.now() - startTime,
      resultCount: results.length,
      userId: getCurrentUser()?.id
    });

    // Return markdown formatted results
    return {
      content: [{
        type: "text",
        text: `# Search Results

Found ${results.length} products matching "${params.query}":

${results.map((r, i) => `${i + 1}. **${r.name}** - $${r.price}`).join('\n')}`
      }]
    };
  } catch (error) {
    // Log errors
    analytics.track('tool_error', {
      toolName: 'products_search',
      error: error.message,
      duration: Date.now() - startTime
    });

    throw error;
  }
}
```

### Monitor Performance

Track tool execution time:

```javascript theme={null}
class ToolMetrics {
  private metrics = new Map<string, number[]>();

  record(toolName: string, duration: number) {
    if (!this.metrics.has(toolName)) {
      this.metrics.set(toolName, []);
    }
    this.metrics.get(toolName)!.push(duration);
  }

  getStats(toolName: string) {
    const durations = this.metrics.get(toolName) || [];
    return {
      count: durations.length,
      avg: durations.reduce((a, b) => a + b, 0) / durations.length,
      max: Math.max(...durations),
      min: Math.min(...durations)
    };
  }
}

const metrics = new ToolMetrics();

async execute(params) {
  const start = Date.now();
  try {
    const result = await performOperation(params);
    return result;
  } finally {
    metrics.record('products_search', Date.now() - start);
  }
}
```

## Version Management

### Version Your Tools

Include version info in tool names or metadata:

```javascript theme={null}
// Option 1: Include version in name for breaking changes
navigator.modelContext.registerTool({
  name: 'products_search_v2',
  description: 'Search products (v2 - new schema)',
  // ...
});

// Option 2: Include version in response metadata
async execute(params) {
  const results = await performSearch(params);

  return {
    content: [{
      type: "text",
      text: `# Search Results (API v2.0.0)

${results.map(r => `- ${r.title}`).join('\n')}

---
*Using API version 2.0.0*`
    }]
  };
}
```

### Deprecate Gracefully

Warn when tools will be removed:

```javascript theme={null}
navigator.modelContext.registerTool({
  name: 'legacy_search',
  description: '⚠️ DEPRECATED: Use products_search instead. This tool will be removed in v3.0.',
  async execute(params) {
    console.warn('legacy_search is deprecated, use products_search');
    const results = await legacySearch(params);

    return {
      content: [{
        type: "text",
        text: `⚠️ **DEPRECATION WARNING**

This tool is deprecated and will be removed in v3.0.
Please use \`products_search\` instead.

---

# Search Results

${results.map(r => `- ${r.title}`).join('\n')}`
      }]
    };
  }
});
```

## Quick Reference

<AccordionGroup>
  <Accordion title="What goes into model context">
    * Tool name, description, and input schema ONLY
    * JSDoc and code comments are NOT sent to the model
    * Use detailed descriptions and parameter `.describe()` methods
    * Reference other tools by name in descriptions
    * Mention if tool execution changes the tool list
  </Accordion>

  <Accordion title="Tool design">
    * Prefer consolidated tools over many single-purpose tools
    * Reduces context consumption
    * Use `domain_verb_noun` naming pattern
    * Be specific and descriptive in all metadata
  </Accordion>

  <Accordion title="Tool descriptions">
    * Include what the tool does and when to use it
    * Describe return data format
    * List prerequisites and dependencies on other tools
    * Mention if this tool registers/unregisters other tools
    * Use parameter `.describe()` for detailed parameter info
  </Accordion>

  <Accordion title="Performance for voice models">
    * Use optimistic updates - update local state first
    * Return instantly, sync to backend in background
    * Don't block on async API calls
    * Maintain in-app state for fast operations
  </Accordion>

  <Accordion title="Response format">
    * Return markdown strings instead of JSON objects
    * Markdown is more readable for AI models
    * Better for natural language presentation
    * Include helpful context and formatting
  </Accordion>

  <Accordion title="Input validation">
    * Prefer Zod schemas for TypeScript projects
    * Use `.describe()` on every parameter (goes to model)
    * Validate both types and business logic
    * Provide helpful error messages
  </Accordion>

  <Accordion title="Security">
    * Validate user authentication
    * Check authorization for protected resources
    * Sanitize all inputs
    * Rate limit tool calls
    * Never expose sensitive data
  </Accordion>

  <Accordion title="Testing">
    * Unit test registration and execution
    * Test error handling
    * Test with real AI agents using MCP-B Extension
  </Accordion>
</AccordionGroup>

## Additional Resources

<CardGroup cols={2}>
  <Card title="Core Concepts" icon="diagram-project" href="/concepts/overview">
    Learn about WebMCP architecture and design
  </Card>

  <Card title="Quick Start" icon="rocket" href="/quickstart">
    Get started with your first tool
  </Card>

  <Card title="Security Guide" icon="shield" href="/security">
    Security best practices and guidelines
  </Card>

  <Card title="API Reference" icon="code" href="/packages/global">
    Complete API documentation
  </Card>

  <Card title="React Integration" icon="react" href="/packages/react-webmcp">
    Using WebMCP with React
  </Card>

  <Card title="Examples Repository" icon="github" href="https://github.com/WebMCP-org/examples">
    See real-world implementations
  </Card>
</CardGroup>
