From 99cc1853aa3d342ac3998539d4620a957675bfee Mon Sep 17 00:00:00 2001 From: StillHammer Date: Sun, 25 Jan 2026 11:06:51 +0700 Subject: [PATCH] Add /unregister endpoint and document Claude Code hooks for graceful shutdown --- .gitignore | 1 + README.md | 37 +++++++++++++++++++++++++++++++++++++ broker/index.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/.gitignore b/.gitignore index 6d7449c..0fefe1b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ node_modules/ .state/ conversations/ data/ +drafts/ *.log *.db diff --git a/README.md b/README.md index c2482b2..e337500 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,7 @@ mcp-claude-duo/ | Endpoint | Method | Description | |----------|--------|-------------| | `/register` | POST | Register a partner | +| `/unregister` | POST | Unregister / go offline | | `/talk` | POST | Send a message | | `/listen/:partnerId` | GET | Long-poll for messages | | `/conversations` | POST | Create group conversation | @@ -197,6 +198,42 @@ See [docs/db-schema.md](docs/db-schema.md) for full schema documentation. | `BROKER_PORT` | `3210` | Broker listen port | | `PARTNER_NAME` | `Claude` | Display name for the partner | +### Graceful Shutdown with Hooks + +To properly mark your Claude instance as offline when the MCP stops, configure a Claude Code hook. + +**1. Create a settings file (if not exists):** + +Create or edit `~/.claude/settings.json`: + +```json +{ + "hooks": { + "Stop": [ + { + "matcher": "duo-partner", + "hooks": [ + { + "type": "command", + "command": "curl -X POST http://localhost:3210/unregister -H \"Content-Type: application/json\" -d \"{\\\"partnerId\\\": \\\"$PARTNER_ID\\\"}\"" + } + ] + } + ] + } +} +``` + +**2. Or use the Claude CLI:** + +```bash +claude config set hooks.Stop '[{"matcher": "duo-partner", "hooks": [{"type": "command", "command": "curl -X POST http://localhost:3210/unregister -H \"Content-Type: application/json\" -d \"{\\\"partnerId\\\": \\\"YOUR_PROJECT_NAME\\\"}\""}]}]' +``` + +Replace `YOUR_PROJECT_NAME` with your actual partner ID (usually derived from your project folder name). + +**Note:** Without this hook, partners will remain marked as "online" until the broker restarts or they reconnect. + ## Contributing Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. diff --git a/broker/index.js b/broker/index.js index 67a0923..2242027 100644 --- a/broker/index.js +++ b/broker/index.js @@ -434,6 +434,35 @@ app.post("/partners/:partnerId/notifications", (req, res) => { res.json({ success: true }); }); +/** + * Se désenregistrer / passer offline + * POST /unregister + * Body: { partnerId } + */ +app.post("/unregister", (req, res) => { + const { partnerId } = req.body; + + if (!partnerId) { + return res.status(400).json({ error: "partnerId required" }); + } + + // Fermer la connexion long-polling si active + if (waitingPartners.has(partnerId)) { + const { res: waitingRes, heartbeat, timeout } = waitingPartners.get(partnerId); + clearInterval(heartbeat); + if (timeout) clearTimeout(timeout); + waitingPartners.delete(partnerId); + try { + waitingRes.json({ hasMessages: false, messages: [], reason: "unregistered" }); + } catch {} + } + + DB.setPartnerOffline(partnerId); + console.log(`[BROKER] Unregistered: ${partnerId}`); + + res.json({ success: true }); +}); + /** * Health check */