Skip to content

Model Context Protocol (MCP) Integration ​

This guide explains how to connect AI agents to the Vapor MCP (Model Context Protocol) server for managing virtual machines, storage, and networking through natural language.


Overview ​

Vapor exposes a remote MCP server at /api/v1/mcp using the Streamable HTTP transport defined in the MCP specification (2025-03-26). Any MCP-compatible AI client can connect to this endpoint to discover and invoke 21 infrastructure management tools.

Key Facts ​

PropertyValue
TransportStreamable HTTP (POST/GET/DELETE)
Endpointhttps://<host>:7770/api/v1/mcp
Protocol Version2025-03-26
AuthenticationBearer token (JWT or API token)
TLSEnabled by default (self-signed certs)
Session ManagementIn-memory, 30-minute TTL

1. Authentication ​

The MCP endpoint sits behind Vapor's standard authentication middleware. You must provide a valid Authorization: Bearer <token> header with every request.

API tokens are long-lived, persistent, and ideal for machine-to-machine authentication. They survive server restarts and do not expire unless you set an explicit expiration date.

Step 1 — Log in to obtain a session JWT:

bash
curl -ksS -X POST "https://<host>:7770/api/v1/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"username":"<user>","password":"<pass>","auth_type":"password"}'

Response:

json
{
  "status": "success",
  "data": {
    "token": "eyJhbGci...",
    "expires_at": 1781400787,
    "user": {"username": "awanio", "uid": 1000}
  }
}

Step 2 — Create a persistent API token:

bash
curl -ksS -X POST "https://<host>:7770/api/v1/auth/tokens" \
  -H "Authorization: Bearer <jwt-from-step-1>" \
  -H "Content-Type: application/json" \
  -d '{"name": "mcp-agent"}'

Response:

json
{
  "status": "success",
  "data": {
    "token": "a1b2c3d4e5f6...64-char-hex-string...",
    "token_info": {
      "id": "uuid",
      "name": "mcp-agent",
      "username": "awanio",
      "created_at": "2026-06-13T01:00:00Z"
    }
  }
}

IMPORTANT

Save the token value immediately — it is only shown once. This is the value you will use in Authorization: Bearer <token> headers.

To use an API token with MCP, pass it as a Basic Auth header (the middleware recognizes it):

Authorization: Basic <base64(token:)>

Or embed it as a Bearer token directly — the middleware will try API token validation first, then fall back to JWT parsing.

Option B: JWT Token (Short-Lived) ​

JWTs expire after 24 hours. Use them for quick testing but not for persistent agent configurations.

bash
curl -ksS -X POST "https://<host>:7770/api/v1/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"username":"<user>","password":"<pass>","auth_type":"password"}'

Use the returned data.token in all subsequent MCP requests.


2. Handling Self-Signed TLS Certificates ​

Vapor uses self-signed TLS certificates by default. Most MCP clients will reject the connection with a certificate verification error unless you address this explicitly.

Understanding the Problem ​

When you first start Vapor, it auto-generates self-signed certificates at /var/lib/vapor/certs/. Since these are not signed by a trusted Certificate Authority (CA), any HTTPS client that performs certificate verification will refuse the connection with errors like:

  • CERTIFICATE_VERIFY_FAILED (Python)
  • DEPTH_ZERO_SELF_SIGNED_CERT (Node.js)
  • SSL certificate problem: self-signed certificate (curl)

Obtaining the CA Certificate ​

Before configuring trust, you need to obtain Vapor's server certificate. There are three ways to do this:

  • Option 1 — Download via API (recommended for automation):
    bash
    # Use -k for the initial download since we don't trust the cert yet
    curl -ksS "https://<vapor-host>:7770/api/v1/system/tls/server-cert" \
      -H "Authorization: Bearer <token>" \
      -o vapor-ca.crt
  • Option 2 — Download from the Web UI: Open the Vapor web interface → Settings → TLS Configuration → click "Download CA Certificate". This downloads only the server's public certificate — no private keys.
  • Option 3 — Copy directly from the server:
    bash
    scp root@<vapor-host>:/var/lib/vapor/certs/server.crt ./vapor-ca.crt

Add Vapor's self-signed certificate to the system's trusted certificate store. This is the most secure approach because it preserves TLS verification.

On the machine running the MCP client (using vapor-ca.crt obtained above):

bash
# On Debian/Ubuntu:
sudo cp vapor-ca.crt /usr/local/share/ca-certificates/vapor-ca.crt
sudo update-ca-certificates

# On RHEL/CentOS/Fedora:
sudo cp vapor-ca.crt /etc/pki/ca-trust/source/anchors/vapor-ca.crt
sudo update-ca-trust

