Skip to main content Skip to search

MCP Tool Development

Build tools that AI can understand and use effectively

Tool Anatomy

Every MCP tool consists of four key components that help AI understand when and how to use it.

export const myTool = {
 // 1. Unique identifier
 name: "search_products",
 
 // 2. Clear description for AI understanding
 description: "Search for products in the inventory database by name, category, or price range",
 
 // 3. Input schema (JSON Schema format)
 inputSchema: {
 type: "object",
 properties: {
 query: {
 type: "string",
 description: "Search query (product name or keyword)"
 },
 category: {
 type: "string",
 enum: ["electronics", "clothing", "books", "home"],
 description: "Optional: filter by product category"
 },
 maxPrice: {
 type: "number",
 description: "Optional: maximum price filter"
 }
 },
 required: ["query"]
 },
 
 // 4. Implementation function
 handler: async (args: { query: string; category?: string; maxPrice?: number }) => {
 // Your tool logic here
 return {
 success: true,
 products: [...],
 count: 42
 };
 }
};

Best Practices

📝 Write Clear Descriptions

AI needs to understand when to use your tool and what it does.

❌ Too Vague

"Get data"

✅ Specific & Clear

"Fetch user profile data including name, email, subscription status, and last login date"

🔧 Define Specific Input Schemas

Detailed schemas help AI provide the right data and catch errors early.

// Good: Specific with validation
inputSchema: {
 type: "object",
 properties: {
 email: {
 type: "string",
 format: "email",
 description: "User's email address for account lookup"
 },
 startDate: {
 type: "string",
 format: "date",
 description: "Start date for data range (YYYY-MM-DD)"
 },
 limit: {
 type: "integer",
 minimum: 1,
 maximum: 100,
 default: 10,
 description: "Maximum number of results to return"
 }
 },
 required: ["email"]
}

✅ Handle Errors Gracefully

Return meaningful error messages that help AI understand what went wrong.

handler: async (args) => {
 try {
 // Validate inputs
 if (!args.email || !args.email.includes('@')) {
 return {
 success: false,
 error: "Invalid email format. Please provide a valid email address.",
 code: "INVALID_EMAIL"
 };
 }

 const result = await fetchUserData(args.email);
 
 if (!result) {
 return {
 success: false,
 error: "User not found with the provided email address.",
 code: "USER_NOT_FOUND"
 };
 }

 return {
 success: true,
 user: result,
 timestamp: new Date().toISOString()
 };
 
 } catch (error) {
 return {
 success: false,
 error: "Database connection failed. Please try again later.",
 code: "DATABASE_ERROR"
 };
 }
}

📊 Return Structured Data

Consistent response formats make it easier for AI to process and present results.

// Consistent response structure
return {
 success: true,
 data: {
 users: [...],
 pagination: {
 page: 1,
 limit: 10,
 total: 156,
 hasMore: true
 },
 metadata: {
 searchTerm: "john",
 executionTime: "145ms",
 timestamp: "2024-01-15T10:30:00Z"
 }
 }
};

🚀 Optimize Performance

AI applications expect fast responses. Keep tools under 30 seconds, ideally under 5 seconds.

Performance Tips

  • Use connection pooling for database queries
  • Implement caching for frequently accessed data
  • Set reasonable timeouts for external API calls
  • Paginate large result sets

Common Tool Patterns

🌐 API Integration Tool

Tools that fetch data from external APIs with error handling and rate limiting.

export const weatherTool = {
 name: "get_weather",
 description: "Get current weather conditions for any city worldwide",
 inputSchema: {
 type: "object",
 properties: {
 city: {
 type: "string",
 description: "City name (e.g., 'New York', 'London')"
 },
 units: {
 type: "string",
 enum: ["celsius", "fahrenheit"],
 default: "celsius",
 description: "Temperature units"
 }
 },
 required: ["city"]
 },
 handler: async (args) => {
 try {
 const response = await fetch(
 `https://api.openweathermap.org/data/2.5/weather?q=${args.city}&appid=${process.env.WEATHER_API_KEY}&units=metric`,
 { timeout: 5000 }
 );
 
 if (!response.ok) {
 if (response.status === 404) {
 return {
 success: false,
 error: `City "${args.city}" not found. Please check the spelling.`,
 code: "CITY_NOT_FOUND"
 };
 }
 throw new Error(`Weather API error: ${response.status}`);
 }
 
 const data = await response.json();
 
 return {
 success: true,
 weather: {
 city: data.name,
 country: data.sys.country,
 temperature: args.units === "fahrenheit" 
 ? Math.round(data.main.temp * 9/5 + 32) 
 : Math.round(data.main.temp),
 description: data.weather[0].description,
 humidity: data.main.humidity,
 windSpeed: data.wind.speed
 }
 };
 } catch (error) {
 return {
 success: false,
 error: "Unable to fetch weather data. Please try again later.",
 code: "API_ERROR"
 };
 }
 }
};

📊 Data Processing Tool

Tools that transform, filter, or analyze data with validation and type safety.

export const csvProcessTool = {
 name: "process_csv",
 description: "Parse and analyze CSV data with filtering and aggregation options",
 inputSchema: {
 type: "object",
 properties: {
 csvData: {
 type: "string",
 description: "Raw CSV data to process"
 },
 operations: {
 type: "array",
 items: {
 type: "string",
 enum: ["filter", "sort", "aggregate", "validate"]
 },
 description: "Operations to perform on the data"
 },
 filterColumn: {
 type: "string",
 description: "Column name to filter by (if using filter operation)"
 },
 filterValue: {
 type: "string",
 description: "Value to filter for (if using filter operation)"
 }
 },
 required: ["csvData"]
 },
 handler: async (args) => {
 try {
 // Parse CSV
 const rows = args.csvData.trim().split('\n');
 const headers = rows[0].split(',').map(h => h.trim());
 const data = rows.slice(1).map(row => {
 const values = row.split(',').map(v => v.trim());
 return headers.reduce((obj, header, index) => {
 obj[header] = values[index] || '';
 return obj;
 }, {});
 });

 let processedData = [...data];

 // Apply operations
 if (args.operations?.includes('filter') && args.filterColumn && args.filterValue) {
 processedData = processedData.filter(row => 
 row[args.filterColumn]?.toLowerCase().includes(args.filterValue.toLowerCase())
 );
 }

 return {
 success: true,
 result: {
 originalCount: data.length,
 processedCount: processedData.length,
 headers: headers,
 data: processedData.slice(0, 100), // Limit to 100 rows for performance
 operations: args.operations || []
 }
 };
 } catch (error) {
 return {
 success: false,
 error: "Failed to process CSV data. Please check the format.",
 code: "CSV_PARSE_ERROR"
 };
 }
 }
};

Testing Your Tools

The scaffolding tool includes testing utilities to verify your tools work correctly.

# Run all tests
npm test

# Test in watch mode during development
npm run test:watch

# Health check (tests tool registration)
npm run doctor

# Test specific tool manually
curl -X POST http://localhost:3000/tools/my_tool \
 -H "Content-Type: application/json" \
 -d '{"param1": "value1"}'

Testing Best Practices

  • • Test both success and error cases
  • • Verify input validation works correctly
  • • Test with edge cases (empty strings, large numbers, special characters)
  • • Mock external API calls in tests for reliability

Next Steps

Schema Reference

Complete JSON Schema reference for input validation

View Schema Docs →

Design Patterns

Common patterns for different types of tools

Pattern Library →

Example Tools

Real-world tool implementations

Browse Examples →

Deploy & Monetize

Get your tools live and earning revenue

Deployment Guide →