Files
whetstone_DSL/HANDOFF-2026-03-09-sprint291.md

5.3 KiB
Raw Permalink Blame History

Whetstone Handoff — Sprint 291: HTTP/SSE Daemon Mode

Date: 2026-03-09 Steps: 19831987 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)

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:

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:

# 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:

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

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:

{
  "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=<uuid>
  2. Client sends requests via POST /message?sessionId=<uuid>
  3. Server pushes responses back over the SSE stream as event: message\ndata: <json>

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

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.