Adding custom tools to Mirobody Health is incredibly simple. Write Python functions, and they automatically become available to AI agents through the MCP protocol.
No JSON schemas, no manual bindings, no complex router configurations required!
class WeatherService: """Weather information service""" pass
The class name MUST end with Service for auto-discovery.
3
Add methods
Every public method becomes a tool automatically:
tools/weather_tool.py
Copy
import aiohttpfrom typing import Dictclass WeatherService: """Weather information service""" async def get_weather(self, location: str) -> Dict: """ Get current weather for a location Args: location: City name or coordinates Returns: Weather information including temperature and conditions """ # Your implementation here async with aiohttp.ClientSession() as session: # Call weather API pass
class YourToolService: """ Service description (used in tool discovery) """ def __init__(self): """ Optional: Initialize with configuration """ # Load any required configuration pass async def public_method(self, param: str) -> Dict: """ This becomes a tool (public method) Args: param: Parameter description Returns: Result description """ # This method is exposed as a tool pass def _private_method(self): """ This is NOT exposed (starts with underscore) """ # Helper methods starting with _ are private pass
Only public methods (not starting with _) are exposed as tools. Use private methods (_method_name) for internal helpers.
import aiohttpfrom typing import List, Dictclass MedicalLiteratureService: """Medical literature search tools""" async def search_pubmed( self, query: str, max_results: int = 10 ) -> List[Dict]: """ Search PubMed for medical literature Args: query: Search query max_results: Maximum number of results (default: 10) Returns: List of PubMed articles with titles, abstracts, and links """ # Call PubMed API (no authentication needed) async with aiohttp.ClientSession() as session: # Search implementation pass async def get_drug_interactions( self, drug_names: List[str] ) -> Dict: """ Check for drug interactions using public databases Args: drug_names: List of drug names to check Returns: Interaction information and severity levels """ # Query public drug interaction database pass
Public tools can access external APIs, perform calculations, or query public databases without user authentication.
Write clear docstrings for your methods. They are passed to AI agents as context:
Copy
async def analyze_workout(self, user_info: UserInfo, workout_id: str) -> Dict: """ Analyze workout performance and provide insights This tool examines workout data including heart rate zones, duration, intensity, and calories burned to provide detailed performance analysis and recommendations. Args: user_info: User authentication (auto-provided) workout_id: Unique identifier for the workout Returns: Detailed workout analysis with metrics and suggestions """ pass
The docstring helps agents understand when and how to use your tool.
Type Hints
Use Python type hints for parameters and return values:
Type hints improve code quality and help generate better tool schemas.
Error Handling
Handle errors gracefully and return meaningful messages:
Copy
async def get_data(self, user_info: UserInfo) -> Dict: """Get user data""" try: result = await self._fetch_data(user_info.user_id) if not result: return { "status": "no_data", "message": "No data found for this user" } return result except Exception as e: logging.error(f"Error fetching data: {e}") return { "status": "error", "message": str(e) }
Async Methods
Use async def for all tool methods to avoid blocking:
Here’s a complete example tool for analyzing health trends:
tools/trend_analysis.py
Copy
"""Health Trend Analysis ToolsProvides tools for analyzing health data trends and generating insights."""import loggingfrom datetime import datetime, timedeltafrom typing import Dict, List, Optionalfrom mirobody.utils import UserInfo, execute_queryclass TrendAnalysisService: """Health data trend analysis tools""" def __init__(self): """Initialize the trend analysis service""" self.logger = logging.getLogger(__name__) async def analyze_weekly_trends( self, user_info: UserInfo, indicators: List[str] ) -> Dict[str, Any]: """ Analyze weekly trends for specified health indicators Examines the past 7 days of data to identify trends, averages, and anomalies for the requested health indicators. Args: user_info: User authentication information (auto-provided) indicators: List of indicator names to analyze (e.g., ["HEART_RATE", "STEPS"]) Returns: Trend analysis with statistics and insights for each indicator """ try: # Calculate date range end_date = datetime.now() start_date = end_date - timedelta(days=7) results = {} for indicator in indicators: # Query data for this indicator query = """ SELECT indicator, value_standardized as value, start_time FROM theta_ai.th_series_data WHERE user_id = :user_id AND indicator = :indicator AND start_time >= :start_time AND start_time <= :end_time AND deleted = 0 ORDER BY start_time ASC """ data = await execute_query( query=query, params={ "user_id": user_info.user_id, "indicator": indicator, "start_time": start_date, "end_time": end_date } ) # Analyze the data if data: results[indicator] = self._analyze_indicator(data) else: results[indicator] = { "status": "no_data", "message": f"No data available for {indicator}" } return { "period": { "start": start_date.isoformat(), "end": end_date.isoformat(), "days": 7 }, "trends": results } except Exception as e: self.logger.error(f"Error analyzing trends: {e}") return { "status": "error", "message": str(e) } def _analyze_indicator(self, data: List[Dict]) -> Dict: """Private helper to analyze indicator data""" values = [float(record["value"]) for record in data] return { "count": len(values), "average": sum(values) / len(values), "min": min(values), "max": max(values), "trend": "increasing" if values[-1] > values[0] else "decreasing" }
# In Python consoleimport asynciofrom tools.trend_analysis import TrendAnalysisServicefrom mirobody.utils import UserInfoasync def test(): service = TrendAnalysisService() # Create mock user info for testing user_info = UserInfo(user_id="test_user_123") # Test the tool result = await service.analyze_weekly_trends( user_info, indicators=["HEART_RATE", "DAILY_STEPS"] ) print(result)# Run testasyncio.run(test())
✅ Classes ending with Service
✅ Public methods (not starting with _)
✅ Async methods (async def)
✅ Methods with proper type hints
✅ Methods with docstrings
❌ Classes not ending with Service
❌ Private methods (starting with _)
❌ Non-async methods without special handling
❌ Built-in/magic methods (__init__, __str__, etc.)
from mirobody.utils import UserInfo, execute_queryfrom typing import Dict, List, Optionalfrom datetime import datetime, timedeltaclass ActivityAnalysisService: """Activity and exercise analysis tools""" async def get_daily_summary( self, user_info: UserInfo, date: Optional[str] = None ) -> Dict: """ Get daily activity summary Args: user_info: User authentication (auto-provided) date: Date in YYYY-MM-DD format (default: today) Returns: Daily activity summary with steps, calories, and active time """ if not date: date = datetime.now().strftime("%Y-%m-%d") query = """ SELECT indicator, value_standardized as value FROM theta_ai.th_series_data WHERE user_id = :user_id AND DATE(start_time) = :date AND indicator IN ('DAILY_STEPS', 'DAILY_CALORIES_ACTIVE', 'ACTIVE_TIME') AND deleted = 0 """ results = await execute_query( query=query, params={"user_id": user_info.user_id, "date": date} ) # Format response summary = {indicator: None for indicator in ['DAILY_STEPS', 'DAILY_CALORIES_ACTIVE', 'ACTIVE_TIME']} for record in results: summary[record['indicator']] = float(record['value']) return { "date": date, "steps": summary['DAILY_STEPS'], "active_calories": summary['DAILY_CALORIES_ACTIVE'], "active_minutes": summary['ACTIVE_TIME'] } async def compare_periods( self, user_info: UserInfo, indicator: str, period_days: int = 7 ) -> Dict: """ Compare current period with previous period Args: user_info: User authentication (auto-provided) indicator: Health indicator to compare period_days: Number of days in each period Returns: Comparison with percentage change and trend """ # Implementation pass async def get_achievement_status( self, user_info: UserInfo ) -> Dict: """ Check user's progress toward health goals Args: user_info: User authentication (auto-provided) Returns: Goal progress and achievement status """ # Implementation pass