Skip to content

Commit 89d3d64

Browse files
authored
Merge pull request #218 from chrishayuk/streaming_refactor
Streaming refactor
2 parents 4dc3603 + d416225 commit 89d3d64

68 files changed

Lines changed: 5572 additions & 779 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 92 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,29 @@ A powerful, feature-rich command-line interface for interacting with Model Conte
77

88
**Default Configuration**: MCP CLI defaults to using Ollama with the `gpt-oss` reasoning model for local, privacy-focused operation without requiring API keys.
99

10-
## 🆕 Recent Updates (v0.12.0)
11-
12-
### Performance & Polish (Tier 3)
10+
## 🆕 Recent Updates (v0.14.0)
11+
12+
### Production Hardening (Tier 5)
13+
- **Secret Redaction**: All log output (console and file) is automatically redacted for Bearer tokens, API keys, OAuth tokens, and Authorization headers
14+
- **Structured File Logging**: Optional `--log-file` flag enables rotating JSON log files (10MB, 3 backups) at DEBUG level with secret redaction
15+
- **Per-Server Timeouts**: Server configs now support `tool_timeout` and `init_timeout` overrides, resolved per-server → global → default
16+
- **Thread-Safe OAuth**: Concurrent OAuth flows are serialized with `asyncio.Lock` and copy-on-write header mutation
17+
18+
### Code Quality (Tier 4)
19+
- **Core/UI Separation**: Core modules (`chat/conversation.py`, `chat/tool_processor.py`, `chat/chat_context.py`) no longer import `chuk_term.ui.output` — all logging goes through `logging` module
20+
- **Message Class Clarity**: Local `Message` renamed to `HistoryMessage` (backward-compat alias preserved) to distinguish from `chuk_llm.core.models.Message`
21+
- **Removed Global Singletons**: `_GLOBAL_TOOL_MANAGER` and associated getter/setter functions deleted
22+
- **Integration Test Framework**: Real MCP server tests with `@pytest.mark.integration` marker (SQLite server)
23+
- **Coverage Reporting**: Branch coverage enabled with `fail_under = 60` threshold in pyproject.toml
24+
25+
### Previous: MCP Apps (SEP-1865)
26+
- **Interactive HTML UIs**: MCP servers can now serve interactive HTML applications (charts, tables, maps, markdown viewers) that render in your browser
27+
- **Sandboxed iframes**: Apps run in secure sandboxed iframes with CSP protection
28+
- **WebSocket bridge**: Real-time bidirectional communication between browser apps and MCP servers
29+
- **Automatic launch**: Tools with `_meta.ui` annotations automatically open in the browser when called
30+
- **Session reliability**: Message queuing, reconnection with exponential backoff, deferred tool result delivery
31+
32+
### Previous: Performance & Polish (Tier 3)
1333
- **O(1) Tool Lookups**: Indexed tool lookup replacing O(n) linear scans in both ToolManager and ChatContext
1434
- **Cached LLM Tool Metadata**: Per-provider caching of tool definitions with automatic invalidation
1535
- **Startup Progress**: Real-time progress messages during initialization instead of a single spinner
@@ -18,21 +38,6 @@ A powerful, feature-rich command-line interface for interacting with Model Conte
1838
- **Conversation Export**: Export conversations as Markdown or JSON with metadata (`/export`)
1939
- **Trusted Domains**: Tools from trusted server domains (e.g. chukai.io) skip confirmation prompts
2040

