mcqemu/pyproject.toml
Ryan Malloy 5a2d4703ab Close the remaining review findings: injection, isolation, unbounded work
QEMU command line:
- escape commas in every interpolated path (qopt); a path like
  'data,readonly=on.qcow2' previously injected a drive option
- reject extra_args flags that breach VM isolation (host filesystem
  passthrough, host block devices, spawning chardevs, -runas) and document
  the parameter as operator-only
- detect duplicate host ports across port_forwards instead of failing at
  QEMU launch; auto ports no longer collide with each other

Sandbox isolation:
- sandbox_vm now blocks guest-initiated traffic by default (restrict=on),
  with allow_network=True to opt in. Verified end to end: with identical
  guest network state, a default sandbox reaches neither a host loopback
  service nor the internet, while allow_network=True reaches both
- note in the docstring that the guest agent answers before the guest has
  finished booting

Bounded work per call:
- vm_serial_read seeks a 256KB window from the end instead of reading a
  console log that grows without bound into memory
- cap vm_type_text length and vm_mouse_move deltas
- screenshots get a unique filename and are cleaned up, so a concurrent
  capture cannot swap the frame under vm_click

Identity and liveness:
- attach_vm requires an actual unix socket and stores the resolved path
- attached VMs are judged by connecting, not by a stat that a stale socket
  file would pass
- refuse to act on a PID whose cmdline proves it is a different VM
- a sandbox's base image counts as in use while its overlay is live
- fix a latent NameError in vm_mouse_move's homing branch
2026-08-17 16:17:05 -06:00

61 lines
1.4 KiB
TOML

[project]
name = "mcqemu"
version = "2026.8.16"
description = "MCP server for managing QEMU virtual machines"
readme = "README.md"
requires-python = ">=3.11"
authors = [{ name = "Ryan Malloy", email = "ryan@supported.systems" }]
license = "MIT"
keywords = ["mcp", "qemu", "qmp", "virtualization", "kvm"]
dependencies = [
"fastmcp>=3.4.7,<4",
"qemu.qmp>=0.0.6",
]
[project.scripts]
mcqemu = "mcqemu.server:main"
[dependency-groups]
dev = [
"pytest>=8",
"pytest-asyncio>=1.0",
"ruff>=0.12",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/mcqemu"]
[tool.hatch.build.targets.sdist]
exclude = [
"CLAUDE.md",
".env",
".mcp.json",
"tests/",
".pytest_cache/",
".ruff_cache/",
"dist/",
]
[tool.ruff]
line-length = 100
src = ["src", "tests"]
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM", "ASYNC"]
# ASYNC109: `timeout` params here are deliberate LLM-facing tool parameters.
# ASYNC110: polling an external process's death has no event to await.
# ASYNC240: pathlib use is microsecond stat/exists checks, not bulk I/O.
# ASYNC230: the one blocking read is a bounded 256KB tail, not bulk I/O.
ignore = ["ASYNC109", "ASYNC110", "ASYNC230", "ASYNC240"]
[tool.pytest.ini_options]
asyncio_mode = "auto"
markers = [
"integration: boots real QEMU (run with '-m integration')",
]
addopts = "-m 'not integration'"