Skip to main content

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​

FieldTypeDescription
namestringUnique tool identifier
descriptionstringWhat the tool does
parametersobjectParameter definitions

Parameter Fields​

FieldTypeDescription
typestringParameter type (string, number, boolean, array, object)
descriptionstringParameter description
requiredbooleanWhether parameter is required
defaultanyDefault value
enumarrayList 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​

CodeDescription
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​

  1. Clear names - Use descriptive, action-oriented names
  2. Detailed descriptions - Explain what the tool does
  3. Required parameters - Mark truly required fields
  4. Default values - Provide sensible defaults
  5. Type safety - Use correct parameter types
  6. 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 /tools endpoint
  • 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​