5.3 KiB
5.3 KiB
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, sendsevent: endpointwith session URLPOST /message?sessionId=X— routes JSON-RPC to handler, pushes response to SSEGET /health— status JSON{status, sessions, port}- Per-connection
Sessionstruct: mutex + condition_variable + queue - 25s keepalive timeout (sends SSE comment to prevent proxy drops)
- Writes
{workspace}/.whetstone/daemon.pidon 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— addedrunHttp(int port, const std::string& workspace)editor/src/mcp_main.cpp— added--daemonand--port Nflags (default 7600), PID lockfile, duplicate daemon guard viakill(pid, 0)checkeditor/CMakeLists.txt— FetchContent for cpp-httplib, step1983-1987 targetsDocuments/.mcp.json(not in this repo) — switched fromcommand:(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)
- Client opens
GET /sse— receivesevent: endpoint\ndata: /message?sessionId=<uuid> - Client sends requests via
POST /message?sessionId=<uuid> - Server pushes responses back over the SSE stream as
event: message\ndata: <json>
Thread safety
sessions_map protected bysessionsMtx_- 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
ContentProviderResourceReleasercallback 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 pythonor a language-switching mechanism. As a workaround, run separate daemons on different ports with different languages and point project-specific.mcp.jsonfiles at the appropriate port. - Multi-language daemon: Future improvement — a single daemon that accepts
languageas a per-request parameter rather than a startup flag.