Skip to content
This repository was archived by the owner on May 26, 2026. It is now read-only.

feat(kora): KR-MCP-RUNTIME-SURFACE ST1 — 5 read-only agent-facing tools#112

Merged
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-MCP-RUNTIME-SURFACE-ST1
May 22, 2026
Merged

feat(kora): KR-MCP-RUNTIME-SURFACE ST1 — 5 read-only agent-facing tools#112
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-MCP-RUNTIME-SURFACE-ST1

Conversation

@rafe-walker

Copy link
Copy Markdown
Owner

Summary

Extends the /mcp pipe from PR #101's proof-of-pipe (`kora__daemon_status` only) with 5 read-only tools so other agents — IsoKron PM via Slack #claude-pms, drone tickets, future PMs — can introspect Kora's runtime state programmatically.

Bucket spec: `kora_docs/17_cc_bucket_prompts/KR-MCP-RUNTIME-SURFACE_agent_facing_tools.md` (commit 54032c6).

Base: `feature/phase2-upgrades` — NOT main.

New tools (all READ-ONLY)

Tool Returns
`kora__get_operational_state` current PrimaryState + degradation_reasons + claim_permission + recent transitions (ring buffer, last 20) + state_entered_at
`kora__get_health_rollup` overall / control_plane / worker statuses + 8 per-subsignal projections with last_seen + extra-bag detail
`kora__get_recent_ledger_entries(limit, status?)` last N kora_operation_ledger rows (default 50, max 200), filterable by status enum
`kora__get_recent_chain_events(limit, event_kind?)` last N kora.* chain events via existing read_recent_kora_events helper
`kora__list_active_sea_tickets(limit)` Kora's currently-active Sea_Tickets (claimed / in_progress / failed_retryable)

All five are READ-ONLY: no substrate writes, no ledger rows, no state mutations. ST2 brings the mutating surface + capability gating.

Files

  • `kora_cli/listeners/mcp_tools.py` (NEW, ~570 lines) — Pydantic response models per tool, async executor functions, JSON-RPC dispatch table.
  • `kora_cli/listeners/mcp.py` (+37 lines) — extends TOOLS list at import time; tools/call routes to TOOL_DISPATCH for new tools; tool exception → JSON-RPC -32603 envelope (not 500).
  • `tests/kora_cli/test_listeners/test_mcp_tools.py` (NEW, 20 tests, all passing).

K-DG drift findings (surfaced + corrected in this PR)

  • PrimaryState enum: spec said "STARTING / RUNNING / DRAINING / STOPPED"; actual is BOOTING / READY / ACTIVE / PAUSED / STOPPED per `agent/operational_state.py:70-74` (KR-P2-I-skeleton). ST1 surfaces actual values.
  • HealthRollup attribute names: spec assumed `overall_status / control_plane_status / worker_status`; actual bare `overall / control_plane / worker` per `agent/health_rollup_holder.py:174-176`.
  • TransitionRecord keys: spec assumed `transitioned_at / from_primary_state / to_primary_state`; actual `timestamp / from_state / to_state / trigger` per `agent/operational_state_holder.py:73-82`.
  • `holder.current` is a `@property`, not a method — caught + fixed during testing.

Drift findings affecting ST2 (PRE-FILED for PM ruling before ST2 lands)

  • `actor_has_capability(capability: str)` is IMPLICITLY Kora-only per `plugins/memory/isokron/capability_check.py:69`. The spec's 2-arg `actor_has_capability(actor_kind, capability)` shape doesn't exist.

    ST2 needs either:

    • (a) New substrate-side caller-aware function (heavier — likely a new SECDEF on the capability matrix; substrate-team coord)
    • (b) Bearer-token-→-role ACL via the same `~/.kora/mcp_callers.yaml` PM Q1 chose (lighter — composes cleanly; same file holds `{token_hash → {actor_kind, allowed_caps}}`)

    Recommend (b) — semantically cleaner: Kora's own caps reflect what she's allowed to do; MCP callers' caps reflect what we let them ask her to do. PM ruling needed before ST2 ships.

  • `sea__create_ticket` "blocked" comments still present in `agent/cron_work_class.py` but tool IS live on prod per prior PM acks + memory `feedback_dont_trust_stub_comments_for_liveness.md`. ST2 will use `IsoKronMCPClient.invoke("sea__create_ticket", ...)` via the generic dispatcher.

