Make AI features (AI Copilot + MCP) optional via [ai-features] extra

- Move fastmcp from core requirements.txt to mcp-requirements.txt
- Add MCP_AVAILABLE feature flag in agent/__init__.py (graceful degradation)
- Guard MCP imports/registration in server.py and tasks.py
- Replace ai-copilot/mcp/ai-support extras with single ai-features extra
- Add stub MCP routes returning 501 when MCP is not installed
- Add gns3server-uninstall-ai-features CLI command
- Remove old gns3server-uninstall-ai-copilot command
- Update all error messages and docs to reference ai-features

Closes #2794
This commit is contained in:
YueGuobin committed 2026-06-27 10:14:43 +08:00
1 parent 58d3055dd3
commit 292b60efaa
11 files changed
+117 -58

No files matched your search

+28 -8
View File
@@ -15,14 +15,15 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Agent module with optional AI Copilot support.
Agent module with optional AI Copilot and MCP support.
This module provides the AI Copilot functionality as an optional feature.
If the AI dependencies are not installed, the module will be disabled but
will not prevent the server from starting.
This module provides the AI Copilot and MCP (Model Context Protocol)
functionality as optional features. If the respective dependencies are
not installed, the affected features will be disabled but will not
prevent the server from starting.
Installation:
pip install gns3-server[ai-copilot]
pip install gns3-server[ai-features] # Install all AI features
"""
import logging
@@ -49,7 +50,8 @@ except ImportError as e:
# AI dependencies not installed, disable AI Copilot feature
logging.warning(
f"AI Copilot dependencies not installed: {e}. "
"AI features will be disabled. Install with: pip install gns3-server[ai-copilot]"
"AI features will be disabled. "
"Install with: pip install gns3-server[ai-features]"
)
AI_COPILOT_AVAILABLE = False
@@ -63,7 +65,7 @@ except ImportError as e:
"""
raise RuntimeError(
"AI Copilot is not available. "
"Install AI dependencies with: pip install gns3-server[ai-copilot]"
"Install AI dependencies with: pip install gns3-server[ai-features]"
)
class ProjectAgentManager:
@@ -74,12 +76,30 @@ except ImportError as e:
def __init__(self):
raise RuntimeError(
"AI Copilot is not available. "
"Install AI dependencies with: pip install gns3-server[ai-copilot]"
"Install AI dependencies with: pip install gns3-server[ai-features]"
)
# Feature flag: MCP (Model Context Protocol) is available
MCP_AVAILABLE = False
# Try to import MCP dependencies
try:
import mcp.server.fastmcp # noqa: F401 — test import only
MCP_AVAILABLE = True
except ImportError:
# MCP dependencies not installed, disable MCP feature
logging.warning(
"MCP dependencies not installed. "
"MCP features will be disabled. "
"Install with: pip install gns3-server[ai-features]"
)
MCP_AVAILABLE = False
__all__ = [
"AI_COPILOT_AVAILABLE",
"MCP_AVAILABLE",
"get_project_agent_manager",
"ProjectAgentManager",
]
+1 -1
View File
@@ -39,7 +39,7 @@ else:
async def ai_not_available(path: str = ""):
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED,
detail="AI Copilot is not available. Install AI dependencies with: pip install gns3-server[ai-copilot]"
detail="AI Copilot is not available. Install AI dependencies with: pip install gns3-server[ai-features]"
)
from . import controller
+23 -3
View File
@@ -45,9 +45,26 @@ from gns3server.controller.controller_error import (
from gns3server.api.routes import controller, index
from gns3server.api.routes.compute import compute_api
from gns3server.api.routes import mcp
from gns3server.core import tasks
# MCP is an optional feature — import only if dependencies are installed
from gns3server.agent import MCP_AVAILABLE
if MCP_AVAILABLE:
from gns3server.api.routes import mcp
_mcp_router = mcp.router
else:
from fastapi import APIRouter
_mcp_router = APIRouter(prefix="/mcp", tags=["MCP"])
@_mcp_router.api_route("/{path:path}", methods=["GET", "POST", "DELETE", "PATCH", "PUT"])
async def mcp_not_available(path: str = ""):
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED,
detail="MCP is not available. Install AI dependencies with: pip install gns3-server[ai-features]"
)
import logging
log = logging.getLogger(__name__)
@@ -76,7 +93,9 @@ def get_application() -> FastAPI:
application.include_router(controller.router, prefix="/v3")
application.mount("/static", StaticFiles(packages=[('gns3server', 'static')], html=True), name="static")
application.mount("/v3/compute", compute_api, name="compute")
application.include_router(mcp.router, prefix="/v3", tags=["MCP"])
# Register MCP routes (stub returns 501 if MCP dependencies are not installed)
application.include_router(_mcp_router, prefix="/v3", tags=["MCP"])
return application
@@ -84,7 +103,8 @@ def get_application() -> FastAPI:
app = get_application()
# Register MCP SSE transport routes (Starlette-level, for raw ASGI access)
mcp.register_starlette_routes(app)
if MCP_AVAILABLE:
mcp.register_starlette_routes(app)
# Monkey Patch uvicorn signal handler to detect the application is shutting down
app.state.exiting = False
+6 -3
View File
@@ -84,9 +84,12 @@ async def startup(app: FastAPI) -> None:
m = module.instance()
m.port_manager = PortManager.instance()
# Mark MCP server as ready to accept connections
from gns3server.api.routes.mcp import set_mcp_server_ready
set_mcp_server_ready(True)
# Mark MCP server as ready to accept connections (if MCP is available)
from gns3server.agent import MCP_AVAILABLE
if MCP_AVAILABLE:
from gns3server.api.routes.mcp import set_mcp_server_ready
set_mcp_server_ready(True)
log.info("GNS3 server startup completed")
@@ -1,10 +1,10 @@
#!/usr/bin/env python3
"""
Uninstall AI Copilot dependencies.
Uninstall AI Features dependencies (AI Copilot + MCP).
Usage:
gns3server-uninstall-ai-copilot
gns3server-uninstall-ai-copilot -y
gns3server-uninstall-ai-features
gns3server-uninstall-ai-features -y
"""
import os
@@ -13,38 +13,50 @@ import subprocess
import argparse
def get_ai_packages():
"""Read packages from ai-requirements.txt."""
# Find the requirements file
def _find_base_dir():
"""Find the project root directory."""
if hasattr(sys, '_MEIPASS'):
# PyInstaller bundle
base_dir = os.path.dirname(sys.executable)
else:
# __file__ = gns3server/utils/uninstall_ai_copilot.py
# Need to go up 2 levels to reach project root
base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
return os.path.dirname(sys.executable)
return os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
requirements_file = os.path.join(base_dir, "ai-requirements.txt")
def _read_requirements(filename):
"""Read packages from a requirements file."""
base_dir = _find_base_dir()
filepath = os.path.join(base_dir, filename)
packages = []
with open(requirements_file, "r") as f:
with open(filepath, "r") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
# Remove version specifiers
package = line.split(">=")[0].split("==")[0].split("~=")[0]
packages.append(package)
return packages
def get_ai_packages():
"""Read packages from all AI features requirements files."""
packages = []
packages.extend(_read_requirements("ai-requirements.txt"))
packages.extend(_read_requirements("mcp-requirements.txt"))
# Remove duplicates while preserving order
seen = set()
unique = []
for pkg in packages:
if pkg not in seen:
seen.add(pkg)
unique.append(pkg)
return unique
def uninstall(packages, yes=False):
"""Uninstall packages."""
if not packages:
print("No packages found to uninstall.")
return
print(f"Found {len(packages)} AI Copilot dependencies:")
print(f"Found {len(packages)} AI Features dependencies:")
for pkg in packages:
print(f" - {pkg}")
print()
@@ -70,13 +82,13 @@ def uninstall(packages, yes=False):
except Exception as e:
print(f" Error removing {package}: {e}")
print("\nAI Copilot dependencies have been uninstalled.")
print("You can reinstall them with: pip install gns3-server[ai-copilot]")
print("\nAI Features dependencies have been uninstalled.")
print("You can reinstall them with: pip install gns3-server[ai-features]")
def main():
parser = argparse.ArgumentParser(
description="Uninstall AI Copilot dependencies"
description="Uninstall AI Features dependencies (AI Copilot + MCP)"
)
parser.add_argument(
"-y", "--yes", action="store_true",