# Custom Tools

Guide to creating custom MCP tools for your specific use cases.

## Why Create Custom Tools?

While AIDP provides standard tools for common use cases, you may want to create custom tools for:

* **Domain-Specific Logic**: Business logic unique to your application
* **Data Transformation**: Custom formatting or filtering of AIDP data
* **Integration**: Combining AIDP data with other data sources
* **Specialized Queries**: Pre-configured searches for specific scenarios

***

## Tool Structure

Every MCP tool has three components:

1. **Metadata**: Name, description, and parameters
2. **Handler**: Function that executes the tool
3. **Validation**: Parameter validation logic

### Basic Tool Template

```typescript
import { Tool, ToolHandler } from '@aidp/mcp-server';

export const myCustomTool: Tool = {
  name: 'my_custom_tool',
  description: 'Description of what this tool does',
  parameters: {
    type: 'object',
    properties: {
      param1: {
        type: 'string',
        description: 'Description of param1',
        required: true,
      },
      param2: {
        type: 'number',
        description: 'Description of param2',
        required: false,
        default: 10,
      },
    },
  },
  handler: async (params) => {
    // Tool implementation
    return {
      success: true,
      data: {
        /* result */
      },
    };
  },
};
```

***

## Example 1: Nearby Restaurants Tool

A specialized tool for finding restaurants near a location.

```typescript
import { Tool } from '@aidp/mcp-server';
import { AIDPClient } from '@aidp/sdk';

const client = new AIDPClient({
  apiKey: process.env.AIDP_API_KEY,
});

export const nearbyRestaurants: Tool = {
  name: 'find_nearby_restaurants',
  description: 'Find restaurants near a specific location with filters',
  parameters: {
    type: 'object',
    properties: {
      location: {
        type: 'object',
        description: 'Location coordinates',
        properties: {
          lat: { type: 'number', required: true },
          lon: { type: 'number', required: true },
        },
        required: true,
      },
      cuisine: {
        type: 'string',
        description: 'Cuisine type (e.g., italian, mexican, asian)',
        required: false,
      },
      priceLevel: {
        type: 'string',
        description: 'Price level: $, $$, $$$, $$$$',
        required: false,
      },
      openNow: {
        type: 'boolean',
        description: 'Only show restaurants open now',
        required: false,
        default: false,
      },
      distance: {
        type: 'string',
        description: 'Search radius (e.g., 1km, 5mi)',
        required: false,
        default: '5km',
      },
    },
  },
  handler: async (params) => {
    try {
      // Build search query
      let query = 'restaurants';
      if (params.cuisine) {
        query += ` ${params.cuisine} cuisine`;
      }
      if (params.openNow) {
        query += ' open now';
      }

      // Search AIDP
      const results = await client.search({
        query,
        location: {
          lat: params.location.lat,
          lon: params.location.lon,
          distance: params.distance,
        },
        category: 'restaurants',
        limit: 20,
      });

      // Filter by price level
      let businesses = results.data.businesses;
      if (params.priceLevel) {
        businesses = businesses.filter((b) => b.priceLevel === params.priceLevel);
      }

      // Filter by open now
      if (params.openNow) {
        businesses = businesses.filter((b) => b.isOpenNow);
      }

      return {
        success: true,
        data: {
          restaurants: businesses,
          total: businesses.length,
          filters: {
            cuisine: params.cuisine,
            priceLevel: params.priceLevel,
            openNow: params.openNow,
          },
        },
      };
    } catch (error) {
      return {
        success: false,
        error: {
          code: 'SEARCH_FAILED',
          message: error.message,
        },
      };
    }
  },
};
```

### Usage

```json
{
  "tool": "find_nearby_restaurants",
  "parameters": {
    "location": { "lat": 45.5231, "lon": -122.6765 },
    "cuisine": "italian",
    "priceLevel": "$$",
    "openNow": true,
    "distance": "3km"
  }
}
```

***

## Example 2: Business Comparison Tool

Compare multiple businesses side-by-side.

