fix(server): resolve lint error and clean up production logs
- Fix F821 undefined name `core` by using local import in fallback path - Remove all internal terminology from logs (Phase X, Option B) - Clean Phase/Option B references from tool docstrings (visible to MCP clients) - Suppress websockets DeprecationWarning (noisy, harmless) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
70
server.py
70
server.py
@@ -23,9 +23,13 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import warnings
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
# Suppress noisy deprecation warning from websockets (transitive dependency)
|
||||||
|
warnings.filterwarnings("ignore", category=DeprecationWarning, module="websockets")
|
||||||
|
|
||||||
from fastmcp import FastMCP
|
from fastmcp import FastMCP
|
||||||
from fastmcp.exceptions import ToolError
|
from fastmcp.exceptions import ToolError
|
||||||
from fastmcp.server.dependencies import get_http_headers
|
from fastmcp.server.dependencies import get_http_headers
|
||||||
@@ -45,7 +49,7 @@ from core import (
|
|||||||
get_auth_manager,
|
get_auth_manager,
|
||||||
get_project_manager,
|
get_project_manager,
|
||||||
get_rate_limiter,
|
get_rate_limiter,
|
||||||
# Option B modules (new clean architecture)
|
# Core architecture modules
|
||||||
get_site_manager,
|
get_site_manager,
|
||||||
get_site_registry,
|
get_site_registry,
|
||||||
get_tool_registry,
|
get_tool_registry,
|
||||||
@@ -235,7 +239,9 @@ mcp = FastMCP("MCP Hub")
|
|||||||
_TEMPLATES_DIR = os.path.join(os.path.dirname(__file__), "core", "templates")
|
_TEMPLATES_DIR = os.path.join(os.path.dirname(__file__), "core", "templates")
|
||||||
if not os.path.isdir(_TEMPLATES_DIR):
|
if not os.path.isdir(_TEMPLATES_DIR):
|
||||||
# Fallback for pip-installed package: resolve relative to core package
|
# Fallback for pip-installed package: resolve relative to core package
|
||||||
_TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.abspath(core.__file__)), "templates")
|
import core as _core_pkg
|
||||||
|
|
||||||
|
_TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.abspath(_core_pkg.__file__)), "templates")
|
||||||
templates = Jinja2Templates(directory=_TEMPLATES_DIR)
|
templates = Jinja2Templates(directory=_TEMPLATES_DIR)
|
||||||
logger.info("Jinja2 template engine initialized")
|
logger.info("Jinja2 template engine initialized")
|
||||||
|
|
||||||
@@ -244,7 +250,7 @@ auth_manager = get_auth_manager()
|
|||||||
api_key_manager = get_api_key_manager()
|
api_key_manager = get_api_key_manager()
|
||||||
project_manager = get_project_manager()
|
project_manager = get_project_manager()
|
||||||
audit_logger = get_audit_logger()
|
audit_logger = get_audit_logger()
|
||||||
csrf_manager = get_csrf_manager() # Phase E: CSRF protection
|
csrf_manager = get_csrf_manager()
|
||||||
|
|
||||||
# Initialize site registry (legacy - kept for backward compatibility)
|
# Initialize site registry (legacy - kept for backward compatibility)
|
||||||
site_registry = get_site_registry()
|
site_registry = get_site_registry()
|
||||||
@@ -254,8 +260,7 @@ site_registry.discover_sites(plugin_types)
|
|||||||
# Initialize unified tool generator (legacy - kept for backward compatibility)
|
# Initialize unified tool generator (legacy - kept for backward compatibility)
|
||||||
unified_tool_generator = UnifiedToolGenerator(project_manager)
|
unified_tool_generator = UnifiedToolGenerator(project_manager)
|
||||||
|
|
||||||
# === Option B Architecture (New Clean Architecture) ===
|
# Initialize site manager
|
||||||
# Initialize site manager (replacement for SiteRegistry)
|
|
||||||
site_manager = get_site_manager()
|
site_manager = get_site_manager()
|
||||||
site_manager.discover_sites(plugin_types)
|
site_manager.discover_sites(plugin_types)
|
||||||
|
|
||||||
@@ -280,7 +285,7 @@ logger.info(f"Site breakdown: {site_manager.get_count_by_type()}")
|
|||||||
for full_id, plugin in project_manager.projects.items():
|
for full_id, plugin in project_manager.projects.items():
|
||||||
logger.info(f" - {full_id} ({plugin.get_plugin_name()})")
|
logger.info(f" - {full_id} ({plugin.get_plugin_name()})")
|
||||||
|
|
||||||
# Log discovered sites (Option B)
|
# Log discovered sites
|
||||||
logger.info("\nDiscovered sites:")
|
logger.info("\nDiscovered sites:")
|
||||||
for site_info in site_manager.list_all_sites():
|
for site_info in site_manager.list_all_sites():
|
||||||
alias_display = (
|
alias_display = (
|
||||||
@@ -462,7 +467,6 @@ def extract_plugin_type_from_tool(tool_name: str) -> str | None:
|
|||||||
"""
|
"""
|
||||||
Extract plugin type from tool name for tool visibility filtering.
|
Extract plugin type from tool name for tool visibility filtering.
|
||||||
|
|
||||||
Phase 5.5: Tool Visibility Filter
|
|
||||||
Each API key should only see tools related to its plugin type.
|
Each API key should only see tools related to its plugin type.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@@ -511,8 +515,6 @@ def check_tool_visibility(tool_name: str, api_key_project_id: str) -> bool:
|
|||||||
"""
|
"""
|
||||||
Check if API key has visibility to the requested tool.
|
Check if API key has visibility to the requested tool.
|
||||||
|
|
||||||
Phase 5.5: Tool Visibility Filter
|
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
- Global API keys (project_id="*") see ALL tools
|
- Global API keys (project_id="*") see ALL tools
|
||||||
- Plugin-specific keys (project_id="wordpress_xxx") see only that plugin's tools
|
- Plugin-specific keys (project_id="wordpress_xxx") see only that plugin's tools
|
||||||
@@ -787,7 +789,6 @@ class UserAuthMiddleware(Middleware):
|
|||||||
key = api_key_manager.keys.get(key_id) if key_id else None
|
key = api_key_manager.keys.get(key_id) if key_id else None
|
||||||
|
|
||||||
if key:
|
if key:
|
||||||
# Phase 5.5: Tool Visibility Filter
|
|
||||||
# Check if API key has visibility to this tool
|
# Check if API key has visibility to this tool
|
||||||
if not check_tool_visibility(tool_name, key.project_id):
|
if not check_tool_visibility(tool_name, key.project_id):
|
||||||
plugin_type = extract_plugin_type_from_tool(tool_name)
|
plugin_type = extract_plugin_type_from_tool(tool_name)
|
||||||
@@ -945,16 +946,16 @@ mcp.add_middleware(AuditLoggingMiddleware())
|
|||||||
logger.info("Audit logging middleware enabled")
|
logger.info("Audit logging middleware enabled")
|
||||||
|
|
||||||
|
|
||||||
# === RATE LIMITING (Phase 7.3) ===
|
# === RATE LIMITING ===
|
||||||
|
|
||||||
# Initialize rate limiter
|
# Initialize rate limiter
|
||||||
rate_limiter = get_rate_limiter()
|
rate_limiter = get_rate_limiter()
|
||||||
logger.info("Rate limiter initialized (Phase 7.3)")
|
logger.info("Rate limiter initialized")
|
||||||
|
|
||||||
|
|
||||||
class RateLimitMiddleware(Middleware):
|
class RateLimitMiddleware(Middleware):
|
||||||
"""
|
"""
|
||||||
Middleware to enforce rate limiting for all tool calls (Phase 7.3).
|
Middleware to enforce rate limiting for all tool calls.
|
||||||
|
|
||||||
Uses Token Bucket algorithm to prevent API abuse with multi-level limits:
|
Uses Token Bucket algorithm to prevent API abuse with multi-level limits:
|
||||||
- Per minute: 60 requests (default)
|
- Per minute: 60 requests (default)
|
||||||
@@ -1040,10 +1041,10 @@ class RateLimitMiddleware(Middleware):
|
|||||||
|
|
||||||
# Add rate limiting middleware to MCP server
|
# Add rate limiting middleware to MCP server
|
||||||
mcp.add_middleware(RateLimitMiddleware())
|
mcp.add_middleware(RateLimitMiddleware())
|
||||||
logger.info("Rate limiting middleware enabled (Phase 7.3)")
|
logger.info("Rate limiting middleware enabled")
|
||||||
|
|
||||||
|
|
||||||
# === HEALTH MONITORING (Phase 7.2) ===
|
# === HEALTH MONITORING ===
|
||||||
|
|
||||||
from core import initialize_health_monitor
|
from core import initialize_health_monitor
|
||||||
|
|
||||||
@@ -1054,12 +1055,12 @@ health_monitor = initialize_health_monitor(
|
|||||||
metrics_retention_hours=24,
|
metrics_retention_hours=24,
|
||||||
max_metrics_per_project=1000,
|
max_metrics_per_project=1000,
|
||||||
)
|
)
|
||||||
logger.info("Health monitor initialized (Phase 7.2)")
|
logger.info("Health monitor initialized")
|
||||||
|
|
||||||
|
|
||||||
class HealthMetricsMiddleware(Middleware):
|
class HealthMetricsMiddleware(Middleware):
|
||||||
"""
|
"""
|
||||||
Middleware to track health metrics for all tool calls (Phase 7.2).
|
Middleware to track health metrics for all tool calls.
|
||||||
|
|
||||||
Tracks:
|
Tracks:
|
||||||
- Response time
|
- Response time
|
||||||
@@ -1148,7 +1149,7 @@ class HealthMetricsMiddleware(Middleware):
|
|||||||
|
|
||||||
# Add health metrics middleware
|
# Add health metrics middleware
|
||||||
mcp.add_middleware(HealthMetricsMiddleware())
|
mcp.add_middleware(HealthMetricsMiddleware())
|
||||||
logger.info("Health metrics middleware enabled (Phase 7.2)")
|
logger.info("Health metrics middleware enabled")
|
||||||
|
|
||||||
|
|
||||||
# === ENDPOINT MIDDLEWARE HELPER ===
|
# === ENDPOINT MIDDLEWARE HELPER ===
|
||||||
@@ -1269,7 +1270,7 @@ async def get_project_info(project_id: str) -> str:
|
|||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def check_all_projects_health() -> str:
|
async def check_all_projects_health() -> str:
|
||||||
"""
|
"""
|
||||||
Check health status of all projects with enhanced metrics (Phase 7.2).
|
Check health status of all projects with enhanced metrics.
|
||||||
|
|
||||||
Performs comprehensive health checks on all configured projects including:
|
Performs comprehensive health checks on all configured projects including:
|
||||||
- Accessibility and response time
|
- Accessibility and response time
|
||||||
@@ -1295,7 +1296,7 @@ async def check_all_projects_health() -> str:
|
|||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def get_project_health(project_id: str) -> str:
|
async def get_project_health(project_id: str) -> str:
|
||||||
"""
|
"""
|
||||||
Get detailed health information for a specific project (Phase 7.2).
|
Get detailed health information for a specific project.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
project_id: Full project identifier (e.g., "wordpress_site1")
|
project_id: Full project identifier (e.g., "wordpress_site1")
|
||||||
@@ -1322,7 +1323,7 @@ async def get_project_health(project_id: str) -> str:
|
|||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def get_system_metrics() -> str:
|
async def get_system_metrics() -> str:
|
||||||
"""
|
"""
|
||||||
Get overall MCP server metrics and statistics (Phase 7.2).
|
Get overall MCP server metrics and statistics.
|
||||||
|
|
||||||
Returns system-wide metrics including:
|
Returns system-wide metrics including:
|
||||||
- Uptime
|
- Uptime
|
||||||
@@ -1348,7 +1349,7 @@ async def get_system_metrics() -> str:
|
|||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def get_system_uptime() -> str:
|
async def get_system_uptime() -> str:
|
||||||
"""
|
"""
|
||||||
Get MCP server uptime information (Phase 7.2).
|
Get MCP server uptime information.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
JSON string with uptime in various formats (seconds, minutes, hours, days)
|
JSON string with uptime in various formats (seconds, minutes, hours, days)
|
||||||
@@ -1367,7 +1368,7 @@ async def get_system_uptime() -> str:
|
|||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def get_project_metrics(project_id: str, hours: int = 1) -> str:
|
async def get_project_metrics(project_id: str, hours: int = 1) -> str:
|
||||||
"""
|
"""
|
||||||
Get historical metrics for a specific project (Phase 7.2).
|
Get historical metrics for a specific project.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
project_id: Full project identifier (e.g., "wordpress_site1")
|
project_id: Full project identifier (e.g., "wordpress_site1")
|
||||||
@@ -1397,7 +1398,7 @@ async def get_project_metrics(project_id: str, hours: int = 1) -> str:
|
|||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def export_health_metrics(output_path: str = "logs/metrics_export.json") -> str:
|
async def export_health_metrics(output_path: str = "logs/metrics_export.json") -> str:
|
||||||
"""
|
"""
|
||||||
Export all health metrics to a JSON file (Phase 7.2).
|
Export all health metrics to a JSON file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
output_path: Path to output file (default: logs/metrics_export.json)
|
output_path: Path to output file (default: logs/metrics_export.json)
|
||||||
@@ -1452,7 +1453,7 @@ async def _reset_rate_limit_impl(client_id: str = None) -> str:
|
|||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def get_rate_limit_stats(client_id: str = None) -> str:
|
async def get_rate_limit_stats(client_id: str = None) -> str:
|
||||||
"""
|
"""
|
||||||
Get rate limiting statistics (Phase 7.3).
|
Get rate limiting statistics.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
client_id: Optional client identifier to get specific client stats.
|
client_id: Optional client identifier to get specific client stats.
|
||||||
@@ -1467,7 +1468,7 @@ async def get_rate_limit_stats(client_id: str = None) -> str:
|
|||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def reset_rate_limit(client_id: str = None) -> str:
|
async def reset_rate_limit(client_id: str = None) -> str:
|
||||||
"""
|
"""
|
||||||
Reset rate limit state for a client or all clients (Phase 7.3).
|
Reset rate limit state for a client or all clients.
|
||||||
|
|
||||||
CAUTION: This is an administrative tool. Use with care.
|
CAUTION: This is an administrative tool. Use with care.
|
||||||
|
|
||||||
@@ -1596,25 +1597,18 @@ def register_project_tools():
|
|||||||
This function is called at startup to register all tools
|
This function is called at startup to register all tools
|
||||||
from all discovered projects.
|
from all discovered projects.
|
||||||
|
|
||||||
Option B Architecture (Phase 3 Complete):
|
|
||||||
- Uses ToolRegistry for centralized tool management
|
|
||||||
- Uses ToolGenerator for WordPress plugin (refactored in Phase 3)
|
|
||||||
- Type-safe with Pydantic models in ToolRegistry
|
|
||||||
- Tool specifications from plugin.get_tool_specifications()
|
|
||||||
|
|
||||||
Note: FastMCP requires using the @mcp.tool() decorator or mcp.tool(function)
|
Note: FastMCP requires using the @mcp.tool() decorator or mcp.tool(function)
|
||||||
for tool registration.
|
for tool registration.
|
||||||
"""
|
"""
|
||||||
logger.info("=" * 60)
|
logger.info("=" * 60)
|
||||||
logger.info("TOOL REGISTRATION - Option B Architecture (Phase 3)")
|
logger.info("TOOL REGISTRATION")
|
||||||
logger.info("=" * 60)
|
logger.info("=" * 60)
|
||||||
|
|
||||||
# Phase 3: Use ToolGenerator for refactored plugins
|
|
||||||
logger.info("Generating tools with ToolGenerator...")
|
logger.info("Generating tools with ToolGenerator...")
|
||||||
|
|
||||||
from plugins.wordpress.plugin import WordPressPlugin
|
from plugins.wordpress.plugin import WordPressPlugin
|
||||||
|
|
||||||
# Generate tools for WordPress (refactored in Phase 3)
|
# Generate tools for WordPress
|
||||||
logger.info("Generating WordPress tools from plugin specifications...")
|
logger.info("Generating WordPress tools from plugin specifications...")
|
||||||
try:
|
try:
|
||||||
wordpress_tools = tool_generator.generate_tools(WordPressPlugin, "wordpress")
|
wordpress_tools = tool_generator.generate_tools(WordPressPlugin, "wordpress")
|
||||||
@@ -1810,7 +1804,7 @@ def register_project_tools():
|
|||||||
|
|
||||||
logger.info("=" * 60)
|
logger.info("=" * 60)
|
||||||
|
|
||||||
# System tools count (Phase 7.2 + 7.3 + API Keys):
|
# System tools count (health + rate limit + API keys):
|
||||||
# - 10 system/health/rate limit tools
|
# - 10 system/health/rate limit tools
|
||||||
# - 6 API key management tools
|
# - 6 API key management tools
|
||||||
system_tools_count = 16
|
system_tools_count = 16
|
||||||
@@ -1819,7 +1813,7 @@ def register_project_tools():
|
|||||||
logger.info(
|
logger.info(
|
||||||
f"Total tools available: {total_tools} ({tool_registry.get_count()} plugin + {system_tools_count} system)"
|
f"Total tools available: {total_tools} ({tool_registry.get_count()} plugin + {system_tools_count} system)"
|
||||||
)
|
)
|
||||||
logger.info("🎯 Option B Architecture: Tool count stays constant regardless of site count!")
|
logger.info("Tool count stays constant regardless of site count")
|
||||||
|
|
||||||
return total_tools
|
return total_tools
|
||||||
|
|
||||||
@@ -3100,7 +3094,7 @@ async def health_check(request: Request) -> JSONResponse:
|
|||||||
"status": "healthy",
|
"status": "healthy",
|
||||||
"uptime": uptime_seconds,
|
"uptime": uptime_seconds,
|
||||||
"projects": len(project_manager.projects),
|
"projects": len(project_manager.projects),
|
||||||
"sites": site_manager.get_count(), # Option B
|
"sites": site_manager.get_count(),
|
||||||
"tools": _total_tool_count, # Total tools (plugin + system)
|
"tools": _total_tool_count, # Total tools (plugin + system)
|
||||||
"timestamp": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
|
"timestamp": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user