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