```typescript
export const compareBusinesses: Tool = {
  name: 'compare_businesses',
  description: 'Compare multiple businesses side-by-side',
  parameters: {
    type: 'object',
    properties: {
      businessIds: {
        type: 'array',
        description: 'Array of business IDs to compare',
        items: { type: 'string' },
        required: true,
        minItems: 2,
        maxItems: 5,
      },
      compareFields: {
        type: 'array',
        description: 'Fields to compare',
        items: { type: 'string' },
        required: false,
        default: ['rating', 'price', 'hours', 'services'],
      },
    },
  },
  handler: async (params) => {
    try {
      // Fetch all businesses
      const businesses = await Promise.all(
        params.businessIds.map((id) => client.businesses.get(id))
      );

      // Build comparison
      const comparison = {
        businesses: businesses.map((b) => ({
          id: b.data.id,
          name: b.data.name,
          rating: b.data.averageRating,
          priceLevel: b.data.priceLevel,
          hours: b.data.hours,
          services: b.data.services,
          distance: b.data.distance,
        })),
        summary: {
          highestRated: businesses.reduce((max, b) =>
            b.data.averageRating > max.data.averageRating ? b : max
          ).data.name,
          mostAffordable: businesses.reduce((min, b) =>
            getPriceValue(b.data.priceLevel) < getPriceValue(min.data.priceLevel) ? b : min
          ).data.name,
          closest: businesses.reduce((min, b) => (b.data.distance < min.data.distance ? b : min))
            .data.name,
        },
      };

      return {
        success: true,
        data: comparison,
      };
    } catch (error) {
      return {
        success: false,
        error: {
          code: 'COMPARISON_FAILED',
          message: error.message,
        },
      };
    }
  },
};

function getPriceValue(priceLevel: string): number {
  const map = { $: 1, $$: 2, $$$: 3, $$$$: 4 };
  return map[priceLevel] || 0;
}
```

***

## Example 3: Smart Recommendations Tool

AI-powered recommendations based on user preferences.

```typescript
export const smartRecommendations: Tool = {
  name: 'get_smart_recommendations',
  description: 'Get personalized business recommendations',
  parameters: {
    type: 'object',
    properties: {
      userPreferences: {
        type: 'object',
        description: 'User preferences',
        properties: {
          categories: { type: 'array', items: { type: 'string' } },
          priceRange: { type: 'string' },
          amenities: { type: 'array', items: { type: 'string' } },
        },
        required: true,
      },
      location: {
        type: 'object',
        properties: {
          lat: { type: 'number' },
          lon: { type: 'number' },
        },
        required: true,
      },
      limit: {
        type: 'number',
        default: 5,
        required: false,
      },
    },
  },
  handler: async (params) => {
    try {
      const { userPreferences, location, limit } = params;

      // Search for each preferred category
      const searches = await Promise.all(
        userPreferences.categories.map((category) =>
          client.search({
            query: category,
            location: { ...location, distance: '10km' },
            limit: 10,
          })
        )
      );

      // Combine and score results
      const allBusinesses = searches.flatMap((s) => s.data.businesses);
      const scored = allBusinesses.map((business) => ({
        ...business,
        score: calculateScore(business, userPreferences),
      }));

      // Sort by score and deduplicate
      const unique = Array.from(new Map(scored.map((b) => [b.id, b])).values());
      const sorted = unique.sort((a, b) => b.score - a.score);
      const top = sorted.slice(0, limit);

      return {
        success: true,
        data: {
          recommendations: top,
          total: top.length,
          preferences: userPreferences,
        },
      };
    } catch (error) {
      return {
        success: false,
        error: {
          code: 'RECOMMENDATION_FAILED',
          message: error.message,
        },
      };
    }
  },
};

function calculateScore(business: any, preferences: any): number {
  let score = business.averageRating * 20; // Base score from rating

  // Price match
  if (business.priceLevel === preferences.priceRange) {
    score += 10;
  }

  // Amenities match
  const matchedAmenities =
    business.amenities?.filter((a) => preferences.amenities?.includes(a)).length || 0;
  score += matchedAmenities * 5;

  // Distance penalty
  score -= business.distance / 1000; // Subtract 1 point per km

  return score;
}
```

