HostAtlas / AI & Automation / MCP Server
22 tools OAuth · read-mostly · per-tenant endpoint · audited

Claude · Cursor · Windsurf · ChatGPT read your infra.

Plug an AI assistant into your fleet without giving it a shell. 22 structured tools across hosts, alerts, monitors, backups, recipes — read-mostly, write where you opt in. Per-server toggle stays web-auth-only; the AI can’t flip its own visibility.

→ https://mcp.hostatlas.app copy

Generate a scoped Bearer token in Settings → MCP Tokens, then point your assistant at the endpoint above.

22
tools available today
read
default scope — writes opt-in
audit
every call, every tenant
● MCP connected hostatlas · 22 tools · scope: read
what’s the slowest endpoint on api-prod-eu right now?
→ list_hosts(tag="api", region="eu-*") ← 4 hosts
→ read_logs(host="api-prod-eu-1", source="php-fpm.slow", since="15m") ← 23 entries
The slow-request log shows POST /v1/reports averaging 8.4 s on api-prod-eu-1 over the last 15 min. The other 3 hosts are clean — this is localised to one box. Likely the database query inside ReportController.

Want me to ack the related alert, or show the failing query?
ack_alert show query nothing
— Why MCP instead of SSH

"Plug AI into ops" usually means "give it a shell". This is the alternative.

A free-form shell is too much trust for a probabilistic process. Structured tools with names, schemas, and scopes are reviewable. So is the audit log they generate.

shell is too much surface
Letting an AI ssh into prod is a security review nobody wants to write. MCP is structured tools with names + schemas — not a free-form shell.
reads beat writes 10:1
Most useful AI ops cases are reads — "summarise the fleet", "explain this alert", "show me what changed". Writes stay explicit, gated, audited.
per-host blast radius
Hide a host from MCP entirely with one toggle. That toggle is web-auth-only — the AI can't flip it back. SOC 2-ready audit on every call.
— Setup

Drop a config into your assistant.

Same MCP protocol, three popular hosts. All accept either an npx-bootstrapped stdio bridge or the hosted endpoint directly when the client supports it.

Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.json

{
  "mcpServers": {
    "hostatlas": {
      "command": "npx",
      "args": ["-y", "@hostatlas/mcp", "--token", "$HA_MCP_TOKEN"]
    }
  }
}

Cursor

~/.cursor/mcp.json

{
  "mcpServers": {
    "hostatlas": {
      "url": "https://mcp.hostatlas.app",
      "headers": { "Authorization": "Bearer ${HA_MCP_TOKEN}" }
    }
  }
}

Windsurf

~/.codeium/windsurf/mcp_config.json

{
  "mcpServers": {
    "hostatlas": {
      "command": "npx",
      "args": ["-y", "@hostatlas/mcp"],
      "env": { "HA_MCP_TOKEN": "<your-token>" }
    }
  }
}
— Reference

All 22 tools, expanded.

Every tool has a JSON schema you can introspect via tools/list. The side nav groups them the same way the discovery response does.

