Skip to main content
Glama

calagopus-mcp

A Model Context Protocol server for a running Calagopus panel — users, servers, nodes, nests and eggs, database hosts, database agent hosts and templates, locations, mounts, roles, backups and system health.

Read-only out of the box. Creating records and changing a server's power state are opt-in, off by default, and every one of them requires an explicit two-phase confirmation.

Built and verified against Calagopus 1.2.0.

What it can do

Ask things like:

  • "How many servers are on node X, and how much memory is still unallocated?"

  • "Which servers use the Project Zomboid egg?"

  • "Find the account for someone@example.com and list what they own."

  • "Is the panel clock in sync and are all migrations applied?"

  • "What database agent hosts are configured, and what's left on them?"

Safety

Read side

Property

How it is enforced

Single-panel. Cannot be redirected to another host.

Tools take API paths; a full URL is rejected, and .. is refused.

Secrets stay behind.

Values under password, token, client_secret, access_key, secret_key, connection_string and friends are replaced before the response is rendered. Set CALAGOPUS_ALLOW_SECRETS=true to opt out.

Bounded output.

Responses are summarised by default and hard-capped at CALAGOPUS_MAX_RESPONSE_CHARS; truncation is announced, never silent.

Write side

Writes are off unless you turn them on, and there are only two switches:

CALAGOPUS_ALLOW_CREATE=true   # create nests, locations, nodes, servers, eggs
CALAGOPUS_ALLOW_POWER=true    # start / stop / restart / kill ONE server

Six things hold, in layers:

  1. Not registered means not reachable. With both flags off, no mutating tool is advertised at all. There is no tool to call and no error path to argue past — the model simply cannot see one.

  2. Two-phase confirmation on every change. A tool called without confirm_token changes nothing: it resolves the real target from the panel, renders what would happen, and returns a token. Only a second call carrying that token executes.

  3. Tokens are bound to their arguments. A token is a fingerprint of the operation and every argument. A token issued for "stop server A" will not execute "stop server B", or "kill server A", or the same action with an edited limit. It is single-use and expires (5 minutes by default). This is what makes the preview meaningful: what you read is the only thing that token can do.

  4. No bulk anything. Power actions take one server UUID — not a list. The panel does expose POST /nodes/{node}/servers/power accepting {"type": "all"}, i.e. power-cycle every server on a node in one request. This server never calls it; the route is absent from the write allowlist. Restarting five servers means five previews and five confirmations.

  5. kill is its own tier. It additionally requires acknowledge_data_loss: true, because it SIGKILLs the container with no graceful shutdown and no world save. The preview says so in capitals.

  6. A transport-level allowlist. client.ts will only POST to nine exact route patterns, checked at the point of request. There is no DELETE or PATCH method on the client at all, so nothing can be deleted or modified — only created, plus power state. A wiring mistake cannot reach a route nobody reviewed.

Every executed mutation is logged to stderr with a timestamp, the operation and the resolved target.

Scope it further with the API key itself: grant only the permissions you want reachable. The read tools need admin *.read; creates need the matching *.create; power needs the server permissions control.start, control.stop and control.restart.

What a change looks like

// 1. No confirm_token — nothing is sent to the panel.
{ "server": "95b3a3a4-...", "action": "stop" }
→ {
    "status": "preview_only",
    "nothing_was_changed": true,
    "action": "STOP server \"Zomboid\" (95b3a3a4-...)",
    "target": { "name": "Zomboid", "current_status": "running",
                "node": "Integrated Node", "owner": "BerdiiNN" },
    "warnings": ["Asks the server to shut down gracefully, ... Players are disconnected."],
    "confirm_token": "cfm_...", "token_expires": "..."
  }

// 2. Same arguments plus the token — now it happens.
{ "server": "95b3a3a4-...", "action": "stop", "confirm_token": "cfm_..." }
→ { "status": "executed", ... }

Setup

npm install && npm run build

Create an API key in the panel under Account → API Keys and grant it the admin read permissions you want. Then register the server with your client.

Claude Code:

claude mcp add calagopus --env CALAGOPUS_URL=https://panel.example.com --env CALAGOPUS_API_KEY=c7sp_xxx -- node /absolute/path/to/calagopus-mcp/dist/index.js

Claude Desktop / any MCP client, in mcpServers:

