Skip to content

komodo-mcp (typed, redacted, allowlisted) + names-only change records on Updates - #4

Merged
thedancingdeveloper merged 2 commits into
tdd/patchesfrom
feat/komodo-mcp
Oct 4, 2026
Merged

thedancingdeveloper merged 2 commits into
tdd/patchesfrom
feat/komodo-mcp

Conversation

@thedancingdeveloper

Copy link
Copy Markdown

Implements Vogt WI-846 (Komodo MCP) and WI-865 (change attribution and names-only diffs). They are split into two commits.

1. feat(core): names-only diffs and the asserted actor on Updates (WI-865)

  • New fork-only crate lib/names_diff. It does a Komodo-compatible env parse (including multi-line quoted values), produces sha256:<12hex>/<len> fingerprints, and diffs resource TOML. Values appear only for an allowlist of non-secret fields (default-deny).
  • bin/core/src/tdd: Core adds logs to Updates without touching the schema, the API types or the UI:
    • Config diff (names only) on every resource config update (resource::update).
    • Actor (asserted) from X-Komodo-Actor / X-Komodo-Reason, carried through a task_local (router layer on /write and /execute, plus propagate around the spawned write task). The header is asserted, not authenticated, so it serves attribution only.
    • File change (names only) on WriteStackFileContents.
  • The hooks are additive, one or two lines each and marked FORK (WI-865). The single modified upstream line wraps the write tokio::spawn in tdd::propagate. The conflict table in docs/tdd/MAINTENANCE.md is updated.
  • Core is not redeployed by this PR (operator decision).

2. feat(mcp): bin/mcp / komodo-mcp (WI-846)

A stdio MCP server on the typed komodo_client. Tools:

  • Read-only: stack_lookup, get_stack, env_diff, read_stack_file, container_health, container_logs, stack_history.
  • Mutating: write_stack_file and deploy_stack.
  • Details in bin/mcp/README.md.
  • Redaction is default-deny. The stack is projected per field from Komodo's own types, unclassified fields are withheld, and every_stack_field_is_classified fails when a rebase adds a field. A scrubber is the second layer: it replaces every known env value and common token shape in every result, including logs and error text.
  • Allowlist. The server is read-only by default (KOMODO_MCP_WRITE_STACKS). *prod* is protected and needs an exact name in KOMODO_MCP_ALLOW_PROTECTED. Rules are matched on the resolved stack name, so passing an id cannot bypass them.
  • write_stack_file runs RefreshStackCache first, skips identical content, refuses .env files, and re-refreshes afterwards to verify.
  • deploy_stack polls the Update until it completes and returns pass/fail. On failure it adds the failing stage's tail (HTML stripped, scrubbed); on success it adds container health.
  • History works without the Core change. stack_history and env_diff {last_successful_deploy} diff the Update prev_toml/current_toml client-side, so they work against today's unpatched Core.
  • The binary is also built into scripts/tdd/core-only.Dockerfile at /usr/local/bin/komodo-mcp. CI fmt/test now covers names_diff and komodo_mcp, plus cargo test -p komodo_core tdd::.

Verification

  • cargo test -p names_diff passes (11 tests). cargo test -p komodo_mcp passes (26 tests): unit tests, plus mock-Core end-to-end tests through every tool with fake secrets. The mock deserializes each request into Komodo's request types and covers allowlist and id-bypass refusal, write ordering (refresh, write, refresh) and deploy polling.
  • cargo test -p komodo_core tdd:: passes (5 tests). The documented checks cargo test -p interpolate -p infisical --locked and cargo check -p komodo_core -p komodo_periphery --locked pass.
  • cargo fmt and cargo clippy --all-targets are clean on the fork crates. Upstream files keep their baseline formatting.
  • Live read-only smoke against Core 2.2.0, run locally with toolchain 1.98 (CI pins 1.95):
    • All read tools ran on vogt-dev/vogt-prod, and mutations were refused.
    • Every output was checked against the 33 real env values of both stacks, with the public stack names excluded: 0 leaks.
    • stack_history(vogt-prod) shows the 2026-10-02 UpdateStack as names only: 5 keys added (MYDEVENV2_ASSISTANT_*, VOGT_IMAGE, VOGT_STACK_IMAGE) and MYDEVENV2_FCM_SERVICE_ACCOUNT_JSON changed.
    • No actor shows on that Update, because Core is unpatched.

Not done here

  • Registering the server through mcp-bootstrap, pointing the komodo skill at the MCP, and retiring the curl recipes are follow-ups.
  • Redeploying Core is the operator's call.

🤖 Generated with Claude Code

thedancingdeveloper and others added 2 commits October 4, 2026 06:38
…Updates

Every agent and the operator share one Komodo API key, so an Update's
`operator` cannot say which session changed a stack, and seeing what
changed meant reading prev_toml/current_toml, which hold every env value
in plain text (WI-865).

Core now adds two logs to Updates, leaving the Update schema, the API
types and the UI untouched:

- "Config diff (names only)" on every resource config update: env keys
  added/removed/changed with values shown as sha256 fingerprints, and
  values for allowlisted non-secret fields only (default-deny).
- "Actor (asserted)" from the X-Komodo-Actor / X-Komodo-Reason headers,
  carried by a task-local through the write/execute handlers. Asserted
  by the API key holder, not verified: attribution, not authorization.
- "File change (names only)" on WriteStackFileContents: fingerprints and
  line counts against Komodo's cached copy.

The diff logic lives in a new fork-only crate, lib/names_diff, which the
komodo-mcp binary shares. The Core hooks are one or two lines each and
are marked FORK (WI-865). The one changed upstream line wraps the
spawned write task in tdd::propagate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agents drove Komodo through 483 raw curl calls. Nine sessions tripped
over the body shape (envelope, params.id vs params.stack, method names),
GetStack leaked .config.environment into a transcript, and stack file
writes that skipped RefreshStackCache deployed stale content (WI-846).

bin/mcp is a stdio MCP server built on the fork's typed client:

- stack_lookup, get_stack, env_diff, read_stack_file, container_health,
  container_logs and stack_history are read-only.
- write_stack_file runs RefreshStackCache first, skips identical
  content, refuses .env files and re-reads to verify the write.
- deploy_stack polls the Update to completion. It returns pass/fail
  plus the failing log tail (HTML stripped), or container health on
  success.
- Redaction is default-deny. A stack is projected field by field from
  Komodo's own types, env is names, fingerprints and [[references]]
  only, and unclassified fields are withheld. A test fails when upstream
  adds a field. Every result is also scrubbed of all known env values
  and common token shapes.
- Mutations are read-only by default and allowlisted per stack, matched
  on the resolved name. Protected stacks (*prod*) need an exact opt-in
  that a glob never grants.
- Mutating calls send X-Komodo-Actor/-Reason, which the previous commit
  stamps on the Update. stack_history and env_diff (against
  last_successful_deploy) diff the Update TOMLs client-side using the
  shared lib/names_diff, so they work against an unpatched Core too.

Tests: redaction never emits a fake secret (unit tests plus mock-Core
E2E through every tool), allowlist enforcement including id-based
bypass, and request bodies checked against Komodo's request types.
The binary also ships in the fork's Core image, and CI covers the new
crates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thedancingdeveloper
thedancingdeveloper merged commit 769d43f into tdd/patches Oct 4, 2026
@thedancingdeveloper
thedancingdeveloper deleted the feat/komodo-mcp branch October 4, 2026 06:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant