Eco MCP battery: node-local privileged surface for warded agents to inspect on-disk server state #42

Open
opened 2026-07-03 19:40:19 +00:00 by coilyco-ops · 7 comments
Owner

Goal

Let warded directors and engineers inspect the state of the local Eco game server on disk. Rather than bind-mount the live EcoServer/ tree into every warded container (heavy, and leaks Discord/Chronicler/API tokens sitting in Configs/), expose the state over a network MCP surface the agents fetch. This is the inside-out counterpart to eco-app's existing outside-in MCP.

Two views, this is the second

  • Outside-in (exists) - eco-app's current MCP: hits Eco's public /info endpoint, redacts player names, serves anyone. Live runtime, network-visible.
  • Inside-out (this issue) - a node-local MCP reading what only a process on kai-server can see: raw Configs/, Storage/ saves + Backup/ (×40), the Chronicler SQLite history, per-subsystem Logs/, unredacted admin data, mod install state, and the loopback-only admin endpoints. Privileged, at-rest + admin, warded-agents-only.

Home

Fold into coilyco-gaming/eco-app as a second, privileged MCP surface (distinct deployment from the public one). Decided.

Where it runs - node-local k3s pattern

The Eco server is a native systemd service (eco-server.service) at /home/kai/Steam/steamapps/common/EcoServer/, NOT in k3s. But k3s runs on the same host, so a pod pinned to kai-server:

  • hostPath-mounts EcoServer/{Configs,Storage,Logs,Mods} read-only, and
  • reaches the server's 127.0.0.1:3001 / :3002 admin endpoints over the hostAliases loopback pin.

