Skip to main content
Glama

microvm-mcp

A standalone first pass at private Linux development workspaces over MCP and HTTP. One workspace credential can create and operate its own Firecracker microVMs. Commands execute in a full Debian guest, never in the service process.

This directory is its own Python package, dependency lockfile, image recipe, CLI, and test suite. It can move into another repository unchanged. It imports nothing from fast-agent. The optional fast-agent integration generates configuration for the existing host_fetch transport; no harness or demo code changes are required.

Implemented

  • Official MCP Python SDK, Streamable HTTP at /mcp, plus matching JSON HTTP operations at POST /v1/tools/{tool_name}. Both share validation and authorization.

  • Host-issued opaque workspace tokens, SHA-256 token hashes in SQLite, expiration, rotation without changing ownership, and workspace revocation. Every request is authenticated; every VM/job/file operation checks workspace ownership.

  • Operator-configured guest images, chosen per VM. The recipe builds a Debian 13 image with Python, Node, Git, Bash, GCC and standard build tools. Guest commands run as UID 1000. Customize the trusted image recipe to install system packages; the API does not grant root or passwordless sudo to commands.

  • One jailed Firecracker process and cgroup per VM. Distinct jail UIDs, private network namespaces, CPU/memory/PID limits, no host-directory mounts in the guest.

  • Reflink CoW raw ext4 disks on an XFS/Btrfs host filesystem. Unsupported storage fails preflight. Guest disks are fixed at the image's size (3 GiB in the recipe).

  • Async command handles; separate stdout/stderr returned as readable UTF-8 text; a shared output bound; cancellation/timeouts cover descendants through guest cgroups, including processes that call setsid.

  • Workspace file transfer using base64, bounded to 128 KiB per call, with symlink traversal refused. Larger artifacts move over a guest stream.

  • Host-to-guest byte streams over vsock, authorized like every other operation, for MCP servers in the guest, operator shells, VNC and bulk transfer, with guest networking off.

  • Manual pause (retains RAM), hibernate (full memory/device checkpoint, releases the VMM process), resume, automatic idle suspension, and automatic wake on work.

  • Network off by default. Network on adds IPv4 NAT with guest-source enforcement, private/metadata destination denial and inbound denial. No guest IPv6 egress.

  • Durable metadata and creation/execution retry receipts. A transport-uncertain execution is not replayed automatically. vm_status.jobs exposes known handles for reconciliation; the history is capped at 4096 requests per workspace.

Related MCP server: bunkervm

Debian packages

The project builds three packages for Debian 13 (trixie), amd64:

Package

Contents

microvm-mcp

Service, locked Python dependencies, administrator CLI, image installer, configuration and systemd unit

microvm-mcp-python

Private CPython 3.13.15 runtime; leaves the system Python unchanged

microvm-mcp-firecracker-1.17

Pinned Firecracker and jailer 1.17.0 binaries

Build from the repository root on an amd64 machine with Docker and network access:

scripts/build-debs
# Optional: choose another output directory.
scripts/build-debs /absolute/path/to/packages

The default output directory is dist/debian/, containing the three .deb files, .changes, .buildinfo and SHA256SUMS. The builder verifies pinned upstream archives and dependency hashes, then runs Ruff, pytest and Debian packaging in a container with networking disabled. Lintian checks the resulting packages. Building does not install packages on the host. To test installation, reinstall, removal and state retention in a disposable Debian container:

scripts/test-debs

Copy the three packages to the server and install them with APT:

sudo apt install ./microvm-mcp_*.deb ./microvm-mcp-python_*.deb \
  ./microvm-mcp-firecracker-1.17_*.deb

Installation requires no Python downloads or compilation; APT may fetch ordinary system dependencies. The service remains disabled and stopped until configured. Provision KVM, cgroup v2, reflink-capable storage and a separately built guest kernel/rootfs, then edit /etc/microvm-mcp.toml before enabling the service. See the Debian packaging guide for image installation, startup, upgrade behavior, runtime maintenance and validation details.

