6908 Commits

Author SHA1 Message Date
YueGuobin
1dcbd9199f
docs: document MCP server URL host resolution behavior
Add documentation explaining how _server_url() resolves the host
when Server.host is 0.0.0.0 or :: — using the default route
interface IP instead of hardcoding 127.0.0.1.
2026-06-06 00:52:21 +08:00
YueGuobin
db9772ca7a
fix: resolve MCP server URL host via default route IP when bound to 0.0.0.0
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.
2026-06-06 00:48:22 +08:00
YueGuobin
4d87b57f18
docs: add MCP transport security documentation
Added Transport Security section to MCP service documentation covering:
- Default behaviour (disabled, allow all hosts)
- How to enable protection via gns3_server.conf
- Protection mechanism (Host header validation)
- DNS rebinding attack prevention explanation
- Behaviour summary table
2026-06-05 23:40:01 +08:00
YueGuobin
0e6db9a7b6
fix: correct MCP transport security config to actually allow all hosts by default
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
2026-06-05 23:20:57 +08:00
YueGuobin
bb39238f02
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
2026-06-05 23:06:06 +08:00
Jeremy Grossmann
68f63597eb
Merge pull request #2769 from yueguobin/fix/load-feature-skills
fix: add load_feature_skills() to properly load network planning features
2026-06-05 16:58:06 +02:00
YueGuobin
e76f3970ca
fix: add load_feature_skills() to properly load network planning features
The feature directory contains network planning and design functionalities
(e.g., topology_planner), not device-specific features. These were not being
loaded because load_device_skills() only scanned the device directory.

Changes:
- Added new load_feature_skills() method in SkillsLoader
- Modified reload_skills() to load both device and feature directories
- Device skills: device-specific configurations (e.g., VPCS)
- Feature skills: network planning functionalities (e.g., topology planner)
- Both are now properly loaded into SKILLS_REGISTRY

This ensures that network planning features like topology_planner are available
via the device_skills tool with proper category classification.
2026-06-05 22:25:17 +08:00
Jeremy Grossmann
6497db6f30
Merge pull request #2768 from yueguobin/feature/mcp-service
feat: MCP (Model Context Protocol) service with SSE transport
2026-06-05 10:25:58 +02:00
YueGuobin
24953c1712
refactor: convert all MCP tool parameter descriptions to Annotated+Field
- 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
2026-06-05 14:02:58 +08:00
YueGuobin
62db5caf6a
fix: remove platformdirs upper bound to resolve fastmcp-slim dependency conflict
- Remove platformdirs<3 constraint (was for Debian packaging only)
- fastmcp-slim>=3.4.0 requires platformdirs>=4.0.0
- Debian packaging is no longer a concern
2026-06-05 13:38:25 +08:00
YueGuobin
4c44a32db7
docs: update get_node_console_info description with websocat connection workflow 2026-06-05 13:17:23 +08:00
YueGuobin
b415ae0af6
fix: add missing fastmcp dependency to resolve CI test failures
- Add fastmcp>=3.4.0 to requirements.txt
- Fixes ModuleNotFoundError: No module named 'mcp'
- Resolves CI test failures in compute routes tests
2026-06-05 12:32:27 +08:00
YueGuobin
a056fa3450
refactor: move all imports to top of __init__.py 2026-06-05 01:16:17 +08:00
YueGuobin
f1078a97e2
test: add /v3/mcp/ to allowed public endpoints 2026-06-05 01:07:20 +08:00
YueGuobin
f9f7ba7a53
docs: update memory record to 30 tools 2026-06-05 00:20:34 +08:00
YueGuobin
25dc1e9009
docs: update MCP feature doc to cover all 30 tools and console WS 2026-06-05 00:20:13 +08:00
YueGuobin
f34de4c075
fix: remove console_host/port from get_node_console_info
Return only ws_url + websocat command to avoid LLM misinterpreting
direct telnet connection.
2026-06-04 23:53:03 +08:00
YueGuobin
8775583b83
feat: add get_node_console_info tool
Returns console type, host, port and a suggested command (e.g. telnet,
vncviewer) for connecting to a node's console. Total: 30 tools.
2026-06-04 23:45:34 +08:00
YueGuobin
0c572f2e9c
docs: add MCP service design memory record 2026-06-04 23:25:22 +08:00
YueGuobin
e416ef8d5e
feat: add 3 Compute MCP tools
- Add list_computes, get_compute, get_compute_images
- Total MCP tools: 29
2026-06-04 23:22:14 +08:00
YueGuobin
6889f51737
feat: add 5 Template MCP tools
Add list_templates, get_template, create_template, update_template,
delete_template. Total MCP tools: 26.
2026-06-04 23:21:01 +08:00
YueGuobin
cad216705a
feat: add Node and Link MCP tools, update copyright
- Add 9 node tools and 5 link tools
- Update copyright year to 2026, add author
2026-06-04 23:19:00 +08:00
YueGuobin
5b23ac81d0
docs: add MCP service feature documentation 2026-06-04 23:09:44 +08:00
YueGuobin
ccdd307d54
docs: add MCP service feature documentation 2026-06-04 23:09:08 +08:00
YueGuobin
1e9b3d5879
feat: complete MCP SSE transport with JWT auth
- SSE endpoint at /v3/mcp/transport/sse with token auth
- Supports Authorization: Bearer header and ?token= query param
- JWT validated via GNS3 auth_service, stored in contextvars
- Tool handlers use GNS3 REST API via Gns3Connector with JWT token
- 7 project tools: list_projects, get_project, create_project,
  delete_project, open_project, close_project, get_project_stats