§4 PM-opens — my reads

  • Q1 (caller actor_kind resolution): (B) file-based `~/.kora/mcp_callers.yaml`. Agreed; composes with drift-finding (b).
  • Q2 (multi-token rotation): comma-separated env list + constant-time iterate. Agreed.
  • Q3 (dev-only flag in `tools/list`): YES surface `dev_only: true` in descriptor. Agreed.

§5 ship checklist

  • Base `feature/phase2-upgrades`
  • Title format `feat(kora): KR-MCP-RUNTIME-SURFACE STn — `
  • §4 questions surfaced + reads aligned with PM defaults
  • ST1: NO state mutations, NO ledger writes (enforced by structure — no writer imports in mcp_tools.py)
  • Tests pass locally (113/113 across full daemon + listener + docker suite)
  • K-DG: every drift surface grepped + flagged

End-to-end smoke

```
KORA_DEV=1 KORA_MCP_BEARER_TOKEN=tok KORA_SLACK_SIGNING_SECRET=s \
KORA_PUREMAIL_HMAC_SECRET=s KORA_WEB_PORT=9389 \
KORA_WEBHOOK_PORT=9388 KORA_WEBHOOK_HOST=127.0.0.1 \
kora daemon
```

  • `GET /mcp/tools/list` → 6 tools (`kora__daemon_status` + 5 new)
  • `POST /mcp tools/call kora__get_operational_state` → valid Pydantic JSON; `holder_available=false` (no holder initialized in standalone daemon — honest placeholder)
  • `POST /mcp tools/call kora__get_recent_ledger_entries {limit: 25}` → valid Pydantic JSON; `provider_unavailable=true` (no substrate provider — honest placeholder, no crash)
  • SIGTERM exits 0

What's next

ST2 (after PM rules on the capability-gating drift — finding (b) above): 3 mutating tools (`kora__request_state_transition`, `kora__create_sea_ticket`, `kora__send_webhook_test_event`) + capability gating + bearer-token-→-actor_kind resolution + multi-token rotation support + dev_only flag in descriptor + prd-environment refusal for the test webhook tool.

🤖 Generated with Claude Code

Extends the /mcp pipe from PR #101's proof-of-pipe (kora__daemon_status
only) with 5 read-only tools so other agents — IsoKron PM via Slack
#claude-pms, drone tickets, future PMs — can introspect Kora's
runtime state programmatically.

## New tools

- `kora__get_operational_state` — current PrimaryState (BOOTING /
  READY / ACTIVE / PAUSED / STOPPED) + degradation_reasons +
  claim_permission + recent transitions (ring buffer, last 20) +
  state_entered_at (computed from most-recent transition matching
  current state).
- `kora__get_health_rollup` — overall/control_plane/worker statuses +
  the 8 per-subsignal projections with last_seen + extra-bag detail.
- `kora__get_recent_ledger_entries(limit, status?)` — last N
  kora_operation_ledger rows, default 50, max 200, filterable by
  status enum.
- `kora__get_recent_chain_events(limit, event_kind?)` — last N
  kora.* chain events via existing read_recent_kora_events helper;
  in-process prefix filter on event_kind.
- `kora__list_active_sea_tickets(limit)` — Kora's currently-active
  Sea_Tickets (claimed / in_progress / failed_retryable) assigned
  to her actor.

All five are READ-ONLY: no substrate writes, no ledger rows, no
state mutations. ST2 brings the mutating surface + capability gating.

## Files