Kubernetes

deploy/k8s/ builds a container image from these packages and runs it as a privileged, node-pinned StatefulSet with local storage and a TLS sidecar. The host requirements below still apply to the node. See the Kubernetes deployment guide.

Host setup

For installation directly from source, follow the setup below.

The validated target is Linux x86_64 with real /dev/kvm, cgroup v2, matching Firecracker and jailer 1.17.0, Python 3.11+, ip, nft, and iptables. Firecracker 1.16 has a vsock pause/resume defect; preflight refuses versions older than 1.17. The service currently runs as root to provision jails and networking. Keep its listener on loopback; put authenticated remote access behind TLS.

Install Firecracker/jailer from the operator-verified upstream release. Supply an operator-pinned Firecracker-compatible Linux kernel with ext4, virtio-block, virtio-net, virtio-vsock and cgroup/PID-controller support built in. The real smoke used the Firecracker CI x86_64 vmlinux-6.1.155 kernel. Kernel and rootfs SHA-256 digests are checked before serving.

Use a dedicated reflink-capable volume for the base image and state: reflinks must stay on one filesystem. Reserve enough real capacity for every VM's fully written disk and two RAM checkpoint generations, even though new disks share blocks. Set max_storage_gib, max_memory_mib, and max_vms to the host budget; these are conservative reservations across all retained VMs, including hibernated VMs. Admission and checkpointing also check available disk space. This is not a substitute for storage isolation from unrelated host services.

uv sync --locked
# Docker, mkfs.ext4 >= 1.47.1 and debugfs are needed only to build the image.
# No root: ownership comes from the export, and the script checks it.
scripts/build-image /var/lib/microvm-images
sudo install -d -m 0700 /var/lib/microvm-mcp

Copy examples/service.toml, set the binary/kernel/image paths, and replace the digest placeholders using sha256sum. The image digest pins the deployed image; the Docker recipe uses Debian package repositories and is not a bit-reproducible historical rebuild.

Guest images

Each [images.<id>] table is one bootable image. rootfs and kernel default to <state_dir>/images/<id>/rootfs.ext4 and vmlinux, the layout written by microvm-mcp-install-image, which prints the table to paste. One image is marked default = true; it boots when vm_create names none.

[images.debian-13-dev-v1]
rootfs_sha256 = "..."
kernel_sha256 = "..."
default = true

[images.re-analysis-v1]
rootfs_sha256 = "..."
kernel_sha256 = "..."
min_memory_mib = 4096  # refuse smaller VMs; also the default memory_mib
# memory_mib = 6144    # default when vm_create omits memory_mib

Every configured image's rootfs and kernel digests are verified, and a reflink is probed, before the service accepts requests. Unknown keys and malformed IDs fail startup. A configuration with the older top-level image_id, rootfs, kernel, rootfs_sha256 and kernel_sha256 still works as one default image; mixing both forms is an error. vm_create advertises the configured IDs as an enum, and vm_status reports each VM's image.

A VM copies its kernel and clones its disk at creation, so pause, hibernate and resume never read the image again. Removing an image from the configuration does not strand its paused or hibernated VMs; the service logs them at startup, and they can be resumed, used and destroyed, but no new VM can boot that image. Restoring still requires the Firecracker binary the VM was created with. Storage reservations use each image's own rootfs size, recorded on the VM. Reserve unused host UIDs starting at uid_base, one per VM slot. Do not reuse this range for host users or another service instance.

Networking needs operator-enabled IPv4 forwarding and an explicit uplink. The service uses 10.231.<slot>.0/30 and 10.232.<slot>.0/30; these ranges must not overlap existing routes. For each online VM it adds a private network namespace, veth/TAP, route, nftables isolation/NAT table and narrowly scoped iptables FORWARD rules. It removes those on hibernation or deletion. It never flushes host rules or changes the host's default firewall policies. Add public management networks to blocked_cidrs; private-address filtering cannot recognize a public control plane.