- Claude Code: claude mcp add --transport sse ... -H 'Authorization: Bearer <jwt>'
- Claude Desktop: SSE URL with ?token=<jwt>
2026-06-04 23:06:20 +08:00
YueGuobin
19e7533cd7
feat: support Authorization header and query param for MCP token
- 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
2026-06-04 22:38:49 +08:00
YueGuobin
55b3a7d622
feat: implement standard MCP protocol with SSE transport
- 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
2026-06-04 22:19:43 +08:00
YueGuobin
7086db4226
feat: add MCP (Model Context Protocol) service with project tools
- 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
2026-06-04 13:53:05 +08:00
Jeremy Grossmann
6c7e8fb370
Merge pull request #2767 from yueguobin/fix/docker-container-status-detection
Add running project check for fast duplication
2026-06-03 18:44:22 +02:00
YueGuobin
fb1a5a3385
Add project memory: Docker container stop delay analysis 2026-06-03 22:47:48 +08:00
YueGuobin
e8e1930e0b
Remove extra blank line from merge 2026-06-03 22:22:03 +08:00
YueGuobin
4adaba8e8c
Revert container state detection in create()
The _get_container_state() call in create() has no practical effect:
Docker's POST /containers/create only creates the container without
starting it, so a newly created container can never be in 'running'
or 'paused' state.
2026-06-03 22:20:34 +08:00
YueGuobin
2adc09604c
Fix Docker VM tests for container status detection on node creation
The create() method now calls _get_container_state() after creating the
container to detect if the container is already running. Mock this method
in all test_create_* tests so the new code path doesn't fail with
KeyError: 'State' when the Docker.query mock response lacks a State field.
2026-06-03 22:17:43 +08:00
Guobin Yue
9712e9328a
Merge branch '3.1' into fix/docker-container-status-detection 2026-06-03 21:49:56 +08:00
Jeremy Grossmann
305f1cad21
Merge pull request #2766 from yueguobin/fix/web-ui-update-script-remote-check
Fix web-ui update script to handle custom GitHub URL changes
2026-06-03 09:30:01 +02:00
YueGuobin
7ab04a0f9a
Add running project check for fast duplication 2026-06-03 12:20:26 +08:00
YueGuobin
b6e1f84740
Move running project check before fast duplication
Move the is_running() check from _fast_duplication() to
duplicate() to avoid the error message being wrapped by
the except Exception handler. This ensures the error
message is clean and prevents wasted fast duplication
attempts on running projects.
2026-06-03 12:18:28 +08:00
YueGuobin
c4440d882d
Add running project check for fast duplication
Add is_running() check at the beginning of _fast_duplication()
to prevent duplicating a project while nodes are running.
Previously, only the export/import fallback path had this check,
which meant running nodes were not detected when fast duplication
succeeded. This aligns with the duplicate API behavior and
provides a consistent safeguard against data inconsistencies.
2026-06-03 12:15:27 +08:00
YueGuobin
45b5f8d7e2
Fix Docker container status detection on node creation
When a Docker container is created (e.g., when loading a project), the node
status should reflect the actual container state. Previously, the node status
was always set to 'stopped' even if the container was already running.

