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 → SettingsTLS 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.