sudo .venv/bin/microvm-mcp issue-token \
  --state-dir /var/lib/microvm-mcp --workspace demo-alice \
  --output /run/microvm-alice.token --ttl 86400 --allow-network
sudo .venv/bin/microvm-mcp serve --config /etc/microvm-mcp.toml

--image ID (repeatable) restricts a workspace to those images, the same way --allow-network grants networking; without it the workspace may use any configured image. A refused image returns 403 forbidden.

Issuance is a local administrator action, not an MCP tool. Token files are 0600 and are never printed. Give the trusted client/broker read access to its token file. Reissuing for the same workspace creates another token and preserves its original quota/policy; it does not revoke the old token or widen permissions. Do not put tokens in model prompts, tool arguments, guest environments or images.

sudo .venv/bin/microvm-mcp revoke-workspace \
  --state-dir /var/lib/microvm-mcp --workspace demo-alice

Revocation prevents future access, including refresh by this CLI, and closes open guest streams within 5 seconds. Existing jobs are not automatically killed. An operator needing immediate containment must stop the workspace's VM cgroups as well. An integrated revoke-and-stop admin command is not implemented. Credential expiry likewise does not delete VM state.

MCP and HTTP clients

Configure an HTTP MCP client with URL http://127.0.0.1:8090/mcp and the workspace credential in its Authorization: Bearer ... header. The SDK handles modern and legacy protocol negotiation. This deployment uses provisioned bearer credentials; OAuth login/discovery and interactive client registration are not implemented.

For ordinary HTTP clients, send the same arguments as JSON to the named operation:

POST /v1/tools/vm_create
{"request_id":"workspace-create-1","network":"off","memory_mib":1024,"vcpus":2}

POST /v1/tools/vm_exec
{"vm_id":"<returned id>","request_id":"build-1","command":"python3 -c 'print(2+2)'"}

Requests require the authorization header; errors use non-2xx status codes and {"error":{"code":...,"message":...}}. MCP tool errors use isError=true. Creation and execution require request_id. Reuse the same ID and arguments for a retry. A reused ID with changed arguments is refused. An interrupted operation with an unknown outcome is also refused instead of executing again.

Tools: vm_create, vm_list, vm_status, vm_exec, vm_job_status, vm_job_output, vm_job_cancel, vm_read_file, vm_write_file, vm_suspend, vm_resume, vm_destroy. Discovery advertises their complete JSON schemas. vm_create takes an optional image from the configured IDs. File paths and command working directories are relative to /workspace. There are at most four active jobs and 256 total jobs per VM.

Reading command output

vm_exec returns a job_id. Call vm_job_output while the job runs or after it finishes. Its default response contains separate, readable stdout and stderr strings; an LLM does not need to decode base64 to read command results:

{
  "job_id": "<returned job id>",
  "encoding": "utf-8",
  "stdout": "0, 1, 1, 2, 3, 5\n",
  "stderr": "",
  "next_cursor": {"stdout": 17, "stderr": 0},
  "decoding_errors": {"stdout": false, "stderr": false},
  "state": "completed",
  "exit_code": 0,
  "truncated": false,
  "timed_out": false
}

Omit cursor on the first call. Pass the entire returned next_cursor object as cursor on the next call, alongside vm_id and job_id. Each cursor field is a byte offset in that stream, not a character count. Omitted stream offsets start at zero. Reading is non-destructive: repeating a cursor rereads the same retained bytes. Once the job is completed, continue reading until neither offset advances. An empty response while the job is running is not end-of-output.

The guest supervisor drains both pipes independently. Up to 256 KiB total is retained across the two streams, with at most 16 KiB per stream per call. Excess output is drained and discarded, with truncated=true; it does not block the command. Ordering within each stream is preserved; there is no ordering guarantee between stdout and stderr. Use python3 -u or explicit flushing when you want prompt output from Python.

