When GNS3 server is configured to listen on 0.0.0.0 (all interfaces),
_server_url() was hardcoding 127.0.0.1, making the WebSocket console
URL unreachable from remote MCP clients.
Use the UDP connect trick (connect to 8.8.8.8:80 without sending data)
to discover the default route interface IP, which is the address remote
clients can actually reach.
The MCP library's TransportSecurityMiddleware only supports exact host
matches or "host:*" port wildcards. It does NOT support a standalone "*"
wildcard to mean "allow all hosts" — setting allowed_hosts=["*"] would
reject every connection because no Host header equals "*".
Worse, when transport_security=None was passed to FastMCP while its default
host is "127.0.0.1", FastMCP would auto-enable protection with strict
localhost-only rules, overriding GNS3's intent to allow all hosts.
Root cause analysis:
- FastMCP auto-enables DNS rebinding protection when host is localhost
and no explicit TransportSecuritySettings is provided
- GNS3 was passing transport_security=None (indirectly via FastMCP's default)
when protection was disabled, triggering the auto-enable
- The TransportSecuritySettings "allowed_hosts" list does NOT support "*"
as a catch-all wildcard
This fix:
1. Always pass an explicit TransportSecuritySettings to FastMCP
- Disabled: TransportSecuritySettings(enable_dns_rebinding_protection=False)
- Enabled: TransportSecuritySettings(enable_dns_rebinding_protection=True, ...)
2. Restore mcp_allowed_hosts and mcp_allowed_origins config fields
3. Set mcp_enable_dns_rebinding_protection default to False (allow all hosts)
Behaviour:
- Default (no config change): all hosts can connect to MCP server
- With mcp_enable_dns_rebinding_protection=true: only configured hosts
- Aligns with GNS3 server's 0.0.0.0 binding policy
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
- Replace Args: docstring blocks with Annotated[str, Field(description=...)]
so parameter descriptions appear in inputSchema.properties.*.description
- mcp.server.fastmcp does not parse Args: blocks from docstrings;
only Annotated with pydantic Field injects descriptions into the
structured JSON Schema visible to AI clients via tools/list
- Remove redundant Args: blocks from docstrings (info moved to Field)
- Restore full 4-step websocat workflow in get_node_console_info docstring
with connection, command sending, response receiving, and timeout
- SSE endpoint supports both Authorization: Bearer header and ?token= query param
- Claude Code can use headers (no URL exposure)
- Claude Desktop (EventSource) can use ?token= URL param
- Use FastMCP (Anthropic MCP SDK) for tool registration and SSE transport
- Mount SSE app under /v3/mcp/transport with JWT token authentication
- Token passed via ?token=<jwt> query parameter on SSE connection
- Token validated against GNS3 auth_service and stored in contextvars
- Tool handlers create Gns3Connector with JWT token to call GNS3 REST API
- 7 project tools: list_projects, get_project, create_project, delete_project,
open_project, close_project, get_project_stats
- Unauthenticated SSE connections return 401
- Add MCPTool/MCPToolRegistry system for centralized tool registration
- Add 7 project-related MCP tools: list_projects, get_project, create_project,
delete_project, open_project, close_project, get_project_stats
- Tools use Gns3Connector (custom_gns3fy) to call GNS3 REST API via HTTP loopback,
keeping the MCP layer decoupled from controller internals
- Handlers run in thread pool via asyncio.to_thread() to avoid blocking
the event loop on synchronous requests calls
- Unified POST /v3/mcp/execute endpoint with JWT authentication