The compute_get/compute_images MCP tools typed compute_id as a UUID, so
passing 'local' (the actual id of the built-in compute, which the
compute_images description itself pointed to) was rejected by schema
validation. Both tools now take a string defaulting to 'local', and the
compute_get REST route resolves 'local' through the controller since the
local compute has no database entry.
The device tools reported failures in three shapes: topology-level
entries with only an error key, per-device entries with status 'error'
plus the reason under output (VPCS tool only), and raw exceptions
leaking out of template rendering. Every in-band error entry now
carries status 'failed' and an error message, and invalid Jinja2
templates are reported in-band instead of escaping the handler.
VPCS syntax typed into another node's console is silently discarded
(IOS answers % Invalid input) while the tool still reports success.
get_device_ports_from_topology now carries the GNS3 node type through
to callers, and VPCSCommands fails device preparation with a per-device
error unless the node type is vpcs.
install_appliances_from_image relied on the name+version pair check in
TemplatesService, so the same appliance reached through a second image
(the CSR1000v case) created a second template sharing the name. The auto
path now skips when any template with the same name exists, whatever the
version, and returns a manifest of created and skipped candidates;
POST /images/install replies 200 with that manifest instead of an empty
204, and the image_install MCP tool surfaces it.
POST /appliances/{id}/install replied 204 with an empty body, so the MCP
appliance_install tool crashed with 'Expecting value: line 1 column 1'
while the template had actually been created. The route now returns the
created template (201, response_model=schemas.Template), _create_template
propagates it, and the MCP handler parses the body defensively so an
empty reply degrades to a plain success message.
The controller assigns default names (R-1, R-2, ...) and console ports
in request arrival order. A parallel batch fan-out lets thread scheduling
decide that order, so the first submitted node could end up as R-2.
Batches that rely on default naming (any node without a name) are now
created sequentially; batches with explicit names stay parallel. The
node_create tool description documents the ordering semantics and tells
callers to correlate nodes by node_id.
Splitting the file with keepends=False and rejoining with newlines
dropped the trailing newline of the last line (and every \r of CRLF
files), so returned content was shorter than the file on disk and did
not round-trip. Split with keepends=True and join the selected lines
verbatim; pagination semantics are unchanged.
Batch node/link creation collected results with as_completed, so the
response order followed completion rather than the submitted array and
callers could not correlate entries. Collect in submission order via
pool.map, and report batch deletes as status=success like every other
batch action (the message still says what was deleted).
Per-link markers on serial links need the WAN encapsulation (e.g.
DLT_C_HDLC) so the BPF compiles against the right link layer — the
REST API already accepts it, but the MCP tool never forwarded it.
Create passes it through; update ignores it (changing it would
invalidate the capture file), matching the REST schema semantics.
Symbol tools (symbol_list/get/dimensions/defaults/upload/delete) require
a vision-capable model to be genuinely useful — they shuttle SVG
content, which a text-only LLM cannot inspect or produce. The tool
registrations and imports are commented out (handlers stay in
symbols.py); revisit when multimodal support is worked out.
Loading a .gns3 placed directly in the projects root registered the
shared projects directory as the project path (load_project derives
the path from the file's parent directory). Deleting such an entry ran
rmtree on the projects directory itself, wiping every project until a
root-owned file stopped it, and left a zombie entry in the controller.
Three layers of protection:
- Controller.load_project() refuses a .gns3 whose parent directory is
the projects directory; the normal subdirectory layout is unaffected
- the Project.path setter rejects the projects directory itself and
its ancestors, closing the same hole for POST/PUT with an explicit
path
- Project.delete() uses realpath + commonpath instead of commonprefix:
entries whose path is the projects root are refused, and sibling
directories sharing a string prefix (/srv/projects-evil vs
/srv/projects) are no longer treated as inside the projects dir
Also removes the project_load MCP tool: loading by raw server
filesystem path is a footgun for automated clients; projects can still
be opened by project_id via the remaining tools.
- Slim custom_gns3fy.py to connector-only Gns3Connector and rename to
connector.py; delete the unused Node/Link/Project dataclasses and
endpoint wrapper methods (~3000 lines)
- Move the MCP node/link handler implementations into
gns3_client/api_handlers.py as the shared REST client layer consumed
by both the MCP service and copilot tools; add available_filters
handler (exposed as link_available_filters MCP tool) and
build_gns3_ctx() for copilot callers
- Rewrite the tools_v2 node/link tools on top of the handlers: batch
lifecycle actions now run in parallel, node creation is a single
POST, project-wide status reads replace per-node GETs
- Port Project.nodes_inventory/links_summary aggregation into
project_inventory.py (output shape preserved) and rewrite the
topology reader / project info tools on it, dropping the unused
stats/snapshots/drawings calls
- Delete the dead mcp/nodes.py and mcp/links.py (NODE_TOOLS/LINK_TOOLS
had no consumers; __init__ imports handlers from api_handlers)
- Retarget mcp handler tests to patch api_handlers._get_connector and
replace test_custom_gns3fy.py with inventory contract tests
The MCP project_create tool has passed auto_close=False since 8f8abe410
(2026-06-13), but create_project_handler only forwarded {"name": name}
to the REST API — auto_close was silently dropped and the controller's
Project.__init__ default (True, unchanged since 2016) won. Every project
created via MCP since June has auto_close=true on disk and closes when
the last client disconnects.
- projects.py: forward auto_close when present in params
- test_handlers.py: assert the forwarded json_data (with and without
auto_close)
- test_tool_params.py: the tool/handler param consistency test never
actually checked anything — three blind spots now fixed:
1. dispatch is asyncio.to_thread(_run_handler_sync, ...) whose
node.func is an Attribute, not a Name — no call ever matched
2. tools that build 'params' as a variable before passing it were
skipped; now the initial dict literal is resolved (extra-passed
direction only)
3. tool functions are async defs (ast.AsyncFunctionDef) but the
enclosing-function lookup only matched ast.FunctionDef, so tool_name
was always None
Also: map the two marker handlers missing from HANDLER_FILES, skip
handlers that forward params.items() generically (wildcard), union
passed keys across multi-branch dispatches (node_create single/batch),
and drop two dead helpers.
Verified: full suite 1568 passed; reverting the handler fix turns both
test_tool_handler_param_consistency and test_create red.
MCP is an optional AI feature that already depends on
agent.gns3_copilot (Gns3Connector, nornir/netmiko tools) and whose
MCP_AVAILABLE feature flag lives in gns3server/agent. Moving it there
collocates all AI features under one tree and removes AI code from the
core REST routes.
- git mv gns3server/api/routes/mcp -> gns3server/agent/mcp (no content changes)
- api/server.py, core/tasks.py: update import paths
- tests: tests/api/routes/mcp -> tests/agent/mcp, rewrite patch BASE and
handler imports; fix MCP_DIR depth in test_tool_params.py
- agent/__init__.py: probe the SDK via importlib.import_module so the
top-level name "mcp" is not bound in the agent namespace (it would
shadow the new gns3server.agent.mcp subpackage and break
'from gns3server.agent import mcp')
- docs: update source file paths
Verified: full suite 1567 passed; 82 MCP tools registered, SSE mounted
at /v3/mcp/transport.
SkillsLoader.load_device_skills() now supports two device layouts: the
existing single file and a split directory (device/<device>/_base.yaml +
one YAML per protocol topic). Topic files are merged into the device
skill under 'topics', keyed by their 'topic' field; mismatched
device_type, missing _base.yaml and duplicate topics are handled with
explicit log-and-skip.
get_skill() and DeviceSkillsTool gain a 'topic' parameter following the
injection list -> index -> issue pattern. Topic bodies are never
returned without an explicit topic request - index/summary/full all
serve a topic index instead - so growing a device with new protocol
topics no longer grows the token cost of device-level lookups.
Also fix reload_skills() to actually drop injection skills that fail
validate_skill_format() instead of logging 'skipping' and merging them
anyway.
The vendored gns3fy Node model dropped default_username/default_password
from the API response, and the nornir groups hardcoded empty
credentials, so drivers that require authentication (gns3_ruijie_telnet,
stock netmiko SSH/telnet) could not log in.
Carry the per-node credentials through nodes_inventory() into the
nornir hosts data at host level, where they override the group's empty
fallback. Missing or cleared ("") values keep inheriting from the
group, so no-auth drivers are unaffected.
The vendored gns3fy Node model and its nodes_inventory() now carry the
node's netmiko_device_type field, and get_device_ports_from_topology()
resolves the Netmiko device type from it first, falling back to the
device_type:<type> tag. Nodes created from a template inherit the value
from the template automatically, so automation tooling gets the correct
Netmiko driver without tags.
The vendored gns3fy copy keeps its type lists as literals (the module is
shared with the standalone MCP service and cannot import server enums),
and CONSOLE_TYPES had drifted: 'ssh' and 'docker_exec' were missing while
both are valid server-side. Impact: the copilot topology reader validates
the whole node list in one pydantic pass, so a single vendor NOS node
(console_type 'docker_exec') made it drop the entire project and return
zero devices to every copilot device tool.
Add the missing values plus drift tests asserting the vendored lists
cover the server enums (skipped when ai-features extras are absent).
Replace the `xpra=6.4*` version clamp on the apt-get install command
line with APT pinning via /etc/apt/preferences.d/pin-xpra.
The previous specifier stopped resolving once xpra 6.5 was released:
the xpra metapackage was pinned to 6.4 while its sub-packages
(xpra-client, xpra-server, xpra-codecs, ...) were unconstrained and
defaulted to 6.5, so apt could not satisfy the dependency graph and
the "Build Docker images" action failed.
Pinning all xpra* packages at the preferences level constrains the
whole set consistently (xpra-html5 is handled separately since it
follows a different version scheme, locked to v19). Blocking every
other version with priority -1 ensures that, if the pinned version
ever disappears from the repository, apt fails loudly instead of
silently upgrading to the latest release.
FixesGNS3/gns3-registry#1044
- 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
Dynamips node creation (e.g., Cisco 7200 with multiple adapters)
can exceed the previous 10-second timeout, causing MCP tools to
fail with Read timed out errors.
- Move node file operations (list_files, delete_file) from Gns3Connector to Node class
- Add link capture/reset operations (reset, start_capture, stop_capture) to Link class
- Convert all MCP handlers to use conn.http_call() directly instead of
Gns3Connector/Node/Link abstraction methods
- Register 4 new MCP tools: reset_link, start_capture, stop_capture,
download_capture_file
- Add list_node_files, get_node_file, write_node_file, delete_node_file methods to Gns3Connector
- Add MCP handlers with offset/limit line-based reading for get_node_file
- Auto-truncate files >50KB with truncated flag in response
- Rich metadata returned (total_lines, total_bytes, has_more, etc.)
- Tool docstrings guide AI to check file sizes before reading chunked
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.
Update the default download repository address for GNS3 skills from
yueguobin/GNS3-Skills to gns3/gns3-skills to use the official
organization repository.
This affects:
- Default skills_repo_url in server configuration schema
- Skills manager default repository URL
- Skills configuration defaults
- All documentation references
- Add _validate_bpf_syntax() method to validate BPF expressions
- Use tshark with 1-second timeout for syntax checking
- Check for "Invalid" in output to detect syntax errors
- Validate BPF filters before applying them to links
- Handle tshark not installed scenario gracefully
- Support both single and multiple BPF expressions
- Return detailed error messages for syntax validation failures
- Add show_filters_icon parameter with default value False
- Pass show_filters_icon to link.update() in set and clear operations
- Update tool description to explain default behavior
- Remove "clear" action from description to simplify interface
- Hide filter icon in GNS3 Web UI by default for cleaner UI during fault injection
Add comprehensive packet filter management functionality to GNS3-Copilot,
enabling AI-powered fault injection scenarios with network simulation
capabilities like latency, packet loss, and corruption.
## Changes
### New Features
- **GNS3PacketFilterTool**: New LangChain tool for managing packet filters
on GNS3 links with support for delay, packet loss, corruption,
frequency_drop, and BPF filtering
- Actions: get_available, set, get, clear
- Integrated into troubleshooting_injection mode for fault scenarios
### API Integration
- **Link.available_filters()**: Added method to custom_gns3fy.py Link class
- Queries available filter types for specific links
- API v3+ only (raises ValueError for v2 connectors)
- Returns filter definitions with parameters and constraints
### Tool Integration
- Added GNS3PacketFilterTool to TROUBLESHOOTING_INJECTION_MODE_TOOLS
- Positioned as 3rd tool in fault injection workflow
- Optimized for troubleshooting practice scenarios
## Files Modified
- gns3server/agent/gns3_copilot/agent/gns3_copilot.py
- gns3server/agent/gns3_copilot/gns3_client/custom_gns3fy.py
- gns3server/agent/gns3_copilot/tools_v2/__init__.py
## Files Added
- gns3server/agent/gns3_copilot/tools_v2/gns3_packet_filter.py
## Testing
- All validation tests passed
- Version checking verified (v3+ only)
- Tool integration confirmed in troubleshooting mode
- Register packet_analysis_skills as a LangChain tool for LLM
- LLM can query protocol field definitions before calling packet_analysis
- Follows the same pattern as DeviceSkillsTool and InjectionSkillsTool