MCP is exposed over the tailnet sidecar (reuse eco-app's deploy idiom), ACL'd to the ward/kai-server tailnet, not the public ingress. This is a node-agent MCP: the same DaemonSet-or-node-pinned + hostPath shape as node-exporter / promtail / CSI node plugins.

Tool inventory (provisional)

The disk-backed groups are the primary inside-out scope and remain required. RCON is an additive live-query group. It does not replace save, backup, config, event-history, log, or mod inspection.

Save + world (Storage/): eco_save_status (Game.eco/Game.db size + age since last autosave), eco_backup_list (Backup/ count, cadence, newest/oldest), eco_world_meta (size, seed, cluster centers, world-days).

Config (Configs/, redacted, enum-not-path): eco_config_get (named .eco/.diff.json), eco_config_diff (diff vs *.original.json - what's tuned off default), eco_mod_configs (MightyMooseCore, DiscordLink, StrangeWorlds, NidToolbox - secrets stripped).

Chronicler / events (SQLite, read RO): eco_events_recent (joins, laws, elections, deaths), eco_player_activity (from EcoReplay.db / Chronicler, redaction-gated).

Logs (Logs/<subsystem>/): eco_log_tail, eco_log_grep (named subsystem enum: MightyMooseCore, NidToolbox, web, Kestrel, migrations).

Live runtime (loopback :3001 admin/exporter): eco_live_status (players, world time, next meteor, uptime - unredacted), eco_service_health (systemd active-state + last restart).

RCON query (loopback :3002, authenticated, enum-only): eco_rcon_query runs only the approved fixed v1 enum below. The caller supplies one enum value and no command text or arguments. The server owns the literal mapping, bounds and redacts output, serializes connections, fails closed on unknown or changed commands, and records the enum plus outcome without credentials or returned player data. Protocol context: Eco RCON.

Approved v1 enum:

  • online_players -> manage players
  • meteor_status -> meteor status
  • world_time -> time now
  • climate_status -> climate status
  • sea_level -> sim sealevel
  • population_changes -> sim showpopulationchanges
  • active_elections -> civics elections
  • government -> civics showgovernment
  • civics_tick -> civics showtick
  • currencies -> money currencies
  • weather_status -> weather status
  • initial_spawn_positions -> initialspawn list

Mods (Mods/): eco_mods_installed (modules + versions, drift vs push-mods manifest).

Security boundary

  • Caller - warded directors/engineers only; tailnet ACL, not public.
  • Read-only - every mount is RO and every tool is a read. RCON is query-only through a compiled enum. The surface exposes no arbitrary command text, command chaining, save, shutdown, maintenance, restart, update, player administration, permission change, world mutation, or mod mutation. Those operations stay with operator scripts and systemd, never this MCP.
  • Redaction - three levels: public (names hashed), operator (names shown, secrets stripped), raw (operator + secrets - default DENY). Proposed default: operator for the trusted warded callers; raw off by default. Secrets (Discord bot token, /eco/server-api-token, any token in Configs) never returned above raw.
  • No arbitrary path read - config/log tools take an enum of known names, not a filesystem path, so a tool can't be walked into ../../.ssh.

Proposed defaults for the open questions

  • Redaction default = operator (names shown, secrets always stripped). Open to holding at public.
  • Scope = at-rest first (disk + logs + Chronicler - the state only this surface can serve). Live-runtime tools reuse eco-app's existing /info client as a thin add-on, not a reimplementation.
  • Chronicler read = read the SQLite file directly RO (don't link the mod's interop).
  • Generalize later - build the Eco battery concrete first, then extract the reusable node-local spine (node-pin scaffold + hostPath + tailnet sidecar + redaction levels + enum-not-path file access) as a general node-local MCP template, with Eco as instance #1. Don't abstract before one real instance exists.

Build phases

  1. Scaffold the MCP (Python, mcp SDK) with eco_save_status + eco_backup_list + eco_config_get against a local checkout of the state dirs - prove tools + redaction on real files.
  2. Node-local deploy: k3s manifest in coilyco-bridge/deploy/services/, hostPath RO mounts, node pin, loopback pin, tailnet sidecar, ward ACL.
  3. Add Chronicler + logs + live-runtime tool groups, including the enum-only read-only RCON query tool.
  4. Warded-agent smoke: a director/engineer session calls the tailnet MCP and inspects state end-to-end.
  5. If generalizing: extract the node-local spine into a template, leave Eco as instance #1.

Context

  • Live server root: /home/kai/Steam/steamapps/common/EcoServer/ (the eco-server-setup skill's /home/ubuntu/games/eco/ paths are stale legacy).
  • On-disk state confirmed: Storage/Game.db (256MB world SQLite), Game.eco save, EcoReplay.db, Backup/ ×40, Configs/*.eco + *.diff.json + *.original.json, Logs/{MightyMooseCore,NidToolbox,web,Microsoft.AspNetCore.*,migrations}/.
  • Server listens 0.0.0.0:3001 (Kestrel/ASP.NET, 200 on / and /api) + :3002.
  • eco-app already owns the outside-in public MCP (replaced the deprecated standalone coilysiren/eco-mcp-app).

Filed from a spec drafting session on kai-server.

## Goal Let warded **directors and engineers** inspect the state of the local Eco game server on disk. Rather than bind-mount the live `EcoServer/` tree into every warded container (heavy, and leaks Discord/Chronicler/API tokens sitting in `Configs/`), expose the state over a **network MCP surface** the agents fetch. This is the **inside-out** counterpart to eco-app's existing outside-in MCP. ## Two views, this is the second - **Outside-in (exists)** - eco-app's current MCP: hits Eco's public `/info` endpoint, redacts player names, serves anyone. Live runtime, network-visible. - **Inside-out (this issue)** - a node-local MCP reading what only a process on kai-server can see: raw `Configs/`, `Storage/` saves + `Backup/` (×40), the Chronicler SQLite history, per-subsystem `Logs/`, unredacted admin data, mod install state, and the loopback-only admin endpoints. Privileged, at-rest + admin, warded-agents-only. ## Home Fold into **`coilyco-gaming/eco-app`** as a **second, privileged MCP surface** (distinct deployment from the public one). Decided. ## Where it runs - node-local k3s pattern The Eco server is a native systemd service (`eco-server.service`) at `/home/kai/Steam/steamapps/common/EcoServer/`, NOT in k3s. But k3s runs on the same host, so a pod **pinned to kai-server**: - `hostPath`-mounts `EcoServer/{Configs,Storage,Logs,Mods}` **read-only**, and - reaches the server's `127.0.0.1:3001` / `:3002` admin endpoints over the `hostAliases` loopback pin. MCP is exposed over the tailnet sidecar (reuse eco-app's deploy idiom), ACL'd to the ward/kai-server tailnet, not the public ingress. This is a **node-agent MCP**: the same DaemonSet-or-node-pinned + hostPath shape as node-exporter / promtail / CSI node plugins. ## Tool inventory (provisional) The disk-backed groups are the primary inside-out scope and remain required. RCON is an additive live-query group. It does not replace save, backup, config, event-history, log, or mod inspection. **Save + world** (`Storage/`): `eco_save_status` (Game.eco/Game.db size + age since last autosave), `eco_backup_list` (Backup/ count, cadence, newest/oldest), `eco_world_meta` (size, seed, cluster centers, world-days). **Config** (`Configs/`, redacted, enum-not-path): `eco_config_get` (named .eco/.diff.json), `eco_config_diff` (diff vs *.original.json - what's tuned off default), `eco_mod_configs` (MightyMooseCore, DiscordLink, StrangeWorlds, NidToolbox - secrets stripped). **Chronicler / events** (SQLite, read RO): `eco_events_recent` (joins, laws, elections, deaths), `eco_player_activity` (from EcoReplay.db / Chronicler, redaction-gated). **Logs** (`Logs/<subsystem>/`): `eco_log_tail`, `eco_log_grep` (named subsystem enum: MightyMooseCore, NidToolbox, web, Kestrel, migrations). **Live runtime** (loopback `:3001` admin/exporter): `eco_live_status` (players, world time, next meteor, uptime - unredacted), `eco_service_health` (systemd active-state + last restart). **RCON query** (loopback `:3002`, authenticated, enum-only): `eco_rcon_query` runs only the approved fixed v1 enum below. The caller supplies one enum value and no command text or arguments. The server owns the literal mapping, bounds and redacts output, serializes connections, fails closed on unknown or changed commands, and records the enum plus outcome without credentials or returned player data. Protocol context: [Eco RCON](https://wiki.play.eco/en/RCON). Approved v1 enum: - `online_players` -> `manage players` - `meteor_status` -> `meteor status` - `world_time` -> `time now` - `climate_status` -> `climate status` - `sea_level` -> `sim sealevel` - `population_changes` -> `sim showpopulationchanges` - `active_elections` -> `civics elections` - `government` -> `civics showgovernment` - `civics_tick` -> `civics showtick` - `currencies` -> `money currencies` - `weather_status` -> `weather status` - `initial_spawn_positions` -> `initialspawn list` **Mods** (`Mods/`): `eco_mods_installed` (modules + versions, drift vs push-mods manifest). ## Security boundary - **Caller** - warded directors/engineers only; tailnet ACL, not public. - **Read-only** - every mount is RO and every tool is a read. RCON is query-only through a compiled enum. The surface exposes no arbitrary command text, command chaining, save, shutdown, maintenance, restart, update, player administration, permission change, world mutation, or mod mutation. Those operations stay with operator scripts and systemd, never this MCP. - **Redaction** - three levels: `public` (names hashed), `operator` (names shown, secrets stripped), `raw` (operator + secrets - default DENY). **Proposed default: `operator`** for the trusted warded callers; `raw` off by default. Secrets (Discord bot token, `/eco/server-api-token`, any token in Configs) never returned above `raw`. - **No arbitrary path read** - config/log tools take an enum of known names, not a filesystem path, so a tool can't be walked into `../../.ssh`. ## Proposed defaults for the open questions - **Redaction default** = `operator` (names shown, secrets always stripped). Open to holding at `public`. - **Scope** = **at-rest first** (disk + logs + Chronicler - the state only this surface can serve). Live-runtime tools reuse eco-app's existing `/info` client as a thin add-on, not a reimplementation. - **Chronicler read** = read the SQLite file directly RO (don't link the mod's interop). - **Generalize later** - build the Eco battery concrete first, then extract the reusable node-local spine (node-pin scaffold + hostPath + tailnet sidecar + redaction levels + enum-not-path file access) as a general node-local MCP template, with Eco as instance #1. Don't abstract before one real instance exists. ## Build phases 1. Scaffold the MCP (Python, mcp SDK) with `eco_save_status` + `eco_backup_list` + `eco_config_get` against a local checkout of the state dirs - prove tools + redaction on real files. 2. Node-local deploy: k3s manifest in `coilyco-bridge/deploy/services/`, hostPath RO mounts, node pin, loopback pin, tailnet sidecar, ward ACL. 3. Add Chronicler + logs + live-runtime tool groups, including the enum-only read-only RCON query tool. 4. Warded-agent smoke: a director/engineer session calls the tailnet MCP and inspects state end-to-end. 5. If generalizing: extract the node-local spine into a template, leave Eco as instance #1. ## Context - Live server root: `/home/kai/Steam/steamapps/common/EcoServer/` (the eco-server-setup skill's `/home/ubuntu/games/eco/` paths are stale legacy). - On-disk state confirmed: `Storage/Game.db` (256MB world SQLite), `Game.eco` save, `EcoReplay.db`, `Backup/` ×40, `Configs/*.eco` + `*.diff.json` + `*.original.json`, `Logs/{MightyMooseCore,NidToolbox,web,Microsoft.AspNetCore.*,migrations}/`. - Server listens `0.0.0.0:3001` (Kestrel/ASP.NET, 200 on `/` and `/api`) + `:3002`. - eco-app already owns the outside-in public MCP (replaced the deprecated standalone `coilysiren/eco-mcp-app`). _Filed from a spec drafting session on kai-server._
Owner

Two refinements (director session, 2026-07-05):

1. REST is out of scope — MCP-only. The surface stays a network MCP; no parallel REST API. (It still consumes the loopback admin REST endpoints, but only re-exposes them as MCP tools.)

2. Access model: add an /admin mount + the Authelia OAuth overlay for the claude.ai-hosted-connector path, complementing the tailnet-agent path this spec already has. Two callers, one surface:

  • Warded agents / local Claude Code (on the tailnet) — reach the privileged MCP directly; the tailnet already authenticates them. This is the spec's original tailnet ACL, warded-agents-only model.
  • Kai via claude.ai (off-tailnet, hosted connector) — needs an OAuth 2.1 gate. Reuse the auth overlay from coilyco-bridge/deploy#27 (Traefik ForwardAuth -> Authelia, node-stats-mcp is instance #1) and its fleet generalization deploy#30. claude.ai is the OAuth client; Authelia issues the token; no Anthropic identity to gate on; a single-user custom connector needs no DCR.

Ergonomics: the auth is not eco-app code — it is an ingress-layer overlay (deploy repo). eco-app only adds the /admin Starlette mount, feature-flagged (ECO_ADMIN_ENABLED) so only the privileged node-local deployment exposes it. eco-mcp.coilysiren.me already serves eco-app's MCP through Traefik + external-dns + LE, so there is no ingress to invent, only a gate to add in front of one path.

Threat model unchanged: the overlay authenticates who, it does not relax this spec's redaction levels — secrets stay default-DENY even for an authenticated caller. On-disk data access (hostPath RO, node-pin) is orthogonal to the auth gate and stays as specified.

**Two refinements (director session, 2026-07-05):** **1. REST is out of scope — MCP-only.** The surface stays a network MCP; no parallel REST API. (It still *consumes* the loopback admin REST endpoints, but only re-exposes them as MCP tools.) **2. Access model: add an `/admin` mount + the Authelia OAuth overlay for the claude.ai-hosted-connector path**, complementing the tailnet-agent path this spec already has. Two callers, one surface: - **Warded agents / local Claude Code** (on the tailnet) — reach the privileged MCP directly; the tailnet already authenticates them. This is the spec's original `tailnet ACL, warded-agents-only` model. - **Kai via claude.ai** (off-tailnet, hosted connector) — needs an OAuth 2.1 gate. Reuse the **auth overlay** from [coilyco-bridge/deploy#27](https://forgejo.coilysiren.me/coilyco-bridge/deploy/issues/27) (Traefik ForwardAuth -> Authelia, node-stats-mcp is instance #1) and its fleet generalization [deploy#30](https://forgejo.coilysiren.me/coilyco-bridge/deploy/issues/30). claude.ai is the OAuth *client*; Authelia issues the token; no Anthropic identity to gate on; a single-user custom connector needs no DCR. **Ergonomics:** the auth is **not eco-app code** — it is an ingress-layer overlay (deploy repo). eco-app only adds the `/admin` Starlette mount, feature-flagged (`ECO_ADMIN_ENABLED`) so only the privileged node-local deployment exposes it. `eco-mcp.coilysiren.me` already serves eco-app's MCP through Traefik + external-dns + LE, so there is no ingress to invent, only a gate to add in front of one path. **Threat model unchanged:** the overlay authenticates *who*, it does not relax this spec's redaction levels — secrets stay default-DENY even for an authenticated caller. On-disk data access (hostPath RO, node-pin) is orthogonal to the auth gate and stays as specified.
Author
Owner

The remaining design boundary is now locked and the issue is buildable.

  • Run /admin only in a separate node-pinned deployment with ECO_ADMIN_ENABLED=1. The ordinary public eco-app Deployment keeps the feature disabled.
  • Mount only the named Eco Storage, Configs, Logs, and Mods roots read-only. Tool inputs remain fixed enums, never caller-provided paths.
  • Keep the default redaction level at operator. Secret fields stay stripped. raw remains disabled in deployment, regardless of caller identity.
  • Warded tailnet callers use the direct private route. The claude.ai connector uses the established Traefik plus Authelia OAuth overlay from deploy#27 and deploy#30. No unauthenticated public route exists.
  • Phase 1 already ships under #59. The next repository work is the deploy-side node pin, read-only mounts, authenticated route, and smoke contract. Live mount and OAuth verification remains an operator checkpoint.

The issue no longer needs a product or exposure decision first. Removing consult and retaining interactive for implementation plus live verification.

The remaining design boundary is now locked and the issue is buildable. * Run `/admin` only in a separate node-pinned deployment with `ECO_ADMIN_ENABLED=1`. The ordinary public eco-app Deployment keeps the feature disabled. * Mount only the named Eco `Storage`, `Configs`, `Logs`, and `Mods` roots read-only. Tool inputs remain fixed enums, never caller-provided paths. * Keep the default redaction level at `operator`. Secret fields stay stripped. `raw` remains disabled in deployment, regardless of caller identity. * Warded tailnet callers use the direct private route. The claude.ai connector uses the established Traefik plus Authelia OAuth overlay from deploy#27 and deploy#30. No unauthenticated public route exists. * Phase 1 already ships under #59. The next repository work is the deploy-side node pin, read-only mounts, authenticated route, and smoke contract. Live mount and OAuth verification remains an operator checkpoint. The issue no longer needs a product or exposure decision first. Removing `consult` and retaining `interactive` for implementation plus live verification.
Author
Owner

Kai selected read-only RCON on 2026-07-25.

The #42 capability inventory now includes one enum-only eco_rcon_query tool. The security boundary is:

  • no caller-supplied command text or command chaining
  • only a compiled, audited enum of commands demonstrated read-only for the deployed Eco version
  • bounded and redacted responses
  • audit records contain the command enum and outcome, never credentials or returned player data
  • save, shutdown, maintenance, restart, update, player administration, permission changes, world mutations, and mod mutations remain excluded

Ordinary implementation and tests can proceed headlessly. HITL remains for approval of the initial exact command enum, operator-side credential and loopback wiring, and the first live redaction smoke.

Kai selected read-only RCON on 2026-07-25. The #42 capability inventory now includes one enum-only `eco_rcon_query` tool. The security boundary is: * no caller-supplied command text or command chaining * only a compiled, audited enum of commands demonstrated read-only for the deployed Eco version * bounded and redacted responses * audit records contain the command enum and outcome, never credentials or returned player data * save, shutdown, maintenance, restart, update, player administration, permission changes, world mutations, and mod mutations remain excluded Ordinary implementation and tests can proceed headlessly. HITL remains for approval of the initial exact command enum, operator-side credential and loopback wiring, and the first live redaction smoke.
Author
Owner

Kai approved the 12-command read-only RCON core on 2026-07-25.

The issue body now records the exact external enum and internal command mapping. RCON remains additive. The privileged MCP still includes all disk-backed groups:

  • save and world state
  • backup inventory and cadence
  • named redacted configs and diffs
  • Chronicler and replay event history
  • bounded log tail and grep
  • installed mod inventory and drift
  • live runtime status
  • the approved enum-only RCON query group

The six privacy-heavy RCON candidates remain deferred. No free-form RCON arguments or mutating commands entered scope.

Kai approved the 12-command read-only RCON core on 2026-07-25. The issue body now records the exact external enum and internal command mapping. RCON remains additive. The privileged MCP still includes all disk-backed groups: * save and world state * backup inventory and cadence * named redacted configs and diffs * Chronicler and replay event history * bounded log tail and grep * installed mod inventory and drift * live runtime status * the approved enum-only RCON query group The six privacy-heavy RCON candidates remain deferred. No free-form RCON arguments or mutating commands entered scope.
Author
Owner

Implementation contract

Goal - Ship the repository-owned portion of the privileged inside-out Eco MCP: complete the read-only disk capability groups, add the approved 12-command enum-only RCON query, and author the precedent-matched node-local deployment declaration.

Done-condition - Eco-app focused and full repository gates pass, deploy rendering and repository gates pass, both commits land through their resolved workflows, and #42 records the exact remaining operator-only rollout and live-smoke evidence.

Non-goals - No live deployment, restart, secret read or mutation, raw redaction enablement, free-form RCON, mutating RCON, arbitrary filesystem path access, save-file editing, direct workload execution, or production iteration. The six privacy-heavy RCON candidates remain deferred.

## Implementation contract **Goal** - Ship the repository-owned portion of the privileged inside-out Eco MCP: complete the read-only disk capability groups, add the approved 12-command enum-only RCON query, and author the precedent-matched node-local deployment declaration. **Done-condition** - Eco-app focused and full repository gates pass, deploy rendering and repository gates pass, both commits land through their resolved workflows, and #42 records the exact remaining operator-only rollout and live-smoke evidence. **Non-goals** - No live deployment, restart, secret read or mutation, raw redaction enablement, free-form RCON, mutating RCON, arbitrary filesystem path access, save-file editing, direct workload execution, or production iteration. The six privacy-heavy RCON candidates remain deferred.
Author
Owner

Repository work is complete.

  • eco-app main now ships fourteen privileged read-only tools, including the exact twelve-query RCON enum and fixed Storage, Configs, Logs, and Mods readers.
  • The app gate passes lint and smoke. All 42 admin tests pass. The full native suite has 557 passes with only the three existing Windows portability failures tracked in #160.
  • Deploy and Authelia declarations are committed and pushed on coilyco-bridge/deploy:feat/eco-admin-mcp-42. The branch remains off deploy main because main would trigger live CD.
  • Offline eco-app and Authelia renders pass. The deploy catalog passes with the known Windows-only symlink, Forgejo runner installer, and TruffleHog launcher hooks excluded. No secret finding was reported.
  • Live secret provisioning, rollout, OAuth proof, disk smoke, and all twelve RCON command-path checks are handed to coilyco-bridge/deploy#228 with the interactive label.

#42 stays open until Ops returns that live evidence.

Repository work is complete. * eco-app main now ships fourteen privileged read-only tools, including the exact twelve-query RCON enum and fixed Storage, Configs, Logs, and Mods readers. * The app gate passes lint and smoke. All 42 admin tests pass. The full native suite has 557 passes with only the three existing Windows portability failures tracked in #160. * Deploy and Authelia declarations are committed and pushed on `coilyco-bridge/deploy:feat/eco-admin-mcp-42`. The branch remains off deploy main because main would trigger live CD. * Offline eco-app and Authelia renders pass. The deploy catalog passes with the known Windows-only symlink, Forgejo runner installer, and TruffleHog launcher hooks excluded. No secret finding was reported. * Live secret provisioning, rollout, OAuth proof, disk smoke, and all twelve RCON command-path checks are handed to coilyco-bridge/deploy#228 with the `interactive` label. #42 stays open until Ops returns that live evidence.
Owner

I've been back and forth on this a TON - and concluded that this belongs in eco-app b/c its a live service functionality.

eco-mods is public mods, c#, shipped to mod.io

eco-ops is ... generally things that shouldn't be public. and scripted ops surfaces, CLI scripted.

like -maybe- eco-ops might host this, but eco-app already have the server infra.

I've been back and forth on this a TON - and concluded that this belongs in eco-app b/c its a live service functionality. eco-mods is public mods, c#, shipped to mod.io eco-ops is ... generally things that shouldn't be public. and scripted ops surfaces, CLI scripted. like -maybe- eco-ops might host this, but eco-app already have the server infra.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/eco-app#42
No description provided.