{
  "mcpServers": {
    "calagopus": {
      "command": "node",
      "args": ["/absolute/path/to/calagopus-mcp/dist/index.js"],
      "env": {
        "CALAGOPUS_URL": "https://panel.example.com",
        "CALAGOPUS_API_KEY": "c7sp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      }
    }
  }
}

For running the server or the smoke test by hand, a .env beside package.json is read at startup (see .env.example). Anything already in the environment wins, so an MCP client's own config is never overridden by a stale local file.

Environment

Variable

Default

Purpose

CALAGOPUS_URL

(required)

Panel base URL. A trailing /api or / is trimmed for you.

CALAGOPUS_API_KEY

(required)

API key, c7sp_ + 43 characters.

CALAGOPUS_TIMEOUT_MS

30000

Per-request timeout.

CALAGOPUS_MAX_RESPONSE_CHARS

60000

Cap on a single tool result.

CALAGOPUS_ALLOW_SECRETS

false

Stop redacting secret-looking fields.

CALAGOPUS_INSECURE_TLS

false

Accept self-signed certificates. Process-wide — use only against a lab panel.

CALAGOPUS_ALLOW_CREATE

false

Register the create tools.

CALAGOPUS_ALLOW_POWER

false

Register the server power tool.

CALAGOPUS_CONFIRM_TTL_MS

300000

How long a confirmation token stays valid.

Tools

36 read tools, plus 6 write tools when enabled. Start with calagopus_panel_info.

Orientation calagopus_panel_info · calagopus_search · calagopus_get_system_health · calagopus_list_endpoints · calagopus_get

Userscalagopus_list_users · calagopus_get_user · calagopus_get_user_servers · calagopus_get_user_activity · calagopus_list_roles · calagopus_get_activity

Serverscalagopus_list_servers · calagopus_get_server · calagopus_get_server_resource (allocations, variables, backups, databases, database_instances, mounts, available_mounts, logs, install_logs)

Nodescalagopus_list_nodes · calagopus_get_node · calagopus_get_node_resource (servers, allocations, available_allocations, allocation_ips, backups, mounts, capacity, database_hosts, database_agent_hosts, transfers, system_overview, system_stats, system_logs, resources) · calagopus_list_locations · calagopus_list_mounts

Nests & eggscalagopus_list_nests · calagopus_list_eggs · calagopus_get_egg · calagopus_get_egg_variables · calagopus_get_egg_servers · calagopus_list_egg_repositories · calagopus_list_egg_configurations

Databasescalagopus_list_database_hosts · calagopus_get_database_host · calagopus_list_database_agent_hosts · calagopus_get_database_agent_host · calagopus_get_database_agent_host_resource (instances, capacity, config, system_overview, system_stats) · calagopus_list_database_agent_templates

Backups & SSOcalagopus_list_backup_configurations · calagopus_list_system_backup_policies · calagopus_list_oauth_providers · calagopus_list_extensions

Create (needs CALAGOPUS_ALLOW_CREATE)calagopus_create_nest · calagopus_create_location · calagopus_create_node · calagopus_create_server · calagopus_install_egg_from_repository · calagopus_import_egg

Power (needs CALAGOPUS_ALLOW_POWER)calagopus_server_power

calagopus_create_server previews more than it is asked to: it resolves the node, egg and owner by name, and checks the egg's declared variables so a missing required value shows up in the preview rather than as a rejected create.

summary vs full

List tools return a trimmed projection plus a pagination block. Raw panel records are big — a server inlines its whole node, owner and egg (~15 KB each), and an egg carries its install script and upstream copy (~13 KB) — so a page of 25 in full detail would be several hundred KB of context. Pass detail: "full" on a single-record tool when you genuinely need the scripts or every field.

Not covered by a dedicated tool?

calagopus_list_endpoints searches the panel's live OpenAPI document, so it reflects that panel's version including routes added by extensions. Feed a path you find into calagopus_get:

calagopus_list_endpoints { filter: "backup-configurations" }
calagopus_get { path: "/api/admin/backup-configurations/<uuid>/stats" }

Interpreting errors

Errors come back in-band with a hint. The one worth recognising:

500 … error sending request for url (http://…) — the panel is healthy but could not reach the daemon behind that endpoint. The node or database agent is offline, still booting, or has the wrong URL configured. Panel-stored records (calagopus_get_node, calagopus_list_servers, …) still read fine; only the live system_*, capacity, resources, transfers and log facets need the daemon up.

A 403 names the exact admin permission the key is missing.

Testing

scripts/smoke.mjs connects over real stdio MCP, lists the tools, and calls every one of them against a live panel — discovering UUIDs from earlier calls so per-record tools run with real identifiers. It fails if any tool is left unexercised.

CALAGOPUS_URL=http://localhost:8000 CALAGOPUS_API_KEY=c7sp_xxx npm run smoke

Facets that need a wings daemon or database agent are counted as skipped rather than failed when that daemon is unreachable.

scripts/safety.mjs is the suite that matters for the write side. It launches the server three times — read-only, create-enabled, power-enabled — and asserts the guard rails: that no mutating tool exists by default, that a dry run changes nothing, that a token is refused against a different server, a different action, or on a second use, that kill demands acknowledgement, that no bulk power tool exists, and that the read-side escape hatch has no method or body argument.

npm run safety              # guard rails only, no writes
SAFETY_LIVE=1 npm run safety   # also creates a real nest + location, named mcp-safety-*
npm test                    # both suites

Without SAFETY_LIVE, the suite performs no writes at all — it stops at previews. SAFETY_LIVE=1 additionally creates a real nest and location, and spends one token on a real start to prove replay protection; the records it creates stay behind, because this server cannot delete them by design. Remove them from the panel yourself.

Layout

src/
  index.ts          stdio wiring and conditional tool registration
  config.ts         environment parsing and validation
  client.ts         HTTP client, POST allowlist, error typing, hints
  confirm.ts        two-phase confirmation tokens
  format.ts         redaction, per-resource projections, size cap
  tools/
    common.ts       shared arg shapes, result rendering, error guard, twoPhase
    system.ts users.ts servers.ts nodes.ts eggs.ts databases.ts raw.ts
    create.ts       create tools   (gated on CALAGOPUS_ALLOW_CREATE)
    power.ts        power tool     (gated on CALAGOPUS_ALLOW_POWER)

Adding a read resource is normally: a projection in format.ts, and a registerTool call in the matching tools/*.ts. Adding a write also means adding its route to POST_ALLOWLIST in client.ts and a case to scripts/safety.mjs.

License

MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/BerdiiNN/calagopus-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server