gns3-server/docs/features/mcp-service.md

13 KiB

MCP (Model Context Protocol) Service

Overview

GNS3 Server provides a standard Model Context Protocol (MCP) interface, allowing AI assistants like Claude to interact with GNS3 network simulations through SSE (Server-Sent Events) transport.

The MCP service exposes GNS3 project management operations as MCP tools that can be discovered and called by MCP clients.

Endpoints

Path Method Description
/v3/mcp/ GET MCP service metadata
/v3/mcp/transport/sse GET SSE stream (MCP connection)
/v3/mcp/transport/messages/ POST JSON-RPC messages

Authentication

The SSE endpoint supports two types of credentials, passed the same way.

  1. Authorization header (recommended):

    Authorization: Bearer <jwt_or_api_key>
    
  2. Query parameter (for clients that don't support custom headers):

    GET /v3/mcp/transport/sse?token=<jwt_or_api_key>
    

Option 1: JWT Token (24h expiry)

curl -X POST http://localhost:3080/v3/access/users/authenticate \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "admin"}'

Default lifetime is 1440 minutes (24 hours). Configurable in gns3_server.conf:

jwt_access_token_expire_minutes = 1440  ; 24 hours

API keys never expire and can be revoked individually. Create one via the REST API:

# Create an API key (requires a JWT to authenticate)
curl -X POST http://localhost:3080/v3/access/api-keys \
  -H "Authorization: Bearer <your_jwt>" \
  -H "Content-Type: application/json" \
  -d '{"name": "MCP Production"}'
# Response: {"api_key": "gns3_a1b2c3d4...", "api_key_id": "...", ...}
# ⚠️ The key is only shown once — save it immediately.

API key management endpoints:

Endpoint Description
POST /v3/access/api-keys Create a new key (returns plaintext once)
GET /v3/access/api-keys List all your keys
POST /v3/access/api-keys/{id}/revoke Revoke a key (can be restored)
POST /v3/access/api-keys/{id}/restore Restore a revoked key
DELETE /v3/access/api-keys/{id} Permanently delete a key

Both JWT tokens and API keys work for MCP and REST API endpoints interchangeably.

Available Tools

82 tools across 12 categories:

Project (15)

Tool Description
project_list List all projects
project_get Get project details
project_create Create a project
project_delete Delete a project
project_open Open a closed project
project_close Close an open project
project_stats Get project statistics
project_update Update project properties
project_duplicate Duplicate a project
project_readme_get Get project README content
project_readme_update Update project README
project_lock Lock project (prevent edits)
project_unlock Unlock project
project_load Load project from path
project_locked Check if project is locked

Node (22)

Tool Description
node_list List all nodes in a project
node_get Get node details
node_create Create a node from template
node_delete Delete a node
node_update Update node properties
node_start Start a node
node_stop Stop a node
node_reload Reload a node
node_suspend Suspend a node
node_console Get WebSocket console URL
node_file_list List files in node directory
node_file_get Read a file (with offset/limit)
node_file_write Write a file
node_file_delete Delete a file
node_start_all Start all nodes
node_stop_all Stop all nodes
node_suspend_all Suspend all nodes
node_reload_all Reload all nodes
node_duplicate Duplicate a node
node_isolate Isolate a node (suspend links)
node_unisolate Un-isolate a node (resume links)
node_links List links connected to a node
Tool Description
link_list List all links in a project
link_get Get link details
link_create Create a link between nodes
link_delete Delete a link
link_update Update link (suspend, filters)
link_reset Reset link (delete + recreate)
link_capture_start Start packet capture
link_capture_stop Stop packet capture
link_capture_download Get PCAP download URL

Template (5)

Tool Description
template_list List all templates
template_get Get template details
template_create Create a template (Docker needs image)
template_update Update a template
template_delete Delete a template

Compute (3)

Tool Description
compute_list List registered remote computes
compute_get Get compute details (requires UUID)
compute_images List emulator images on a compute

Snapshot (4)

Tool Description
snapshot_list List snapshots
snapshot_create Create a snapshot
snapshot_delete Delete a snapshot
snapshot_restore Restore a snapshot

Drawing (5)

Tool Description
drawing_list List drawings on canvas
drawing_get Get drawing details
drawing_create Create drawing (SVG label/shape/image)
drawing_update Update drawing (position, rotation, SVG)
drawing_delete Delete a drawing

Symbol (6)

Tool Description
symbol_list List all symbols
symbol_get Get symbol download URL
symbol_dimensions Get symbol dimensions
symbol_defaults Get default symbol mapping
symbol_upload Upload a custom symbol (SVG content)
symbol_delete Delete a custom symbol (built-in: 403)

Appliance (3)

Tool Description
appliance_list List appliances from template library
appliance_get Get appliance details
appliance_install Create template from appliance (images must exist locally)

Image (5)

Tool Description
image_list List all images
image_get Get image details
image_delete Delete an image
image_prune Remove images not referenced by any template
image_install Auto-create templates from uploaded images by checksum

Server (2)

Tool Description
server_version Get GNS3 server version
server_statistics Get server statistics (computes, projects, nodes)

Device Config (3)

Tool Description
device_config_send Push config commands to devices via console (Nornir + Netmiko)
device_command_run Run read-only show commands on devices
vpcs_config_set Configure VPCS devices (IP, gateway, etc.)

Requires nodes to be started first. Device type is auto-detected from the node's device_type:<type> tag.

Configuration

Claude Code (CLI)

# Option A: Using API key (recommended — never expires)
claude mcp add --transport sse My_GNS3_Server \
  http://localhost:3080/v3/mcp/transport/sse \
  -H "Authorization: Bearer gns3_a1b2c3d4..."

# Option B: Using JWT token (expires after 24h)
TOKEN=$(curl -s -X POST http://localhost:3080/v3/access/users/authenticate \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "admin"}' | python3 -c \
  "import sys,json; print(json.load(sys.stdin)['access_token'])")

claude mcp add --transport sse My_GNS3_Server \
  http://localhost:3080/v3/mcp/transport/sse \
  -H "Authorization: Bearer $TOKEN"

Claude Desktop

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "My_GNS3_Server": {
      "url": "http://localhost:3080/v3/mcp/transport/sse?token=your_jwt_or_api_key"
    }
  }
}