Text decoding preserves whitespace, line breaks, and tracebacks. Incomplete UTF-8 characters at a page boundary or at the current end of a running stream wait for the next bytes. Invalid bytes, including incomplete characters at EOF or the retention limit, become �; decoding_errors flags the affected streams for that response. A literal valid � does not set the flag.

For exact binary output, request "encoding":"base64". The response then has stdout_base64 and stderr_base64 instead of the text fields, and omits decoding_errors. The same byte cursors and limits apply. File transfer tools continue to use base64.

The host retrieves output over Firecracker's Unix-socket-to-vsock bridge to the supervisor on guest port 1024, then returns it through the authenticated HTTP/MCP API. Guest networking can remain off. Workspace credentials stay on the host.

Upgrade note: this replaces the previous combined data_base64 output and integer cursor. Update clients and regenerate optional adapter manifests. Rebuild the guest image, pin its new SHA-256 in the service config, and create new VMs. Existing guests and restored checkpoints keep their old supervisor; restarting the host service alone does not upgrade them.

Guest streams

A host client can open a two-way byte stream to a vsock port in an owned VM:

GET /v1/vms/{vm_id}/streams/{port}   (WebSocket upgrade)
Authorization: Bearer <workspace credential>

The service connects through the VM's Firecracker vsock socket, the same path as the control channel, so it works with network=off. Connections go host to guest only. A program in the guest listens on its own port; nothing listens by default. For example, started with vm_exec:

socat VSOCK-LISTEN:5000,fork EXEC:'my-mcp-server --stdio'   # if socat is in the image

The fork option starts a fresh process per connection. A long-lived server that holds state across reconnections, such as a Ghidra session, should accept connections itself.

Requests pass the same host allowlist, no-Origin, bearer and ownership checks as the tools. Refusals happen before any guest connection: 401 for credentials, 404 for another workspace's VM, 403 for port 1023 (the job broker pipe), port 1024 (the guest supervisor), or ports outside stream_ports, 409 for a full stream quota or no guest listener. Opening a stream wakes a suspended VM. Binary messages carry bytes; text messages are sent as their UTF-8 bytes. Client messages are limited to 1 MiB.

  • EOF. An empty binary message from the client is end-of-file for the guest. Firecracker's vsock bridge has no half-close: the guest reads the remaining data and then EOF, but the connection then closes in both directions, so a guest cannot reply after it. Protocols that need a reply after the input ends need an in-band end marker. The stream closes with code 1000 when the guest closes its end.

  • Lifecycle. An open stream counts as activity, so automatic idle suspension waits for it to close. Manual suspend refuses with busy unless force=true. Any suspension or destroy closes the VM's streams with code 1001 and reason suspended or destroyed. Guest listeners survive; clients reconnect after resume. Credentials are rechecked every 5 seconds, and revocation or expiry closes the stream with 1008. vm_status reports the number of open streams.

  • Limits. Configure them in the service TOML (defaults shown):

    stream_ports = [[1025, 65535]]   # inclusive ranges; [] disables streams; 1023 and 1024 are refused
    max_streams = 32                 # host-wide
    max_streams_per_workspace = 8
    max_streams_per_vm = 4
    stream_rate_bytes = 33554432     # per direction per stream; 0 is unlimited

    Streams grant no authority beyond vm_exec, which already moves data into and out of the workspace's own VM, so there is no separate token grant.

  • Logging. Open and close are logged with workspace, VM, port, duration, byte counts and close reason, never payload.

microvm-mcp attach bridges stdin/stdout to a stream. That makes a guest stdio MCP server usable as an ordinary MCP client's command. The token is read from a file and never appears on the command line:

microvm-mcp attach --endpoint http://127.0.0.1:8090 \
  --token-file /run/microvm-alice.token --vm <vm_id> --port 5000
# Upload: the guest program reads EOF after the file.
microvm-mcp attach ... --port 5001 < archive.tar
# Download: keep the stream open after stdin ends; the guest closes it.
microvm-mcp attach ... --port 5002 --ignore-stdin-eof < /dev/null > out.bin