list_hosts
Return hosts in the calling tenant, optionally filtered. Excludes any host the admin has hidden from MCP via the per-server toggle.
{ "tag": "string optional, comma-separated", "region": "string optional, glob: eu-*, us-east-*", "status": "string optional: healthy | warn | critical | offline", "limit": "integer default 50, max 500" }
→ list_hosts(tag="prod", status="critical")
← [{"name":"keywest","region":"eu-fra","status":"critical","cpu":92,"mem":71}, …]
get_host
Full host detail: services, certs, IPs, agent version, recent metric peaks. Returns not-found if the host is hidden from MCP.
{ "host": "string required, hostname or UUID", "include": "string[] optional: services, certs, processes, tags" }
→ get_host(host="keywest", include=["services","certs"])
← {"name":"keywest","services":[…],"certs":[…],"agent":"v1.0.74"}
list_processes
Live process list for a host with CPU / RAM / args. Filter by user, command, or minimum CPU share.
{ "host": "string required", "user": "string optional", "min_cpu": "number optional, % threshold", "limit": "integer default 25, max 200" }
→ list_processes(host="keywest", min_cpu=5)
← [{"pid":1842,"user":"www-data","cpu":18.4,"cmd":"php-fpm: pool www"}, …]
list_connections
Active TCP / UDP connections per host. For "what is listening on 5433" or "who is connecting to mysql" questions.
{ "host": "string required", "state": "string optional: LISTEN | ESTABLISHED | TIME_WAIT", "port": "integer optional, local port match" }
→ list_connections(host="db-prod-01", state="LISTEN")
← [{"local":"0.0.0.0:5432","proc":"postgres"}, {"local":"127.0.0.1:6432","proc":"pgbouncer"}, …]
list_containers
Docker / Podman containers per host: image, status, exposed ports, restart count.
{ "host": "string required", "running": "boolean optional, default true" }
→ list_containers(host="ci-builder-03")
← [{"name":"runner-3","image":"buildkit:0.13","status":"Up 4h","ports":["1234:1234"]}, …]
read_configs
Snapshot of monitored config files (nginx, php-fpm, postgres, app conf). Returns content + last-modified per path.
{ "host": "string required", "path": "string optional, glob filter" }
→ read_configs(host="keywest", path="/etc/nginx/*.conf")
← [{"path":"/etc/nginx/nginx.conf","mtime":"2026-05-12T11:04Z","sha256":"…"}, …]
read_cron
Active cron entries per host (system + per-user), with last-run status from the agent's execution tracker.
{ "host": "string required", "user": "string optional, default = all users" }
→ read_cron(host="api-prod-eu-1")
← [{"user":"root","expr":"*/5 * * * *","cmd":"/usr/local/bin/check.sh","last":"ok"}, …]
read_firewall
UFW / firewalld rules as the agent sees them. Source of truth = the on-host kernel state, not a config file.
{ "host": "string required" }
→ read_firewall(host="bastion-eu-1")
← {"engine":"ufw","default_in":"deny","rules":[{"port":22,"src":"any","action":"allow"}, …]}
read_logs
Server-side log tail with regex filter. The AI does not need to know the per-host log path conventions — sources are named (nginx, php-fpm, postgres, app).
{ "host": "string required", "source": "string required, e.g. nginx, php-fpm, postgres, app", "grep": "string optional, server-side regex", "since": "duration optional, e.g. 15m, 1h", "limit": "integer default 100, max 1000" }
→ read_logs(host="api-prod-eu-1", source="php-fpm.slow", since="15m")
← 23 entries · top class: ReportController · longest 8.4s
list_monitors
All configured uptime + heartbeat monitors, with the latest state for each.
{ "kind": "string optional: uptime | heartbeat", "status": "string optional: up | down | unknown" }
→ list_monitors(kind="heartbeat", status="down")
← [{"name":"nightly-backup","last_seen":"2026-05-13T03:14Z","status":"down","grace":"24h"}, …]
list_alerts
Currently firing + recently resolved alert events. Filter by host, severity, source, or time window.
{ "status": "string optional: firing | resolved (default firing)", "severity": "string optional: critical | warning | info", "host": "string optional", "since": "duration optional, e.g. 24h" }
→ list_alerts(severity="critical")
← [{"uuid":"3f9c…","title":"High CPU: hostatlas1","value":"81.3%","fired_at":"2026-05-13T17:04Z"}, …]
list_incidents
Open + recently resolved incidents with severity and duration. Useful when the assistant should report on "what is currently broken".
{ "status": "string optional: open | resolved", "severity": "string optional", "since": "duration optional" }
→ list_incidents(status="open")
← [{"uuid":"a4e2…","severity":"high","duration":"23m","alerts":3,"hosts":["api-prod-eu-1"]}]
get_incident
Full detail for one incident: timeline, attached alerts, status notes, related deploys.
{ "uuid": "string required" }
→ get_incident(uuid="a4e2…")
← {"uuid":"a4e2…","timeline":[…],"alerts":[…],"deploys":[…],"notes":[…]}
list_backup_paths
Monitored backup paths per host. Each entry knows last-modified, file age, expected schedule.
{ "host": "string optional, default = all hosts", "stale": "boolean optional, only return paths older than expected schedule" }
→ list_backup_paths(stale=true)
← [{"host":"db-prod-01","path":"/srv/backup/postgres.dump","age":"38h","expected":"24h"}]
list_offsite_backups
Off-site (S3) backups present per tenant, with size + age + restore-readiness.
{ "host": "string optional", "since": "duration optional" }
→ list_offsite_backups(host="db-prod-01")
← [{"key":"db-prod-01/2026-05-13/full.dump.enc","size":"4.1 GB","age":"6h"}, …]
list_db_backups
Per-server database backups. Surfaces every restore-ready snapshot the agent tracks.
{ "host": "string optional", "engine": "string optional: mysql | postgres | mongodb" }
→ list_db_backups(host="db-prod-01", engine="postgres")
← [{"db":"hostatlas","ts":"2026-05-13T03:00Z","size":"1.8 GB","verified":true}, …]
list_domains
Domains tracked by the platform with SSL status + uptime score. Filterable by registrar, expiring-soon, monitor status.
{ "expiring_in": "duration optional, e.g. 30d", "registrar": "string optional" }
→ list_domains(expiring_in="30d")
← [{"domain":"acme.app","expires":"2026-06-04","registrar":"Cloudflare","ssl_days":18}, …]
get_domain
Detail for one domain: cert chain, DNS records, monitor history, recent uptime events.
{ "domain": "string required, FQDN" }
→ get_domain(domain="acme.app")
← {"domain":"acme.app","cert":{"issuer":"Let's Encrypt","expires":"2026-08-12"},"dns":[…]}
run_recipe
Execute a recipe on a host. Audit-logged. The caller token must hold the recipes:run scope; without it, this tool is omitted from the discovery list entirely.
{ "host": "string required, hostname or UUID", "recipe": "string required, recipe key", "inputs": "object optional, k/v map matching declared inputs", "dry_run": "boolean default false" }
→ run_recipe(host="api-prod-eu-1", recipe="restart-php-fpm")
← {"audit_id":"e8f3a2c1","status":"ok","duration_ms":1410,"steps":4}
ack_alert
Acknowledge a firing alert on behalf of the calling token. Resolution still requires explicit human confirmation through the UI / CLI.
{ "uuid": "string required, alert event UUID", "note": "string optional, free-text rationale" }
→ ack_alert(uuid="3f9c…", note="under investigation, see #ops-451")
← {"uuid":"3f9c…","acknowledged_at":"2026-05-14T18:42Z","by":"mcp-token:claude-desktop"}
activate_attack_mode
Enable stricter ingress / fail2ban / IDS rules per host. Requires the elevated scope; emits a prominent audit-log entry.
{ "host": "string required", "duration": "duration optional, default 4h (auto-revert)" }
→ activate_attack_mode(host="api-prod-eu-1", duration="2h")
← {"host":"api-prod-eu-1","mode":"active","reverts_at":"2026-05-14T20:42Z"}
deactivate_attack_mode
Counterpart to activate. Lifts the strict-rule set early. Same elevated-scope requirement.
{ "host": "string required" }
→ deactivate_attack_mode(host="api-prod-eu-1")
← {"host":"api-prod-eu-1","mode":"normal"}
endpoint + auth
The per-tenant MCP endpoint. OAuth + scoped Bearer tokens, identical to the REST API. Generate tokens under Settings → MCP Tokens; revoke by deleting.
Endpoint: https://mcp.hostatlas.app Protocol: mcp-protocol/2025-06-18 Auth: Authorization: Bearer <token> Scopes: read (default) write (run_recipe, ack_alert) elevated (attack mode) Rate limit: per-token, scales with tenant tier
$ curl -H "Authorization: Bearer $TOKEN" https://mcp.hostatlas.app/tools/list
← {"tools":[{"name":"list_hosts","description":"…"}, …22 total]}
— Honest answers

