MCP server that gives LLMs visual access to bare metal server consoles via iKVM/IPMI. Capture screenshots of remote server screens for AI-powered debugging of bare metal deployments.
This MCP (Model Context Protocol) server exposes bare metal server iKVM consoles as tools that LLMs can use. An LLM can list available servers and request screenshots of their console output — useful for debugging boot issues, kernel panics, network misconfigurations, and other problems visible on the server screen.
LLM ──MCP──► ikvm-mcp server ──OVH API──► get viewer URL
│
├──KVM WebSocket──► extract JPEG frame ──► PNG screenshot (AMI/ASRockRack BMC)
└──VNC/RFB over WebSocket──► capture framebuffer ──► PNG screenshot (standard VNC)
- The MCP server authenticates with the cloud provider API (OVH)
- Requests an iKVM/IPMI HTML5 console session
- Establishes a BMC session (extracts session cookie and CSRF token from the viewer page)
- Connects to the KVM WebSocket, receives JPEG video frames
- Extracts the first complete JPEG frame and converts it to PNG
- Returns the image to the LLM via MCP
| Provider | Status |
|---|---|
| OVH | Supported |
List all available bare metal servers with iKVM/IPMI access.
Parameters: none
Returns: JSON array of server objects:
[
{
"id": "ns1234567.ip-1-2-3.eu",
"name": "ns1234567.ip-1-2-3.eu",
"provider": "ovh",
"datacenter": "sbg3",
"ip": "1.2.3.4"
}
]Capture a screenshot of a server's iKVM/IPMI console screen. Returns a PNG image optimized for LLM vision (2x upscale + brightness boost). Set raw=true to get the original unprocessed image.
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
serverId |
string | (required) | Server identifier (e.g., ns1234567.ip-1-2-3.eu) |
raw |
boolean | false |
Return the raw screenshot without LLM optimization |
Returns: PNG image content block (base64-encoded)
- Bun >= 1.0
- OVH API credentials (Application Key, Application Secret, Consumer Key)
- Create an application at https://eu.api.ovh.com/createApp/ (or your region's equivalent:
ca.api.ovh.com,api.us.ovhcloud.com) - Note the Application Key and Application Secret
- Request a consumer key with the required permissions:
curl -X POST https://eu.api.ovh.com/1.0/auth/credential \
-H "X-Ovh-Application: YOUR_APP_KEY" \
-H "Content-Type: application/json" \
-d '{
"accessRules": [
{ "method": "GET", "path": "/dedicated/server" },
{ "method": "GET", "path": "/dedicated/server/*" },
{ "method": "POST", "path": "/dedicated/server/*/features/ipmi/access" },
{ "method": "GET", "path": "/dedicated/server/*/features/ipmi/access" },
{ "method": "GET", "path": "/dedicated/server/*/task/*" }
]
}'- The response includes a
consumerKeyand avalidationUrl— open the URL in your browser to authorize the key
git clone https://github.com/xd-ventures/ovh-ikvm-mcp.git
cd ovh-ikvm-mcp
bun installSet the required environment variables:
export OVH_ENDPOINT="eu" # API region: eu, ca, or us
export OVH_APPLICATION_KEY="your-app-key"
export OVH_APPLICATION_SECRET="your-app-secret"
export OVH_CONSUMER_KEY="your-consumer-key"bun startThe server starts on http://localhost:3001/mcp by default.
A pre-built image is published to GitHub Container Registry on every push to main.
docker run --rm \
-e OVH_ENDPOINT=eu \
-e OVH_APPLICATION_KEY=your-app-key \
-e OVH_APPLICATION_SECRET=your-app-secret \
-e OVH_CONSUMER_KEY=your-consumer-key \
-p 3001:3001 \
ghcr.io/xd-ventures/ovh-ikvm-mcp:latestThe container runs as a non-root user and includes a health check on /health.
You can also build the image locally:
docker build -t ovh-ikvm-mcp .
docker run --rm -e OVH_ENDPOINT=eu -e OVH_APPLICATION_KEY=... -e OVH_APPLICATION_SECRET=... -e OVH_CONSUMER_KEY=... -p 3001:3001 ovh-ikvm-mcpThe server uses Streamable HTTP transport (not stdio), so all agents connect to it over HTTP. Start the server first, then configure your agent to point at it.
1. Start the server:
cd ovh-ikvm-mcp
export OVH_ENDPOINT="eu"
export OVH_APPLICATION_KEY="your-app-key"
export OVH_APPLICATION_SECRET="your-app-secret"
export OVH_CONSUMER_KEY="your-consumer-key"
bun startYou should see: ikvm-mcp server listening on http://localhost:3001/mcp
2. Configure your agent (pick one):
Register via the CLI:
claude mcp add ikvm --transport http http://localhost:3001/mcpOr add to your project's .mcp.json so teammates get it automatically:
{
"mcpServers": {
"ikvm": {
"type": "http",
"url": "http://localhost:3001/mcp"
}
}
}Verify it's connected:
claude mcp listMake sure the OVH environment variables are set in the terminal where you run bun start.
Add to your Claude Desktop MCP config:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"ikvm": {
"url": "http://localhost:3001/mcp"
}
}
}After saving, restart Claude Desktop. The hammer icon in the input box confirms the MCP tools are loaded.
Add to .cursor/mcp.json in your project root (or ~/.cursor/mcp.json for global):
{
"mcpServers": {
"ikvm": {
"url": "http://localhost:3001/mcp"
}
}
}After saving, open Cursor Settings > MCP and verify the ikvm server shows a green indicator.
Add to ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"ikvm": {
"serverUrl": "http://localhost:3001/mcp"
}
}
}Once connected, you can use these prompts with any agent:
List servers and take a screenshot:
List my bare metal servers using the ikvm MCP, then take a screenshot
of the first server's console. Describe what you see on the screen.
Debug a server that won't boot:
My server ns1234567.ip-1-2-3.eu is stuck during boot. Take a screenshot
of its console and diagnose the issue. If you see a kernel panic or
GRUB error, suggest how to fix it.
Monitor server state:
Take a screenshot of ns1234567.ip-1-2-3.eu console. Is the server
at a login prompt, showing an error, or still booting? If it's at
a login prompt, the OS reinstall was successful.
Compare raw vs optimized output:
Take two screenshots of ns1234567.ip-1-2-3.eu — one default (optimized)
and one with raw=true. Compare the readability.
# Install dependencies
bun install
# Run tests
bun test
# Run with watch mode
bun run dev
# Type check
bun run typecheck
# Lint
bun run lint
# Lint and auto-fix
bun run lint:fix
# Format
bun run formatTests use Bun's built-in test runner with:
- Mock BMC server — simulates ASRockRack/AMI BMC with session auth and JPEG frame WebSocket
- Test VNC server — minimal RFB server serving a known image for VNC client tests
- Mock OVH API — simulates OVH REST API endpoints with auth validation
- In-memory MCP transport — tests MCP tool invocation without HTTP overhead
Run the full verification suite:
bun run typecheck && bun run lint && bun testsrc/
├── index.ts # Entry point — Bun HTTP server with MCP transport
├── kvm/
│ ├── types.ts # KVM/BMC session types
│ ├── bmc-session.ts # BMC session establishment (cookie + CSRF extraction)
│ ├── screenshot.ts # KVM screenshot: IVTP WebSocket → AST2500 decode → PNG
│ ├── optimize.ts # LLM vision optimization (2x upscale + brightness boost)
│ └── decoder-fetcher.ts # Runtime fetcher for AST2500 decoder from BMC
├── vnc/
│ ├── rfb-client.ts # VNC/RFB protocol client over WebSocket
│ ├── encodings.ts # RFB framebuffer encoding decoders (Raw, CopyRect)
│ ├── types.ts # RFB protocol types and constants
│ └── screenshot.ts # High-level: connect → capture → PNG encode
├── providers/
│ ├── types.ts # Provider interface (listServers, getScreenshot)
│ └── ovh/
│ ├── api.ts # OVH API client with request signing
│ ├── provider.ts # OVH provider implementation
│ └── types.ts # OVH-specific types
└── mcp/
└── server.ts # MCP server setup + tool definitions
Adding a new provider means implementing the Provider interface:
interface Provider {
name: string;
listServers(): Promise<Server[]>;
getScreenshot(serverId: string): Promise<Buffer>;
}OVH servers use ASRockRack/AMI BMC firmware with a proprietary WebSocket KVM protocol at wss://<host>/kvm. The KVM client:
- Fetches the viewer redirect page and extracts the
QSESSIONIDcookie andgarcCSRF token - Connects to the KVM WebSocket endpoint
- Scans incoming binary messages for JPEG SOI (
0xFFD8) / EOI (0xFFD9) markers - Extracts the first complete JPEG frame and converts it to PNG
The VNC client connects directly to the WebSocket endpoint exposed by iKVM viewers that use standard VNC, performing the RFB protocol handshake (version negotiation, security, framebuffer request) without needing a headless browser.
Supported RFB features:
- Protocol versions: 3.3, 3.7, 3.8
- Security: None, VNC Authentication (DES challenge-response)
- Encodings: Raw, CopyRect
This project connects to BMC firmware by American Megatrends Inc. (AMI). The AST2500 video decoder is fetched at runtime from the BMC's web interface and is not distributed with this project. See NOTICE for details.
See CONTRIBUTING.md for development setup, workflow, and guidelines.
This project follows the Contributor Covenant 3.0 code of conduct.
To report security vulnerabilities, see SECURITY.md.
Apache 2.0 — see LICENSE.