It exits 0 when the guest closes normally and 1 on refusal or any other close.

Job broker pipe

vm_exec accepts an optional broker_pipe_token: a one-time unguessable token (secrets.token_urlsafe(32) or another 43–128 character [A-Za-z0-9_-] value). The service stores only its SHA-256, in memory. The token is not written to receipts, VM status, logs, or the guest, and a retried request_id does not replace the hash. The guest is told broker_pipe: true, the job id, and a separate random route secret. That secret only routes guest connections to this job's pipe, and like the hash it lives only in memory. The response adds "broker_pipe": true once the guest confirms it started the listener. An image built before the pipe returns "broker_pipe": false, and no pipe is kept.

For that job the guest supervisor listens on /run/fa-broker/broker.sock inside a mount namespace private to the job, mode 0660 and group 1000. Other jobs do not see the path. The listener accepts a connection only when the peer process is in this job's cgroup, which also refuses another job that reaches the socket through /proc/<pid>/root. The listener runs as nobody (uid 65534), so job processes cannot read its route secret, which reaches it on a pipe and never in argv. Its cgroup is a sibling of the job's under the jobs cgroup. It freezes with the jobs at suspension and is killed when the job ends, including by cancellation or timeout. It relays at most 32 connections at a time. The job environment is the workspace broker client, with a non-secret placeholder capability and no storage credentials:

FA_WORKSPACE_BACKEND=broker
FA_WORKSPACE_BROKER_URL=http://127.0.0.1/v1/call
FA_WORKSPACE_BROKER_CAPABILITY=fa-broker-placeholder
FA_WORKSPACE_MAX_OBJECT_BYTES=67108864

FA_WORKSPACE_SCOPE is unset. The HTTP target is POST /v1/call on /run/fa-broker/broker.sock. Guest networking stays off. A client that opens that socket and writes the broker request, including the placeholder bearer token, is carried to the host unchanged.

Each accepted socket is forwarded to the host on Firecracker's guest-to-host vsock <uds_path>_1023 (port 1023). It starts with a 97-byte preamble: the 32-hex job id, the 64-hex route secret, and a newline. Raw bytes follow. Any guest process can open port 1023, so the host routes only on a matching route secret. A job id alone reaches nothing. The host reads every preamble without blocking, with at most 64 pending at once and 2 seconds each. Port 1023 is not a public stream and is not served at /v1/vms/{vm_id}/streams/{port}.

Jobs share uid 1000, so this isolation covers who can connect. It does not cover debugging: a job allowed to ptrace another job's processes can act as them. Run one job per VM when that matters.

The controller that knows the token attaches:

GET /v1/vms/{vm_id}/jobs/{job_id}/broker   (WebSocket upgrade)
Authorization: Bearer <workspace credential>
X-Broker-Pipe: <broker_pipe_token>

The same host allowlist, no-Origin, and bearer checks apply. A wrong token, another workspace, an unknown or finished job, or a VM without this pipe is 404. A second concurrent attach is 409, and so is a fifth attached pipe on one VM. WebSocket messages are binary. The payload is a byte stream of big-endian frames, which a message may split or combine:

Kind

type

Layout

open

1

u8 type, u32 conn_id

data

2

u8 type, u32 conn_id, u32 length, payload with length <= 65536

close

3

u8 type, u32 conn_id

conn_id is nonzero. The service assigns it and sends open when a guest connection arrives. The controller sends data and close only. The service copies bytes and does not interpret them. The controller splices its end to the local broker and replaces Authorization: Bearer fa-broker-placeholder with the session capability. That splice stays outside this service.

  • Close. A close from the controller is delivered after the data queued before it. A close from the service means the guest connection is gone. Firecracker does not forward a guest half-close to the host, so a guest client may shut down its write side and still read the reply. Each side ignores frames for a connection it has already closed. A conn_id the service never assigned closes the pipe with 1002.

  • Limits. One attachment carries at most 64 guest connections; 32 more can wait for a controller. Up to 1 MiB of controller data is buffered for each guest connection. When a guest stops reading, the service stops reading the WebSocket. After 10 seconds without progress, that connection alone is closed. stream_rate_bytes applies to each direction of an attachment. The service reads guest data only as fast as the WebSocket drains, and holds no thread per pipe or connection.

