158 lines
5.3 KiB
Markdown
158 lines
5.3 KiB
Markdown
|
|
# 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<string>
|
|||
|
|
- 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=<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
|
|||
|
|
|
|||
|
|
```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.
|