# On macOS:
sudo security add-trusted-cert -d -r trustRoot \
  -k /Library/Keychains/System.keychain vapor-ca.crt

After trusting the certificate, all HTTPS clients on that machine will accept Vapor's TLS connection without special flags.

Approach 2: Specify the CA Bundle at Runtime ​

Point your MCP client or HTTP library at Vapor's certificate file directly, without modifying the system trust store.

For curl:

bash
curl --cacert /path/to/vapor-server.crt -X POST "https://<host>:7770/api/v1/mcp" ...

For Node.js (used by most MCP client SDKs):

bash
export NODE_EXTRA_CA_CERTS=/path/to/vapor-server.crt

For Python:

python
import httpx
client = httpx.Client(verify="/path/to/vapor-server.crt")

Approach 3: Disable TLS Verification (Development Only) ​

WARNING

This disables all certificate verification and is vulnerable to man-in-the-middle attacks. Only use this in isolated development environments.

For curl:

bash
curl -k ...

For Node.js:

bash
export NODE_TLS_REJECT_UNAUTHORIZED=0

For Python:

python
import httpx
client = httpx.Client(verify=False)

Replace the self-signed certificate with one issued by a trusted CA (e.g., Let's Encrypt, internal PKI):

yaml
# vapor.conf
tls_enabled: true
tls_cert_file: "/etc/ssl/vapor/fullchain.pem"
tls_key_file: "/etc/ssl/vapor/privkey.pem"

Then restart Vapor:

bash
sudo systemctl restart vapor

This eliminates all certificate trust issues for any client.


3. MCP Protocol Flow ​

The MCP Streamable HTTP transport uses a single endpoint (/api/v1/mcp) with three HTTP methods:

POST   /api/v1/mcp   ← JSON-RPC messages (initialize, tools/list, tools/call, ping)
GET    /api/v1/mcp   ← Server-Sent Events stream (for server notifications)
DELETE /api/v1/mcp   ← Terminate session

Connection Lifecycle ​

┌─────────┐                          ┌──────────┐
│  Client  │                          │  Vapor   │
└────┬─────┘                          └────┬─────┘
     │                                     │
     │  POST initialize                    │
     │────────────────────────────────────►│
     │◄────────────────────────────────────│
     │  200 + Mcp-Session-Id header        │
     │                                     │
     │  POST notifications/initialized     │
     │  (with Mcp-Session-Id header)       │
     │────────────────────────────────────►│
     │◄────────────────────────────────────│
     │  202 Accepted                       │
     │                                     │
     │  POST tools/list                    │
     │  (with Mcp-Session-Id header)       │
     │────────────────────────────────────►│
     │◄────────────────────────────────────│
     │  200 + tool definitions             │
     │                                     │
     │  POST tools/call                    │
     │  (with Mcp-Session-Id header)       │
     │────────────────────────────────────►│
     │◄────────────────────────────────────│
     │  200 + tool result                  │
     │                                     │
     │  DELETE (session termination)       │
     │  (with Mcp-Session-Id header)       │
     │────────────────────────────────────►│
     │◄────────────────────────────────────│
     │  204 No Content                     │
     └                                    └

Step-by-Step with curl ​

1. Initialize:

bash
curl -ksS -D /tmp/headers.txt -X POST "https://localhost:7770/api/v1/mcp" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-03-26",
      "capabilities": {},
      "clientInfo": {"name": "my-agent", "version": "1.0"}
    }
  }'

Extract the session ID from the response headers:

bash
SESSION_ID=$(grep -i 'Mcp-Session-Id' /tmp/headers.txt | awk '{print $2}' | tr -d '\r')

2. Acknowledge initialization:

bash
curl -ksS -X POST "https://localhost:7770/api/v1/mcp" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -H "Mcp-Session-Id: ${SESSION_ID}" \
  -d '{"jsonrpc": "2.0", "method": "notifications/initialized"}'

3. List available tools:

bash
curl -ksS -X POST "https://localhost:7770/api/v1/mcp" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -H "Mcp-Session-Id: ${SESSION_ID}" \
  -d '{"jsonrpc": "2.0", "id": 2, "method": "tools/list"}'

4. Call a tool:

bash
curl -ksS -X POST "https://localhost:7770/api/v1/mcp" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -H "Mcp-Session-Id: ${SESSION_ID}" \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "vm_list",
      "arguments": {}
    }
  }'

5. Terminate session:

bash
curl -ksS -X DELETE "https://localhost:7770/api/v1/mcp" \
  -H "Authorization: Bearer <token>" \
  -H "Mcp-Session-Id: ${SESSION_ID}"

