# Whetstone Handoff — Sprint 291: HTTP/SSE Daemon Mode **Date:** 2026-03-09 **Steps:** 1983–1987 **Tests:** 5/5 passing --- ## What Was Built Eliminates the dual-process bug where each Claude Code session spawned its own `whetstone_mcp` stdio process with independent in-memory state. `start_recording` in one session and `get_metrics` in another would hit different processes and lose data. Recording state, workspace, buffers, and AST were all split. **Fix:** HTTP+SSE daemon mode. One shared daemon process, all sessions connect via MCP HTTP+SSE transport. Single in-memory state for everything. ### New files - `editor/src/MCPHttpServer.h` — HTTP+SSE server (Step 1983) - Uses cpp-httplib (fetched via FetchContent, header-only) - `GET /sse` — opens SSE stream, sends `event: endpoint` with session URL - `POST /message?sessionId=X` — routes JSON-RPC to handler, pushes response to SSE - `GET /health` — status JSON `{status, sessions, port}` - Per-connection `Session` struct: mutex + condition_variable + queue - 25s keepalive timeout (sends SSE comment to prevent proxy drops) - Writes `{workspace}/.whetstone/daemon.pid` on start, removes on exit - `editor/src/Sprint291IntegrationSummary.h` — sprint metadata (Step 1986) - `editor/tests/step1987_test.cpp` — integration tests (Step 1987) - `tools/start-whetstone-daemon.sh` — idempotent startup script (Step 1986) ### Modified files - `editor/src/MCPBridge.h` — added `runHttp(int port, const std::string& workspace)` - `editor/src/mcp_main.cpp` — added `--daemon` and `--port N` flags (default 7600), PID lockfile, duplicate daemon guard via `kill(pid, 0)` check - `editor/CMakeLists.txt` — FetchContent for cpp-httplib, step1983-1987 targets - `Documents/.mcp.json` (not in this repo) — switched from `command:` (stdio) to `"type": "sse", "url": "http://127.0.0.1:7600/sse"` --- ## How to Use It ### Starting the daemon (do this once before any Claude Code sessions) ```bash cd /home/bill/Documents/CLionProjects/whetstone_DSL tools/start-whetstone-daemon.sh # prints PID when ready; idempotent — safe to run again if already up ``` Verify it's running: ```bash curl http://127.0.0.1:7600/health # → {"status":"ok","sessions":0,"port":7600} ``` ### Autostart (so you don't have to do it manually) Add to `~/.bashrc` or `~/.profile`: ```bash # Auto-start whetstone daemon if not running if ! curl -sf http://127.0.0.1:7600/health >/dev/null 2>&1; then /home/bill/Documents/CLionProjects/whetstone_DSL/tools/start-whetstone-daemon.sh >/dev/null 2>&1 & fi ``` Or as a systemd user service: ```bash cat > ~/.config/systemd/user/whetstone-daemon.service << 'EOF' [Unit] Description=Whetstone MCP Daemon After=network.target [Service] Type=simple ExecStart=/home/bill/Documents/CLionProjects/whetstone_DSL/editor/build-native/whetstone_mcp \ --daemon \ --port 7600 \ --workspace /home/bill/Documents/CLionProjects/whetstone_DSL \ --language cpp Restart=on-failure RestartSec=5 [Install] WantedBy=default.target EOF systemctl --user enable --now whetstone-daemon ``` ### Stopping the daemon ```bash kill $(cat /home/bill/Documents/CLionProjects/whetstone_DSL/.whetstone/daemon.pid) # or pkill -f "whetstone_mcp --daemon" ``` ### Falling back to stdio mode Change `.mcp.json` back to: ```json { "mcpServers": { "whetstone": { "command": "/home/bill/Documents/CLionProjects/whetstone_DSL/editor/build-native/whetstone_mcp", "args": ["--workspace", "/home/bill/Documents/CLionProjects/whetstone_DSL", "--language", "cpp"] } } } ``` --- ## Architecture Notes ### MCP HTTP+SSE transport (2024-11-05 spec) 1. Client opens `GET /sse` — receives `event: endpoint\ndata: /message?sessionId=` 2. Client sends requests via `POST /message?sessionId=` 3. Server pushes responses back over the SSE stream as `event: message\ndata: ` ### Thread safety - `sessions_` map protected by `sessionsMtx_` - Each session has its own mutex + cv + queue — POST handler and SSE provider communicate via cv.notify_one() - cpp-httplib handles one thread per connection automatically ### Session lifecycle - Session created on `GET /sse`, stored in map - Session removed via the httplib `ContentProviderResourceReleaser` callback when SSE connection closes - PID lockfile prevents two daemons on the same workspace --- ## Build ```bash cd /home/bill/Documents/CLionProjects/whetstone_DSL/editor cmake -S . -B build-native -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_PREFIX_PATH="$(pwd)/build-native/vcpkg_installed/x64-linux" cmake --build build-native --target whetstone_mcp step1987_test --parallel ./build-native/step1987_test # 5/5 should pass ``` cpp-httplib is fetched via FetchContent (v0.18.1) — no vcpkg required. --- ## What Comes Next - **Phase 7 (TBD):** Plan in a new session - **Daemon autostart:** Systemd user service is the cleanest long-term solution - **Language selection:** Current daemon starts with `--language cpp`. For Python projects, the daemon needs `--language python` or a language-switching mechanism. As a workaround, run separate daemons on different ports with different languages and point project-specific `.mcp.json` files at the appropriate port. - **Multi-language daemon:** Future improvement — a single daemon that accepts `language` as a per-request parameter rather than a startup flag.