Questions that come up before pointing an AI at the fleet.

How do I authenticate?

OAuth + scoped Bearer tokens, identical to the REST API. Per-tenant client setup, per-user delegation. Generate tokens in Settings → MCP Tokens; revoke instantly by deleting.

Can I run my own MCP server pointing at HostAtlas data?

Yes — the protocol is open. We provide a per-tenant hosted endpoint; self-hosted is a thin proxy over the REST API with the same scope model.

Audit trail?

Every MCP tool call lands in the same audit log as UI / CLI actions. Searchable by tool name, calling token, tenant, time. Exportable for compliance evidence.

How does the per-server toggle work?

Each host has an "expose to MCP" flag. When off, the host is invisible to every MCP tool — list_hosts won't return it, get_host returns not-found. The toggle is web-auth-only; AI tokens can't see or flip it.

Rate limits?

Per-token, per-tenant. Generous on read tools, tighter on writes. Visible in the dashboard alongside the token; alertable when approaching limits.

Why not just give the AI a shell?

You can — HostAtlas has SSH Gatekeeper for short-lived audited certs. But for the common case (read fleet state, summarise, suggest action), MCP is the more boring, more reviewable surface.

— Works with

Connected capabilities.

AI assistants without giving up the keys.

MCP server available on every plan; rate limits + scope grants scale with tier.

Get early access Protocol docs