- **`kora_cli/listeners/mcp_tools.py`** (NEW, ~570 lines) — Pydantic
  response models per tool, async executor functions, JSON-RPC
  dispatch table (TOOL_DISPATCH).
- **`kora_cli/listeners/mcp.py`** (+37 lines) — extends TOOLS list
  with the 5 new descriptors at module-import time; tools/call now
  routes to mcp_tools.TOOL_DISPATCH for the new tools; tool
  exception → JSON-RPC -32603 envelope (not 500).
- **`tests/kora_cli/test_listeners/test_mcp_tools.py`** (NEW, 20
  tests, all passing) — descriptor surface + holder-unavailable +
  provider-unavailable paths + limit clamping (1/50/200/-5) + bearer
  auth still enforced + unknown tool -32602 + tool-exception -32603.

## K-DG drift findings (surfaced + corrected in this PR)

- **PrimaryState enum**: spec said "STARTING / RUNNING / DRAINING /
  STOPPED"; actual is BOOTING / READY / ACTIVE / PAUSED / STOPPED
  per `agent/operational_state.py:70-74` (KR-P2-I-skeleton). ST1
  surfaces actual values.
- **HealthRollup attribute names**: spec assumed
  overall_status / control_plane_status / worker_status; actual
  bare names overall / control_plane / worker per
  `agent/health_rollup_holder.py:174-176`.
- **TransitionRecord keys**: spec assumed transitioned_at /
  from_primary_state / to_primary_state; actual timestamp /
  from_state / to_state / trigger per
  `agent/operational_state_holder.py:73-82`.
- **`holder.current`** is a `@property`, not a method call —
  caught + fixed during testing (initial draft did `holder.current()`).

## Drift findings affecting ST2 (PRE-FILED, not addressed here)

- **`actor_has_capability(capability: str)`** is IMPLICITLY Kora-only
  per `plugins/memory/isokron/capability_check.py:69`. The bucket
  spec's 2-arg `actor_has_capability(actor_kind, capability)` shape
  doesn't exist. ST2 will need either a new substrate-side
  caller-aware function (heavy lift) OR a bearer-token-→-role ACL
  via the same `~/.kora/mcp_callers.yaml` PM Q1 chose for caller
  identity (lighter, composes with Q1 default). PM ruling needed
  before ST2 ships.
- **`sea__create_ticket` "blocked" comments**: still in
  `agent/cron_work_class.py:34, 137, 206` but tool is LIVE on prod
  per prior PM acks + `feedback_dont_trust_stub_comments_for_liveness.md`
  memory. ST2 will use `IsoKronMCPClient.invoke("sea__create_ticket", ...)`
  via the generic dispatcher.

## §5 ship checklist

- [x] Base `feature/phase2-upgrades`
- [x] Title format `feat(kora): KR-MCP-RUNTIME-SURFACE STn — <scope>`
- [x] §4 questions surfaced + reads aligned with PM defaults
- [x] ST1: NO state mutations, NO ledger writes (enforced by code
      structure — no writer imports in mcp_tools.py)
- [x] Tests pass locally (113/113 across full suite)
- [x] K-DG: every drift surface grepped + flagged in PR body

## End-to-end smoke

`kora daemon` with --listener filtering + KORA_MCP_BEARER_TOKEN set:
- `GET /mcp/tools/list` → 6 tools (kora__daemon_status + 5 new)
- `POST /mcp` tools/call kora__get_operational_state → valid Pydantic
  JSON; holder_available=false (no holder in `kora daemon` standalone)
- `POST /mcp` tools/call kora__get_recent_ledger_entries → valid
  Pydantic JSON; provider_unavailable=true (no provider in test
  daemon)
- SIGTERM exits 0

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rafe-walker rafe-walker merged commit d036dd4 into feature/phase2-upgrades May 22, 2026
@rafe-walker rafe-walker deleted the feat/kora-KR-MCP-RUNTIME-SURFACE-ST1 branch May 22, 2026 06:37
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant