MCP Personal Assistant Agent

#!/usr/bin/env python3 import sys import os import logging import dotenv from contextlib import asynccontextmanager from typing import AsyncIterator, Dict, Any from dataclasses import dataclass # Check Python version if sys.version_info < (3, 10): print("Error: Python 3.10 or higher is required for the MCP server.") print(f"Current Python version: {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}") print("\nPlease upgrade your Python version or use a virtual environment with Python 3.10+.") print("Example using conda:") print(" conda create -n mcp-env python=3.10") print(" conda activate mcp-env") print("\nExample using venv (if Python 3.10+ is installed):") print(" python3.10 -m venv venv") print(" source venv/bin/activate") sys.exit(1) from typing import Any, Dict, List from mcp.server.fastmcp import FastMCP, Context # Load environment variables dotenv.load_dotenv() # Configure logging logging.basicConfig( level=getattr(logging, os.getenv('LOG_LEVEL', 'INFO').upper()), format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger("mcp-pa-agent") @dataclass class AppContext: """Type-safe context for the MCP server""" google_apis_initialized: bool = False home_assistant_connected: bool = False db_connection: Any = None task_store_path: str = "tasks_data.json" @asynccontextmanager async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]: """Manage resource lifecycle for the server""" context = AppContext() # Initialize resources on startup try: logger.info("Initializing server resources...") # Initialize database if using Redis if os.getenv("REDIS_HOST"): try: from redis import Redis logger.info(f"Connecting to Redis at {os.getenv('REDIS_HOST')}:{os.getenv('REDIS_PORT', '6379')}") context.db_connection = Redis( host=os.getenv("REDIS_HOST"), port=int(os.getenv("REDIS_PORT", 6379)), password=os.getenv("REDIS_PASSWORD", None) ) context.db_connection.ping() # Test connection logger.info("Redis connection successful") except Exception as e: logger.error(f"Failed to connect to Redis: {str(e)}") # Check Google API credentials if all([ os.getenv("GOOGLE_CLIENT_ID"), os.getenv("GOOGLE_CLIENT_SECRET"), os.getenv("GOOGLE_REFRESH_TOKEN") ]): context.google_apis_initialized = True logger.info("Google API credentials found") else: logger.warning("Google API credentials missing - calendar and email features will be unavailable") # Check Home Assistant configuration if os.getenv("HOME_ASSISTANT_URL") and os.getenv("HOME_ASSISTANT_TOKEN"): context.home_assistant_connected = True logger.info(f"Home Assistant configuration found: {os.getenv('HOME_ASSISTANT_URL')}") else: logger.warning("Home Assistant configuration missing - smart home features will be unavailable") # Log successful initialization server.info("Resources initialized successfully") yield context except Exception as e: logger.error(f"Error during initialization: {str(e)}") raise finally: # Clean up on shutdown logger.info("Cleaning up server resources...") if context.db_connection: try: context.db_connection.close() logger.info("Redis connection closed") except Exception as e: logger.error(f"Error closing Redis connection: {str(e)}") server.info("Resources cleaned up successfully") # Initialize FastMCP server mcp = FastMCP("Personal Assistant Agent", description="A versatile personal assistant that helps with calendar, tasks, emails, and more", lifespan=app_lifespan) # Import module functions from modules.calendar_functions import * from modules.tasks_functions import * from modules.email_functions import * from modules.knowledge_functions import * from modules.smarthome_functions import * # Register the imported functions with MCP # Note: The @mcp.tool() decorators in each module file will automatically # register these functions with the MCP server def main(): try: logger.info('Starting MCP Personal Assistant server...') # Log current configuration logger.info(f"Server configured with modules: {', '.join(['calendar', 'tasks', 'email', 'knowledge', 'smarthome'])}") # Log environment check if os.getenv("GOOGLE_CLIENT_ID"): logger.info("Google API credentials found") else: logger.warning("Google API credentials missing - calendar and email features will be unavailable") if os.getenv("HOME_ASSISTANT_URL"): logger.info("Home Assistant configuration found") else: logger.warning("Home Assistant configuration missing - smart home features will be unavailable") if os.getenv("DUCKDUCKGO_API_KEY"): logger.info("DuckDuckGo API key found") else: logger.warning("DuckDuckGo API key missing - web search will be unavailable") if os.getenv("WEATHER_API_KEY"): logger.info("Weather API key found") else: logger.warning("Weather API key missing - weather information will use mock data") if os.getenv("NEWS_API_KEY"): logger.info("News API key found") else: logger.warning("News API key missing - news retrieval will use mock data") # Run the server with stdio transport mcp.run(transport='stdio') logger.info('MCP Personal Assistant server started successfully') except Exception as e: logger.error(f'Failed to start MCP Personal Assistant server: {str(e)}') raise if __name__ == "__main__": main()