chore(F.15): FastMCP 3.x upgrade + legacy cleanup — v3.5.0
- Upgrade fastmcp from >=2.14.0,<3.0.0 to >=3.0.0,<4.0.0 - Remove server_multi.py (legacy multi-endpoint server) - Remove core/project_manager.py (legacy project manager) - Refactor HealthMonitor to use SiteManager exclusively - Replace _tool_manager._tools with tracked _tool_counts dict - Clean core/__init__.py exports - Update CHANGELOG, CLAUDE.md, tests - Auto-format fixes (black + ruff) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
19
CHANGELOG.md
19
CHANGELOG.md
@@ -5,6 +5,25 @@ All notable changes to MCP Hub will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [3.5.0] — 2026-04-02
|
||||
|
||||
### FastMCP Upgrade & Legacy Cleanup (Track F.15)
|
||||
|
||||
Major cleanup release: upgraded to FastMCP 3.x, removed all legacy modules, and simplified the architecture.
|
||||
|
||||
#### Changed
|
||||
- **FastMCP 3.x**: Upgraded from `fastmcp>=2.14.0,<3.0.0` to `fastmcp>=3.0.0,<4.0.0`
|
||||
- **HealthMonitor refactored**: Removed `ProjectManager` dependency — now uses `SiteManager` exclusively for site discovery and health checks
|
||||
- **Endpoint introspection**: Replaced internal `_tool_manager._tools` access with tracked `_tool_counts` dict in `MCPEndpointFactory`
|
||||
|
||||
#### Removed
|
||||
- **`server_multi.py`**: Legacy multi-endpoint server entry point (replaced by unified `server.py` since v3.0)
|
||||
- **`core/project_manager.py`**: Legacy project manager shell — HealthMonitor no longer depends on it
|
||||
- **Legacy exports**: Removed `ProjectManager` and `get_project_manager` from `core/__init__.py`
|
||||
- References from `pyproject.toml` (py-modules, ruff per-file-ignores), community build script, and documentation
|
||||
|
||||
---
|
||||
|
||||
## [3.4.0] — 2026-03-31
|
||||
|
||||
### OpenPanel Plugin — Public Release (Track F.10)
|
||||
|
||||
@@ -69,7 +69,6 @@ All configured in `pyproject.toml`:
|
||||
|
||||
```
|
||||
├── server.py # Primary entry point
|
||||
├── server_multi.py # Alternative multi-endpoint server
|
||||
├── core/ # Layer 1: Core system modules
|
||||
├── plugins/ # Layer 2: Plugin system (9 plugins)
|
||||
├── core/templates/ # Jinja2 templates (dashboard + OAuth)
|
||||
@@ -93,8 +92,6 @@ Layer 3: API & Web UI (server.py + core/dashboard/) — FastMCP server, Starle
|
||||
### Entry Points
|
||||
|
||||
- **`server.py`** (~3500 lines) — Primary entry point. Handles FastMCP server, Starlette routes, middleware, plugins.
|
||||
- **`server_multi.py`** — Alternative multi-endpoint server (legacy, predates unified `server.py` endpoints).
|
||||
|
||||
### Multi-Endpoint Architecture
|
||||
|
||||
```
|
||||
@@ -171,10 +168,6 @@ Web UI at the server root, built with Starlette + Jinja2 + HTMX + Tailwind CSS.
|
||||
**Admin pages**: Login, Home, Projects, API Keys, OAuth Clients, Audit Logs, Health, Settings.
|
||||
**User pages**: Login (OAuth), Home, My Sites (list/add/edit/test/delete), Connect (API keys + config snippets + OAuth clients), Profile.
|
||||
|
||||
### Legacy Modules (Deprecated)
|
||||
|
||||
`core/project_manager.py` (retained for HealthMonitor compatibility), `core/site_registry.py`, `core/unified_tools.py` — kept for backward compatibility. New code should use `SiteManager`, `ToolRegistry`, and `ToolGenerator` instead.
|
||||
|
||||
## Commit Style
|
||||
|
||||
```
|
||||
@@ -197,7 +190,6 @@ v4 development cycle with 15 phases. See `docs/ROADMAP.md` (Track F) and `.claud
|
||||
## Gotchas
|
||||
|
||||
- Test files exist in both `tests/` (proper) and root directory (legacy `test_*.py`). Run `pytest tests/` for organized tests only.
|
||||
- `server_multi.py` is the alternative multi-endpoint entry point; `server.py` is the primary
|
||||
- `wordpress-plugin/` contains companion WP plugins (openpanel, airano-mcp-seo-bridge) — these are PHP, not Python
|
||||
- `env.example` has "FUTURE" labels for Supabase/Gitea but both are fully implemented
|
||||
- 4 plugins are tested for public use: WordPress, WooCommerce, Supabase, OpenPanel. Others are admin-only or disabled.
|
||||
|
||||
@@ -37,14 +37,9 @@ from core.health import (
|
||||
initialize_health_monitor,
|
||||
)
|
||||
|
||||
# Project and Site Management
|
||||
from core.project_manager import ProjectManager, get_project_manager
|
||||
|
||||
# Rate Limiting
|
||||
from core.rate_limiter import RateLimitConfig, RateLimiter, get_rate_limiter
|
||||
from core.site_manager import SiteConfig, SiteManager, get_site_manager
|
||||
|
||||
# Legacy (kept for backward compatibility, will be removed in v2.0)
|
||||
from core.tool_generator import ToolGenerator
|
||||
|
||||
# Tool Management (Option B architecture)
|
||||
@@ -56,9 +51,7 @@ __all__ = [
|
||||
"get_auth_manager",
|
||||
"APIKeyManager",
|
||||
"get_api_key_manager",
|
||||
# Project/Site Management
|
||||
"ProjectManager",
|
||||
"get_project_manager",
|
||||
# Site Management
|
||||
"SiteManager",
|
||||
"SiteConfig",
|
||||
"get_site_manager",
|
||||
|
||||
@@ -10,12 +10,12 @@ import json
|
||||
import logging
|
||||
import os
|
||||
import secrets
|
||||
|
||||
import bcrypt
|
||||
from dataclasses import asdict, dataclass
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
import bcrypt
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
@@ -850,9 +850,7 @@ async def get_all_projects(
|
||||
is_admin = False
|
||||
current_user_id = None
|
||||
if user_session:
|
||||
if hasattr(user_session, "user_type") and user_session.user_type == "master":
|
||||
is_admin = True
|
||||
elif isinstance(user_session, dict) and user_session.get("role") == "admin":
|
||||
if hasattr(user_session, "user_type") and user_session.user_type == "master" or isinstance(user_session, dict) and user_session.get("role") == "admin":
|
||||
is_admin = True
|
||||
elif isinstance(user_session, dict) and "user_id" in user_session:
|
||||
current_user_id = user_session["user_id"]
|
||||
|
||||
@@ -48,6 +48,7 @@ class MCPEndpointFactory:
|
||||
self.tool_registry = tool_registry
|
||||
self.middleware_classes = middleware_classes or []
|
||||
self.endpoints: dict[str, FastMCP] = {}
|
||||
self._tool_counts: dict[str, int] = {}
|
||||
self._tool_handlers: dict[str, Callable] = {}
|
||||
|
||||
def register_tool_handler(self, tool_name: str, handler: Callable):
|
||||
@@ -92,8 +93,9 @@ class MCPEndpointFactory:
|
||||
for middleware in custom_middleware:
|
||||
mcp.add_middleware(middleware)
|
||||
|
||||
# Store endpoint
|
||||
# Store endpoint and tool count
|
||||
self.endpoints[config.path] = mcp
|
||||
self._tool_counts[config.path] = len(tools)
|
||||
|
||||
logger.info(f" - Endpoint {config.path} created successfully")
|
||||
|
||||
@@ -282,9 +284,7 @@ class MCPEndpointFactory:
|
||||
"""
|
||||
info = []
|
||||
for path, mcp in self.endpoints.items():
|
||||
# Get tool count
|
||||
# Note: This requires accessing FastMCP internals
|
||||
tool_count = len(mcp._tool_manager._tools) if hasattr(mcp, "_tool_manager") else 0
|
||||
tool_count = self._tool_counts.get(path, 0)
|
||||
|
||||
info.append(
|
||||
{
|
||||
|
||||
@@ -149,11 +149,7 @@ class EndpointRegistry:
|
||||
tool_count = 0
|
||||
|
||||
if mcp:
|
||||
# Try to get tool count from FastMCP
|
||||
try:
|
||||
tool_count = len(mcp._tool_manager._tools)
|
||||
except AttributeError:
|
||||
pass
|
||||
tool_count = self.factory._tool_counts.get(path, 0)
|
||||
|
||||
endpoints.append(
|
||||
EndpointInfo(
|
||||
|
||||
@@ -23,7 +23,6 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from core.audit_log import AuditLogger
|
||||
from core.project_manager import ProjectManager
|
||||
from core.site_manager import SiteManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -130,7 +129,6 @@ class HealthMonitor:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
project_manager: ProjectManager,
|
||||
audit_logger: AuditLogger | None = None,
|
||||
metrics_retention_hours: int = 24,
|
||||
max_metrics_per_project: int = 1000,
|
||||
@@ -140,13 +138,11 @@ class HealthMonitor:
|
||||
Initialize health monitor.
|
||||
|
||||
Args:
|
||||
project_manager: Project manager instance
|
||||
audit_logger: Optional audit logger for logging health events
|
||||
metrics_retention_hours: Hours to retain historical metrics
|
||||
max_metrics_per_project: Maximum metrics to store per project
|
||||
site_manager: Optional SiteManager for comprehensive site discovery
|
||||
site_manager: SiteManager for site discovery
|
||||
"""
|
||||
self.project_manager = project_manager
|
||||
self.site_manager = site_manager
|
||||
self.audit_logger = audit_logger
|
||||
self.metrics_retention_hours = metrics_retention_hours
|
||||
@@ -410,15 +406,7 @@ class HealthMonitor:
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
# Get plugin instance from ProjectManager
|
||||
plugin = self.project_manager.projects.get(project_id)
|
||||
|
||||
if plugin:
|
||||
# Perform health check via plugin instance
|
||||
health_result = await plugin.health_check()
|
||||
elif self.site_manager:
|
||||
# Site exists in SiteManager but not legacy ProjectManager
|
||||
# Create a temporary plugin instance for a proper health check
|
||||
if self.site_manager:
|
||||
health_result = await self._site_manager_health_check(project_id)
|
||||
else:
|
||||
return ProjectHealthStatus(
|
||||
@@ -524,7 +512,7 @@ class HealthMonitor:
|
||||
|
||||
async def _site_manager_health_check(self, project_id: str) -> dict[str, Any]:
|
||||
"""
|
||||
Health check for sites managed by SiteManager (not in legacy ProjectManager).
|
||||
Health check for a site via SiteManager.
|
||||
|
||||
Creates a temporary plugin instance and calls its health_check() method,
|
||||
falling back to a basic HTTP check if plugin instantiation fails.
|
||||
@@ -650,8 +638,8 @@ class HealthMonitor:
|
||||
"""
|
||||
health_statuses = {}
|
||||
|
||||
# Collect all known project/site IDs from both sources
|
||||
all_project_ids = set(self.project_manager.projects.keys())
|
||||
# Collect all known site IDs from SiteManager
|
||||
all_project_ids = set()
|
||||
if self.site_manager:
|
||||
for site_info in self.site_manager.list_all_sites():
|
||||
all_project_ids.add(site_info["full_id"])
|
||||
@@ -860,7 +848,6 @@ def get_health_monitor() -> HealthMonitor | None:
|
||||
|
||||
|
||||
def initialize_health_monitor(
|
||||
project_manager: ProjectManager,
|
||||
audit_logger: AuditLogger | None = None,
|
||||
site_manager: SiteManager | None = None,
|
||||
**kwargs,
|
||||
@@ -869,16 +856,13 @@ def initialize_health_monitor(
|
||||
Initialize the global health monitor.
|
||||
|
||||
Args:
|
||||
project_manager: Project manager instance
|
||||
audit_logger: Optional audit logger
|
||||
site_manager: Optional SiteManager for comprehensive site discovery
|
||||
site_manager: SiteManager for site discovery
|
||||
**kwargs: Additional configuration options
|
||||
|
||||
Returns:
|
||||
HealthMonitor instance
|
||||
"""
|
||||
global _health_monitor
|
||||
_health_monitor = HealthMonitor(
|
||||
project_manager, audit_logger, site_manager=site_manager, **kwargs
|
||||
)
|
||||
_health_monitor = HealthMonitor(audit_logger=audit_logger, site_manager=site_manager, **kwargs)
|
||||
return _health_monitor
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
"""
|
||||
Project Manager (Legacy)
|
||||
|
||||
Retained for backward compatibility with HealthMonitor.
|
||||
Sites are now managed via the web dashboard (DB-based).
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from plugins import BasePlugin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ProjectManager:
|
||||
"""
|
||||
Legacy project manager — retained for HealthMonitor compatibility.
|
||||
|
||||
Sites are now managed via the web dashboard and stored in SQLite.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize project manager."""
|
||||
self.projects: dict[str, BasePlugin] = {}
|
||||
self.logger = logging.getLogger("ProjectManager")
|
||||
|
||||
def get_project(self, full_id: str) -> BasePlugin | None:
|
||||
"""
|
||||
Get a project plugin instance.
|
||||
|
||||
Args:
|
||||
full_id: Full project identifier (plugin_type_project_id)
|
||||
|
||||
Returns:
|
||||
Plugin instance or None
|
||||
"""
|
||||
return self.projects.get(full_id)
|
||||
|
||||
def get_all_projects(self) -> dict[str, BasePlugin]:
|
||||
"""Get all project instances."""
|
||||
return self.projects.copy()
|
||||
|
||||
def get_projects_by_type(self, plugin_type: str) -> dict[str, BasePlugin]:
|
||||
"""
|
||||
Get all projects of a specific type.
|
||||
|
||||
Args:
|
||||
plugin_type: Plugin type to filter by
|
||||
|
||||
Returns:
|
||||
Dict of project_id -> plugin
|
||||
"""
|
||||
prefix = plugin_type + "_"
|
||||
return {
|
||||
full_id: plugin
|
||||
for full_id, plugin in self.projects.items()
|
||||
if full_id.startswith(prefix)
|
||||
}
|
||||
|
||||
def get_all_tools(self) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get all MCP tools from all projects.
|
||||
|
||||
Returns:
|
||||
List of tool definitions
|
||||
"""
|
||||
all_tools = []
|
||||
|
||||
for full_id, plugin in self.projects.items():
|
||||
try:
|
||||
tools = plugin.get_tools()
|
||||
all_tools.extend(tools)
|
||||
self.logger.debug(f"Loaded {len(tools)} tools from {full_id}")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error loading tools from {full_id}: {e}", exc_info=True)
|
||||
|
||||
self.logger.debug(f"Total tools loaded: {len(all_tools)}")
|
||||
return all_tools
|
||||
|
||||
async def check_all_health(self) -> dict[str, dict[str, Any]]:
|
||||
"""
|
||||
Check health of all projects.
|
||||
|
||||
Returns:
|
||||
Dict mapping project ID to health status
|
||||
"""
|
||||
health_results = {}
|
||||
|
||||
for full_id, plugin in self.projects.items():
|
||||
try:
|
||||
health = await plugin.health_check()
|
||||
health_results[full_id] = health
|
||||
except Exception as e:
|
||||
health_results[full_id] = {
|
||||
"healthy": False,
|
||||
"message": f"Health check failed: {str(e)}",
|
||||
}
|
||||
|
||||
return health_results
|
||||
|
||||
def get_project_info(self, full_id: str) -> dict[str, Any] | None:
|
||||
"""
|
||||
Get information about a specific project.
|
||||
|
||||
Args:
|
||||
full_id: Full project identifier
|
||||
|
||||
Returns:
|
||||
Project info dict or None
|
||||
"""
|
||||
plugin = self.get_project(full_id)
|
||||
if plugin:
|
||||
return plugin.get_project_info()
|
||||
return None
|
||||
|
||||
def list_projects(self) -> list[dict[str, Any]]:
|
||||
"""
|
||||
List all projects with basic information.
|
||||
|
||||
Returns:
|
||||
List of project info dicts
|
||||
"""
|
||||
return [
|
||||
{"id": full_id, "type": plugin.get_plugin_name(), "project_id": plugin.project_id}
|
||||
for full_id, plugin in self.projects.items()
|
||||
]
|
||||
|
||||
|
||||
# Global project manager instance
|
||||
_project_manager: ProjectManager | None = None
|
||||
|
||||
|
||||
def get_project_manager() -> ProjectManager:
|
||||
"""Get the global project manager instance."""
|
||||
global _project_manager
|
||||
if _project_manager is None:
|
||||
_project_manager = ProjectManager()
|
||||
return _project_manager
|
||||
@@ -483,8 +483,6 @@ async def delete_user_factor(client: SupabaseClient, user_id: str, factor_id: st
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
|
||||
|
||||
async def search_users(
|
||||
client: SupabaseClient, query: str, page: int = 1, per_page: int = 50
|
||||
) -> str:
|
||||
|
||||
@@ -77,8 +77,12 @@ class WPCLIManager:
|
||||
try:
|
||||
# First, test if we have Docker socket access
|
||||
test_process = await asyncio.create_subprocess_exec(
|
||||
"docker", "version", "--format", "{{.Server.Version}}",
|
||||
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||
"docker",
|
||||
"version",
|
||||
"--format",
|
||||
"{{.Server.Version}}",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
|
||||
test_stdout, test_stderr = await asyncio.wait_for(
|
||||
@@ -99,10 +103,15 @@ class WPCLIManager:
|
||||
|
||||
# Now check for our specific container using exact name match
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
"docker", "ps", "--all",
|
||||
"--filter", f"name=^{self.container_name}$",
|
||||
"--format", "{{.Names}}|{{.Status}}",
|
||||
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||
"docker",
|
||||
"ps",
|
||||
"--all",
|
||||
"--filter",
|
||||
f"name=^{self.container_name}$",
|
||||
"--format",
|
||||
"{{.Names}}|{{.Status}}",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
|
||||
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=5.0)
|
||||
@@ -117,8 +126,13 @@ class WPCLIManager:
|
||||
if not output:
|
||||
# Container not found - get list of available containers for helpful error
|
||||
list_process = await asyncio.create_subprocess_exec(
|
||||
"docker", "ps", "--all", "--format", "{{.Names}}",
|
||||
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||
"docker",
|
||||
"ps",
|
||||
"--all",
|
||||
"--format",
|
||||
"{{.Names}}",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
list_stdout, _ = await list_process.communicate()
|
||||
available = list_stdout.decode().strip().split("\n") if list_stdout else []
|
||||
@@ -170,9 +184,14 @@ class WPCLIManager:
|
||||
try:
|
||||
# Try to run wp --version
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
"docker", "exec", self.container_name,
|
||||
"wp", "--version", "--allow-root",
|
||||
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||
"docker",
|
||||
"exec",
|
||||
self.container_name,
|
||||
"wp",
|
||||
"--version",
|
||||
"--allow-root",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
|
||||
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=5.0)
|
||||
@@ -291,7 +310,9 @@ class WPCLIManager:
|
||||
)
|
||||
|
||||
# 4. Build docker exec command (split command into args to prevent shell injection)
|
||||
cmd_parts = ["docker", "exec", self.container_name, "wp"] + command.split() + ["--allow-root"]
|
||||
cmd_parts = (
|
||||
["docker", "exec", self.container_name, "wp"] + command.split() + ["--allow-root"]
|
||||
)
|
||||
|
||||
self.logger.info(f"Executing: {' '.join(cmd_parts)}")
|
||||
|
||||
@@ -582,17 +603,29 @@ class WPCLIManager:
|
||||
try:
|
||||
# Try GNU stat first, fall back to BSD stat
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
"docker", "exec", self.container_name,
|
||||
"stat", "-c", "%s", export_path,
|
||||
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||
"docker",
|
||||
"exec",
|
||||
self.container_name,
|
||||
"stat",
|
||||
"-c",
|
||||
"%s",
|
||||
export_path,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, _ = await asyncio.wait_for(process.communicate(), timeout=5.0)
|
||||
if process.returncode != 0:
|
||||
# BSD stat fallback
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
"docker", "exec", self.container_name,
|
||||
"stat", "-f", "%z", export_path,
|
||||
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||
"docker",
|
||||
"exec",
|
||||
self.container_name,
|
||||
"stat",
|
||||
"-f",
|
||||
"%z",
|
||||
export_path,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, _ = await asyncio.wait_for(process.communicate(), timeout=5.0)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "mcphub-server"
|
||||
version = "3.4.0"
|
||||
version = "3.5.0"
|
||||
description = "AI-native management hub for WordPress, WooCommerce, and self-hosted services via Model Context Protocol (MCP)"
|
||||
authors = [
|
||||
{name = "MCP Hub", email = "contact@mcphub.dev"}
|
||||
@@ -28,7 +28,7 @@ classifiers = [
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
"fastmcp>=2.14.0,<3.0.0",
|
||||
"fastmcp>=3.0.0,<4.0.0",
|
||||
"httpx>=0.25.0",
|
||||
"aiohttp>=3.9.0",
|
||||
"pydantic>=2.5.0",
|
||||
@@ -69,7 +69,7 @@ requires = ["setuptools>=68.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.setuptools]
|
||||
py-modules = ["server", "server_multi"]
|
||||
py-modules = ["server"]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
include = ["core*", "plugins*"]
|
||||
@@ -208,7 +208,6 @@ unfixable = []
|
||||
"tests/*" = ["S101", "B007"]
|
||||
"tests/legacy/*" = ["E402", "B007", "F401"]
|
||||
"server.py" = ["E402", "F405", "F403", "UP045", "B023"] # Late imports + star imports + dynamic types
|
||||
"server_multi.py" = ["E402", "F405", "F403", "UP045"]
|
||||
"core/__init__.py" = ["F401", "F403"] # Re-exports
|
||||
"plugins/__init__.py" = ["F401"]
|
||||
"plugins/*/__init__.py" = ["F401", "F403", "F405"]
|
||||
|
||||
32
server.py
32
server.py
@@ -51,7 +51,6 @@ from core import (
|
||||
get_api_key_manager,
|
||||
get_audit_logger,
|
||||
get_auth_manager,
|
||||
get_project_manager,
|
||||
get_rate_limiter,
|
||||
# Core architecture modules
|
||||
get_site_manager,
|
||||
@@ -67,6 +66,8 @@ from core.dashboard.routes import (
|
||||
api_get_config,
|
||||
api_list_keys,
|
||||
api_list_sites,
|
||||
# K.5: Settings routes
|
||||
api_save_setting,
|
||||
api_test_site,
|
||||
api_update_site,
|
||||
# E.2: OAuth Social Login routes
|
||||
@@ -108,8 +109,6 @@ from core.dashboard.routes import (
|
||||
# F.3: Service pages
|
||||
dashboard_service_page,
|
||||
dashboard_services_list,
|
||||
# K.5: Settings routes
|
||||
api_save_setting,
|
||||
dashboard_settings_page,
|
||||
# E.3: Site Management pages
|
||||
dashboard_sites_add,
|
||||
@@ -299,7 +298,6 @@ logger.info("Jinja2 template engine initialized")
|
||||
# Initialize managers
|
||||
auth_manager = get_auth_manager()
|
||||
api_key_manager = get_api_key_manager()
|
||||
project_manager = get_project_manager()
|
||||
audit_logger = get_audit_logger()
|
||||
csrf_manager = get_csrf_manager()
|
||||
|
||||
@@ -1106,7 +1104,6 @@ from core import initialize_health_monitor
|
||||
|
||||
# Initialize health monitor
|
||||
health_monitor = initialize_health_monitor(
|
||||
project_manager=project_manager,
|
||||
audit_logger=audit_logger,
|
||||
site_manager=site_manager,
|
||||
metrics_retention_hours=24,
|
||||
@@ -1385,15 +1382,11 @@ async def get_project_info(project_id: str) -> str:
|
||||
import json
|
||||
|
||||
try:
|
||||
# Try legacy ProjectManager first
|
||||
info = project_manager.get_project_info(project_id)
|
||||
|
||||
# Fallback to SiteManager if legacy finds nothing
|
||||
if info is None:
|
||||
for site_data in site_manager.list_all_sites():
|
||||
if site_data["full_id"] == project_id:
|
||||
info = site_data
|
||||
break
|
||||
info = None
|
||||
for site_data in site_manager.list_all_sites():
|
||||
if site_data["full_id"] == project_id:
|
||||
info = site_data
|
||||
break
|
||||
|
||||
if info is None:
|
||||
return f"Project '{project_id}' not found. Use list_projects to see available projects."
|
||||
@@ -3783,12 +3776,11 @@ Use get_endpoints() to see all available MCP endpoints."""
|
||||
import json
|
||||
|
||||
try:
|
||||
info = project_manager.get_project_info(project_id)
|
||||
if info is None:
|
||||
for site_data in site_manager.list_all_sites():
|
||||
if site_data["full_id"] == project_id:
|
||||
info = site_data
|
||||
break
|
||||
info = None
|
||||
for site_data in site_manager.list_all_sites():
|
||||
if site_data["full_id"] == project_id:
|
||||
info = site_data
|
||||
break
|
||||
if info is None:
|
||||
return f"Project '{project_id}' not found. Use list_projects to see available projects."
|
||||
return json.dumps(info, indent=2)
|
||||
|
||||
1152
server_multi.py
1152
server_multi.py
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@ if sys.platform == "win32":
|
||||
# Add project root to path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from core import AuditLogger, ProjectManager, initialize_health_monitor
|
||||
from core import AuditLogger, initialize_health_monitor
|
||||
|
||||
|
||||
async def test_health_monitor():
|
||||
@@ -35,11 +35,9 @@ async def test_health_monitor():
|
||||
|
||||
# Initialize components
|
||||
print("\n1. Initializing components...")
|
||||
project_manager = ProjectManager()
|
||||
audit_logger = AuditLogger()
|
||||
|
||||
health_monitor = initialize_health_monitor(
|
||||
project_manager=project_manager,
|
||||
audit_logger=audit_logger,
|
||||
metrics_retention_hours=24,
|
||||
max_metrics_per_project=1000,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Tests for Admin System Unification (F.4)."""
|
||||
|
||||
|
||||
from core.dashboard.auth import (
|
||||
DashboardSession,
|
||||
get_session_display_info,
|
||||
|
||||
Reference in New Issue
Block a user