Skip to content

Releases: raychao-oao/infra-mcp

v2.0.0 — Resource model: layer + roots replace path guessing

Choose a tag to compare

@raychao-oao raychao-oao released this 28 Jul 14:44

Why this release exists

A fleet-wide census found that 32 of 34 recorded data/config paths — and 6 of 6 stray static paths — pointed at directories that did not exist. The root cause was structural: registration guessed sub-paths for every service, but most services were never deployed by this server, so the guesses became fiction that purge offered to delete and audits reported as real. v2 removes the possibility of that class of record.

Breaking changes

  • Schema: service_deployments columns app_path / static_path / data_path / config_path / log_path are removed, replaced by layer (standard / nonstandard), project_root, deploy_root, workspace_url, and path_overrides (JSON). Sub-paths are derived from roots by convention via a single resolver; only deviations are stored.
  • register_service is now allocation-only (standard layer): it assigns roots that deploy_service consumes. The five old path parameters are gone.
  • update_service field set changed accordingly; layer is updatable, and promoting a record to standard re-validates all effective paths.

New

  • record_service (tool #42): registers services that already exist and were not deployed by this server. Zero defaults — unknown facts stay null instead of being invented. Deploy refuses these records; purge never deletes their directories.
  • scripts/migrate_resource_model.py: two-phase migration (add+backfill while old code runs → drop after new code is live), dry-run by default, per-record report, re-run-safe, refuses to drop columns on an unmigrated DB.
  • Health check now probes the deployments table, so a schema/DB regression turns /health unhealthy and the auto-deploy rollback can actually catch it (previously it only probed port allocations and reported healthy on a broken schema).
  • Test suite from zero to 50 tests (pytest + pytest-asyncio), covering the model, path resolution, both registration modes, deploy/purge guards, and the migration script against fixture DBs.

Docs

docs/MCP-API.md (allocation-vs-recording model, full record_service section), docs/Data-Models.md (new schema), README tool count.

v1.1.0 — Audit tools that can be trusted

Choose a tag to compare

@raychao-oao raychao-oao released this 27 Jul 15:23

38 → 41 tools. The theme of this release is that the security tooling from v1.0.0 was not trustworthy, and a tool nobody trusts protects nothing.

The audit tools existed the whole time. They were ignored, and they had earned it: every service scored 0.0 because the Caddy config path was derived as {project}-{service}.caddy while real filenames follow the hostname, and check_listening_ports graded 0.0.0.0 as less severe than a Tailscale address. Six services sat reachable on a public IP for roughly three months behind that noise.

New tools

  • check_firewall — asks whether a host actually filters packets, not whether a firewall service claims to be enabled. A ufw package in rc state leaves /etc/ufw/ufw.conf saying ENABLED=yes and systemctl is-enabled ufw answering enabled while no filter exists at all. Checks live iptables/ip6tables rules, package states and persistence — IPv6 separately, because purging ufw can leave the v6 chain at policy ACCEPT while v4 looks healthy.
  • reconcile_ports — three-way drift check between what is listening, what is allocated, and what was released.
  • update_service — correct a deployment record without touching a server. Records used to be editable only by opening the SQLite file, so in practice they were left wrong, and every tool that trusts them inherited the error.

Audit correctness

  • Three outcomes, not two. SECURE / VULNERABLE / UNVERIFIED. "I could not determine the bind address" is neither a clean bill of health nor a finding; the score is computed over what could actually be judged.
  • Locate, don't derive. Caddy sites are found by hostname, port and static root; systemd units by WorkingDirectory and ExecStart. Real units are named after what they run, not after {project}-{service}.
  • Every service on record is audited, not only those marked deployed — the exposed six were not among the two that filter had been passing.
  • Addresses are parsed, not string-matched. Loopback, Tailscale (100.64.0.0/10, fd7a:115c:a1e0::/48), private and public ranges are graded by reachability.
  • Speed is a correctness property. One ServerSnapshot per server instead of round trips per check: a fleet audit went from 3–4 minutes to about 11 seconds. A slow audit gets skipped exactly like a noisy one.
  • Deliberate states are no longer warnings: a reserved port with nothing listening, a service with no Caddy site, a static site with no backend port.

purge_service

  • It had never once returned successfully. After marking a record purged it re-read it with a query that excludes purged rows, got None, and raised — reporting PURGE_FAILED for work that had completed. The obvious responses to that message, retry or clean up by hand, are both wrong.
  • It could report success while the site was still serving. Caddy-removal and file-removal failures were dropped on the floor. Any failed step now yields PURGE_INCOMPLETE naming what was left behind.
  • Refuses to run when another live deployment shares the record's hostname, port or paths — a superseded record often still describes its successor's resources. Adds dry_run and force.

Also

  • register_service no longer invents paths. A static_path on a service with no static files made deploy_service create /var/www/{project}/ and symlink to it; a defaulted log_path pointed at a directory nothing creates. Docker services get no guessed paths at all.
  • Optional pull-based auto-deploy (deploy/auto-update.sh + systemd timer) with health check and automatic rollback.
  • Security: patched CVE-2026-48710 (Starlette BadHost), hardened the auth middleware.
  • ReadWritePaths in the sample unit now covers the trees the tools manage. With ProtectSystem=strict, a server whose INFRA_LOCAL_SERVER names itself could manage every host except its own — and sudo does not help, since a read-only mount is read-only for root too.

Full changelog: v1.0.0...v1.1.0

v1.0.0 — Initial Public Release

Choose a tag to compare

@raychao-oao raychao-oao released this 13 May 15:21

Infrastructure MCP Server v1.0.0

First public release of infra-mcp — a Model Context Protocol server that gives AI agents a structured interface to infrastructure primitives.

What's included

35 MCP tools across 6 categories:

  • Ports — allocate and release ports from a managed pool (3000–9999)
  • Services — deploy, restart, stop, audit, and inspect services on VPS via SSH + systemd + Caddy
  • Tunnels — register and manage Cloudflare Tunnels (per-server main tunnels)
  • DNS — create, update, delete, and list Cloudflare DNS records
  • Access — manage Cloudflare Access applications and policies
  • Git — create, list, inspect, and delete Gitea repositories

Architecture:

  • FastAPI + JSON-RPC 2.0 transport (MCP-compatible)
  • SQLite resource database (ports, tunnels, deployments)
  • Cloudflare Access + optional API key authentication
  • Zero Trust deployment — server binds to 127.0.0.1, all traffic via Cloudflare Tunnel
  • SSH-based remote command execution with shell injection prevention

Security

This release includes a full security audit pass: shell injection prevention, input validation, safe Caddy/systemd config generation, and hardened JSON-RPC error handling.

Getting Started

See QUICKSTART.md and docs/MCP-Client-Setup.md.