MCP Unified Server
by getfounded
#!/usr/bin/env python3
import sys
import os
from pathlib import Path
import json
from datetime import datetime
from contextlib import asynccontextmanager
from dotenv import load_dotenv
import logging
import uvicorn
# MCP SDK imports
from mcp.server.fastmcp import FastMCP, Context
logging.basicConfig(
level=logging.DEBUG if os.environ.get(
"MCP_LOG_LEVEL", "").lower() == "debug" else logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
stream=sys.stderr
)
# Add app/tools directory to path to import modules
tools_path = Path(__file__).parent / "app" / "tools"
sys.path.append(str(tools_path))
# Load environment variables
load_dotenv()
# Initialize MCP server
mcp = FastMCP(
"Unified MCP Server",
dependencies=["newsapi-python", "msal", "python-dotenv",
"httpx", "pillow", "requests", "pandas", "python-pptx", "nltk"]
)
# Add a health check endpoint
@mcp.tool(name="health_check")
async def health_check(ctx: Context):
try:
# Check if all critical components are working
status = {
"status": "ok",
"timestamp": datetime.now().isoformat(),
"python_version": sys.version,
"registered_tools_count": len(mcp.registered_tools),
"uptime_seconds": (datetime.now() - datetime.fromisoformat(mcp.startup_time)).total_seconds()
if hasattr(mcp, 'startup_time') else 0
}
# Add more detailed component status checks here
return status
except Exception as e:
logging.error(f"Health check failed: {str(e)}")
return {
"status": "error",
"error": str(e),
"timestamp": datetime.now().isoformat()
}
# Initialize PowerPoint tools
try:
from app.tools.ppt import get_ppt_tools, PowerPointTools, set_external_mcp
# Pass our MCP instance to the ppt module
set_external_mcp(mcp)
ppt_available = True
# Register PowerPoint tools
ppt_tools = get_ppt_tools()
for tool_name, tool_func in ppt_tools.items():
# Register each PowerPoint tool with the main MCP instance
tool_name_str = tool_name if isinstance(
tool_name, str) else tool_name.value
mcp.tool(name=tool_name_str)(tool_func)
# Add PowerPoint dependencies to MCP dependencies
mcp.dependencies.extend([
"python-pptx",
"nltk",
"pillow"
])
logging.info("PowerPoint tools registered successfully.")
except ImportError as e:
ppt_available = False
logging.warning(f"Could not load PowerPoint tools: {e}")
# Initialize Playwright tools
try:
from app.tools.browser_automation import get_playwright_tools, set_external_mcp, initialize
# Pass our MCP instance to the playwright module
set_external_mcp(mcp)
# Initialize playwright tools
initialize()
# Register playwright tools
playwright_tools = get_playwright_tools()
for tool_name, tool_func in playwright_tools.items():
# Register each playwright tool with the main MCP instance
tool_name_str = tool_name if isinstance(
tool_name, str) else tool_name.value
mcp.tool(name=tool_name_str)(tool_func)
# Add Playwright dependencies to MCP dependencies
mcp.dependencies.extend([
"playwright"
])
logging.info("Playwright tools registered successfully.")
except ImportError as e:
logging.warning(f"Could not load Playwright tools: {e}")
# Initialize Filesystem tools
try:
from app.tools.filesystem import get_filesystem_tools, set_external_mcp, initialize_fs_tools
# Pass our MCP instance to the filesystem module
set_external_mcp(mcp)
# Get allowed directories from environment variable
env_dirs = os.environ.get("MCP_FILESYSTEM_DIRS", "")
allowed_dirs = [os.path.expanduser(d.strip())
for d in env_dirs.split(",") if d.strip()]
# Default to user's home directory if no dirs specified
if not allowed_dirs:
allowed_dirs = [os.path.expanduser("~")]
initialize_fs_tools(allowed_dirs)
# Register filesystem tools
fs_tools = get_filesystem_tools()
for tool_name, tool_func in fs_tools.items():
# Register each filesystem tool with the main MCP instance
mcp.tool(name=tool_name)(tool_func)
logging.info("Filesystem tools registered successfully.")
except ImportError as e:
logging.warning(f"Could not load filesystem tools: {e}")
# Initialize Time tools
try:
from app.tools.time_tools import get_time_tools, set_external_mcp, initialize_time_tools
# Pass our MCP instance to the time tools module
set_external_mcp(mcp)
# Initialize time tools
initialize_time_tools()
# Register time tools
time_tools = get_time_tools()
for tool_name, tool_func in time_tools.items():
# Register each time tool with the main MCP instance
tool_name_str = tool_name if isinstance(
tool_name, str) else tool_name.value
mcp.tool(name=tool_name_str)(tool_func)
logging.info("Time tools registered successfully.")
except ImportError as e:
logging.warning(f"Could not load time tools: {e}")
# Initialize Sequential Thinking tools
try:
from app.tools.sequential_thinking import get_sequential_thinking_tools, set_external_mcp, initialize_thinking_service
# Pass our MCP instance to the sequential thinking module
set_external_mcp(mcp)
# Initialize sequential thinking tools
initialize_thinking_service()
# Register sequential thinking tools
thinking_tools = get_sequential_thinking_tools()
for tool_name, tool_func in thinking_tools.items():
# Register each sequential thinking tool with the main MCP instance
mcp.tool(name=tool_name)(tool_func)
logging.info("Sequential Thinking tools registered successfully.")
except ImportError as e:
logging.warning(f"Could not load Sequential Thinking tools: {e}")
# Initialize FRED API tools
try:
from app.tools.fred import get_fred_api_tools, set_external_mcp, initialize_fred_api_service, initialize
# Pass our MCP instance to the FRED module
set_external_mcp(mcp)
# Initialize FRED tools with API key from environment variable
fred_api_key = os.environ.get("FRED_API_KEY")
if fred_api_key:
# Call the module's initialize function
initialize(mcp)
# Register FRED tools
fred_tools = get_fred_api_tools()
for tool_name, tool_func in fred_tools.items():
# Register each FRED tool with the main MCP instance
mcp.tool(name=tool_name)(tool_func)
# Add FRED dependencies to MCP dependencies
mcp.dependencies.extend(["fredapi", "pandas"])
logging.info("FRED API tools registered successfully.")
else:
logging.warning(
"FRED API key not configured. FRED API tools will not be available.")
except ImportError as e:
logging.warning(f"Could not load FRED API tools: {e}")
# Initialize YFinance tools
try:
from app.tools.yfinance import get_yfinance_tools, set_external_mcp, initialize
# Pass our MCP instance to the yfinance module
set_external_mcp(mcp)
# Initialize YFinance tools
if initialize(mcp):
# Register YFinance tools
yfinance_tools = get_yfinance_tools()
for tool_name, tool_func in yfinance_tools.items():
# Register each YFinance tool with the main MCP instance
tool_name_str = tool_name if isinstance(
tool_name, str) else tool_name.value
mcp.tool(name=tool_name_str)(tool_func)
# Add YFinance dependencies to MCP dependencies
mcp.dependencies.extend(["yfinance", "pandas", "numpy"])
logging.info("YFinance tools registered successfully.")
else:
logging.warning("Failed to initialize YFinance tools.")
except ImportError as e:
logging.warning(f"Could not load YFinance tools: {e}")
# Initialize Excel tools
try:
from app.tools.excel import get_xlsx_tools, set_external_mcp, initialize_xlsx_service
# Pass our MCP instance to the xlsx module
set_external_mcp(mcp)
# Initialize xlsx service
initialize_xlsx_service()
# Register xlsx tools
xlsx_tools = get_xlsx_tools()
for tool_name, tool_func in xlsx_tools.items():
# Register each xlsx tool with the main MCP instance
tool_name_str = tool_name if isinstance(
tool_name, str) else tool_name.value
mcp.tool(name=tool_name_str)(tool_func)
# Add Excel dependencies to MCP dependencies
mcp.dependencies.extend([
"xlsxwriter",
"pandas",
"openpyxl",
"xlrd"
])
logging.info("Excel tools registered successfully.")
except ImportError as e:
logging.warning(f"Could not load Excel tools: {e}")
# Initialize Brave Search tools
try:
from app.tools.brave_search import get_brave_search_tools, set_external_mcp, initialize_brave_search
# Pass our MCP instance to the brave search module
set_external_mcp(mcp)
# Initialize brave search tools with API key from environment variable
brave_api_key = os.environ.get("BRAVE_API_KEY")
if brave_api_key:
initialize_brave_search(brave_api_key)
# Register brave search tools
brave_tools = get_brave_search_tools()
for tool_name, tool_func in brave_tools.items():
# Register each brave search tool with the main MCP instance
mcp.tool(name=tool_name)(tool_func)
logging.info("Brave Search tools registered successfully.")
else:
logging.warning(
"Brave Search API key not configured. Brave Search tools will not be available.")
except ImportError as e:
logging.warning(f"Could not load Brave Search tools: {e}")
# Initialize World Bank tools
try:
from app.tools.worldbank import get_worldbank_tools, get_worldbank_resources, set_external_mcp, initialize_worldbank_service
# Pass our MCP instance to the world bank module
set_external_mcp(mcp)
# Initialize world bank tools
initialize_worldbank_service()
# Register world bank tools
worldbank_tools = get_worldbank_tools()
for tool_name, tool_func in worldbank_tools.items():
# Register each world bank tool with the main MCP instance
mcp.tool(name=tool_name)(tool_func)
# Register world bank resources
worldbank_resources = get_worldbank_resources()
for resource_path, resource_func in worldbank_resources.items():
# Register each world bank resource with the main MCP instance
mcp.resource(resource_path)(resource_func)
logging.info("World Bank tools registered successfully.")
except ImportError as e:
logging.warning(f"Could not load World Bank tools: {e}")
# Initialize News API tools
try:
from app.tools.news_api import get_news_api_tools, set_external_mcp, initialize_news_api_service
# Pass our MCP instance to the news api module
set_external_mcp(mcp)
# Initialize news api tools with API key from environment variable
news_api_key = os.environ.get("NEWS_API_KEY")
if news_api_key:
initialize_news_api_service(news_api_key)
# Register news api tools
news_api_tools = get_news_api_tools()
for tool_name, tool_func in news_api_tools.items():
# Register each news api tool with the main MCP instance
mcp.tool(name=tool_name)(tool_func)
logging.info("News API tools registered successfully.")
else:
logging.warning(
"News API key not configured. News API tools will not be available.")
except ImportError as e:
logging.warning(f"Could not load News API tools: {e}")
# Initialize VAPI tools
try:
from app.tools.vapi import get_vapi_tools, set_external_mcp, initialize_vapi_service
# Pass our MCP instance to the VAPI module
set_external_mcp(mcp)
# Initialize VAPI tools
if initialize_vapi_service():
# Register VAPI tools
vapi_tools = get_vapi_tools()
for tool_name, tool_func in vapi_tools.items():
# Register each VAPI tool with the main MCP instance
tool_name_str = tool_name if isinstance(
tool_name, str) else tool_name.value
mcp.tool(name=tool_name_str)(tool_func)
# Add VAPI dependencies to MCP dependencies
mcp.dependencies.extend(["vapi"])
logging.info("VAPI tools registered successfully.")
else:
logging.warning("Failed to initialize VAPI tools.")
except ImportError as e:
logging.warning(f"Could not load VAPI tools: {e}")
# Initialize Document Management tools
try:
from app.tools.document_management import get_pdf_tools, set_external_mcp, initialize_pdf_service
# Pass our MCP instance to the document management module
set_external_mcp(mcp)
# Initialize PDF service
initialize_pdf_service()
# Register PDF tools
pdf_tools = get_pdf_tools()
for tool_name, tool_func in pdf_tools.items():
# Register each PDF tool with the main MCP instance
mcp.tool(name=tool_name)(tool_func)
# Add PDF dependencies to MCP dependencies
mcp.dependencies.extend(
["PyPDF2", "pdf2image", "pytesseract", "Pillow", "reportlab"])
logging.info("Document Management tools registered successfully.")
except ImportError as e:
logging.warning(f"Could not load Document Management tools: {e}")
# Initialize Streamlit tools
try:
from app.tools.streamlit import get_streamlit_tools, set_external_mcp, initialize
# Pass our MCP instance to the streamlit module
set_external_mcp(mcp)
# Initialize Streamlit tools
# Get custom apps directory from environment variable if set
apps_dir = os.environ.get("STREAMLIT_APPS_DIR")
if initialize(mcp):
# Register Streamlit tools
streamlit_tools = get_streamlit_tools()
for tool_name, tool_func in streamlit_tools.items():
# Register each Streamlit tool with the main MCP instance
tool_name_str = tool_name if isinstance(
tool_name, str) else tool_name.value
mcp.tool(name=tool_name_str)(tool_func)
# Add Streamlit dependencies to MCP dependencies
mcp.dependencies.extend(
["streamlit", "pandas", "numpy", "matplotlib", "plotly"])
logging.info("Streamlit tools registered successfully.")
else:
logging.warning(
"Failed to initialize Streamlit tools. Make sure streamlit is installed.")
except ImportError as e:
logging.warning(f"Could not load Streamlit tools: {e}")
# Validate required environment variables
REQUIRED_ENV_VARS = {
"BRAVE_API_KEY": "For Brave Search functionality",
"NEWS_API_KEY": "For NewsAPI functionality",
"FRED_API_KEY": "your_fred_api_key",
"STREAMLIT_APPS_DIR": "/path/to/streamlit/apps",
"MCP_FILESYSTEM_DIRS": "/path/to/allowed/dir1,/path/to/allowed/dir2",
"MCP_LOG_LEVEL": "info",
"VAPID_API_KEY": "your_vapid_api_key",
}
missing_vars = [var for var in REQUIRED_ENV_VARS if not os.environ.get(var)]
if missing_vars:
logging.warning("The following environment variables are missing:")
for var in missing_vars:
logging.warning(f" - {var}: {REQUIRED_ENV_VARS[var]}")
logging.warning("Some functionality may be limited.")
# Initialize JSON-RPC method for tool discovery
@mcp.tool(name="initialize")
async def initialize(ctx: Context):
"""Return initialization information including available tools."""
tool_list = []
# Extract registered tools from the MCP instance
for tool_name, tool_func in mcp.registered_tools.items():
tool_info = {
"name": tool_name,
"description": getattr(tool_func, "__doc__", "No description available"),
"parameters": getattr(tool_func, "__annotations__", {})
}
tool_list.append(tool_info)
return {
"status": "ok",
"server_name": mcp.name,
"version": getattr(mcp, "version", "1.0.1"),
"tools": tool_list
}
# Start the server
host = os.environ.get("SERVER_HOST", "0.0.0.0")
port = int(os.environ.get("SERVER_PORT", "8000"))
# Server Lifespan and Startup
@asynccontextmanager
async def server_lifespan(server: FastMCP):
"""Server lifespan manager - initialize and cleanup resources"""
try:
# Log startup message
logging.info("Starting Unified MCP Server...")
# Initialize any services that need async initialization
# (none in our current implementation)
# Pass any shared context to the request handlers
yield {
"startup_time": datetime.now().isoformat()
}
finally:
# Cleanup on shutdown
logging.info("Shutting down Unified MCP Server...")
# Close any open resources or connections
# Set lifespan context manager
mcp.lifespan = server_lifespan
if __name__ == "__main__":
# Add debugging info
logging.info("Starting MCP Unified Server...")
logging.debug(f"Python version: {sys.version}")
# Use configuration from environment variables if available
# Must be 0.0.0.0 for containers
host = os.environ.get("MCP_HOST", "0.0.0.0")
# Check both PORT and MCP_PORT
port = int(os.environ.get("PORT", os.environ.get("MCP_PORT", "8000")))
# Default to info instead of debug
log_level = os.environ.get("MCP_LOG_LEVEL", "info")
# Enable detailed logging for troubleshooting
if log_level.lower() == "debug":
logging.info("Debug logging enabled")
logging.debug(
f"Environment variables: {json.dumps({k: v for k, v in os.environ.items() if not k.startswith('_')}, indent=2)}")
# Update configuration
mcp.config = {
"host": host,
"port": port,
"log_level": log_level
}
# Run the server using the MCP's own method instead of direct uvicorn
logging.info(f"Starting server at http://{host}:{port}")
mcp.run()