***

## Registering Custom Tools

### In Configuration File

Add to `mcp-config.yml`:

```yaml
tools:
  enabled:
    - search_businesses
    - get_business

  custom:
    - name: find_nearby_restaurants
      path: ./tools/nearby-restaurants.js
    - name: compare_businesses
      path: ./tools/compare-businesses.js
    - name: get_smart_recommendations
      path: ./tools/smart-recommendations.js
```

### Programmatically

```typescript
import { MCPServer } from '@aidp/mcp-server';
import { nearbyRestaurants, compareBusinesses, smartRecommendations } from './tools';

const server = new MCPServer({
  port: 3000,
  aidpApiKey: process.env.AIDP_API_KEY,
});

// Register custom tools
server.registerTool(nearbyRestaurants);
server.registerTool(compareBusinesses);
server.registerTool(smartRecommendations);

// Start server
await server.start();
```

***

## Best Practices

### 1. Clear Naming

Use descriptive, action-oriented names:

✅ **Good**: `find_nearby_restaurants`, `compare_businesses` ❌ **Bad**: `tool1`, `search`, `get_data`

### 2. Comprehensive Descriptions

Provide detailed descriptions:

```typescript
{
  name: 'find_nearby_restaurants',
  description: 'Find restaurants near a specific location. Supports filtering by cuisine type, price level, and current availability. Returns up to 20 results sorted by distance.',
  // ...
}
```

### 3. Validate Parameters

Always validate input parameters:

```typescript
handler: async (params) => {
  // Validate required parameters
  if (!params.location?.lat || !params.location?.lon) {
    return {
      success: false,
      error: {
        code: 'INVALID_PARAMETERS',
        message: 'Location coordinates are required',
      },
    };
  }

  // Validate ranges
  if (params.limit && (params.limit < 1 || params.limit > 50)) {
    return {
      success: false,
      error: {
        code: 'INVALID_PARAMETERS',
        message: 'Limit must be between 1 and 50',
      },
    };
  }

  // Continue with tool logic...
};
```

### 4. Handle Errors Gracefully

Provide helpful error messages:

```typescript
try {
  // Tool logic
} catch (error) {
  return {
    success: false,
    error: {
      code: 'TOOL_ERROR',
      message: `Failed to execute tool: ${error.message}`,
      details: {
        originalError: error.toString(),
      },
    },
  };
}
```

### 5. Cache Expensive Operations

Cache results to improve performance:

```typescript
const cache = new Map();

handler: async (params) => {
  const cacheKey = JSON.stringify(params);

  if (cache.has(cacheKey)) {
    return cache.get(cacheKey);
  }

  const result = await expensiveOperation(params);
  cache.set(cacheKey, result);

  return result;
};
```

***

## Testing Custom Tools

See [Testing Guide](https://amistan.gitbook.io/aidp-docs/for-developers/mcp-integration/testing) for comprehensive testing strategies.

### Quick Test

```bash
curl -X POST http://localhost:3000/mcp/call \
  -H "Content-Type: application/json" \
  -d '{
    "tool": "find_nearby_restaurants",
    "parameters": {
      "location": {"lat": 45.5231, "lon": -122.6765},
      "cuisine": "italian",
      "openNow": true
    }
  }'
```

***

## Next Steps

* [Testing Your Tools →](https://amistan.gitbook.io/aidp-docs/for-developers/mcp-integration/testing)
* [Standard Tools Reference →](https://amistan.gitbook.io/aidp-docs/for-developers/mcp-integration/tools)
* [MCP Server Setup →](https://amistan.gitbook.io/aidp-docs/for-developers/mcp-integration/setup)

***

## Support

* 📧 Email: <custom-tools@aidp.dev>
* 💬 GitHub Discussions: [github.com/aidp/platform/discussions](https://github.com/aidp/platform/discussions)
* 📚 Documentation: [docs.aidp.dev/mcp/custom-tools](https://docs.aidp.dev/mcp/custom-tools)