An open pipe counts as activity, so idle suspension waits for it. Manual suspend refuses with busy unless force=true. Suspend and destroy close the pipe with WebSocket code 1001 and reason suspended or destroyed. The pipe is kept across suspension: after resume the same token attaches again. The pipe is removed once the service sees its job completed, through a status refresh, job polling, or the idle sweep, which checks VMs with pipes every 5 seconds. An attached controller then gets 1001 job finished. If the guest rejects the vm_exec, no pipe is kept. Credentials are rechecked every 5 seconds; revocation or expiry closes it with 1008. Close and open are logged with workspace, VM, job, duration, byte counts, and reason. The token, the route secret, and the payload are never logged. Neither secret survives a service restart.

Upgrade note: existing guest images ignore broker_pipe until the image is rebuilt and its digest is pinned. Restarting the host service does not upgrade a running guest or a restored checkpoint. Old images keep working; they report "broker_pipe": false and do not open the socket.

Suspension and failure semantics

Creation defaults to hibernating after 300 idle seconds. Set idle_seconds=0 to disable it, or choose idle_mode="pause". Active managed commands, including detached descendants, prevent automatic suspension. Status polling does not keep an otherwise idle workspace awake. Background system daemons are not counted as user activity. This is a scheduling heuristic, not a host security boundary.

Manual suspend defaults to mode="hibernate" and refuses active jobs. With force=true, it freezes them. VM status, list and cached job status never wake a suspended VM. Execution, file transfers, output retrieval and cancellation do. Cancellation of a frozen job resumes the guest before terminating its cgroup. Managed command timeouts exclude suspension; suspension can extend wall time.

Before stopping vCPUs, the supervisor freezes the managed jobs' entire cgroup subtree and pauses timeout accounting. On boot and every resume, the host sends its current wall time through the private vsock control channel. The guest sets CLOCK_REALTIME before thawing jobs, and restores their remaining timeout budgets under the supervisor lock before timeout checks resume. This works with both tsc and kvm-clock, including network-off VMs. Failed synchronization stops the VMM and marks the VM failed. Old images/checkpoints without this lifecycle protocol are rejected; rebuild and pin the guest image and create new VMs when upgrading.

Firecracker's clock_realtime snapshot option is deliberately disabled: with kvm-clock, it also advances monotonic time across a restore. Wall time is instead corrected separately, without changing the monotonic clock. This is a correction at boot/resume, subject to local control-channel latency, not continuous NTP/PTP synchronization; the host clock must itself be accurate. Applications' own timers and sleeps still follow guest kernel semantics: plain pause advances monotonic time, while hibernation normally excludes time spent stopped. The supervisor compensates its managed command timeouts for either behavior, but does not rewrite application timers. Wall-clock deadlines can expire during suspension.

Hibernation pauses the guest, writes a full memory/device checkpoint, drains disk I/O, retains the corresponding private disk, fsyncs checkpoint metadata, stops the process and removes its network resources. Resume restores that same VM on the same host. It does not clone a checkpoint into a second VM or promise live TCP connections survive. The snapshot's Firecracker binary digest is checked. Never modify a hibernated VM disk externally or restore stale memory over a disk that has since run. The state machine marks interrupted mutations failed rather than silently rolling back to such a checkpoint.

Graceful service shutdown checkpoints running/paused VMs, including active jobs. After a service crash, hibernated VMs remain restorable; other VMs are marked failed and leftover processes are killed through their owned cgroups. They can be inspected/deleted, but automatic recovery of in-flight commands is deferred. There is no HA, cross-host migration, VM sharing, built-in interactive terminal (a stream can carry one that the guest provides), inbound web preview, automatic VM retention expiry, or public production deployment in this first pass. Guest roots/checkpoints are sensitive workspace data and remain inside the service's private state directory.

