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