4. Available Tools ​

The Vapor MCP server exposes 21 tools grouped by domain:

Virtual Machine Management ​

ToolDescription
vm_listList all VMs with status, UUID, vCPUs, memory, disk info
vm_getGet detailed VM info by UUID or name
vm_actionLifecycle actions: start, shutdown, reboot, pause, resume, reset, force-off, force-reboot
vm_deleteDelete a VM (optionally remove disk volumes)
vm_snapshotsList/create/revert/delete VM snapshots
vm_backupsList/create/delete VM backups (full/incremental/differential)
vm_templatesList/get/delete VM templates
vm_metricsGet live CPU, memory, disk, and network metrics

Virtualization Networking ​

ToolDescription
virt_networkManage libvirt virtual networks: list, get, start, stop, delete, dhcp_leases

Virtualization Storage ​

ToolDescription
virt_storage_poolManage storage pools: list, get, start, stop, delete
virt_volumeManage storage volumes: list, get, create, delete, resize
virt_isoManage ISO images: list, get, delete

Host Networking ​

ToolDescription
host_network_interfacesList physical/virtual network interfaces
host_network_bridgesList Linux bridge devices
host_network_bondsList network bond devices
host_network_vlansList VLAN interfaces

Host Storage ​

ToolDescription
host_storage_disksList physical block devices
host_storage_lvm_vgsList LVM volume groups
host_storage_lvm_lvsList LVM logical volumes
host_storage_lvm_pvsList LVM physical volumes
host_storage_raidList RAID/MD arrays

5. Client Configuration ​

Claude Desktop ​

Add the following to your Claude Desktop configuration file:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
json
{
  "mcpServers": {
    "vapor": {
      "type": "streamableHttp",
      "url": "https://<vapor-host>:7770/api/v1/mcp",
      "headers": {
        "Authorization": "Bearer <your-api-token>"
      }
    }
  }
}

For self-signed certificates, trust the CA at the OS level (see Section 2, Approach 1) before launching Claude Desktop.

Cursor / VS Code MCP Extensions ​

In your project's .cursor/mcp.json or the equivalent MCP configuration:

json
{
  "mcpServers": {
    "vapor": {
      "type": "streamableHttp",
      "url": "https://<vapor-host>:7770/api/v1/mcp",
      "headers": {
        "Authorization": "Bearer <your-api-token>"
      }
    }
  }
}

If using self-signed certificates with a Node.js-based MCP client, set the environment variable before launching:

bash
export NODE_EXTRA_CA_CERTS=/path/to/vapor-server.crt

6. Error Handling ​

JSON-RPC Errors ​

The MCP server uses standard JSON-RPC 2.0 error codes:

CodeMeaningDescription
-32700Parse errorInvalid JSON in request body
-32600Invalid requestMissing jsonrpc field or bad version
-32601Method not foundUnknown method name
-32602Invalid paramsBad parameters for method
-32603Internal errorServer-side error

Tool Errors ​

Tool errors are returned as successful JSON-RPC responses with isError: true in the result:

json
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "content": [{"type": "text", "text": "Failed to start VM: domain is already running"}],
    "isError": true
  }
}

This allows the AI agent to see the error message and decide how to proceed.

Authentication Errors ​

HTTP StatusMeaning
401Missing or invalid Authorization header
403Valid token but insufficient permissions

7. Session Management ​

  • Sessions are created automatically when you send initialize.
  • The server returns an Mcp-Session-Id header — include it in all subsequent requests.
  • Sessions expire after 30 minutes of inactivity.
  • Sessions are stored in-memory and do not survive server restarts.
  • To terminate a session early, send DELETE /api/v1/mcp with the session header.

If a session expires, the client should re-initialize by sending a new initialize request.


8. Security Considerations ​

  1. Always use TLS. The MCP endpoint transmits authentication tokens and may return sensitive infrastructure data. Never run Vapor without TLS in a networked environment.
  2. Use API tokens for agents. API tokens are scoped per-user, persistent, and can be revoked individually without affecting other sessions. Create dedicated tokens for each AI agent integration and name them descriptively (e.g., claude-desktop, cursor-ide).
  3. Restrict network access. If the Vapor host is only accessed from specific machines, use firewall rules to limit access to port 7770.
  4. Revoke tokens when no longer needed:
    bash
    curl -ksS -X DELETE "https://<host>:7770/api/v1/auth/tokens/<token-id>" \
      -H "Authorization: Bearer <jwt>"
  5. Monitor tool calls. All MCP tool invocations are logged by Vapor with session IDs for audit purposes. Check server logs for [MCP] entries.