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

@@ -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",