# MCP-CLI Roadmap > **Vision:** Transform mcp-cli from an interactive MCP runtime into a programmable AI operations environment — the neutral runtime where agent capabilities live. ### The Stack ``` Model ↓ Skill (what to do — portable behaviour) ↓ Plan (how to do it — execution graph) ↓ Tools (do it — MCP servers) ↓ World ``` Today most systems jump straight from model → tools. Skills become the **stable abstraction layer**. Plans make execution **repeatable**. MCP-CLI is the runtime that binds them. ### The Ecosystem Gap | Layer | Status | |---------------------|---------------------------------------| | Models | Interchangeable | | Tools (MCP servers) | Standardized | | Agents | Ad-hoc | | **Skills** | **Fragmented (Claude/Codex proprietary)** | The missing piece: a **portable capability layer between prompts and tools**. If mcp-cli becomes the neutral runtime for that layer, it becomes the distribution platform for agent behaviour — Docker for AI capabilities. --- ## Tier 1: Foundation (Fix What Breaks Today) ✅ COMPLETE ### 1.1 Truncate Large Tool Results **Problem:** Tool results stored in full in conversation history with no size limit. A single maritime API result can be 100K+ chars, causing 1.87M token overflow. **Files:** `src/mcp_cli/config/defaults.py`, `src/mcp_cli/chat/tool_processor.py` - Add `DEFAULT_MAX_TOOL_RESULT_CHARS = 100_000` (~25K tokens) - Add `_truncate_tool_result(content, max_chars)` to `ToolProcessor` - Head + truncation notice + tail (preserves value binding `**RESULT: $vN = ...**`) - `max_chars=0` disables - Call in `_add_tool_result_to_history()` before creating the Message - Safe: value binding extraction uses raw result object, not formatted string ### 1.2 Strip Old Reasoning Content **Problem:** Thinking models (kimi-k2.5, DeepSeek) produce 100K+ chars of reasoning per turn, all sent back on every subsequent API call. **File:** `src/mcp_cli/chat/conversation.py` - Add `_prepare_messages_for_api()` — replaces inline message serialization - Add `_strip_old_reasoning_content(messages)` — keep only most recent reasoning - Update 3 call sites in `_handle_streaming_completion` and `_handle_regular_completion` ### 1.3 Conversation History Sliding Window **Problem:** `conversation_history` reads ALL events without limit. No eviction or compression. **File:** `src/mcp_cli/chat/chat_context.py` - Add `max_history_messages` parameter (default ~200) - Keep system prompt + last N messages - Warn when approaching threshold - Summarize evicted messages into compact "earlier context" block ### 1.4 Enable Infinite Context Mode **Problem:** SessionManager has `infinite_context`, context packing, and auto-summarization — all disabled. ChatContext hardcodes `infinite_context=False`. **Files:** `src/mcp_cli/chat/chat_context.py`, `src/mcp_cli/chat/chat_handler.py` - Make `infinite_context` configurable (CLI flag or per-provider default) - Configure `token_threshold` and `max_turns_per_segment` per model - Leverage SessionManager's built-in context packing ### 1.5 Streaming Buffer Caps **Problem:** `StreamingState.accumulated_content` and `reasoning_content` are unbounded with O(n^2) concat. **File:** `src/mcp_cli/display/models.py` - Add `max_accumulated_size` cap (1MB) to StreamingState - Chunk count limit for stalled stream detection - Use list-join pattern instead of string concatenation --- ## Tier 2: Efficiency & Resilience ✅ COMPLETE ### 2.1 Eliminate Triple Tool Result Storage **Problem:** Tool results stored 3x: SessionManager events, `tool_history` list, procedural memory. **Files:** `src/mcp_cli/chat/tool_processor.py`, `src/mcp_cli/chat/chat_context.py` - Remove `self.context.tool_history: list[ToolExecutionRecord]` (unbounded in-memory list) - Read from `tool_memory.memory.tool_log[-limit:]` on demand - Single source of truth: SessionManager for flow, procedural memory for learning ### 2.2 Enforce Procedural Memory Limits **Problem:** `max_patterns_per_tool` exists but is never enforced. Patterns grow unbounded. - Enforce with LRU eviction - Store only first 100 chars of result, not full payloads - Add per-session memory cap ### 2.3 System Prompt Optimization **Problem:** With 50+ tools, system prompt includes all tool names. Rebuilds on every model change. **Files:** `src/mcp_cli/chat/system_prompt.py`, `src/mcp_cli/chat/chat_context.py` - For 20+ tool servers, show categories + "... and X more" - Cache system prompt with dirty flag (only regenerate when tools change) ### 2.4 Stale Connection Recovery **Problem:** No health checks for long-lived MCP connections. Server dies = permanent failure. **File:** `src/mcp_cli/tools/manager.py` - Health check on tool execution failure - Automatic reconnection with backoff - `--reconnect-on-failure` CLI flag ### 2.5 Tool Batch Timeout **Problem:** If one tool hangs in a parallel batch, everything blocks. No global timeout envelope. **File:** `src/mcp_cli/tools/execution.py` - Wrap task gathering in `asyncio.wait_for` with configurable timeout - Cancel remaining tasks on timeout ### 2.6 Narrower Exception Handlers **Problem:** Broad `except Exception` masks critical issues. Inconsistent `CancelledError` handling. **Files:** `src/mcp_cli/chat/conversation.py`, `src/mcp_cli/chat/streaming_handler.py` - Catch specific exceptions (APIError, TimeoutError, ValueError) - Consistent `asyncio.CancelledError` handling across all async loops ### 2.7 Provider Validation **Problem:** Invalid API keys accepted silently. Chat fails only after user starts typing. **Files:** `src/mcp_cli/model_management/model_manager.py`, `src/mcp_cli/chat/chat_handler.py` - Quick auth validation in `ChatContext.initialize()` - Optional connection test on `add_runtime_provider()` ### 2.8 LLM-Visible Context Management Notices **Problem:** Tier 1 truncation, sliding window eviction, and reasoning stripping happen silently. The LLM sees truncated results but doesn't know data was removed, so it can't adjust its strategy (e.g., request smaller date ranges, fewer fields, or paginated results). **Files:** `src/mcp_cli/chat/tool_processor.py`, `src/mcp_cli/chat/conversation.py`, `src/mcp_cli/chat/chat_context.py` - **Tool result truncation notice:** When `_truncate_tool_result` fires, inject a system-level hint into the conversation: `"The previous tool result was truncated from {N} to {M} chars. Consider requesting less data (smaller date range, fewer fields, pagination)."` - **Sliding window eviction notice:** When messages are evicted, inject: `"Context window: {N} older messages were evicted. Key context may need to be re-established."` - **Reasoning stripping notice:** When old reasoning is stripped, inject a compact note so the model knows it lost its earlier chain of thought - **Context compaction notice:** When SessionManager compacts/summarizes, surface the summary to the model so it knows what was compressed - Design as injectable system messages placed just before the next API call, not permanently stored in history - Configurable: `--context-notices / --no-context-notices` flag (default on) --- ## MCP Apps (SEP-1865) ✅ COMPLETE Interactive HTML UIs served by MCP servers, rendered in sandboxed browser iframes. ### Implementation - **Host/Bridge/HostPage architecture:** Local websockets server per app, iframe sandbox with postMessage ↔ WebSocket bridge - **Tool meta propagation:** `_meta.ui` on tool definitions triggers automatic app launch on tool call - **structuredContent recovery:** Extracts `structuredContent` from JSON text blocks when CTP transport discards it - **Security hardening:** XSS prevention (html.escape), CSP domain sanitization, tool name validation, URL scheme validation - **Session reliability:** Message queue with drain-on-reconnect, exponential backoff, state reset, duplicate prevention, push-to-existing-app - **Spec compliance:** `ui/initialize`, `ui/resource-teardown`, `ui/notifications/host-context-changed`, sandbox capabilities - **Robustness:** Tool execution timeout, safe JSON serialization, circular reference protection, initialization timeout - **Test coverage:** 96 tests across bridge, host, security, session, models, and meta propagation ### Known Limitations - Map and video views depend on server-side app JavaScript (not an mcp-cli issue) - `ui/notifications/tool-input-partial` (streaming argument assembly) deferred to future work - HTTPS/TLS for remote deployment not yet implemented - CTP transport `_normalize_mcp_response` discards `structuredContent` — recovered via text block extraction --- ## Tier 3: Performance & Polish ✅ COMPLETE ### 3.1 Tool Lookup Index **Problem:** `get_tool_by_name()` is O(n) per call — iterates all tools. **File:** `src/mcp_cli/tools/manager.py` - `_tool_index: dict[str, ToolInfo]` with lazy build on first access → O(1) lookups - Dual-key indexing: fully qualified name + simple name - Invalidated via `_invalidate_caches()` when tools change ### 3.2 Cache Tool Metadata Per Provider **Problem:** `get_tools_for_llm()` re-filters and re-validates every call. **File:** `src/mcp_cli/tools/manager.py` - `_llm_tools_cache: dict[str, list[dict]]` keyed by provider name - Returns cached tools on hit, rebuilds on miss - Invalidated alongside tool index on tool state changes - Bypassed when `MCP_CLI_DYNAMIC_TOOLS=1` ### 3.3 Startup Progress **Problem:** No feedback during server init. Blank screen if >2s. **Files:** `src/mcp_cli/tools/manager.py`, `src/mcp_cli/chat/chat_handler.py`, `src/mcp_cli/chat/chat_context.py` - `on_progress` callback passed through initialization chain - Reports: "Loading server configuration...", "Connecting to N server(s)...", "Discovering tools...", "Adapting N tools for {provider}..." - Chat handler wires callback to `output.info()` for real-time display ### 3.4 Token & Cost Tracking **Problem:** Zero visibility into token usage or API costs. **Files:** `src/mcp_cli/chat/token_tracker.py`, `src/mcp_cli/commands/usage/usage.py` - `TokenTracker` with `TurnUsage` Pydantic models - Per-turn input/output tracking with chars/4 estimation fallback - `/usage` command (aliases: `/tokens`, `/cost`) for cumulative display - Integrated with conversation export ### 3.5 Session Persistence & Resume **Problem:** Conversations lost on exit. No way to resume. **Files:** `src/mcp_cli/chat/session_store.py`, `src/mcp_cli/commands/sessions/sessions.py` - File-based persistence at `~/.mcp-cli/sessions/` - `/sessions list`, `/sessions save`, `/sessions load `, `/sessions delete ` - Auto-save every 10 turns via `auto_save_check()` in ChatContext ### 3.6 Conversation Export **Files:** `src/mcp_cli/commands/export/export.py`, `src/mcp_cli/chat/exporters.py` - `/export markdown [filename]` and `/export json [filename]` - Includes tool calls with arguments, tool results (truncated), token usage metadata - Markdown: formatted sections by role; JSON: structured with version and timestamps --- ## Tier 4: Code Quality ✅ COMPLETE ### 4.1 Remove Vestigial Global ToolManager **Files:** `src/mcp_cli/tools/manager.py`, `src/mcp_cli/run_command.py` - Deleted `_GLOBAL_TOOL_MANAGER`, `get_tool_manager()`, `set_tool_manager()` from manager.py - Removed `set_tool_manager` import and both call sites from run_command.py - ToolManager is now injected via constructors everywhere (zero external call sites for the global) ### 4.2 Rename Local Message Class **Files:** `src/mcp_cli/chat/models.py`, `src/mcp_cli/chat/chat_context.py`, `src/mcp_cli/chat/response_models.py` - Renamed `class Message` → `class HistoryMessage` in models.py with updated docstring - Added backward-compat alias `Message = HistoryMessage` (existing imports still work) - Updated chat_context.py to import and use `HistoryMessage` in all type annotations - Updated `ToolProcessorContext` protocol to use `HistoryMessage` - Added clarifying comments to response_models.py distinguishing the two Message classes ### 4.3 Standardize Logging in Core **Files:** `src/mcp_cli/chat/conversation.py`, `src/mcp_cli/chat/tool_processor.py`, `src/mcp_cli/chat/chat_context.py` - Replaced ~30 `output.*` calls with `log.*` across all three core modules - Removed `from chuk_term.ui import output` imports from all three files - Core modules now use `logging` only; UI modules continue to use `output` for user-facing messages ### 4.4 Integration Tests **Files:** `tests/integration/conftest.py`, `tests/integration/test_echo_roundtrip.py` - Created integration test framework with `@pytest.mark.integration` marker - `tool_manager_sqlite` fixture: real ToolManager with SQLite MCP server - Tests: tool lifecycle (discover tools, get server info), tool execution (list_tables, read_query), LLM tool adaptation (OpenAI format validation) - Graceful skip when server unavailable ### 4.5 Coverage Reporting **File:** `pyproject.toml` - Added `[tool.coverage.run]` and `[tool.coverage.report]` sections - Branch coverage enabled, `fail_under = 60` (conservative start, ratchet up) - Standard exclusions: `pragma: no cover`, `TYPE_CHECKING`, `__main__`, `@overload` --- ## Tier 5: Production Hardening ✅ COMPLETE ### 5.1 Structured File Logging + Secret Redaction **Files:** `src/mcp_cli/config/logging.py`, `src/mcp_cli/config/defaults.py`, `src/mcp_cli/main.py` - Added `SecretRedactingFilter` with 5 regex patterns: Bearer tokens, sk-* API keys, api_key values, OAuth access_tokens, Authorization headers - Filter always active on console handler (not just file logging) - Added optional `RotatingFileHandler` via `--log-file` CLI option - File handler: JSON format, DEBUG level, 10MB rotation with 3 backups, secret redaction - Added `DEFAULT_LOG_DIR`, `DEFAULT_LOG_MAX_BYTES`, `DEFAULT_LOG_BACKUP_COUNT` to defaults.py - 16 tests in `tests/config/test_logging_redaction.py` ### 5.2 Server Health Monitoring ✅ COMPLETE **Files:** `src/mcp_cli/tools/manager.py`, `src/mcp_cli/commands/servers/health.py`, `src/mcp_cli/chat/conversation.py`, `src/mcp_cli/main.py` - **Health-check-on-failure**: `ToolManager.execute_tool()` detects connection errors via `_is_connection_error()`, runs `_diagnose_server()` to enrich error messages with server status - **`/health` command**: New `HealthCommand` checks one or all servers via `tool_manager.check_server_health()`, shows status (healthy/unhealthy/timeout/error) and latency - **Background health polling**: `ConversationProcessor._health_poll_loop()` runs at `--health-interval` seconds, logs status transitions (e.g. healthy → unhealthy) - **`--health-interval` CLI flag**: Enables background polling (0 = disabled, default) - **Note**: Server *reconnect* still requires upstream `StreamManager` hooks; health *monitoring* is complete ### 5.3 Per-Server Configuration **Files:** `src/mcp_cli/config/server_models.py`, `src/mcp_cli/tools/config_loader.py`, `src/mcp_cli/tools/manager.py` - Added `tool_timeout` and `init_timeout` fields to HTTPServerConfig, STDIOServerConfig, UnifiedServerConfig, ServerConfigInput - Updated `detect_server_types()` to read timeout fields from config - Added `_get_server_timeout()` helper to ToolManager: per-server → global → default resolution - Updated `execute_tool()` to use per-server timeout when available ### 5.4 Thread-Safe OAuth **Files:** `src/mcp_cli/tools/manager.py`, `tests/tools/test_oauth_safety.py` - Added `self._oauth_lock = asyncio.Lock()` in ToolManager `__init__` - Wrapped `_handle_oauth_flow()` body in `async with self._oauth_lock:` - Replaced direct dict mutation with copy-on-write for `transport.configured_headers` - Tests: 3 concurrent OAuth flows verify lock serialization, per-server timeout resolution --- ## AI Virtual Memory Integration (Experimental) ✅ COMPLETE OS-style virtual memory for conversation context management, powered by `chuk-ai-session-manager`. ### Implementation - **`--vm` CLI flag**: Enables VM subsystem in SessionManager; system prompt replaced with VM-packed `developer_message` containing rules, manifest (page index), and working set content - **`--vm-budget`**: Token budget for conversation events (system prompt uncapped on top); forces earlier page creation and eviction at low values - **`--vm-mode`**: `passive` (runtime-managed, default), `relaxed` (VM-aware conversation), `strict` (model-driven paging with page_fault/search_pages tools) - **Budget-aware context filtering**: `_vm_filter_events()` groups conversation events into logical turns, includes newest-first within budget, guarantees minimum 3 recent turns; evicted content preserved as VM pages in developer_message - **`/memory` slash command** (aliases: `/vm`, `/mem`): Dashboard showing mode, turn, budget, working set utilization, page table, fault/eviction/TLB metrics; subcommands for page listing, page detail, and full stats dump - **VM tool wiring (strict/relaxed)**: `page_fault` and `search_pages` tools injected into `openai_tools` for non-passive modes; intercepted in `tool_processor.py` before MCP guard checks and executed locally via `MemoryManager`; short-content annotation guides model to fault adjacent `[assistant]` response pages; `[user]`/`[assistant]` hint prefixes in manifest - **E2E demo**: 8 recall scenarios (simple facts, creative content, tool results, negative case, deep detail, multi-fault, structured data, image description) with distractor tools; validates correct tool selection and content recall ### Multimodal Content Re-analysis ✅ COMPLETE **Files:** `src/mcp_cli/chat/tool_processor.py`, `src/mcp_cli/chat/models.py`, `src/mcp_cli/commands/memory/memory.py` - **Multi-block tool results**: `_build_page_content_blocks()` detects page modality and returns `list[dict]` (text + image_url blocks) for image pages with URLs/data URIs, or JSON string with modality/compression metadata for text/structured pages - **HistoryMessage content type**: Extended from `str | None` to `str | list[dict[str, Any]] | None` to support OpenAI multi-block content format - **`_add_tool_result_to_history()`**: Accepts multi-block content, skips truncation for list content - **Compression-aware notes**: Compressed pages (ABSTRACT/REFERENCE) include a note guiding the model to re-fault at target_level=0 for full content; short pages suggest checking for the adjacent assistant response page - **`/memory page --download`**: Exports page content to `~/.mcp-cli/downloads/` with modality-aware extensions (.txt, .json, .png) and base64 data URI decoding - **Modality metadata display**: `/memory page ` shows MIME type, dimensions, duration, and caption when available ### Files | File | Change | |------|--------| | `src/mcp_cli/config/defaults.py` | `DEFAULT_ENABLE_VM`, `DEFAULT_VM_MODE`, `DEFAULT_VM_BUDGET` | | `src/mcp_cli/chat/chat_context.py` | VM params in init/create, `_vm_filter_events()`, VM context in `conversation_history`, `_health_interval` | | `src/mcp_cli/chat/chat_handler.py` | Thread `enable_vm`, `vm_mode`, `vm_budget`, `health_interval` to ChatContext | | `src/mcp_cli/chat/conversation.py` | Background health polling (`_health_poll_loop`, `_start_health_polling`, `_stop_health_polling`) | | `src/mcp_cli/chat/tool_processor.py` | `_build_page_content_blocks()`, multi-block `_add_tool_result_to_history()` | | `src/mcp_cli/chat/models.py` | `HistoryMessage.content` extended to `str \| list[dict] \| None` | | `src/mcp_cli/main.py` | `--vm`, `--vm-mode`, `--vm-budget`, `--health-interval` CLI options | | `src/mcp_cli/tools/manager.py` | `check_server_health()`, `_diagnose_server()`, `_is_connection_error()` | | `src/mcp_cli/commands/memory/` | `MemoryCommand` with summary/pages/page/stats/download subcommands | | `src/mcp_cli/commands/servers/health.py` | `HealthCommand` — `/health` slash command | --- ## Tier 6: Execution Graphs & Plans ✅ COMPLETE > **Shift:** conversation → reasoning → tools **becomes** intent → plan → execution → memory → replay > > **Spec:** `specs/6.0-planner-integration.md` > **Integration:** `chuk-ai-planner>=0.2` — graph-based plan DSL, executor, LLM plan generation ### 6.0 Planner Foundation Wiring ✅ Bridge `chuk-ai-planner` to mcp-cli's MCP tool execution layer. **Files:** - `src/mcp_cli/planning/backends.py` — `McpToolBackend` (implements `ToolExecutionBackend` protocol, wraps `ToolManager.execute_tool()`) - `src/mcp_cli/planning/context.py` — `PlanningContext` (state container: graph store, tool manager, plan registry, tool catalog) - `src/mcp_cli/planning/executor.py` — `PlanRunner` (orchestrates plan execution with guard integration, dry-run, checkpointing) - `src/mcp_cli/planning/__init__.py` — Public API **Key integration:** chuk-ai-planner's `ToolProcessorBackend` calls `CTP.process()` for registered Python functions. `McpToolBackend` instead calls `ToolManager.execute_tool()` for real MCP server tools — same protocol interface, different execution path. ### 6.1 Plan Commands ✅ ``` mcp plan create "add auth to this API" mcp plan list mcp plan show mcp plan run mcp plan run --dry-run mcp plan delete mcp plan resume ``` **Files:** - `src/mcp_cli/commands/plan/plan.py` — `PlanCommand` (unified command, supports CHAT + CLI + INTERACTIVE) - `src/mcp_cli/config/enums.py` — `PlanAction` enum **Chat mode:** `/plan create "description"`, `/plan list`, `/plan run ` - Plan = persistent, inspectable execution graph (DAG of tool calls + decisions) - Plans are serialized as JSON at `~/.mcp-cli/plans/` - `--dry-run` shows what would execute without side effects - Plans can be parameterized: `mcp plan run --var date=2026-03-01` ### 6.2 Plan Execution with Guards ✅ Plan execution respects mcp-cli's existing guard infrastructure: - Pre-execution: `ToolStateManager.check_all_guards()` — budget, runaway, per-tool limits - Post-execution: `ToolStateManager.record_tool_call()` — tracking + value binding - Step error handling: retry (via `PlanStep.max_retries`), fallback, or pause for user input - Budget shared with conversation — plan execution counts against same limits - 55 tests covering guard integration, PlanRegistry round-trips, DAG visualization ### 6.3 Execution Checkpointing & Resume ✅ - After each step: persist state to `~/.mcp-cli/plans/{id}_state.json` - `mcp plan resume ` — loads checkpoint, skips completed steps, continues - Tracks: completed steps, variable bindings, failed steps, timing ### 6.4 Simulation / Dry-Run Mode ✅ Critical for trust. Show planned tool calls without executing them. ``` mcp plan run --dry-run ``` - Walks plan in topological order - Resolves `${var}` references - Displays each step: tool name, resolved arguments, dependencies - Reports estimated tool call count - No side effects — safe to run in production ### 6.5 Parallel Step Execution ✅ Independent plan steps execute concurrently via topological batch ordering: - `_compute_batches()` uses Kahn's BFS topological sort to group steps into parallel batches - Steps within a batch run concurrently via `asyncio.gather()` with semaphore-controlled concurrency - Batches execute sequentially to respect dependency ordering - `max_concurrency` parameter (default: 4) limits concurrent tool calls - Diamond DAG (1 → 2,3,4 → 5) executes with 3 batches: [1], [2,3,4], [5] - Variable resolution: `${var}`, `${var.field.subfield}`, template strings — type-preserving for single refs ### 6.6 DAG Visualization ✅ Terminal visualization of plan execution: - Terminal: ASCII DAG rendering with step status indicators (○ pending, ◉ running, ● completed, ✗ failed) - `render_plan_dag()` function for terminal display - Parallel step indicator (∥) marks steps that run concurrently within a batch - Browser: MCP App panel with D3 force-directed graph, live WebSocket updates (Future) ### 6.7 Re-planning ✅ Adaptive re-planning when execution hits problems (opt-in via `enable_replan=True`): - On step failure: injects failure context (completed steps, error, remaining steps, variables) into PlanAgent - PlanAgent generates a revised plan for the remaining work - Revised plan executes with the current variable context (no recursive re-planning) - Results merged: completed steps from original + steps from revised plan - `max_replans` parameter (default: 2) limits re-planning attempts - `PlanExecutionResult.replanned` flag indicates whether re-planning occurred - Disabled by default — failure just fails without LLM involvement ### 6.8 Model-Driven Planning (Plan as a Tool) ✅ The model can autonomously create and execute plans during conversation — no `/plan` command required. When the model determines a task needs multi-step orchestration, it calls an internal `plan` tool to decompose the task into a structured execution graph, then executes it — all within the normal chat flow. **Internal tools (intercepted before MCP routing, like VM tools):** | Tool | Purpose | |------|---------| | `plan_create` | Model describes a goal → PlanAgent generates a plan DAG → returns plan ID + step summary | | `plan_execute` | Model passes plan ID → PlanRunner executes → returns results + variables | | `plan_create_and_execute` | Combined: generate + execute in one call (common case) | **How it works:** ``` User: "What's the weather like for sailing in Raglan tomorrow?" Model (internally): This needs geocoding then weather lookup. → calls plan_create_and_execute(goal="Get weather forecast for Raglan, NZ") → PlanAgent generates: [geocode Raglan] → [get weather for coords] → PlanRunner executes both steps via MCP servers → Results flow back to model as tool result Model: "Tomorrow in Raglan: 18°C, light winds from the SW at 12 km/h, partly cloudy. Good conditions for sailing." ``` **Key design decisions:** - **Intercepted like VM tools:** `plan_create`, `plan_execute`, `plan_create_and_execute` are caught in `tool_processor.py` before MCP guard routing, executed locally via PlanRunner - **Model decides when to plan:** The system prompt describes the planning tools; the model calls them when it determines multi-step orchestration is more effective than sequential tool calls - **Plans are ephemeral by default:** Created during conversation, not persisted unless the model or user explicitly saves them. Reduces clutter vs `/plan create` - **Shares guard budget:** Plan tool calls count against the same budget as regular tool calls - **Display integration:** Plan execution renders with the same `StreamingDisplayManager` callbacks as regular tool calls — the user sees each step executing in real time - **Variable flow:** Plan results are returned as the tool result, so the model can reference them naturally in its response - **Opt-in via system prompt:** The planning tools only appear when `--enable-plan-tools` is set (or equivalent config), so the model doesn't attempt planning on simple tasks **Files:** - `src/mcp_cli/chat/tool_processor.py` — Intercept `plan_create` / `plan_execute` / `plan_create_and_execute` before MCP routing - `src/mcp_cli/planning/tools.py` — Tool definitions (OpenAI function format) and execution handlers - `src/mcp_cli/chat/system_prompt.py` — Inject planning tool descriptions when enabled - `src/mcp_cli/config/defaults.py` — `DEFAULT_ENABLE_PLAN_TOOLS = False` **Why this matters:** Today: User types `/plan create "get weather for Raglan"` → plan generated → user types `/plan run ` → result shown. Three interactions. With 6.8: User asks a question → model decides it needs a plan → creates and executes it → answers. One interaction. The model becomes a self-orchestrating agent when the task demands it, and a simple chatbot when it doesn't. --- ## Dashboard & Multi-Modal ✅ COMPLETE > **Goal:** Give users a real-time browser UI for conversations and enable multi-modal input (images, text files, audio) across CLI and browser. ### D.1 Dashboard Infrastructure ✅ Real-time browser dashboard alongside chat mode via `--dashboard` flag. **Files:** `src/mcp_cli/dashboard/` — `server.py`, `bridge.py`, `launcher.py`, `config.py`, `router.py` - HTTP + WebSocket server on a single port (`server.py`) - Bridge integrates chat engine events → browser clients (`bridge.py`) - Router supports multi-agent coordination (`router.py`) - Shell host page manages view iframes and WebSocket connection (`shell.html`) - Session replay on connect: `CONVERSATION_HISTORY` + `ACTIVITY_HISTORY` ### D.2 Dashboard Views ✅ Five tabbed views in the browser UI. **Files:** `src/mcp_cli/dashboard/static/views/` — `agent-terminal.html`, `activity-stream.html`, `plan-viewer.html`, `tool-registry.html`, `config-panel.html` - **Agent Terminal**: Chat bubbles, streaming tokens, markdown rendering, syntax highlighting, search - **Activity Stream**: Tool call/result pairs, reasoning steps, state transitions - **Plan Viewer**: DAG visualization with real-time step progress - **Tool Registry**: Browse tools, trigger execution from browser - **Config Panel**: View/switch providers, models, system prompt ### D.3 Multi-Modal Attachments ✅ Attach images, text files, and audio to messages via CLI and browser. **Files:** `src/mcp_cli/chat/attachments.py`, `src/mcp_cli/chat/chat_handler.py`, `src/mcp_cli/chat/chat_context.py`, `src/mcp_cli/commands/attach/attach.py`, `src/mcp_cli/main.py` - `/attach` command with staging, list, clear (aliases: `/file`, `/image`) - `--attach` CLI flag (repeatable) for first-message attachments - Inline `@file:path` references parsed from message text - Image URL auto-detection (HTTP/HTTPS `.png`, `.jpg`, `.gif`, `.webp`) - `AttachmentStaging` on `ChatContext` — drain-on-send lifecycle - `build_multimodal_content()` assembles content block lists - Supported: PNG, JPEG, GIF, WebP, HEIC, MP3, WAV, 25+ text/code extensions - 20 MB max per file, 10 attachments per message ### D.4 Dashboard Attachment Visualization ✅ Render attachments in the browser UI. **Files:** `src/mcp_cli/dashboard/bridge.py`, `src/mcp_cli/dashboard/static/views/agent-terminal.html`, `src/mcp_cli/dashboard/static/views/activity-stream.html` - Lightweight attachment descriptors over WebSocket (no large base64 payloads) - Image thumbnails for files <100KB, metadata badges for larger files - Expandable text previews (first 2000 chars) - Audio players (HTML5 `