Two things completed in this session: (1) reorder op implemented and deployed; (2) persistent MCP -32602 bug root-caused and fixed.
The reorder op is live on both pomelo (local) and gravlax (production). All three interfaces work:
• HTTP: POST or PATCH /{key} with {"op": "reorder", "order": [<id>, ...]}
• MCP tool: reorder(key, order, if_rev?) — delegates to db_patch
• CLI: notes reorder <key> <id> [<id> ...]
Strict validation before any mutation: 422 on missing/unknown/duplicate IDs. Supports if_rev for optimistic concurrency.
Tests: 18 HTTP tests in test_reorder.py, 11 MCP tests in test_mcp_tools.py (TestReorder class). All passing.
Also added PATCH /{key} route as a proper HTTP method (previously only POST did patch ops).
Claude Code on pomelo reaches the MCP server via stunnel, NOT via Apache:
pomelo:~/.claude.json → http://127.0.0.1:8023/mcp/
↓ stunnel client (pomelo /etc/stunnel/stunnel.conf)
gravlax:18023
↓ stunnel server (gravlax /etc/stunnel/stunnel.conf)
gravlax:127.0.0.1:8023 ← gdata-mcp-server.service
The Apache proxy (https://www.critchley.biz/mcp → gravlax:8023) is used by other clients (e.g. cloud-based Claude, Codex). Apache Streamable HTTP requests show in other_vhosts_access.log; stunnel requests show only in the service journal.
~/.claude.json had "type": "sse". The SSE MCP client caches session state. After a server restart (mid-session), it reconnects (new session_id from GET /mcp/) but skips the initialize handshake because its internal state says "already initialized". The server rejects every subsequent tool call with -32602.
Changed "type": "sse" to "type": "http" (Streamable HTTP) in ~/.claude.json. Streamable HTTP is stateless: every POST to /mcp/ is a self-contained JSON-RPC call — no session, no initialize step, no stale state after restart.
Verified: curl POST to http://127.0.0.1:8023/mcp/ with both initialize and tools/call payloads both return 200 with correct results.
The fix takes effect on the next Claude Code session start. The current session still fails (old SSE connection already established).
Added to the <Location /mcp> block in /etc/apache2/sites-enabled/www.critchley.biz-ssl.conf while debugging:
ProxyPass http://127.0.0.1:8023/mcp flushpackets=on disablereuse=On
SetEnv proxy-initial-not-pooled 1
SetEnv no-gzip 1
SetEnv gzip-only-text/html 1
These are defensive and correct for SSE proxying. disablereuse=On prevents stale pooled connections after restarts. no-gzip prevents mod_deflate buffering the stream.
• gdata-mcp-server.service: running, SSE + Streamable HTTP both enabled (MCP_SSE default true)
• All tests pass locally (python -m pytest test_reorder.py test_mcp_tools.py)
• Code committed and rsync'd to gravlax
After restarting Claude Code, run any MCP tool (e.g. mcp__gdata__keys). Should return results without -32602. If it fails, check ~/.claude.json still has "type": "http".