This repository was archived by the owner on May 26, 2026. It is now read-only.
feat(kora): KR-MCP-SEND-TOOLS — expose Slack DM + email send via /mcp#130
Merged
Merged
Conversation
4d42011 to
ccaae9e
Compare
Bridges CC#3's SlackClient (#122) + CC#1's PurelymailClient (#124) onto the agent-facing /mcp surface. Other agents (other PMs, drone tickets, future Kora-driven multi-agent workflows) can now drive Kora-initiated sends. `kora__send_slack_dm`: - Args: channel_id (D-prefix or KORA_SLACK_JOSHUA_USER_ID), text (≤4000 chars), thread_ts (optional) - requires_cap_gate=True (-32001 capability_denied if absent) - Channel-broadcast defense: U-prefix + C-prefix rejected at MCP layer (operator pre-resolves to DM channel) - SlackClient-unavailable surface: -32001 with "slack_client_unavailable" prefix (KORA_SLACK_BOT_TOKEN unset or daemon not running with slack_client listener) - Bot identity = Kora (NOT caller-controllable) `kora__send_email`: - Args: to (≤10), subject, body_text, body_html (optional), in_reply_to (optional) - requires_cap_gate=True - from_addr derived from KORA_PUREMAIL_SMTP_USERNAME — NOT caller-controllable (prevents sender impersonation) - NO attachments in this bucket (deferred to KR-MCP-SEND-TOOLS-ATTACHMENTS; file-upload semantics aren't trivially MCP-compatible) - PurelymailClient-unavailable surface: -32001 with "purelymail_client_unavailable" prefix Both tools added to ST2_TOOL_DESCRIPTORS + ST2_TOOL_DISPATCH; schema validation surfaces -32602 invalid_params for malformed inputs before any client invocation. `kora_cli/listeners/slack_client_listener.py` (NEW): - SlackClientListener: startup constructs SlackClient if KORA_SLACK_BOT_TOKEN set; FAIL-SOFT if unset (daemon boot continues; outbound disabled with INFO log) - current_slack_client() module-level accessor mirroring the current_pool() / current_coordinator() pattern `kora_cli/listeners/purelymail_client_listener.py` (NEW): - PurelymailClientListener: startup constructs PurelymailClient if SMTP envs set; FAIL-SOFT if missing - current_purelymail_client() accessor Both registered at module-import time via the listeners package __init__; both fail-soft so outbound capabilities never block daemon boot. `kora_cli/handlers/slack_dm_handler.py:_get_or_create_slack_client` updated: - Order: cached client → listener accessor → lazy-construct - Lazy-construct fallback PRESERVED so standalone-handler tests (no daemon listeners) keep passing - Production daemon paths get the shared listener instance - Existing reasoning-engine reply path (CC#3 KR-FEAT-AI-RESPONSE- LOOP) goes through this helper → automatically picks up the listener instance once daemon's running `caller_actor_kind` field added to outbound entries in BOTH log files: - slack_dm_log.jsonl: handler's `_append_outbound_log_entry` gains optional caller_actor_kind kwarg (omitted when None to preserve back-compat with consumers expecting absence on handler-driven sends) - email_outbound_log.jsonl: PurelymailClient's send_email gains caller_actor_kind kwarg, propagated to `_append_outbound_log` MCP tool dispatchers pass caller.actor_kind through; handler / runtime-driven sends omit the field (None). Tool 1 rejects channel_ids that don't start with `D` OR match KORA_SLACK_JOSHUA_USER_ID. U... user-IDs are rejected at MCP layer (would require an extra Slack API call to resolve to DM channel; operator/agent must pre-resolve). C... public/private channels also rejected — channel broadcast is a separate threat model + would need its own bucket. - SlackClient bot token never in error envelope or JSONL (sanitized via type-name-only re-raise on exception) - PurelymailClient SMTP password never in error envelope or JSONL (sanitized via same pattern + existing PurelymailClient _sanitize_error) - Diverse-failure-mode tests pin both invariants 36 new tests across 2 files: `tests/kora_cli/test_listeners/test_send_client_listeners.py` (11): - Both listeners registered at import time - Slack listener fail-soft on missing token + WARN log - Slack listener constructs cleanly when token set - PurelymailClient listener fail-soft on missing username OR password - PurelymailClient listener constructs with full env - Both listeners clear singleton on shutdown - Both listeners handle unexpected construction errors `tests/kora_cli/test_listeners/test_mcp_send_tools.py` (25): - 6 registry-side wiring tests (descriptors present, requires_cap_gate=True, dispatch registered, schema caps) - 5 slack input validation tests (empty channel/text; >4000 chars; non-DM channel rejected; Joshua's user ID OK) - 1 SlackClient unavailable - 2 slack happy-path tests (SendResult shape + JSONL entry with caller_actor_kind) - 1 slack token-absence-in-error test - 4 email input validation tests - 2 email client-unavailable tests (listener None + env unset) - 1 email happy-path test (from_addr from env; caller_actor_kind threaded) - 1 email password-absence-in-error test - 1 caller_actor_kind end-to-end propagation test 36/36 pass; 341/341 cross-bucket regression (test_listeners/ + test_heartbeat_probes/ + test_clients/ + test_handlers/). Ruff clean. Agent-facing /mcp surface gains 2 mutating tools (total now: 5 mutating + 8 read = 13 tools). Other agents have a complete operational surface on Kora — read state, request transitions, create Sea_Tickets, send Slack DMs, send emails. The remaining follow-on (attachment support in send_email) ships when the file-upload semantics get a design. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ccaae9e to
8d05ae5
Compare
This was referenced May 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Bridges CC#3's SlackClient (#122) + CC#1's PurelymailClient (#124)
onto the agent-facing `/mcp` surface. Other agents can now drive
Kora-initiated sends through 2 new mutating tools.
Bucket spec: `17_cc_bucket_prompts/KR-MCP-SEND-TOOLS_agent_driven_sends.md`
New tools
Both `requires_cap_gate=True` → -32001 `capability_denied` if
caller's `allowed_caps` doesn't include the cap name.
§5 PM rulings applied
Surface changes
Security carry-forward
Test plan
test_heartbeat_probes/ + test_clients/ + test_handlers/)
via existing handler tests in regression suite)
Cascade
Base: `feature/phase2-upgrades`. Single PR.
After merge
Other agents (other PMs, drone tickets, future Kora-driven multi-
agent workflows) can drive Slack DMs + emails through Kora's MCP
surface. Tool inventory: 8 read + 5 mutating = 13 tools on the
agent-facing surface. Attachment support in `kora__send_email`
remains the open follow-on (file-upload semantics design needed).
🤖 Generated with Claude Code