This fix checks the container state after creation and updates the node
status accordingly:
- If container is running: status = 'started'
- If container is paused: status = 'suspended'
- If container is exited: status = 'stopped' (default)

This ensures that project.is_running() correctly detects running Docker
containers when attempting to export/duplicate a project, fixing the issue
where running Docker nodes were not detected and prompted for shutdown.
2026-06-03 11:51:55 +08:00
YueGuobin
d4fb45f862
Fix web-ui update script to handle custom GitHub URL changes
When using --url parameter with a different GitHub repository, the script
now checks if the existing clone's remote matches the provided URL. If they
don't match, it removes the old clone and re-clones from the new URL.

This prevents errors when switching between different forks or branches
of the gns3-web-ui repository.
2026-06-03 11:25:00 +08:00
Jeremy Grossmann
1d7feaf994
Merge pull request #2765 from GNS3/upgrade-dependencies
Upgrade dependencies
2026-06-02 18:11:51 +02:00
grossmj
5f3978c57c
Upgrade pytest-asyncio to v1.4.0 2026-06-02 18:07:41 +02:00
grossmj
18e20f0918
Upgrade dependencies 2026-06-02 17:48:16 +02:00
Jeremy Grossmann
5ab05fb688
Merge pull request #2762 from yueguobin/fix/docker-container-rename
Fix unnecessary Docker container recreation when renaming a project
2026-06-02 09:34:11 +02:00
YueGuobin
0ba180ad1d
Fix unnecessary Docker container recreation when renaming a project
When renaming a project that has running Docker containers, the containers
were unnecessarily stopped, removed, and recreated, even though the project
name change doesn't affect container configuration.

Root cause:
- Client sends complete project object including variables: [] during rename
- Controller unconditionally notified all computes about the update
- Docker nodes rebuild containers on any project update notification

Solution:
- Only notify compute nodes when variables field has actual content
- Treat None and [] as semantically equivalent (no variables)
- Empty variables don't affect running containers, so no need to update

Impact:
- Project rename operations no longer trigger ~7 second container rebuilds
- Only actual variable changes trigger container recreation
- Fixes issue #2760
2026-06-02 13:04:44 +08:00
grossmj
06b02981df
Development on 3.1.0.dev3 2026-06-02 00:04:50 +02:00
Jeremy Grossmann
1f98d18ae7
Merge pull request #2761 from yueguobin/fix/project-rename-filename-sync
Fix project rename and duplicate issues (#2759)
2026-06-01 23:59:24 +02:00
YueGuobin
b5d3556add
Fix project rename and duplicate issues
Fixes #2759

When renaming a project:
- Update self._filename to match the new project name
- Rename the .gns3 file on disk to keep it in sync
- Add error handling for file rename failures

When duplicating a project:
- Use self._filename (actual filename) instead of self.name
- This handles the case where a project has been renamed
- Prevents 'No such file or directory' errors

The root cause was that project renaming only updated the project name
in memory and in the .gns3 file content, but did not update the actual
.gns3 filename. This caused duplicate operations to fail because they
tried to read a file with the new name that didn't exist.
2026-06-01 22:19:44 +08:00
Jeremy Grossmann
ed6273413e
Merge pull request #2758 from yueguobin/verify-resource-pool-deletion
Fix: properly delete resource records when deleting resource pools
2026-06-01 10:55:26 +02:00
YueGuobin
f3aaa902c6
Fix double deletion issue in remove_resource_from_pool
Remove duplicate delete_resource call from remove_resource_from_pool since
the API layer already handles resource deletion. This prevents conflicts
where the API layer tries to delete a resource that was already deleted by
the repository layer.

The complete fix is now:
- remove_resource_from_pool: Only removes resource from pool (API handles deletion)
- delete_resource_pool: Deletes all resource records before deleting pool
2026-06-01 12:46:15 +08:00