Optional fast-agent-demo integration

The adapter uses host_fetch, so the host owns the token and TLS. The service does not know about fast-agent sessions, manifests, state envelopes or the VMM. Generate a fragment and append it to the demo's existing tool manifest:

.venv/bin/microvm-mcp demo-manifest \
  --endpoint http://127.0.0.1:8090 --vm-class exec > microvm-tools.fragment.toml

The fragment assumes that the existing manifest defines an exec class (the packaged demo does). That class is schema-required but inert for host_fetch. Add this entry to the host's existing private secrets file:

[secrets.microvm_workspace]
file = "/run/microvm-alice.token"

Run the demo with that manifest, --secrets, --stdio-tools and --expose-tools, as its normal launcher already does. Use an HTTPS endpoint and a matching service Host allowlist when the service is remote. The returned tool output has the normal fast-agent host_fetch wrapper, {"status":200,"body":{...}}.

One static demo credential means one workspace for all sessions in that demo instance. It must not be presented as per-user isolation on a shared public demo. For the first demo, use one separately configured demo instance per workspace. Shared multi-user hosting needs a future host-side adapter that derives workspace identity from authenticated context and chooses its credential. A caller-supplied session_id/workspace_id is not authorization. Neither service transport accepts an ownership override, and the generated tool schemas contain no token field.

examples/tool_probe_agent.py exercises this path without an LLM. Boot it with fa-serve and the generated manifest, then send a turn with {"input":{"tool":"vm_create","args":{"request_id":"probe-1"}}} and a normal front-end session ID. Its purpose is integration validation only.

Validation

uv run --locked ruff check src guest tests scripts examples
uv run --locked ruff format --check src guest tests scripts examples
uv run --locked pytest
# Run on a provisioned host. Missing KVM, jailer, reflinks etc. are hard failures.
sudo .venv/bin/python scripts/smoke.py --config /etc/microvm-mcp.toml --network
# Longer suspension probes, including jobs with a three-second timeout:
sudo .venv/bin/python scripts/smoke.py --config /etc/microvm-mcp.toml \
  --network --clock-suspend-seconds 30

The unit suite uses a fake backend that cannot execute code. The separate real smoke boots jailed VMs, checks Fibonacci output, separate streams, UTF-8 pagination, binary output, and output limits, compiles generated C, checks disk isolation, preserves a running process across suspension, checks wall time and managed timeouts across pause, hibernation, and pause-to-hibernate transitions, checks automatic hibernation, verifies network policy, and denies cross-workspace access. For every configured image it boots a VM and checks the kernel copy's digest and the guest root filesystem's identity across hibernate/resume. With two or more images, it checks that a token restricted to one is refused for the other. It runs an MCP stdio round trip through attach and a guest vsock stream, copies 100 MiB each way, and checks refusals, half-close behavior and stream closure on hibernation. Run it with the legacy single-image configuration and with an [images] configuration listing two distinct images. It deletes its VMs and revokes its credentials in cleanup. See VALIDATION.md for measured results and the history of smoke failures and fixes.

License

Licensed under the Apache License, Version 2.0.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI agents to create, manage, and execute code in isolated Firecracker microVM sandboxes via the MCP protocol, with support for sandbox lifecycle and file operations.
    3
    Apache 2.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables time-travel debugging for AI agent sandboxes using Firecracker microVMs, with tools for code execution, file operations, and VM snapshot/restore via MCP.
    40 PyPI
    1
    MIT
  • A
    license
    B
    quality
    B
    maintenance
    Provides an MCP control plane for a dedicated Linux VM, enabling shell-equivalent command execution, bounded filesystem and Git workspace operations, persistent process and coding-agent orchestration, artifact presentation, and optional upstream MCP bridge re-exports.
    27
    1
    ISC