Transport Security

MCP server uses FastMCP's DNS rebinding protection to prevent attackers from exploiting DNS resolution to access the MCP endpoint through unauthorized domains.

Default Behaviour

DNS rebinding protection is disabled by default, allowing connections from any host. This aligns with GNS3 server's default host = 0.0.0.0 binding policy, which is designed for VM distribution scenarios where users access the server from various network locations.

Enabling Protection

Add to gns3_server.conf under the [Server] section:

; Enable DNS rebinding protection for MCP server
mcp_enable_dns_rebinding_protection = True

; Allowed hosts (comma-separated, "host:*" port wildcard patterns only)
mcp_allowed_hosts = 127.0.0.1:*,localhost:*,192.168.1.3:*

; Allowed origins (comma-separated)
mcp_allowed_origins = http://127.0.0.1:*,http://localhost:*,http://192.168.1.3:*

Note

: The MCP library only supports "host:*" port wildcard patterns (e.g., "192.168.1.3:*"). Standalone "*" wildcards are not supported.

Protection Mechanism

When protection is enabled, the MCP server validates the Host header of incoming SSE connection requests:

# Verify the request's Host header matches allowed patterns
validate_request  check Host header  421 Misdirected Request if invalid

This prevents DNS rebinding attacks:

  1. Attacker registers evil.com pointing to your server's IP
  2. User's browser makes requests to evil.com:3080
  3. MCP server checks Host header = "evil.com:3080"
  4. "evil.com:3080" is not in allowed_hosts → connection rejected

Behaviour Summary

mcp_enable_dns_rebinding_protection Result
False (default) All hosts allowed
True + correct hosts configured Only configured hosts allowed
True + missing/wrong hosts Connections rejected with 421

For public-facing MCP servers, set allowed_hosts to your server's domain name.

Architecture

sequenceDiagram
    participant Client as Claude Code / Claude Desktop
    participant MCP as MCP Service
    participant Auth as JWT Auth
    participant GNS3 as GNS3 REST API

    Note over Client: 1. Connect with JWT
    Client->>MCP: GET /sse (token in header or query)
    MCP->>Auth: Validate Token
    Auth-->>MCP: Token Valid
    MCP-->>Client: event: endpoint /messages/?session_id=xxx

    Note over Client: 2. Initialize
    Client->>MCP: POST /messages/ (initialize)
    MCP-->>Client: event: message (protocolVersion, capabilities)

    Note over Client: 3. List & Call Tools
    Client->>MCP: POST /messages/ (tools/list)
    MCP-->>Client: event: message (tools list)

    Client->>MCP: POST /messages/ (tools/call list_projects)
    MCP->>GNS3: Gns3Connector HTTP request
    GNS3-->>MCP: Projects data
    MCP-->>Client: event: message (tool result)

Internal Implementation

  • FastMCP (Anthropic MCP SDK) is used for tool registration and SSE transport
  • The SSE app is mounted as a Starlette sub-application under /v3/mcp/transport
  • JWT tokens are validated using GNS3's existing auth_service
  • Tool handlers use Gns3Connector (from custom_gns3fy) to call GNS3's own REST API, keeping the MCP layer decoupled
  • The JWT token is stored in a contextvars.ContextVar so it is available within tool handler threads (Python ≥ 3.9 propagates contextvars through asyncio.to_thread)

Console WebSocket

The get_node_console_info tool returns a WebSocket URL for connecting to a node's console. This endpoint is protocol-agnostic — it works for telnet, ssh, and vnc console types alike. The WebSocket simply proxies raw byte streams between the client and the compute node; protocol negotiation (e.g. SSH key exchange) happens on the compute side.

The WebSocket URL is constructed using the server's _server_url(), which resolves the host as follows:

Server.host value Resolved host in URL
Specific IP or hostname (e.g. 192.168.1.3) Used as-is
0.0.0.0 (IPv4 any, default) Detected via default route interface IP
:: (IPv6 any) Detected via default route interface IP
Detection failure Fallback to 127.0.0.1

When Server.host is 0.0.0.0 (listen on all interfaces), the MCP server discovers the default route interface IP using a UDP socket connect to 8.8.8.8:80 — no network data is sent, the operating system simply selects the interface that would be used for the default route. This ensures the returned WebSocket URL uses a reachable address (e.g. 192.168.1.3 instead of 127.0.0.1).

If the configured host is already a specific IP or hostname (not 0.0.0.0), it is used directly in the URL without modification.

Use websocat to connect from the command line:

# The host in the URL is automatically resolved to a reachable address
websocat ws://192.168.1.3:3080/v3/projects/{project_id}/nodes/{node_id}/console/ws?token=<jwt>

Source Files

File Purpose
gns3server/api/routes/mcp/__init__.py FastMCP server, tool decorators, SSE transport, JWT auth wrapper
gns3server/api/routes/mcp/projects.py Project tool handlers
gns3server/api/routes/mcp/nodes.py Node tool handlers
gns3server/api/routes/mcp/links.py Link tool handlers
gns3server/api/routes/mcp/templates.py Template tool handlers
gns3server/api/routes/mcp/computes.py Compute tool handlers
gns3server/api/server.py Mounts MCP routes via register_starlette_routes()