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
| Property | Value |
|---|---|
| Transport | Streamable HTTP (POST/GET/DELETE) |
| Endpoint | https://<host>:7770/api/v1/mcp |
| Protocol Version | 2025-03-26 |
| Authentication | Bearer token (JWT or API token) |
| TLS | Enabled by default (self-signed certs) |
| Session Management | In-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.
Option A: API Token (Recommended for AI Agents)
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:
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:
{
"status": "success",
"data": {
"token": "eyJhbGci...",
"expires_at": 1781400787,
"user": {"username": "awanio", "uid": 1000}
}
}Step 2 — Create a persistent API token:
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:
{
"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.
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
Approach 1: Trust the Self-Signed CA (Recommended for Production)
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):
# 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.crtAfter 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:
curl --cacert /path/to/vapor-server.crt -X POST "https://<host>:7770/api/v1/mcp" ...For Node.js (used by most MCP client SDKs):
export NODE_EXTRA_CA_CERTS=/path/to/vapor-server.crtFor 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:
curl -k ...For Node.js:
export NODE_TLS_REJECT_UNAUTHORIZED=0For Python:
import httpx
client = httpx.Client(verify=False)Approach 4: Use a Proper CA Certificate (Recommended for Multi-User Production)
Replace the self-signed certificate with one issued by a trusted CA (e.g., Let's Encrypt, internal PKI):
# vapor.conf
tls_enabled: true
tls_cert_file: "/etc/ssl/vapor/fullchain.pem"
tls_key_file: "/etc/ssl/vapor/privkey.pem"Then restart Vapor:
sudo systemctl restart vaporThis 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 sessionConnection 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:
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:
SESSION_ID=$(grep -i 'Mcp-Session-Id' /tmp/headers.txt | awk '{print $2}' | tr -d '\r')2. Acknowledge initialization:
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:
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:
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:
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
| Tool | Description |
|---|---|
vm_list | List all VMs with status, UUID, vCPUs, memory, disk info |
vm_get | Get detailed VM info by UUID or name |
vm_action | Lifecycle actions: start, shutdown, reboot, pause, resume, reset, force-off, force-reboot |
vm_delete | Delete a VM (optionally remove disk volumes) |
vm_snapshots | List/create/revert/delete VM snapshots |
vm_backups | List/create/delete VM backups (full/incremental/differential) |
vm_templates | List/get/delete VM templates |
vm_metrics | Get live CPU, memory, disk, and network metrics |
Virtualization Networking
| Tool | Description |
|---|---|
virt_network | Manage libvirt virtual networks: list, get, start, stop, delete, dhcp_leases |
Virtualization Storage
| Tool | Description |
|---|---|
virt_storage_pool | Manage storage pools: list, get, start, stop, delete |
virt_volume | Manage storage volumes: list, get, create, delete, resize |
virt_iso | Manage ISO images: list, get, delete |
Host Networking
| Tool | Description |
|---|---|
host_network_interfaces | List physical/virtual network interfaces |
host_network_bridges | List Linux bridge devices |
host_network_bonds | List network bond devices |
host_network_vlans | List VLAN interfaces |
Host Storage
| Tool | Description |
|---|---|
host_storage_disks | List physical block devices |
host_storage_lvm_vgs | List LVM volume groups |
host_storage_lvm_lvs | List LVM logical volumes |
host_storage_lvm_pvs | List LVM physical volumes |
host_storage_raid | List 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
{
"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:
{
"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:
export NODE_EXTRA_CA_CERTS=/path/to/vapor-server.crt6. Error Handling
JSON-RPC Errors
The MCP server uses standard JSON-RPC 2.0 error codes:
| Code | Meaning | Description |
|---|---|---|
| -32700 | Parse error | Invalid JSON in request body |
| -32600 | Invalid request | Missing jsonrpc field or bad version |
| -32601 | Method not found | Unknown method name |
| -32602 | Invalid params | Bad parameters for method |
| -32603 | Internal error | Server-side error |
Tool Errors
Tool errors are returned as successful JSON-RPC responses with isError: true in the result:
{
"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 Status | Meaning |
|---|---|
| 401 | Missing or invalid Authorization header |
| 403 | Valid token but insufficient permissions |
7. Session Management
- Sessions are created automatically when you send
initialize. - The server returns an
Mcp-Session-Idheader — 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/mcpwith the session header.
If a session expires, the client should re-initialize by sending a new initialize request.
8. Security Considerations
- Always use TLS. The MCP endpoint transmits authentication tokens and may return sensitive infrastructure data. Never run Vapor without TLS in a networked environment.
- 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). - Restrict network access. If the Vapor host is only accessed from specific machines, use firewall rules to limit access to port 7770.
- Revoke tokens when no longer needed:bash
curl -ksS -X DELETE "https://<host>:7770/api/v1/auth/tokens/<token-id>" \ -H "Authorization: Bearer <jwt>" - Monitor tool calls. All MCP tool invocations are logged by Vapor with session IDs for audit purposes. Check server logs for
[MCP]entries.