How to Add Custom Tools¶
Extend your agents with custom tools using the Model Context Protocol (MCP). Build a custom tool in minutes, not hours.
Time: 10-15 minutes
Difficulty: Beginner
What You'll Learn¶
- Create a simple MCP server with custom tools
- Connect the MCP server to Hector
- Use your custom tools in agents
- Best practices for tool development
Understanding MCP¶
MCP (Model Context Protocol) is an open standard for connecting AI agents to tools and data sources.
Benefits: - Fast development - 5-10 minutes to create a tool - Language agnostic - Python, JavaScript, Go, etc. - Standardized - Works with any MCP-compliant agent - Ecosystem - 150+ tools available via Composio
Quick Example: Weather Tool¶
Let's build a simple weather tool that agents can use.
Step 1: Create MCP Server¶
Create weather_server.py
:
#!/usr/bin/env python3
"""
Simple MCP server providing weather information.
"""
from mcp.server import Server, Tool
from mcp.types import TextContent
import json
# Create MCP server
server = Server("weather-server")
@server.tool()
def get_weather(city: str) -> str:
"""
Get current weather for a city.
Args:
city: Name of the city
Returns:
Weather information as a string
"""
# In production, call real weather API
# For demo, return mock data
weather_data = {
"San Francisco": "Sunny, 72°F",
"New York": "Cloudy, 65°F",
"London": "Rainy, 55°F",
"Tokyo": "Clear, 68°F"
}
weather = weather_data.get(city, f"Weather data not available for {city}")
return f"Weather in {city}: {weather}"
@server.tool()
def get_forecast(city: str, days: int = 3) -> str:
"""
Get weather forecast for a city.
Args:
city: Name of the city
days: Number of days to forecast (default: 3)
Returns:
Forecast information as a string
"""
# Mock forecast
forecast = []
for i in range(days):
forecast.append(f"Day {i+1}: Partly cloudy, 70°F")
return f"Forecast for {city}:\n" + "\n".join(forecast)
if __name__ == "__main__":
# Run server on port 3000
server.run(port=3000)
Step 2: Install Dependencies¶
pip install mcp-server
Step 3: Start MCP Server¶
python weather_server.py
# Output:
# MCP server listening on http://localhost:3000
# Tools available: get_weather, get_forecast
Step 4: Configure in Hector¶
Create config.yaml
:
# MCP Tools Configuration
tools:
weather_server:
type: "mcp"
server_url: "http://localhost:3000"
description: "Weather information tools"
# Agent using weather tools
agents:
weather_assistant:
llm: "gpt-4o"
prompt:
system_role: |
You are a helpful weather assistant.
Use the weather tools (get_weather, get_forecast)
to provide accurate weather information.
reasoning:
engine: "chain-of-thought"
max_iterations: 10
# LLM Configuration
llms:
gpt-4o:
type: "openai"
model: "gpt-4o-mini"
api_key: "${OPENAI_API_KEY}"
Step 5: Test It¶
hector serve --config config.yaml
# In another terminal
hector call --config config.yaml weather_assistant "What's the weather in San Francisco?"
Agent response:
Let me check the weather for you.
[Tool: get_weather("San Francisco")]
The weather in San Francisco is currently Sunny with a temperature of 72°F.
That's it! You've created and integrated a custom tool.
More Complex Example: Database Tool¶
Create a tool that queries a database:
#!/usr/bin/env python3
from mcp.server import Server
import sqlite3
server = Server("database-server")
@server.tool()
def query_users(name: str = None, limit: int = 10) -> str:
"""
Query users from database.
Args:
name: Optional name filter
limit: Maximum results (default: 10)
Returns:
JSON string of user records
"""
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
if name:
cursor.execute(
"SELECT * FROM users WHERE name LIKE ? LIMIT ?",
(f"%{name}%", limit)
)
else:
cursor.execute("SELECT * FROM users LIMIT ?", (limit,))
results = cursor.fetchall()
conn.close()
return json.dumps([
{"id": row[0], "name": row[1], "email": row[2]}
for row in results
])
@server.tool()
def create_user(name: str, email: str) -> str:
"""
Create a new user.
Args:
name: User's name
email: User's email
Returns:
Success message with user ID
"""
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
cursor.execute(
"INSERT INTO users (name, email) VALUES (?, ?)",
(name, email)
)
user_id = cursor.lastrowid
conn.commit()
conn.close()
return f"User created successfully with ID: {user_id}"
if __name__ == "__main__":
server.run(port=3001)
Using Composio (150+ Pre-built Tools)¶
Composio provides MCP servers for popular services:
Step 1: Start Composio Server¶
# Install
npm install -g @composio/cli
# Start server
composio server start --port 3000
# Available tools:
# - github_*: GitHub operations
# - slack_*: Slack messaging
# - notion_*: Notion database
# - gmail_*: Gmail operations
# ... and 150+ more
Step 2: Configure in Hector¶
tools:
composio:
type: "mcp"
server_url: "http://localhost:3000"
description: "Composio - 150+ app integrations"
# Authentication handled by Composio server
agents:
automation:
llm: "gpt-4o"
prompt:
system_role: |
You are an automation assistant with access to:
- GitHub (create issues, PRs)
- Slack (send messages, notifications)
- Notion (manage pages, databases)
Use the Composio tools to automate workflows.
reasoning:
engine: "chain-of-thought"
max_iterations: 20
Step 3: Use the Tools¶
hector call --config config.yaml automation \
"Create a GitHub issue for the authentication bug and notify the team on Slack"
Agent automatically: 1. Creates GitHub issue 2. Sends Slack notification 3. Returns confirmation
Best Practices¶
1. Clear Tool Descriptions¶
@server.tool()
def analyze_sentiment(text: str) -> str:
"""
Analyze the sentiment of text.
Args:
text: The text to analyze (max 1000 characters)
Returns:
JSON with sentiment (positive/negative/neutral) and confidence score
Example:
analyze_sentiment("I love this product!")
-> {"sentiment": "positive", "confidence": 0.95}
"""
# Implementation
Good descriptions help the LLM use tools correctly.
2. Input Validation¶
@server.tool()
def send_email(to: str, subject: str, body: str) -> str:
"""Send an email."""
# Validate inputs
if not to or "@" not in to:
return "Error: Invalid email address"
if len(body) > 10000:
return "Error: Email body too long (max 10000 characters)"
# Send email
...
3. Error Handling¶
@server.tool()
def fetch_data(url: str) -> str:
"""Fetch data from URL."""
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
return response.text
except requests.exceptions.Timeout:
return "Error: Request timed out"
except requests.exceptions.HTTPError as e:
return f"Error: HTTP {e.response.status_code}"
except Exception as e:
return f"Error: {str(e)}"
4. Type Hints¶
from typing import List, Dict, Optional
@server.tool()
def search_products(
query: str,
category: Optional[str] = None,
max_results: int = 10
) -> str:
"""Search products with optional filters."""
# Type hints help MCP generate correct schemas
5. Structured Responses¶
import json
@server.tool()
def get_user_info(user_id: int) -> str:
"""Get user information."""
user = database.get_user(user_id)
# Return structured data as JSON
return json.dumps({
"id": user.id,
"name": user.name,
"email": user.email,
"created_at": user.created_at.isoformat()
})
Advanced: Authentication¶
For tools that need authentication:
from mcp.server import Server
import os
server = Server("secure-server")
@server.tool()
def secure_operation(api_key: str, resource_id: str) -> str:
"""
Perform a secure operation.
Args:
api_key: User's API key
resource_id: Resource to access
"""
# Validate API key
if api_key != os.getenv("VALID_API_KEY"):
return "Error: Invalid API key"
# Perform operation
...
In Hector config:
tools:
custom_mcp:
type: "mcp"
server_url: "http://localhost:3000"
description: "Custom MCP tools"
# Add authentication in MCP server if needed
MCP vs gRPC Plugins¶
Feature | MCP | gRPC Plugins |
---|---|---|
Setup time | 5-10 minutes | Hours/Days |
Language | Python, JS, Go, etc. | Any with gRPC |
Use case | Quick tools, integrations | Complex logic, high performance |
Ecosystem | 150+ via Composio | Custom only |
Performance | Good | Excellent |
Use MCP for: - Quick prototypes - API integrations - Simple tools - External services
Use gRPC plugins for: - Custom LLMs - High-performance tools - Complex business logic - Enterprise integrations
Debugging MCP Tools¶
Test MCP Server Directly¶
# List available tools
curl http://localhost:3000/tools
# Call tool directly
curl -X POST http://localhost:3000/call \
-H "Content-Type: application/json" \
-d '{
"tool": "get_weather",
"arguments": {"city": "San Francisco"}
}'
Enable Debug in Hector¶
agents:
debug_agent:
reasoning:
show_tool_execution: true
show_debug_info: true
Check MCP Server Logs¶
# MCP servers log to stdout
python weather_server.py 2>&1 | tee mcp.log
Production Deployment¶
Docker for MCP Server¶
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY weather_server.py .
EXPOSE 3000
CMD ["python", "weather_server.py"]
docker build -t weather-mcp .
docker run -d -p 3000:3000 weather-mcp
Health Checks¶
@server.health_check()
def health() -> Dict[str, str]:
"""Health check endpoint."""
return {
"status": "healthy",
"tools": len(server.tools),
"uptime": server.uptime()
}
Monitoring¶
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@server.tool()
def my_tool(arg: str) -> str:
logger.info(f"Tool called with arg: {arg}")
try:
result = perform_operation(arg)
logger.info(f"Tool succeeded")
return result
except Exception as e:
logger.error(f"Tool failed: {e}")
raise
Next Steps¶
- Tools - Understand the tool system
- Build a Coding Assistant - Use tools in practice
- Architecture - gRPC plugin development
- MCP Documentation - Official MCP docs
Related Topics¶
- Agent Overview - Understanding agents
- Configuration Reference - Tool configuration
- Multi-Agent Orchestration - Tools in multi-agent systems