Creating MCP Tools
Develop custom MCP tools to extend AI capabilities.
Overview
MCP (Model Context Protocol) tools expose custom functionality to AI agents. Create tools for your APIs, databases, and services.
Tool Schema
MCP tools follow a standardized schema:
{
"name": "get_order_status",
"description": "Get the current status of an order",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Unique order identifier",
"required": true
},
"include_details": {
"type": "boolean",
"description": "Include detailed order information",
"default": false
}
}
}
}
Tool Structure
Required Fields
| Field | Type | Description |
|---|---|---|
name | string | Unique tool identifier |
description | string | What the tool does |
parameters | object | Parameter definitions |
Parameter Fields
| Field | Type | Description |
|---|---|---|
type | string | Parameter type (string, number, boolean, array, object) |
description | string | Parameter description |
required | boolean | Whether parameter is required |
default | any | Default value |
enum | array | List of allowed values |
Tool Examples
Order Status Tool
{
"name": "get_order_status",
"description": "Retrieve the current status and tracking information for an order",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Order ID or tracking number",
"required": true
},
"include_items": {
"type": "boolean",
"description": "Include list of order items",
"default": false
}
},
"required": ["order_id"]
}
}
Weather Tool
{
"name": "get_weather",
"description": "Get current weather conditions and forecast for a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name or postal code",
"required": true
},
"units": {
"type": "string",
"description": "Temperature units",
"enum": ["celsius", "fahrenheit"],
"default": "celsius"
},
"include_forecast": {
"type": "boolean",
"description": "Include 5-day forecast",
"default": false
}
},
"required": ["location"]
}
}
CRM Lookup Tool
{
"name": "get_customer_info",
"description": "Retrieve customer information from CRM system",
"parameters": {
"type": "object",
"properties": {
"customer_id": {
"type": "string",
"description": "Customer unique identifier",
"required": true
},
"include_orders": {
"type": "boolean",
"description": "Include order history",
"default": true
},
"include_tickets": {
"type": "boolean",
"description": "Include support tickets",
"default": false
}
},
"required": ["customer_id"]
}
}
Inventory Check Tool
{
"name": "check_inventory",
"description": "Check product availability and stock levels",
"parameters": {
"type": "object",
"properties": {
"product_id": {
"type": "string",
"description": "Product SKU or ID",
"required": true
},
"location": {
"type": "string",
"description": "Store or warehouse location",
"default": "main"
}
},
"required": ["product_id"]
}
}
Tool Implementation
Python Server
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Optional, List
import requests
app = FastAPI()
# Tool definitions
TOOLS = [
{
"name": "get_order_status",
"description": "Get order status",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Order ID",
"required": True
}
},
"required": ["order_id"]
}
}
]
class OrderStatusRequest(BaseModel):
order_id: str
@app.get("/tools")
async def list_tools():
return {"tools": TOOLS}
@app.post("/tools/get_order_status")
async def get_order_status(request: OrderStatusRequest):
# Call internal order API
api_url = f"https://orders.company.com/api/v1/orders/{request.order_id}"
headers = {"Authorization": f"Bearer {os.getenv('ORDER_API_KEY')}"}
response = requests.get(api_url, headers=headers)
if response.status_code != 200:
raise HTTPException(status_code=404, detail="Order not found")
return response.json()
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
JavaScript Server
const express = require('express');
const app = express();
app.use(express.json());
// Tool definitions
const TOOLS = [
{
name: 'get_weather',
description: 'Get current weather for a location',
parameters: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'City or postal code',
required: true
},
units: {
type: 'string',
description: 'Temperature units',
enum: ['celsius', 'fahrenheit'],
default: 'celsius'
}
},
required: ['location']
}
}
];
// List available tools
app.get('/tools', (req, res) => {
res.json({ tools: TOOLS });
});
// Execute get_weather tool
app.post('/tools/get_weather', async (req, res) => {
const { location, units } = req.body;
try {
// Call weather API
const weatherResponse = await fetch(
`https://api.weather.com/v1/current?location=${location}&units=${units}`,
{
headers: {
'Authorization': `Bearer ${process.env.WEATHER_API_KEY}`
}
}
);
const weatherData = await weatherResponse.json();
res.json(weatherData);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch weather' });
}
});
app.listen(8000, () => {
console.log('MCP server running on port 8000');
});
Error Handling
Standard Error Format
{
"error": {
"code": "ORDER_NOT_FOUND",
"message": "Order with ID '12345' not found",
"details": {
"order_id": "12345",
"suggestions": ["12346", "12347"]
}
}
}
Error Codes
| Code | Description |
|---|---|
NOT_FOUND - Resource not found | |
UNAUTHORIZED - Invalid or missing authentication | |
RATE_LIMITED - Too many requests | |
INVALID_PARAMS - Invalid parameters | |
SERVER_ERROR - Internal server error |
Best Practices
Tool Design
- Clear names - Use descriptive, action-oriented names
- Detailed descriptions - Explain what the tool does
- Required parameters - Mark truly required fields
- Default values - Provide sensible defaults
- Type safety - Use correct parameter types
- Error messages - Provide helpful error messages
API Integration
- Use retry logic for failed requests
- Implement rate limiting
- Cache responses when appropriate
- Validate inputs before calling APIs
- Log all API calls
Security
- Never expose internal API keys
- Use environment variables for secrets
- Validate and sanitize inputs
- Implement authentication
- Use HTTPS for all connections
Testing
Unit Tests
import pytest
from fastapi.testclient import TestClient
client = TestClient(app)
def test_list_tools():
response = client.get("/tools")
assert response.status_code == 200
assert "tools" in response.json()
def test_get_order_status():
response = client.post(
"/tools/get_order_status",
json={"order_id": "12345"}
)
assert response.status_code == 200
assert "status" in response.json()
def test_invalid_order():
response = client.post(
"/tools/get_order_status",
json={"order_id": "INVALID"}
)
assert response.status_code == 404
Integration Tests
def test_full_flow():
# List tools
response = client.get("/tools")
tools = response.json()["tools"]
assert any(t["name"] == "get_order_status" for t in tools)
# Use tool
response = client.post(
"/tools/get_order_status",
json={"order_id": "TEST_ORDER"}
)
assert response.status_code == 200
result = response.json()
assert result["status"] in ["pending", "shipped", "delivered"]
Troubleshooting
Tool Not Found
Problem: Agent can't find tool
Solution:
- Verify tool is registered
- Check
/toolsendpoint - Ensure tool name is correct
- Restart MCP server
Invalid Parameters
Problem: Tool receives wrong parameter types
Solution:
- Verify parameter schema
- Check required fields
- Validate default values
- Review type definitions
API Connection Fails
Problem: MCP server can't connect to backend
Solution:
- Check API credentials
- Verify API URLs are correct
- Test API connectivity
- Review firewall rules
Next Steps
- Setup - Configure MCP servers
- Examples - See MCP in action
- Agent Documentation - Use MCP in agents