21-
### Architecture & Performance
22-
- **Updated to chuk-llm v0.16+**: Dynamic model discovery with capability-based selection, llama.cpp integration (1.53x faster), 52x faster imports
23-
- **Updated to chuk-tool-processor v0.13+**: Now using CTP's production-grade middleware (retry, circuit breaker, rate limiting)
24-
- **Slimmed ToolManager**: Reduced from 2000+ lines to ~800 lines by delegating to StreamManager while keeping OAuth, filtering, and LLM adaptation
25-
26-
### Reliability Improvements
27-
- **Transport Failure Detection**: Automatic tracking of consecutive transport failures with warnings and recovery suggestions
28-
- **Enhanced Tool Processing**: Improved MCP SDK ToolResult handling with proper content extraction from nested structures
29-
- **Connection Monitoring**: Built-in health checks with automatic detection of unhealthy connections
30-
31-
### Bug Fixes
32-
- **Fixed cmd mode**: `--provider` and `--model` flags now work correctly in command mode (PR #188)
33-
- **OAuth Improvements**: Enhanced OAuth token handling and storage
34-
- **Pydantic Migration**: Clean migration to Pydantic for better validation and type safety
35-
3641
## 🔄 Architecture Overview
3742

3843
The MCP CLI is built on a modular architecture with clean separation of concerns:
@@ -88,6 +93,14 @@ MCP CLI supports all providers and models from CHUK-LLM, including cutting-edge
8893
- **Middleware**: Retry with exponential backoff, circuit breakers, and rate limiting via CTP
8994
- **Streaming Tool Calls**: Support for tools that return streaming data
9095

96+
### MCP Apps (Interactive UIs)
97+
- **Browser-based UIs**: MCP servers can serve interactive HTML applications that render in your browser
98+
- **Automatic Detection**: Tools with `_meta.ui` annotations automatically launch browser apps on tool call
99+
- **Sandboxed Execution**: Apps run in secure sandboxed iframes with Content Security Policy protection
100+
- **WebSocket Bridge**: Real-time JSON-RPC bridge between browser apps and MCP tool servers
101+
- **Session Persistence**: Message queuing during disconnects, automatic reconnection, deferred tool result delivery
102+
- **structuredContent Support**: Full MCP spec compliance including structured content extraction and forwarding
103+
91104
### Advanced Configuration Management
92105
- **Environment Integration**: API keys and settings via environment variables
93106
- **File-based Config**: YAML and JSON configuration files
@@ -112,6 +125,7 @@ Comprehensive documentation is available in the `docs/` directory:
112125
- **[Token Management](docs/TOKEN_MANAGEMENT.md)** - Comprehensive token management for providers and servers including OAuth, bearer tokens, and API keys
113126

114127
### Specialized Documentation
128+
- **[MCP Apps](docs/MCP_APPS.md)** - Interactive browser UIs served by MCP servers (SEP-1865)
115129
- **[OAuth Authentication](docs/OAUTH.md)** - OAuth flows, storage backends, and MCP server integration
116130
- **[Streaming Integration](docs/STREAMING.md)** - Real-time response streaming architecture
117131
- **[Package Management](docs/PACKAGE_MANAGEMENT.md)** - Dependency organization and feature groups
@@ -167,6 +181,9 @@ git clone https://github.com/chrishayuk/mcp-cli
167181
cd mcp-cli
168182
pip install -e "."
169183
mcp-cli --help
184+
185+
# Optional: Enable MCP Apps (interactive browser UIs)
186+
pip install -e ".[apps]"
170187
```
171188

172189
### Using Different Models
@@ -233,6 +250,7 @@ Global options available for all modes and commands:
233250
- `--token-backend`: Override token storage backend (`auto`, `keychain`, `windows`, `secretservice`, `encrypted`, `vault`)
234251
- `--verbose`: Enable detailed logging
235252
- `--quiet`: Suppress non-essential output
253+
- `--log-file`: Write debug logs to a rotating file (secrets auto-redacted)
236254

237255
### Environment Variables
238256

@@ -343,6 +361,47 @@ mcp-cli token backends # Show available storage backends
343361
mcp-cli --token-backend encrypted token list # Use specific backend
344362
```
345363

364+
## 🌐 MCP Apps (Interactive Browser UIs)
365+
366+
MCP Apps allow tool servers to provide interactive HTML UIs that render in your browser. When a tool has a `_meta.ui` annotation pointing to a UI resource, mcp-cli automatically launches a local web server and opens the app in your browser.
367+
368+
### Prerequisites
369+
370+
```bash
371+
# Install the apps extra (adds websockets dependency)
372+
pip install "mcp-cli[apps]"
373+
```
374+
375+
### How It Works
376+
377+
1. Connect to an MCP server that provides app-enabled tools
378+
2. Call a tool that has `_meta.ui` metadata (e.g., `show_chart`, `show_table`)
379+
3. mcp-cli automatically fetches the UI resource, starts a local server, and opens your browser
380+
4. The app receives tool results in real-time via WebSocket
381+
382+
### Example
383+
384+
```bash
385+
# Connect to a server with app-enabled tools
386+
mcp-cli --server view_demo
387+
388+
# In chat, ask for something visual:
389+
> Show me the sales data as a chart
390+
# Browser opens automatically with an interactive chart
391+
392+
# The /tools command shows which tools have app UIs (APP column)
393+
> /tools
394+
```
395+
396+
### Architecture
397+
398+
- **Host page** serves a sandboxed iframe with the app HTML
399+
- **WebSocket bridge** proxies JSON-RPC between the browser and MCP servers
400+
- **Security**: Iframe sandbox, CSP protection, XSS prevention, URL scheme validation
401+
- **Reliability**: Message queuing during disconnects, exponential backoff reconnection, deferred tool result delivery
402+
403+
See [MCP Apps Documentation](docs/MCP_APPS.md) for the full guide.
404+
346405
## 🤖 Using Chat Mode
347406

348407
Chat mode provides the most advanced interface with streaming responses and intelligent tool usage.
@@ -1138,6 +1197,9 @@ Enable verbose logging for troubleshooting:
11381197
```bash
11391198
mcp-cli --verbose chat --server sqlite
11401199
mcp-cli --log-level DEBUG interactive --server sqlite
1200+
1201+
# Write debug logs to a rotating file (secrets are automatically redacted)
1202+
mcp-cli --log-file ~/.mcp-cli/logs/debug.log --server sqlite
11411203
```
11421204

11431205
## 🔒 Security Considerations
@@ -1152,6 +1214,10 @@ mcp-cli --log-level DEBUG interactive --server sqlite
11521214
- **API Keys**: Only needed for cloud providers (OpenAI, Anthropic, etc.), stored securely using token management system
11531215
- **OAuth 2.0 Support**: Secure authentication for MCP servers using PKCE and resource indicators (RFC 7636, RFC 8707)
11541216

1217+
### Log Security
1218+
- **Secret Redaction**: All log output (console and file) is automatically redacted for Bearer tokens, API keys (sk-*), OAuth access tokens, and Authorization headers
1219+
- **Rotating File Logs**: Optional `--log-file` with JSON format, 10MB rotation, and 3 backup files
1220+
11551221
### Execution Security
11561222
- **Tool Validation**: All tool calls are validated before execution
11571223
- **Timeout Protection**: Configurable timeouts prevent hanging operations (v0.13+)
@@ -1160,6 +1226,13 @@ mcp-cli --log-level DEBUG interactive --server sqlite
11601226
- **File Access**: Filesystem access can be disabled with `--disable-filesystem`
11611227
- **Transport Monitoring**: Automatic detection of connection failures with warnings (v0.11+)
11621228

1229+
### MCP Apps Security
1230+
- **Iframe Sandbox**: Apps run in sandboxed iframes with restricted permissions
1231+
- **Content Security Policy**: Server-supplied CSP domains are validated and sanitized
1232+
- **XSS Prevention**: Tool names and user-supplied content are HTML-escaped before template injection
1233+
- **URL Scheme Validation**: `ui/open-link` only allows `http://` and `https://` schemes
1234+
- **Tool Name Validation**: Bridge rejects tool names not matching the MCP spec character set
1235+
11631236
## 🚀 Performance Features
11641237

11651238
### LLM Provider Performance (v0.16+)
@@ -1196,6 +1269,7 @@ Install with specific features:
11961269
```bash
11971270
pip install "mcp-cli[cli]" # Basic CLI features
11981271
pip install "mcp-cli[cli,dev]" # CLI with development tools
1272+
pip install "mcp-cli[apps]" # MCP Apps (interactive browser UIs)
11991273
```
12001274

12011275
## 🤝 Contributing

architecture.md

Lines changed: 91 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,93 @@ Every user-facing feature must have a working example in the `examples/` directo
112112

113113
---
114114

115-
## Known Violations (Tier 4 Backlog)
115+
## MCP Apps (SEP-1865)
116116

117-
Architecture review performed after Tier 2. These are tracked for remediation in Tier 4 (Code Quality).
117+
MCP Apps are interactive HTML UIs served by MCP servers and rendered in the user's browser via sandboxed iframes. When a tool has a `_meta.ui` annotation, mcp-cli launches a local web server that bridges the browser and the MCP backend.
118+
119+
### Architecture
120+
121+
```
122+
Browser Python Backend MCP Server
123+
┌─────────────────┐ ┌──────────────────┐ ┌──────────────┐
124+
│ Host Page (JS) │──WS──│ AppBridge │──MCP──│ Tool Server │
125+
│ ┌─────────────┐ │ │ (bridge.py) │ │ │
126+
│ │ App iframe │ │ └──────────────────┘ └──────────────┘
127+
│ │ (sandboxed) │ │ │
128+
│ └─────────────┘ │ ┌──────────────────┐
129+
│ postMessage ↕ │ │ AppHostServer │
130+
└─────────────────┘ │ (host.py) │
131+
└──────────────────┘
132+
```
133+
134+
- **`host.py`**`AppHostServer` manages lifecycle: port allocation, HTTP serving (host page + app HTML), WebSocket server, browser launch
135+
- **`host_page.py`** — JavaScript host page template; bridges iframe postMessage ↔ WebSocket, handles `ui/initialize`, display modes, reconnection
136+
- **`bridge.py`**`AppBridge` handles JSON-RPC protocol: proxies `tools/call` and `resources/read` to MCP servers, manages message queue for disconnected WS, formats tool results per MCP spec
137+
- **`models.py`** — Pydantic models: `AppInfo`, `AppState` (PENDING → INITIALIZING → READY → CLOSED), `HostContext`
138+
139+
### Security Model
140+
141+
- **Iframe sandbox:** `allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox`
142+
- **XSS prevention:** Tool names are `html.escape()`d before template injection
143+
- **CSP domain sanitization:** Server-supplied domains validated against `^[a-zA-Z0-9\-.:/*]+$`
144+
- **Tool name validation:** Bridge rejects tool names not matching `^[a-zA-Z0-9_\-./]+$`
145+
- **URL scheme validation:** `ui/open-link` only allows `http://` and `https://` schemes
146+
- **Safe JSON serialization:** `_safe_json_dumps()` with `_to_serializable()` fallback; circular reference protection
147+
148+
### Session Reliability
149+
150+
- **Message queue:** `_pending_notifications: deque[str]` (maxlen=50) queues notifications when WS is disconnected
151+
- **Drain on reconnect:** `drain_pending()` flushes queued messages when WS reconnects
152+
- **State reset:** `set_ws()` resets state to INITIALIZING, closes old WS
153+
- **Reconnect notification:** Host page sends `ui/notifications/reconnected` to app iframe on WS reconnect
154+
- **Exponential backoff:** WS reconnection uses 1s→30s exponential backoff with reset on success
155+
- **Initialization timeout:** Configurable JS timeout (default 30s) shows "initialization timed out" if app never initializes
156+
- **Deferred tool result delivery:** Initial tool results are stored on the bridge and pushed only after the app sends `ui/notifications/initialized`, preventing race conditions where postMessage is dropped before the app sets up its listener
157+
- **Duplicate prevention:** `launch_app()` closes previous instance before launching new one
158+
- **Push to existing:** `tool_processor.py` pushes new tool results to running apps instead of re-launching
159+
160+
### Spec Compliance
161+
162+
- `ui/initialize` response includes protocol version, host capabilities (with sandbox details), host info, host context
163+
- `ui/resource-teardown` sent to iframe on `beforeunload`
164+
- `ui/notifications/host-context-changed` sent after display mode changes
165+
- `structuredContent` recovered from JSON text blocks when transport loses it (CTP normalization)
166+
167+
---
168+
169+
## Two Message Classes
170+
171+
The codebase has two classes that represent messages, serving different purposes:
172+
173+
- **`chuk_llm.core.models.Message`** (re-exported via `chat/response_models.py`) — canonical LLM message with typed `ToolCall` objects. Used by `tool_processor.py` and `conversation.py`.
174+
- **`mcp_cli.chat.models.HistoryMessage`** (aliased as `Message` for backward compat) — SessionManager-compatible message with `tool_calls: list[dict]`. Used by `chat_context.py`.
175+
176+
The roundtrip: chuk_llm Message → `to_dict()` → SessionEvent → `from_dict()` → HistoryMessage → `to_dict()` → API.
177+
178+
## Secret Redaction
179+
180+
`SecretRedactingFilter` in `config/logging.py` is always active on all log handlers (console and file). It redacts:
181+
182+
- Bearer tokens (`Authorization: Bearer eyJ...`)
183+
- API keys (`sk-proj-...`, `sk-...`)
184+
- Generic `api_key=...` / `api-key: ...` values
185+
- OAuth access tokens in JSON (`"access_token": "..."`)
186+
- Authorization headers (`Authorization: Basic ...`)
187+
188+
The filter is a module-level singleton (`secret_filter`) that can be added to custom handlers.
189+
190+
---
191+
192+
## Known Violations (Remaining)
193+
194+
Architecture review performed after Tier 2. Tier 4 (Code Quality) resolved the most impactful issues. Remaining items are tracked here.
118195

119196
### Core/UI Separation (#5)
120197

198+
**Resolved in Tier 4.3:** `chat/conversation.py`, `chat/tool_processor.py`, and `chat/chat_context.py` no longer import `chuk_term.ui.output`. All core logging goes through the `logging` module.
199+
200+
**Remaining:**
201+
121202
| File | Issue | Severity |
122203
|------|-------|----------|
123204
| `chat/ui_manager.py` | Imports `prompt_toolkit`, `display/`, `commands/` | HIGH — move to `interactive/` |
@@ -130,15 +211,17 @@ Architecture review performed after Tier 2. These are tracked for remediation in
130211
| File | Issue | Severity |
131212
|------|-------|----------|
132213
| `chat/chat_context.py` | `openai_tools: list[dict]` instead of typed model | MEDIUM |
133-
| `chat/models.py` | `Message.tool_calls: list[dict]` instead of `list[ToolCallData]` | MEDIUM |
214+
| `chat/models.py` | `HistoryMessage.tool_calls: list[dict]` instead of `list[ToolCallData]` | MEDIUM — by design for SessionManager compat |
134215
| `chat/conversation.py` | `_validate_tool_messages()` works on raw dicts | MEDIUM — by design at serialization boundary |
135216

136217
### Explicit Dependencies (#7)
137218

219+
**Resolved in Tier 4.1:** `_GLOBAL_TOOL_MANAGER` singleton removed. ToolManager is constructor-injected everywhere.
220+
221+
**Remaining (deferred — low impact):**
222+
138223
| File | Issue | Severity |
139224
|------|-------|----------|
140-
| `chat/tool_processor.py` | Uses `get_tool_state()`, `get_search_engine()` globals | MEDIUM |
141-
| `chat/conversation.py` | Uses `get_tool_state()` global | MEDIUM |
142-
| `chat/tool_processor.py` | Uses `get_preference_manager()` global | LOW |
143-
144-
These are deferred to **Tier 4.1 (Replace Global Singletons)** and **Tier 4.2 (Consolidate Message Classes)**.
225+
| `chat/tool_processor.py` | Uses `get_tool_state()`, `get_search_engine()` globals | MEDIUM — external library singletons |
226+
| `chat/conversation.py` | Uses `get_tool_state()` global | MEDIUM — external library singleton |
227+
| `chat/tool_processor.py` | Uses `get_preference_manager()` global | LOW — 15 call sites, marginal payoff |

0 commit comments

Comments
 (0)