fix(ci): fix black/ruff failures, add CoC and PR template
- Fix Python formatting (sync no longer strips blank lines from .py files) - Remove test_community_build.py (tests private sync module) - Fix ruff warnings in test files - Add CODE_OF_CONDUCT.md - Add .github/PULL_REQUEST_TEMPLATE.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
102
server.py
102
server.py
@@ -143,6 +143,7 @@ _dcr_rate_limits: dict = (
|
||||
{}
|
||||
) # {ip: {"minute": count, "hour": count, "minute_reset": timestamp, "hour_reset": timestamp}}
|
||||
|
||||
|
||||
def is_redirect_uri_allowed_for_open_dcr(redirect_uris: list) -> bool:
|
||||
"""
|
||||
Check if all redirect_uris match the allowlist patterns.
|
||||
@@ -166,6 +167,7 @@ def is_redirect_uri_allowed_for_open_dcr(redirect_uris: list) -> bool:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def check_dcr_rate_limit(client_ip: str) -> tuple[bool, str]:
|
||||
"""
|
||||
Check if DCR request is within rate limits.
|
||||
@@ -215,6 +217,7 @@ def check_dcr_rate_limit(client_ip: str) -> tuple[bool, str]:
|
||||
|
||||
return True, ""
|
||||
|
||||
|
||||
# Validate auth mode
|
||||
if OAUTH_AUTH_MODE not in ["required", "optional", "trusted_domains"]:
|
||||
logger.warning(f"Invalid OAUTH_AUTH_MODE '{OAUTH_AUTH_MODE}'. Defaulting to 'trusted_domains'")
|
||||
@@ -282,9 +285,11 @@ for site_info in site_manager.list_all_sites():
|
||||
|
||||
logger.info("=" * 60)
|
||||
|
||||
|
||||
# === MCP INSTRUCTIONS HELPER ===
|
||||
# Phase K.2: Auto-discovery of available sites for AI assistants
|
||||
|
||||
|
||||
def generate_mcp_instructions(plugin_type: str = None, site_locked: str = None) -> str:
|
||||
"""
|
||||
Generate MCP server instructions for AI assistants.
|
||||
@@ -421,12 +426,15 @@ Use get_endpoints() to see all available MCP endpoints.
|
||||
|
||||
If working with a single site, use it directly without asking the user."""
|
||||
|
||||
|
||||
# Set instructions for admin endpoint (after sites are discovered)
|
||||
mcp.instructions = generate_mcp_instructions()
|
||||
logger.info("Admin MCP instructions configured with site discovery")
|
||||
|
||||
|
||||
# === AUTHENTICATION MIDDLEWARE ===
|
||||
|
||||
|
||||
def extract_project_from_tool(tool_name: str) -> str:
|
||||
"""
|
||||
Extract project_id from tool name.
|
||||
@@ -444,6 +452,7 @@ def extract_project_from_tool(tool_name: str) -> str:
|
||||
# The actual project will be validated when the tool is executed
|
||||
return "*"
|
||||
|
||||
|
||||
def extract_plugin_type_from_tool(tool_name: str) -> str | None:
|
||||
"""
|
||||
Extract plugin type from tool name for tool visibility filtering.
|
||||
@@ -491,6 +500,7 @@ def extract_plugin_type_from_tool(tool_name: str) -> str | None:
|
||||
# System tools (no plugin type)
|
||||
return None
|
||||
|
||||
|
||||
def check_tool_visibility(tool_name: str, api_key_project_id: str) -> bool:
|
||||
"""
|
||||
Check if API key has visibility to the requested tool.
|
||||
@@ -553,6 +563,7 @@ def check_tool_visibility(tool_name: str, api_key_project_id: str) -> bool:
|
||||
# Check if plugin types match
|
||||
return tool_plugin_type == key_plugin_type
|
||||
|
||||
|
||||
def determine_required_scope(tool_name: str) -> str:
|
||||
"""
|
||||
Determine required scope for a tool.
|
||||
@@ -580,6 +591,7 @@ def determine_required_scope(tool_name: str) -> str:
|
||||
# Everything else is read
|
||||
return "read"
|
||||
|
||||
|
||||
class UserAuthMiddleware(Middleware):
|
||||
"""
|
||||
Middleware to enforce Bearer token authentication for all MCP tool calls.
|
||||
@@ -830,12 +842,15 @@ class UserAuthMiddleware(Middleware):
|
||||
logger.error(f"Authentication error: {e}", exc_info=True)
|
||||
raise ToolError(f"Authentication error: {str(e)}")
|
||||
|
||||
|
||||
# Add authentication middleware to MCP server
|
||||
mcp.add_middleware(UserAuthMiddleware())
|
||||
logger.info("Authentication middleware enabled")
|
||||
|
||||
|
||||
# === AUDIT LOGGING MIDDLEWARE ===
|
||||
|
||||
|
||||
class AuditLoggingMiddleware(Middleware):
|
||||
"""
|
||||
Middleware to log all tool calls for audit purposes.
|
||||
@@ -918,16 +933,19 @@ class AuditLoggingMiddleware(Middleware):
|
||||
# Don't let logging errors break the tool call
|
||||
logger.error(f"Failed to log audit entry: {log_error}", exc_info=True)
|
||||
|
||||
|
||||
# Add audit logging middleware to MCP server
|
||||
mcp.add_middleware(AuditLoggingMiddleware())
|
||||
logger.info("Audit logging middleware enabled")
|
||||
|
||||
|
||||
# === RATE LIMITING (Phase 7.3) ===
|
||||
|
||||
# Initialize rate limiter
|
||||
rate_limiter = get_rate_limiter()
|
||||
logger.info("Rate limiter initialized (Phase 7.3)")
|
||||
|
||||
|
||||
class RateLimitMiddleware(Middleware):
|
||||
"""
|
||||
Middleware to enforce rate limiting for all tool calls (Phase 7.3).
|
||||
@@ -1011,10 +1029,12 @@ class RateLimitMiddleware(Middleware):
|
||||
# Rate limit check passed - proceed with tool execution
|
||||
return await call_next(context)
|
||||
|
||||
|
||||
# Add rate limiting middleware to MCP server
|
||||
mcp.add_middleware(RateLimitMiddleware())
|
||||
logger.info("Rate limiting middleware enabled (Phase 7.3)")
|
||||
|
||||
|
||||
# === HEALTH MONITORING (Phase 7.2) ===
|
||||
|
||||
from core import initialize_health_monitor
|
||||
@@ -1028,6 +1048,7 @@ health_monitor = initialize_health_monitor(
|
||||
)
|
||||
logger.info("Health monitor initialized (Phase 7.2)")
|
||||
|
||||
|
||||
class HealthMetricsMiddleware(Middleware):
|
||||
"""
|
||||
Middleware to track health metrics for all tool calls (Phase 7.2).
|
||||
@@ -1116,12 +1137,15 @@ class HealthMetricsMiddleware(Middleware):
|
||||
# Don't let metrics tracking errors break the tool call
|
||||
logger.error(f"Failed to record health metric: {metric_error}", exc_info=True)
|
||||
|
||||
|
||||
# Add health metrics middleware
|
||||
mcp.add_middleware(HealthMetricsMiddleware())
|
||||
logger.info("Health metrics middleware enabled (Phase 7.2)")
|
||||
|
||||
|
||||
# === ENDPOINT MIDDLEWARE HELPER ===
|
||||
|
||||
|
||||
def add_endpoint_middleware(endpoint_mcp, endpoint_name: str = "unknown"):
|
||||
"""
|
||||
Add authentication, audit, and rate limiting middleware to an endpoint.
|
||||
@@ -1138,8 +1162,10 @@ def add_endpoint_middleware(endpoint_mcp, endpoint_name: str = "unknown"):
|
||||
endpoint_mcp.add_middleware(RateLimitMiddleware())
|
||||
logger.debug(f"Added auth/audit/rate-limit middleware to {endpoint_name} endpoint")
|
||||
|
||||
|
||||
# === SYSTEM TOOLS ===
|
||||
|
||||
|
||||
# Internal implementation functions (not decorated) for system tools
|
||||
async def _list_projects_impl() -> str:
|
||||
"""Internal implementation for listing projects."""
|
||||
@@ -1153,6 +1179,7 @@ async def _list_projects_impl() -> str:
|
||||
logger.error(f"Error listing projects: {e}", exc_info=True)
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def list_projects() -> str:
|
||||
"""
|
||||
@@ -1166,6 +1193,7 @@ async def list_projects() -> str:
|
||||
"""
|
||||
return await _list_projects_impl()
|
||||
|
||||
|
||||
# Phase K.2.3: Helper for site discovery in plugin endpoints
|
||||
async def _list_sites_impl(plugin_type: str) -> str:
|
||||
"""
|
||||
@@ -1204,6 +1232,7 @@ async def _list_sites_impl(plugin_type: str) -> str:
|
||||
{"error": str(e), "message": f"Failed to list {plugin_type} sites"}, indent=2
|
||||
)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def get_project_info(project_id: str) -> str:
|
||||
"""
|
||||
@@ -1228,6 +1257,7 @@ async def get_project_info(project_id: str) -> str:
|
||||
logger.error(f"Error getting project info: {e}", exc_info=True)
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def check_all_projects_health() -> str:
|
||||
"""
|
||||
@@ -1253,6 +1283,7 @@ async def check_all_projects_health() -> str:
|
||||
logger.error(f"Error checking health: {e}", exc_info=True)
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def get_project_health(project_id: str) -> str:
|
||||
"""
|
||||
@@ -1279,6 +1310,7 @@ async def get_project_health(project_id: str) -> str:
|
||||
logger.error(f"Error getting project health: {e}", exc_info=True)
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def get_system_metrics() -> str:
|
||||
"""
|
||||
@@ -1304,6 +1336,7 @@ async def get_system_metrics() -> str:
|
||||
logger.error(f"Error getting system metrics: {e}", exc_info=True)
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def get_system_uptime() -> str:
|
||||
"""
|
||||
@@ -1322,6 +1355,7 @@ async def get_system_uptime() -> str:
|
||||
logger.error(f"Error getting uptime: {e}", exc_info=True)
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def get_project_metrics(project_id: str, hours: int = 1) -> str:
|
||||
"""
|
||||
@@ -1351,6 +1385,7 @@ async def get_project_metrics(project_id: str, hours: int = 1) -> str:
|
||||
logger.error(f"Error getting project metrics: {e}", exc_info=True)
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def export_health_metrics(output_path: str = "logs/metrics_export.json") -> str:
|
||||
"""
|
||||
@@ -1369,6 +1404,7 @@ async def export_health_metrics(output_path: str = "logs/metrics_export.json") -
|
||||
logger.error(f"Error exporting metrics: {e}", exc_info=True)
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
|
||||
# Internal implementations for rate limiting tools
|
||||
async def _get_rate_limit_stats_impl(client_id: str = None) -> str:
|
||||
"""Internal implementation for getting rate limit stats."""
|
||||
@@ -1387,6 +1423,7 @@ async def _get_rate_limit_stats_impl(client_id: str = None) -> str:
|
||||
logger.error(f"Error getting rate limit stats: {e}", exc_info=True)
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
|
||||
async def _reset_rate_limit_impl(client_id: str = None) -> str:
|
||||
"""Internal implementation for resetting rate limit."""
|
||||
try:
|
||||
@@ -1403,6 +1440,7 @@ async def _reset_rate_limit_impl(client_id: str = None) -> str:
|
||||
logger.error(f"Error resetting rate limit: {e}", exc_info=True)
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def get_rate_limit_stats(client_id: str = None) -> str:
|
||||
"""
|
||||
@@ -1417,6 +1455,7 @@ async def get_rate_limit_stats(client_id: str = None) -> str:
|
||||
"""
|
||||
return await _get_rate_limit_stats_impl(client_id)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def reset_rate_limit(client_id: str = None) -> str:
|
||||
"""
|
||||
@@ -1433,8 +1472,10 @@ async def reset_rate_limit(client_id: str = None) -> str:
|
||||
"""
|
||||
return await _reset_rate_limit_impl(client_id)
|
||||
|
||||
|
||||
# === DYNAMIC TOOL REGISTRATION ===
|
||||
|
||||
|
||||
def create_dynamic_tool(name: str, description: str, handler, input_schema: dict):
|
||||
"""
|
||||
Create a dynamic tool wrapper that works with FastMCP's decorator pattern.
|
||||
@@ -1533,6 +1574,7 @@ async def {name}({param_str}):
|
||||
|
||||
return dynamic_wrapper
|
||||
|
||||
|
||||
def register_project_tools():
|
||||
"""
|
||||
Dynamically register all project tools from plugins.
|
||||
@@ -1767,9 +1809,11 @@ def register_project_tools():
|
||||
|
||||
return total_tools
|
||||
|
||||
|
||||
# Register all project tools at startup
|
||||
_total_tool_count = register_project_tools()
|
||||
|
||||
|
||||
# === OAUTH 2.1 ENDPOINTS ===
|
||||
|
||||
from urllib.parse import urlencode
|
||||
@@ -1778,6 +1822,7 @@ from core.oauth import OAuthError, get_oauth_server
|
||||
|
||||
oauth_server = get_oauth_server()
|
||||
|
||||
|
||||
def get_oauth_base_url(request: Request) -> str:
|
||||
"""
|
||||
Get the correct base URL for OAuth endpoints.
|
||||
@@ -1800,6 +1845,7 @@ def get_oauth_base_url(request: Request) -> str:
|
||||
|
||||
return str(request.base_url).rstrip("/")
|
||||
|
||||
|
||||
@mcp.custom_route("/.well-known/oauth-authorization-server", methods=["GET"])
|
||||
async def oauth_metadata(request: Request) -> JSONResponse:
|
||||
"""
|
||||
@@ -1850,6 +1896,7 @@ async def oauth_metadata(request: Request) -> JSONResponse:
|
||||
|
||||
return JSONResponse(metadata)
|
||||
|
||||
|
||||
@mcp.custom_route("/mcp/.well-known/oauth-authorization-server", methods=["GET"])
|
||||
async def oauth_metadata_mcp_path(request: Request) -> JSONResponse:
|
||||
"""
|
||||
@@ -1860,6 +1907,7 @@ async def oauth_metadata_mcp_path(request: Request) -> JSONResponse:
|
||||
"""
|
||||
return await oauth_metadata(request)
|
||||
|
||||
|
||||
@mcp.custom_route("/.well-known/oauth-protected-resource", methods=["GET"])
|
||||
async def oauth_protected_resource(request: Request) -> JSONResponse:
|
||||
"""
|
||||
@@ -1880,6 +1928,7 @@ async def oauth_protected_resource(request: Request) -> JSONResponse:
|
||||
|
||||
return JSONResponse(metadata)
|
||||
|
||||
|
||||
async def oauth_protected_resource_path(request: Request) -> JSONResponse:
|
||||
"""
|
||||
OAuth 2.0 Protected Resource Metadata for path-specific requests (RFC 9728)
|
||||
@@ -1901,6 +1950,7 @@ async def oauth_protected_resource_path(request: Request) -> JSONResponse:
|
||||
|
||||
return JSONResponse(metadata)
|
||||
|
||||
|
||||
@mcp.custom_route("/oauth/register", methods=["POST"])
|
||||
async def oauth_register(request: Request) -> JSONResponse:
|
||||
"""
|
||||
@@ -2128,6 +2178,7 @@ async def oauth_register(request: Request) -> JSONResponse:
|
||||
logger.error(f"Error in dynamic client registration: {e}", exc_info=True)
|
||||
return JSONResponse({"error": "server_error", "error_description": str(e)}, status_code=500)
|
||||
|
||||
|
||||
@mcp.custom_route("/oauth/authorize", methods=["GET"])
|
||||
async def oauth_authorize(request: Request):
|
||||
"""
|
||||
@@ -2268,6 +2319,7 @@ async def oauth_authorize(request: Request):
|
||||
status_code=500,
|
||||
)
|
||||
|
||||
|
||||
@mcp.custom_route("/oauth/authorize/confirm", methods=["POST"])
|
||||
async def oauth_authorize_confirm(request: Request):
|
||||
"""
|
||||
@@ -2458,6 +2510,7 @@ async def oauth_authorize_confirm(request: Request):
|
||||
status_code=500,
|
||||
)
|
||||
|
||||
|
||||
@mcp.custom_route("/oauth/token", methods=["POST"])
|
||||
async def oauth_token(request: Request) -> JSONResponse:
|
||||
"""
|
||||
@@ -2567,8 +2620,10 @@ async def oauth_token(request: Request) -> JSONResponse:
|
||||
logger.error(f"OAuth token error: {e}", exc_info=True)
|
||||
return JSONResponse({"error": "server_error", "error_description": str(e)}, status_code=500)
|
||||
|
||||
|
||||
# === OAUTH CLIENT MANAGEMENT TOOLS ===
|
||||
|
||||
|
||||
# Internal implementation for OAuth register
|
||||
async def _oauth_register_client_impl(
|
||||
client_name: str,
|
||||
@@ -2606,6 +2661,7 @@ async def _oauth_register_client_impl(
|
||||
logger.error(f"Error registering OAuth client: {e}", exc_info=True)
|
||||
raise ToolError(f"Failed to register client: {e}")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def oauth_register_client(
|
||||
client_name: str,
|
||||
@@ -2631,6 +2687,7 @@ async def oauth_register_client(
|
||||
client_name, redirect_uris, grant_types, allowed_scopes, metadata
|
||||
)
|
||||
|
||||
|
||||
# Internal implementations for OAuth tools
|
||||
async def _oauth_list_clients_impl() -> dict:
|
||||
"""Internal implementation for listing OAuth clients."""
|
||||
@@ -2656,6 +2713,7 @@ async def _oauth_list_clients_impl() -> dict:
|
||||
logger.error(f"Error listing OAuth clients: {e}", exc_info=True)
|
||||
raise ToolError(f"Failed to list clients: {e}")
|
||||
|
||||
|
||||
async def _oauth_revoke_client_impl(client_id: str) -> dict:
|
||||
"""Internal implementation for revoking OAuth client."""
|
||||
from core.oauth import get_client_registry
|
||||
@@ -2682,6 +2740,7 @@ async def _oauth_revoke_client_impl(client_id: str) -> dict:
|
||||
logger.error(f"Error revoking OAuth client: {e}", exc_info=True)
|
||||
raise ToolError(f"Failed to revoke client: {e}")
|
||||
|
||||
|
||||
async def _oauth_get_client_info_impl(client_id: str) -> dict:
|
||||
"""Internal implementation for getting OAuth client info."""
|
||||
from core.oauth import get_client_registry
|
||||
@@ -2707,6 +2766,7 @@ async def _oauth_get_client_info_impl(client_id: str) -> dict:
|
||||
logger.error(f"Error getting OAuth client info: {e}", exc_info=True)
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def oauth_list_clients() -> dict:
|
||||
"""
|
||||
@@ -2719,6 +2779,7 @@ async def oauth_list_clients() -> dict:
|
||||
"""
|
||||
return await _oauth_list_clients_impl()
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def oauth_revoke_client(client_id: str) -> dict:
|
||||
"""
|
||||
@@ -2735,6 +2796,7 @@ async def oauth_revoke_client(client_id: str) -> dict:
|
||||
"""
|
||||
return await _oauth_revoke_client_impl(client_id)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def oauth_get_client_info(client_id: str) -> dict:
|
||||
"""
|
||||
@@ -2748,8 +2810,10 @@ async def oauth_get_client_info(client_id: str) -> dict:
|
||||
"""
|
||||
return await _oauth_get_client_info_impl(client_id)
|
||||
|
||||
|
||||
# === PHASE X.3: SYSTEM TOOLS ===
|
||||
|
||||
|
||||
# Internal implementations for Phase X.3 system tools
|
||||
async def _get_endpoints_impl() -> dict:
|
||||
"""Internal implementation for listing endpoints."""
|
||||
@@ -2862,6 +2926,7 @@ async def _get_endpoints_impl() -> dict:
|
||||
logger.error(f"Error listing endpoints: {e}", exc_info=True)
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
async def _get_system_info_impl() -> dict:
|
||||
"""Internal implementation for getting system info."""
|
||||
try:
|
||||
@@ -2900,6 +2965,7 @@ async def _get_system_info_impl() -> dict:
|
||||
logger.error(f"Error getting system info: {e}", exc_info=True)
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
async def _get_audit_log_impl(
|
||||
limit: int = 50, event_type: str = None, project_id: str = None
|
||||
) -> dict:
|
||||
@@ -2930,6 +2996,7 @@ async def _get_audit_log_impl(
|
||||
logger.error(f"Error getting audit log: {e}", exc_info=True)
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
async def _set_rate_limit_config_impl(
|
||||
requests_per_minute: int = None, requests_per_hour: int = None, requests_per_day: int = None
|
||||
) -> dict:
|
||||
@@ -2961,21 +3028,25 @@ async def _set_rate_limit_config_impl(
|
||||
logger.error(f"Error setting rate limit config: {e}", exc_info=True)
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def get_endpoints() -> dict:
|
||||
"""List all available MCP endpoints (Phase X.3)."""
|
||||
return await _get_endpoints_impl()
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def get_system_info() -> dict:
|
||||
"""Get comprehensive system information (Phase X.3)."""
|
||||
return await _get_system_info_impl()
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def get_audit_log(limit: int = 50, event_type: str = None, project_id: str = None) -> dict:
|
||||
"""Get audit log entries (Phase X.3)."""
|
||||
return await _get_audit_log_impl(limit, event_type, project_id)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def set_rate_limit_config(
|
||||
requests_per_minute: int = None, requests_per_hour: int = None, requests_per_day: int = None
|
||||
@@ -2985,11 +3056,13 @@ async def set_rate_limit_config(
|
||||
requests_per_minute, requests_per_hour, requests_per_day
|
||||
)
|
||||
|
||||
|
||||
# === HEALTH CHECK ENDPOINT ===
|
||||
|
||||
# Track server start time for uptime calculation
|
||||
server_start_time = time.time()
|
||||
|
||||
|
||||
@mcp.custom_route("/health", methods=["GET"])
|
||||
async def health_check(request: Request) -> JSONResponse:
|
||||
"""
|
||||
@@ -3019,8 +3092,10 @@ async def health_check(request: Request) -> JSONResponse:
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# === API KEYS MANAGEMENT TOOLS ===
|
||||
|
||||
|
||||
# Internal implementation functions (not decorated)
|
||||
async def _manage_api_keys_create_impl(
|
||||
project_id: str, scope: str = "read", expires_in_days: int = None, description: str = None
|
||||
@@ -3051,6 +3126,7 @@ async def _manage_api_keys_create_impl(
|
||||
logger.error(f"Error creating API key: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
async def _manage_api_keys_list_impl(project_id: str = None, include_revoked: bool = False) -> dict:
|
||||
"""Internal implementation for listing API keys."""
|
||||
try:
|
||||
@@ -3060,6 +3136,7 @@ async def _manage_api_keys_list_impl(project_id: str = None, include_revoked: bo
|
||||
logger.error(f"Error listing API keys: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
async def _manage_api_keys_get_info_impl(key_id: str) -> dict:
|
||||
"""Internal implementation for getting API key info."""
|
||||
try:
|
||||
@@ -3071,6 +3148,7 @@ async def _manage_api_keys_get_info_impl(key_id: str) -> dict:
|
||||
logger.error(f"Error getting key info: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
async def _manage_api_keys_revoke_impl(key_id: str) -> dict:
|
||||
"""Internal implementation for revoking API key."""
|
||||
try:
|
||||
@@ -3082,6 +3160,7 @@ async def _manage_api_keys_revoke_impl(key_id: str) -> dict:
|
||||
logger.error(f"Error revoking API key: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
async def _manage_api_keys_delete_impl(key_id: str) -> dict:
|
||||
"""Internal implementation for deleting API key."""
|
||||
try:
|
||||
@@ -3093,6 +3172,7 @@ async def _manage_api_keys_delete_impl(key_id: str) -> dict:
|
||||
logger.error(f"Error deleting API key: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
async def _manage_api_keys_rotate_impl(project_id: str) -> dict:
|
||||
"""Internal implementation for rotating API keys."""
|
||||
try:
|
||||
@@ -3108,6 +3188,7 @@ async def _manage_api_keys_rotate_impl(project_id: str) -> dict:
|
||||
logger.error(f"Error rotating API keys: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def manage_api_keys_create(
|
||||
project_id: str, scope: str = "read", expires_in_days: int = None, description: str = None
|
||||
@@ -3137,6 +3218,7 @@ async def manage_api_keys_create(
|
||||
"""
|
||||
return await _manage_api_keys_create_impl(project_id, scope, expires_in_days, description)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def manage_api_keys_list(project_id: str = None, include_revoked: bool = False) -> dict:
|
||||
"""
|
||||
@@ -3171,6 +3253,7 @@ async def manage_api_keys_list(project_id: str = None, include_revoked: bool = F
|
||||
"""
|
||||
return await _manage_api_keys_list_impl(project_id, include_revoked)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def manage_api_keys_get_info(key_id: str) -> dict:
|
||||
"""
|
||||
@@ -3184,6 +3267,7 @@ async def manage_api_keys_get_info(key_id: str) -> dict:
|
||||
"""
|
||||
return await _manage_api_keys_get_info_impl(key_id)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def manage_api_keys_revoke(key_id: str) -> dict:
|
||||
"""
|
||||
@@ -3197,6 +3281,7 @@ async def manage_api_keys_revoke(key_id: str) -> dict:
|
||||
"""
|
||||
return await _manage_api_keys_revoke_impl(key_id)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def manage_api_keys_delete(key_id: str) -> dict:
|
||||
"""
|
||||
@@ -3210,6 +3295,7 @@ async def manage_api_keys_delete(key_id: str) -> dict:
|
||||
"""
|
||||
return await _manage_api_keys_delete_impl(key_id)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def manage_api_keys_rotate(project_id: str) -> dict:
|
||||
"""
|
||||
@@ -3226,10 +3312,12 @@ async def manage_api_keys_rotate(project_id: str) -> dict:
|
||||
"""
|
||||
return await _manage_api_keys_rotate_impl(project_id)
|
||||
|
||||
|
||||
# === SERVER STARTUP ===
|
||||
|
||||
# Create separate MCP instances for each endpoint
|
||||
|
||||
|
||||
def create_system_mcp():
|
||||
"""
|
||||
Create System-only MCP instance (Phase X.3).
|
||||
@@ -3362,6 +3450,7 @@ Use get_endpoints() to see all available MCP endpoints."""
|
||||
logger.info("Created System endpoint with 17 tools")
|
||||
return system_mcp
|
||||
|
||||
|
||||
def create_wordpress_mcp():
|
||||
"""Create WordPress-only MCP instance"""
|
||||
from fastmcp import FastMCP
|
||||
@@ -3410,6 +3499,7 @@ def create_wordpress_mcp():
|
||||
logger.info(f"Created WordPress endpoint with {count} tools (including list_sites)")
|
||||
return wp_mcp
|
||||
|
||||
|
||||
def create_wordpress_advanced_mcp():
|
||||
"""Create WordPress Advanced MCP instance"""
|
||||
from fastmcp import FastMCP
|
||||
@@ -3453,6 +3543,7 @@ def create_wordpress_advanced_mcp():
|
||||
logger.info(f"Created WordPress Advanced endpoint with {count} tools (including list_sites)")
|
||||
return wp_adv_mcp
|
||||
|
||||
|
||||
def create_woocommerce_mcp():
|
||||
"""Create WooCommerce-only MCP instance (Phase D.1)"""
|
||||
from fastmcp import FastMCP
|
||||
@@ -3494,6 +3585,7 @@ def create_woocommerce_mcp():
|
||||
logger.info(f"Created WooCommerce endpoint with {count} tools (including list_sites)")
|
||||
return woo_mcp
|
||||
|
||||
|
||||
def create_gitea_mcp():
|
||||
"""Create Gitea-only MCP instance"""
|
||||
from fastmcp import FastMCP
|
||||
@@ -3535,6 +3627,7 @@ def create_gitea_mcp():
|
||||
logger.info(f"Created Gitea endpoint with {count} tools (including list_sites)")
|
||||
return gitea_mcp
|
||||
|
||||
|
||||
def create_openpanel_mcp():
|
||||
"""Create OpenPanel-only MCP instance"""
|
||||
from fastmcp import FastMCP
|
||||
@@ -3576,6 +3669,7 @@ def create_openpanel_mcp():
|
||||
logger.info(f"Created OpenPanel endpoint with {count} tools (including list_sites)")
|
||||
return openpanel_mcp
|
||||
|
||||
|
||||
def create_n8n_mcp():
|
||||
"""Create n8n-only MCP instance"""
|
||||
from fastmcp import FastMCP
|
||||
@@ -3617,6 +3711,7 @@ def create_n8n_mcp():
|
||||
logger.info(f"Created n8n endpoint with {count} tools (including list_sites)")
|
||||
return n8n_mcp
|
||||
|
||||
|
||||
def create_supabase_mcp():
|
||||
"""Create Supabase-only MCP instance"""
|
||||
from fastmcp import FastMCP
|
||||
@@ -3658,6 +3753,7 @@ def create_supabase_mcp():
|
||||
logger.info(f"Created Supabase endpoint with {count} tools (including list_sites)")
|
||||
return supabase_mcp
|
||||
|
||||
|
||||
def create_appwrite_mcp():
|
||||
"""Create Appwrite-only MCP instance"""
|
||||
from fastmcp import FastMCP
|
||||
@@ -3699,6 +3795,7 @@ def create_appwrite_mcp():
|
||||
logger.info(f"Created Appwrite endpoint with {count} tools (including list_sites)")
|
||||
return appwrite_mcp
|
||||
|
||||
|
||||
def create_directus_mcp():
|
||||
"""Create Directus-only MCP instance"""
|
||||
from fastmcp import FastMCP
|
||||
@@ -3740,6 +3837,7 @@ def create_directus_mcp():
|
||||
logger.info(f"Created Directus endpoint with {count} tools (including list_sites)")
|
||||
return directus_mcp
|
||||
|
||||
|
||||
def create_project_mcp(project_id: str, plugin_type: str, site_id: str, alias: str = None):
|
||||
"""
|
||||
Create MCP instance for a specific project with site-locked tools.
|
||||
@@ -3830,6 +3928,7 @@ def create_project_mcp(project_id: str, plugin_type: str, site_id: str, alias: s
|
||||
logger.info(f"Created Project endpoint for {project_id} with {count} tools")
|
||||
return project_mcp
|
||||
|
||||
|
||||
def create_per_project_endpoints():
|
||||
"""
|
||||
Create MCP endpoints for each discovered site.
|
||||
@@ -3873,6 +3972,7 @@ def create_per_project_endpoints():
|
||||
logger.info(f"Created {len(project_endpoints)} per-project endpoints")
|
||||
return project_endpoints
|
||||
|
||||
|
||||
def create_multi_endpoint_app(transport: str = "streamable-http"):
|
||||
"""Create Starlette app with multiple MCP endpoints"""
|
||||
from contextlib import asynccontextmanager
|
||||
@@ -4205,6 +4305,7 @@ def create_multi_endpoint_app(transport: str = "streamable-http"):
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point for the MCP server."""
|
||||
import argparse
|
||||
@@ -4262,5 +4363,6 @@ def main():
|
||||
logger.error(f"Server error: {e}", exc_info=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user