diff --git a/gns3server/api/routes/mcp/__init__.py b/gns3server/api/routes/mcp/__init__.py index 0190452a4..b3752dd70 100644 --- a/gns3server/api/routes/mcp/__init__.py +++ b/gns3server/api/routes/mcp/__init__.py @@ -39,6 +39,7 @@ from fastapi.responses import Response from pydantic import Field from mcp.server.fastmcp import FastMCP +from mcp.server.transport_security import TransportSecuritySettings from gns3server.config import Config from gns3server.services import auth_service @@ -101,7 +102,22 @@ def _server_url() -> str: # ── FastMCP Server ──────────────────────────────────────────────────── -mcp = FastMCP("GNS3 MCP Server") +def _create_mcp_server() -> FastMCP: + """Create MCP server with security settings from configuration.""" + cfg = Config.instance().settings.Server + + mcp = FastMCP( + "GNS3 MCP Server", + transport_security=TransportSecuritySettings( + enable_dns_rebinding_protection=cfg.mcp_enable_dns_rebinding_protection, + allowed_hosts=cfg.mcp_allowed_hosts, + allowed_origins=cfg.mcp_allowed_origins, + ), + ) + return mcp + + +mcp = _create_mcp_server() # ── Tool handlers ───────────────────────────────────────────────────── diff --git a/gns3server/config_samples/gns3_server.conf b/gns3server/config_samples/gns3_server.conf index c19a60077..36588378a 100644 --- a/gns3server/config_samples/gns3_server.conf +++ b/gns3server/config_samples/gns3_server.conf @@ -183,4 +183,17 @@ memory = 2g ; CPU cores per container (e.g., 1.0, 2.0) cpus = 1.0 ; Process limit per container -pids_limit = 1000 \ No newline at end of file +pids_limit = 1000 +; MCP (Model Context Protocol) transport security settings +; Enable DNS rebinding protection for MCP server +mcp_enable_dns_rebinding_protection = True + +; Allowed hosts for MCP connections (comma-separated, use * for wildcard) +; Default: * (allow all hosts - recommended for GNS3 VM distribution) +; For enhanced security, restrict to specific hosts: 127.0.0.1:*,localhost:*,192.168.1.3:* +mcp_allowed_hosts = * + +; Allowed origins for MCP connections (comma-separated) +; Default: * (allow all origins) +; For enhanced security: http://127.0.0.1:*,http://localhost:*,http://192.168.1.3:* +mcp_allowed_origins = * diff --git a/gns3server/schemas/config.py b/gns3server/schemas/config.py index be66b2853..bb32e48fd 100644 --- a/gns3server/schemas/config.py +++ b/gns3server/schemas/config.py @@ -162,8 +162,28 @@ class ServerSettings(BaseModel): skills_repo_url: str = "https://github.com/gns3/gns3-skills.git" skills_repo_branch: str = "main" skills_auto_update: bool = True + + # MCP (Model Context Protocol) transport security settings + mcp_enable_dns_rebinding_protection: bool = True + mcp_allowed_hosts: list[str] = Field(default=["*"], description="Allowed Host header values for MCP server") + mcp_allowed_origins: list[str] = Field(default=["*"], description="Allowed Origin header values for MCP server") + model_config = ConfigDict(validate_assignment=True, str_strip_whitespace=True) + @field_validator("mcp_allowed_hosts", mode="before") + @classmethod + def split_mcp_allowed_hosts(cls, v): + if v and isinstance(v, str): + return v.split(",") + return list() + + @field_validator("mcp_allowed_origins", mode="before") + @classmethod + def split_mcp_allowed_origins(cls, v): + if v and isinstance(v, str): + return v.split(",") + return list() + @field_validator("additional_images_paths", mode="before") @classmethod def split_additional_images_paths(cls, v):