agent-vm MCP server
Commit Oct 21, 2026Provides an MCP control plane for dedicated Linux VMs, enabling shell execution, filesystem management, and upstream MCP tool bridging.
Also known as DevilTea/agent-vm-mcp
- Stars
- 1
- Tools
- —
- Downloads, 30 days
- —
- Latest version
- —
- Last commit
- Oct 21, 2026
- License
- ISC
What is agent-vm MCP?
agent-vm provides an MCP control plane for dedicated Linux VMs, enabling shell execution, filesystem management, and upstream MCP tool bridging.
Summary generated from the project's README
About agent-vm MCP server
Managing a dedicated Linux environment through an AI client is what agent-vm-mcp enables. This MCP control plane provides shell-equivalent control over a Linux agent VM, allowing developers to execute commands, manage filesystems, and orchestrate coding agents like Codex or Antigravity CLI. It functions as a bridge between the AI and a persistent workspace, offering tools for Git repository management, interactive process sessions, and unified-diff patching for file edits.
The server operates over stdio and requires Node.js 24 and pnpm for setup. Users can configure the environment using XDG-compliant paths or environment variables to manage upstream MCP bridges, CLI capabilities, and system audits. A key differentiator is its ability to re-export tools from optional upstream servers like Playwright or LSP via stdio or Streamable HTTP, using specific policies for filtering and renaming. Because the server provides shell-level access, the VM itself serves as the primary trust boundary for all operations.
What you can do with it
- 1Execute shell commandsAn AI client can run terminal commands with cancellation support and bounded previews within the VM.
- 2Perform precise file editsDevelopers can instruct an agent to apply specific changes to files using a validated patching system.
- 3Bridge external MCP toolsYou can connect and expose tools from other MCP servers like Playwright or LSP through the control plane.
- 4Manage Git workflowsThe server allows for the orchestration of Git repositories and isolated worktrees directly from the agent.
Written from the project's README. Every use case is checked against a quote from it.
README
View on GitHubagent-vm-mcp
agent-vm-mcp is an MCP control plane for a dedicated Linux agent VM. It gives an MCP client shell-equivalent control of that VM, adds durable workspace and coding-agent orchestration, and can re-export tools from optional upstream MCP servers.
[!WARNING] Access to this server is intentionally equivalent to shell access to the VM. The VM is the trust boundary. Do not expose this MCP endpoint to clients you would not trust with the VM, its credentials, mounted data, browser sessions, network reachability, Docker access, or SSH access.
What it provides
The native MCP surface includes:
- finite shell execution with cancellation, bounded previews, and artifact spillover;
- bounded filesystem reads, deterministic directory listing, and strict unified-diff patching;
- managed Git repository stores and isolated Git worktrees;
- persistent interactive process sessions;
- managed bounded coding-agent execution for Codex and Antigravity CLI, with no raw harness TUI fallback on the ChatGPT host;
- CLI capability discovery and a read-only system maintenance audit;
- generic-host projected-skill access; the ChatGPT host intentionally exposes no dot-agents skill surface;
- opaque MCP artifact resources and host-native file/image presentation;
- upstream MCP bridging over stdio or Streamable HTTP, with tool filtering, prefixing, renaming, adapters, and call policies.
A clean checkout starts with no upstream bridges enabled. Playwright, LSP, browser takeover, tunnel integration, and coding harnesses are optional deployment features.
Trust model
The supported model is deliberately simple:
Give the agent a dedicated VM. The operator decides what that VM can reach; inside the VM, assume the agent can reach everything available to its Linux user.
agent-vm-mcp is not a sandbox, privilege boundary, multi-tenant isolation layer, or authorization broker between tools running inside the same VM. Path checks, ownership markers, bridge policies, workspace identity checks, and similar guards exist for orchestration correctness and accidental-cross-control prevention, not to protect secrets from a malicious process already running as the trusted VM user.
For the full security model, see SECURITY.md.
Quick start
Requirements:
- Node.js
24.20.0or a compatible Node 24 release accepted bypackage.json; - pnpm
11.25.0.
Install and start the native core:
pnpm install --frozen-lockfile
pnpm start
The server communicates over stdio. With no machine-local configuration, the built-in bridge configuration is empty, so missing Playwright/LSP services do not prevent startup.
Configuration
Machine-specific configuration belongs under:
$XDG_CONFIG_HOME/agent-vm-mcp/
# or, when XDG_CONFIG_HOME is unset:
~/.config/agent-vm-mcp/
For file-based configuration, resolution is:
- an explicit path supplied by the caller/runtime;
- the corresponding environment-variable override;
- the XDG machine-local file, when it exists;
- the repository's built-in default under
config/.
The main configuration files are:
| Purpose | XDG filename | Environment override |
|---|---|---|
| MCP bridges | bridges.json |
MCP_BRIDGES_CONFIG |
| CLI capabilities | capabilities.json |
AGENT_MCP_CAPABILITIES_CONFIG |
| System audit | system-audit.json |
AGENT_MCP_SYSTEM_AUDIT_CONFIG |
| Shared Playwright stdio proxy | playwright-shared-proxy.json |
PLAYWRIGHT_SHARED_PROXY_CONFIG |
AGENT_MCP_HOST selects connection/deployment-specific host behavior. Supported values are generic (default) and chatgpt. Host-specific extensions are kept out of ordinary tool inputs; for example, ChatGPT file-input metadata is attached to import_file only when AGENT_MCP_HOST=chatgpt. The ChatGPT profile also enables the structured interaction adapter, disables projected-skill protocol/tools, and forbids interactive terminal session launchers through exec/process_start.
See config/README.md for bridge schema, fail-soft/fail-hard behavior, adapters, policies, capability discovery, system-audit configuration, and deployment examples.
Bridge behavior
Every bridge is an optional integration. enabled: true means "attempt to connect and expose this integration at startup"; it does not make the upstream service a prerequisite for the native core.
The contract is:
Invalid configuration fails startup. Unavailable integrations do not.
Examples of unavailable integrations include a missing stdio executable, connection refusal, an offline HTTP service, or an upstream handshake/tool-list failure. Those bridges are reported as state: "unavailable" by mcp_bridge_status, while other bridges and the native core continue.
Invalid JSON, unsupported config versions, duplicate bridge IDs, invalid adapter/policy configuration, unsupported transport definitions, and deterministic exported-tool collisions are startup errors. Partial bridge initialization is rolled back before the error escapes.
MCP Skills (SEP-2640)
The canonical MCP-facing skill surface follows the Final SEP-2640: Skills Extension: the server declares the io.modelcontextprotocol/skills extension and exposes skills/list, skills/get, standard resources/read, and resources/directory/read. Skill identity is its URI, using skill://<skill-name> for a skill root and skill://<skill-name>/<path> for files and directories. Listings include complete parsed SKILL.md frontmatter and a digest/size manifest for every regular file; reads are lazy and bounded by the SEP limits.
The dot-agents projection is an internal publication snapshot, not a second protocol model. It is a version 1 directory containing catalog.json and skills/<name>/...; the catalog source must be dot-agents, and its tree hash is verified before content is served. Filesystem paths are never exposed.
The default projection root is:
${XDG_DATA_HOME:-$HOME/.local/share}/dot-agents/skill-projection/v1/
Set AGENT_MCP_SKILL_PROJECTION_ROOT to use another projection root. On the generic host profile, SEP-2640 and the native skill_list/skill_read compatibility tools consume the validated projection. The ChatGPT host profile intentionally registers neither surface, so ChatGPT does not consume dot-agents skills directly. Projection content remains instructions/data for hosts that explicitly opt into it.
Structured user input
The interaction model is host-neutral. It currently supports single-select, multi-select, free-text, and legacy boolean questions, with stable question/option IDs and optional recommendations. New binary choices should use single_select as well, so they can express an uncertain/custom state without introducing a second overlapping choice primitive; boolean remains accepted for compatibility with existing callers. Every single_select and multi_select question is normalized to include exactly one reserved option with ID other, label Other, and allowCustomInput: true; selecting it requires an inline free-text value. Callers should not add this option. An existing option with ID other or a label equal to Other after trimming surrounding whitespace and ignoring case is normalized in place without creating a duplicate. Other arbitrary options can still set allowCustomInput: true (and optionally customInputPlaceholder) to require their own inline detail only when selected. For multi-select questions, a supplied maxSelections continues to count selected choices including automatic Other; omitting it preserves the existing no-explicit-cap behavior. Host-specific rendering is implemented by adapters rather than encoded in the core request schema.
When AGENT_MCP_HOST=chatgpt, the server exposes request_user_input and binds new calls to the content-addressed v2 MCP Apps resource ui://agent-vm/request-user-input/v2-<sha256-prefix>.html. Its URI includes a digest of the bundled UI HTML, so changing the widget gives the tool a new resource identity. The v1 resource URI and its original HTML remain registered for cards already present in conversations; do not edit chatgpt-app.html in place. The tool result also contains a plain-text fallback, so the request remains understandable if the UI cannot render.
In v2, Submit first sends a normal role: "user" text message through ui/message. That message includes the title, every question prompt, selected labels, stable option IDs, and custom values, so the model can understand the answers from the conversation alone. Only after the host acknowledges the message does the view lock that rendered interaction and ask the server to save its authoritative submitted state. A failed or unacknowledged ui/message leaves the server state pending and the form retryable. If the message succeeds but the later state save fails, the current rendered view stays locked to avoid sending it again automatically. Since the host controls delivery and acknowledgement for ui/message, an acknowledgement lost after the host has accepted a message remains ambiguous; a manual retry in that case can create a duplicate conversation message.
The v2 ChatGPT form renders every user-authored free-text field as a resizable textarea with a default height of two rows, including kind: "text" questions and option-specific custom-input fields. The existing multiline request field remains part of the host-neutral schema for compatibility, but it no longer changes the ChatGPT v2 control type.
After an acknowledged v2 submission, the App replaces editable controls with a visible submitted-response summary generated from the same text formatter used for the ui/message user turn. Persisted submitted interactions hydrate back into the same summary; if only the secondary state save fails, the summary remains visible with the save warning and stays latched against resend.
The historical v1 view and its App-only request_user_input_state / request_user_input_submit tools retain their original read and submit behavior for old cards. V2 uses separate App-only state and submit tools so each version remains associated with its own resource. The server durably stores each normalized request and validated structured answers keyed by immutable interactionId; a later rendering can hydrate persisted answers and show the disabled Submitted state. Re-submitting identical answers is idempotent, while a different answer set is rejected. The UI's sandbox-local localStorage is only a draft cache and is not the submitted-state source of truth. The state file is written atomically under the configured state root with private file permissions; authoritative hydration and secondary persistence require MCP Apps server-tool capability.
The generic host does not expose request_user_input yet. Future hosts can register their own adapter against the same interaction model without adding host-specific fields to the public schema.
Native file and execution tools
read_file returns bounded UTF-8 content plus a whole-file SHA-256 revision. When the resolved target is inside the supplied cwd, it also returns a reusable { cwd, path } locator so a later edit_files call does not need to reconstruct the relative path.
edit_files is the model-oriented filesystem mutation surface. It supports exact ordered edits to existing UTF-8 files, non-overwriting file creation, and revision-guarded file deletion. The whole batch is prevalidated before mutation; preflight failures are zero-write. On Linux, mutation paths are anchored to opened O_DIRECTORY|O_NOFOLLOW parent handles, so replacing a validated parent path with a symlink cannot redirect commit or rollback into another directory tree. Once commit begins, cancellation does not interrupt it. Ordinary commit failures trigger rollback, while rollback refuses to overwrite a target that was independently changed and preserves recovery evidence instead. Multi-file edits are not claimed to be process-crash-atomic.
exec accepts exactly one of a shell command or literal argv. Prefer argv when shell syntax is unnecessary so quoting and argument boundaries stay explicit; use command for pipes, redirects, and compound shell expressions. Raw coding-harness execution remains blocked in both modes.
Core mutating tools return machine-readable error envelopes with stable error.code, human-readable message, and tool-specific details when available. Schema-validation errors remain protocol-level input errors.
Artifacts and host presentation
Artifacts use opaque process-local URIs such as:
artifact://agent-vm/<id>
Artifact registration and user presentation are separate operations. Tool-generated artifacts such as oversized exec stdout/stderr are model/internal references by default: the tool result carries an opaque URI but does not attach a user-facing resource_link. The model can inspect text artifacts in bounded chunks with read_artifact; supported image artifacts can also be returned as standard MCP image content.
present_file explicitly registers and presents a VM file to the user. present_artifact explicitly presents an already-registered artifact. Those presentation tools return standard MCP resource_link content; small text files are additionally embedded as MCP resource content. Bridge adapters register output artifacts without automatically presenting file links, while image-producing adapters may attach standard MCP image content for same-turn model inspection.
Caller-owned files registered by present_file remain live references, not immutable snapshots. Their metadata describes the file when it was registered; a caller that mutates the underlying file later is changing what the live reference points to. Temporary spill files created by exec are artifact-store-owned and cleaned up on expiry or graceful shutdown.
Defaults:
- artifact TTL: 24 hours;
- maximum artifact read size: 50 MiB;
import_filemaximum download size: 256 MiB.
Override them with AGENT_ARTIFACT_TTL_MS, AGENT_ARTIFACT_MAX_BYTES, and AGENT_FILE_IMPORT_MAX_BYTES.
Both read_file and artifact resource reads enforce their source-size limit during I/O through one opened file descriptor rather than relying on a path-level stat followed by an unbounded readFile.
Server and tool-catalog identity
server_info reports the identity of the running MCP process, including the package version, Git revision captured when that process started, dirty-tree state, host profile, PID, and start time. The revision is intentionally captured at server startup rather than read on every call, so a checkout that has been fast-forwarded before its old process is restarted cannot masquerade as the new deployment.
The same tool reports a deterministic SHA-256 identity for the active MCP action catalog. The hash covers the public tool definitions that matter to a host snapshot—name, title/description, input/output schemas, annotations, icons, execution metadata, and _meta—including connected bridge tools. Handler implementation changes alone do not change the catalog hash. server_info is excluded from its own hash to avoid self-reference.
The server_info tool description embeds the catalog marker that ChatGPT saw when it fetched that tool definition. Compare that embedded marker with catalog.marker returned by a live server_info call. If they differ, the running server and the host's frozen action snapshot disagree. Treat that mismatch as a terminal workflow boundary: do not call or rediscover any other tools from that server in the same workflow; return control to the user and refresh the app actions before continuing. For self-deployment, finish cleanup and ancillary work before restarting the MCP/tunnel process, then use one post-restart server_info call as the deployment acceptance check. catalog.toolNames and the reported counts provide an additional sanity check.
Workspaces and Git
workspace_create manages a shared bare repository store plus isolated Git worktrees. A workspace has its own working tree, index, and HEAD; objects, refs, tags, remotes, repository-level config, and stash remain shared within that repository store. createBranch atomically compare-and-creates the local branch ref and uses expected-OID cleanup, so a failed request does not delete a branch that another actor created or subsequently moved. It also fails closed when the requested name is already present locally or on the refreshed origin tracking refs. idempotencyKey persists a pending workspace intent before worktree creation and uses a cross-process owner lock based on Linux PID/start-time identity, so a retry after a lost response or MCP restart reuses or recovers the same workspace identity instead of intentionally creating a second one; reusing the key for different inputs is rejected.
Defaults:
- repository store:
~/.local/share/agent-vm/repositories; - worktrees:
~/workspaces.
Override them with AGENT_REPOSITORY_ROOT and AGENT_WORKSPACE_ROOT.
Workspace lifecycle does not install dependencies, trust repository toolchains, commit/stash changes, start processes, or manage containers. It creates a task branch only when createBranch is explicitly supplied. Repository authentication stays with normal Git credential mechanisms. HTTP(S) clone URLs with embedded userinfo, query parameters, or fragments are rejected to reduce accidental credential persistence.
Processes and coding agents
process_* tools manage generic processes owned by the running MCP server. process_read returns an opaque cursor; pass it back on the next read instead of manually carrying stdout/stderr offsets. Explicit offsets remain available for compatibility and cannot be mixed with a cursor. On graceful shutdown, managed process groups receive SIGTERM and are escalated to SIGKILL after a bounded grace period. These process sessions are not persisted across server restarts.
Normal coding-agent work uses durable background jobs. agent_start writes a job to a separate VM-owned agent-jobd service and returns an immutable runId once SQLite has accepted it. Jobs queue when the configured worker limit is reached; queued results expose position/capacity diagnostics instead of requiring callers to infer why they have not started. No ChatGPT/MCP connection must remain open. agent_poll reads incrementally with at most a 15-second wait and returns one opaque cursor covering stdout, stderr, structured events, and invalid lines; explicit per-stream offsets remain available for compatibility. agent_result returns a bounded output preview plus absolute paths to complete stdout/stderr and structured JSONL logs. agent_list rediscovers jobs across ChatGPT turns, MCP restarts, and background-manager restarts. agent_cancel can cancel queued or running jobs.
- Defaults: two concurrent jobs per VM, two-hour per-job timeout, eight-hour maximum. A job timeout is not the lifetime of an MCP request. Worker health comes from Linux PID/start-time identity and database state, never from guessing that a quiet model is stalled.
- Safe retry: send an
idempotencyKeyunique to a logical job. A repeated identical request returns the samerunId; reusing the key with different inputs is rejected. Without the key, a request whose response was lost may be duplicated by retrying. - Restart semantics: the job-manager and MCP processes are separate from detached worker process groups. Manager restart reattaches to still-running workers without restarting them; reboot or lost workers are marked
interruptedinstead of being replayed. SQLite and log files remain available. A failed/interrupted job must be examined and explicitly reissued. - Persistence: jobs live in the configured
AGENT_JOB_STATE_DIR(default~/.local/state/agent-vm-mcp/jobs); its SQLite database and per-run output directories are private to the VM account. Back up this directory if the results matter. - Scope: V1 is individual Codex/agy jobs only. Orchestrating multiple independent reviewers and final synthesis stays with ChatGPT for now; job groups, dependency graphs and completion notifications are reserved for V2.
agent_run remains the unchanged short, synchronous compatibility helper (15-second default, 30-second maximum) and still uses the preexisting in-MCP bounded runner. It is not a background job and is cancelled if the MCP process terminates. The Codex model for existing MCP-managed invocations is fixed to gpt-5.6-luna / max, independently of dot-agents user defaults; the background service inherits the same configured deployment policy. Antigravity uses non-interactive print mode and structured output.
work_status now includes persistent background jobs plus legacy short-run metadata and managed generic processes. The repository/filesystem evidence is read separately. A successful process exit is not evidence that a review is semantically correct; inspect the saved findings and Git state before claiming completion.
Raw coding-harness TUI execution is not a supported fallback. On the ChatGPT host, exec and process_start reject interactive terminal session launchers (tmux, screen, and script) as well as raw coding-harness execution. Codex and Antigravity work must flow through the managed agent_* tools.
Read-only maintenance audit
system_audit discovers installed tooling and reports update/health information without installing, fetching Git refs, restarting services, or otherwise mutating the VM.
For repositories, local ancestry is evaluated against the local tracking ref and reported as in_sync, ahead, behind, or diverged with counts. A live git ls-remote lookup, when requested, is reported separately as an observation of the current remote head; unequal hashes are not automatically labeled "behind".
Project dependency checks accept valid pnpm outdated --format json output even when pnpm exits non-zero, while a failed invocation with no usable JSON is surfaced as an audit error rather than an empty successful result.
agent-vm MCP questions
Is this server a security sandbox?
No, it is not a sandbox or privilege boundary. The VM itself is the trust boundary, and the agent is assumed to have the same access as the Linux user.
What happens if an upstream MCP bridge is unavailable?
The server will still start; unavailable integrations are reported as unavailable while the native core and other bridges continue to function.