From bb39238f0266e9e13d966d3d85ffcb973891beb0 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Fri, 5 Jun 2026 23:06:06 +0800 Subject: [PATCH] feat: add configurable MCP transport security settings via gns3_server.conf Add MCP transport security configuration to gns3_server.conf with permissive defaults that align with GNS3's design philosophy and VM distribution requirements. ## Changes ### 1. Configuration Schema (gns3server/schemas/config.py) - Added MCP transport security fields to ServerSettings class: - mcp_enable_dns_rebinding_protection (bool, default: True) - mcp_allowed_hosts (list[str], default: ["*"]) - mcp_allowed_origins (list[str], default: ["*"]) - Added field validators to handle comma-separated string input ### 2. MCP Server Initialization (gns3server/api/routes/mcp/__init__.py) - Import TransportSecuritySettings from mcp.server.transport_security - Added _create_mcp_server() function to read configuration - Updated FastMCP instantiation to use configured security settings ### 3. Configuration Sample (gns3server/config_samples/gns3_server.conf) - Added MCP transport security settings section - Documented default behavior and security options - Provided examples for different use cases ## Design Philosophy **Default: Allow All Hosts** (matches GNS3's 0.0.0.0 binding): - VM distribution works out-of-the-box - Users can access from any network location - Security-conscious users can restrict when needed **Security: Optional Restriction**: Users can configure specific hosts for enhanced security: ``ini mcp_allowed_hosts = 127.0.0.1:*,localhost:*,192.168.1.3:* mcp_allowed_origins = http://127.0.0.1:*,http://localhost:*,http://192.168.1.3:* ``` ## Benefits - Flexible: Users can configure based on security requirements - User-friendly: Default matches GNS3's 0.0.0.0 binding philosophy - Maintainable: No code changes needed for different deployment scenarios - Secure: DNS rebinding protection remains enabled with configurable hosts ## Related - Issue #2771 - FastMCP DNS rebinding protection design - Existing skills configuration in ServerSettings --- gns3server/api/routes/mcp/__init__.py | 18 +++++++++++++++++- gns3server/config_samples/gns3_server.conf | 15 ++++++++++++++- gns3server/schemas/config.py | 20 ++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) 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):