grove-mcp 1.0.7 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +103 -9
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -13,6 +13,16 @@
|
|
|
13
13
|
* 2. Server sends: event: endpoint data: /messages/?session_id=<uuid>
|
|
14
14
|
* 3. stdin lines → POST to session endpoint (with affinity cookie if set)
|
|
15
15
|
* 4. SSE message events → stdout lines
|
|
16
|
+
*
|
|
17
|
+
* Reconnect behaviour:
|
|
18
|
+
* On SSE disconnect the bridge reconnects automatically with exponential
|
|
19
|
+
* back-off. Each reconnect creates a new server-side MCP session that
|
|
20
|
+
* must be re-initialized before tool calls can proceed. The bridge stores
|
|
21
|
+
* the original `initialize` request and `notifications/initialized`
|
|
22
|
+
* notification from Claude Desktop and silently replays them on every
|
|
23
|
+
* reconnect so the server session is fully initialized before any queued
|
|
24
|
+
* tool calls are flushed. The replay response is intercepted and NOT
|
|
25
|
+
* forwarded to Claude Desktop, which already considers itself initialized.
|
|
16
26
|
*/
|
|
17
27
|
|
|
18
28
|
const https = require('https');
|
|
@@ -33,6 +43,8 @@ if (process.env.GROVE_OUTLINE_TOKEN)
|
|
|
33
43
|
TOKEN_HEADERS['Grove-Outline-Token'] = process.env.GROVE_OUTLINE_TOKEN;
|
|
34
44
|
if (process.env.GROVE_MATTERMOST_TOKEN)
|
|
35
45
|
TOKEN_HEADERS['Grove-Mattermost-Token'] = process.env.GROVE_MATTERMOST_TOKEN;
|
|
46
|
+
if (process.env.GROVE_NOCODB_TOKEN)
|
|
47
|
+
TOKEN_HEADERS['Grove-Nocodb-Token'] = process.env.GROVE_NOCODB_TOKEN;
|
|
36
48
|
|
|
37
49
|
// Per-user session isolation: scopes Redis session keys so multiple users
|
|
38
50
|
// on the shared 3-pod cluster never overwrite each other's auth tokens.
|
|
@@ -92,6 +104,24 @@ function stripMcpCompat(raw) {
|
|
|
92
104
|
}
|
|
93
105
|
}
|
|
94
106
|
|
|
107
|
+
// ── MCP initialization replay state ─────────────────────────────────────────
|
|
108
|
+
//
|
|
109
|
+
// The MCP session lives on the server; each SSE reconnect creates a new one.
|
|
110
|
+
// Claude Desktop does NOT re-send `initialize` on reconnect — it assumes the
|
|
111
|
+
// session is continuous. We fix this by storing the original handshake and
|
|
112
|
+
// replaying it silently on every reconnect before flushing queued messages.
|
|
113
|
+
|
|
114
|
+
// Raw JSON lines captured from stdin on first connection.
|
|
115
|
+
let storedInitRequest = null; // {"method":"initialize", ...}
|
|
116
|
+
let storedInitNotification = null; // {"method":"notifications/initialized"}
|
|
117
|
+
|
|
118
|
+
// Bridge-internal init replay tracking.
|
|
119
|
+
let bridgeInitId = null; // ID we assign to the replayed initialize request
|
|
120
|
+
let awaitingBridgeInit = false; // true while waiting for the server's init response
|
|
121
|
+
let heldPending = []; // messages queued while awaiting bridge init response
|
|
122
|
+
|
|
123
|
+
// ── Transport state ──────────────────────────────────────────────────────────
|
|
124
|
+
|
|
95
125
|
let sessionPath = null;
|
|
96
126
|
let affinityCookie = null; // sticky-session cookie from SSE response headers
|
|
97
127
|
const pending = [];
|
|
@@ -157,13 +187,57 @@ function connect() {
|
|
|
157
187
|
// Fallback: server sent a plain path instead of JSON
|
|
158
188
|
sessionPath = raw;
|
|
159
189
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
190
|
+
|
|
191
|
+
if (storedInitRequest !== null) {
|
|
192
|
+
// ── Reconnect path ─────────────────────────────────────────
|
|
193
|
+
// New server session — must replay the initialize handshake
|
|
194
|
+
// before any tool calls can proceed. Use a bridge-internal
|
|
195
|
+
// ID so we can intercept the response without forwarding it
|
|
196
|
+
// to Claude Desktop (which already considers itself initialized).
|
|
197
|
+
process.stderr.write('grove-mcp-bridge: reconnected — replaying MCP init\n');
|
|
198
|
+
bridgeInitId = '__bridge_init_' + Date.now();
|
|
199
|
+
awaitingBridgeInit = true;
|
|
200
|
+
// Move all pending messages to held; they'll be flushed after
|
|
201
|
+
// the server acknowledges initialization.
|
|
202
|
+
heldPending = pending.splice(0);
|
|
203
|
+
try {
|
|
204
|
+
const initMsg = JSON.parse(storedInitRequest);
|
|
205
|
+
sendToServer(JSON.stringify(Object.assign({}, initMsg, { id: bridgeInitId })));
|
|
206
|
+
} catch (_) {
|
|
207
|
+
// Malformed stored request — fall back to plain flush
|
|
208
|
+
awaitingBridgeInit = false;
|
|
209
|
+
bridgeInitId = null;
|
|
210
|
+
for (const msg of heldPending) sendToServer(msg);
|
|
211
|
+
heldPending = [];
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
// ── First connection path ──────────────────────────────────
|
|
215
|
+
// Claude Desktop's real initialize request is in pending.
|
|
216
|
+
// Flush everything; the server will process them in order.
|
|
217
|
+
for (const msg of pending) sendToServer(msg);
|
|
218
|
+
pending.length = 0;
|
|
219
|
+
}
|
|
163
220
|
} else if (eventType === 'message' && raw) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
221
|
+
if (awaitingBridgeInit) {
|
|
222
|
+
// Check whether this is the response to our replayed initialize.
|
|
223
|
+
try {
|
|
224
|
+
const msg = JSON.parse(raw);
|
|
225
|
+
if (msg.id === bridgeInitId) {
|
|
226
|
+
// Server acknowledged init — complete the handshake and
|
|
227
|
+
// flush held messages. Do NOT forward to Claude Desktop.
|
|
228
|
+
awaitingBridgeInit = false;
|
|
229
|
+
bridgeInitId = null;
|
|
230
|
+
if (storedInitNotification) sendToServer(storedInitNotification);
|
|
231
|
+
for (const msg of heldPending) sendToServer(msg);
|
|
232
|
+
heldPending = [];
|
|
233
|
+
process.stderr.write('grove-mcp-bridge: MCP init replay complete\n');
|
|
234
|
+
eventType = '';
|
|
235
|
+
continue; // skip stdout.write
|
|
236
|
+
}
|
|
237
|
+
} catch (_) {}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Normal message — strip compat fields and forward to Claude Desktop.
|
|
167
241
|
process.stdout.write(stripMcpCompat(raw) + '\n');
|
|
168
242
|
}
|
|
169
243
|
|
|
@@ -209,6 +283,14 @@ function scheduleReconnect() {
|
|
|
209
283
|
// fired at a dead endpoint during the reconnect delay window.
|
|
210
284
|
sessionPath = null;
|
|
211
285
|
affinityCookie = null;
|
|
286
|
+
// Reset bridge-init state so a clean replay happens on the next connect().
|
|
287
|
+
awaitingBridgeInit = false;
|
|
288
|
+
bridgeInitId = null;
|
|
289
|
+
// Move any messages held mid-replay back to pending so they aren't lost.
|
|
290
|
+
if (heldPending.length > 0) {
|
|
291
|
+
pending.unshift(...heldPending);
|
|
292
|
+
heldPending = [];
|
|
293
|
+
}
|
|
212
294
|
setTimeout(() => {
|
|
213
295
|
connect();
|
|
214
296
|
}, reconnectDelay);
|
|
@@ -256,9 +338,21 @@ const rl = readline.createInterface({
|
|
|
256
338
|
rl.on('line', (line) => {
|
|
257
339
|
if (!line.trim()) return;
|
|
258
340
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
341
|
+
// Capture the initialize handshake for reconnect replay.
|
|
342
|
+
try {
|
|
343
|
+
const msg = JSON.parse(line);
|
|
344
|
+
if (msg.method === 'initialize') storedInitRequest = line;
|
|
345
|
+
if (msg.method === 'notifications/initialized') storedInitNotification = line;
|
|
346
|
+
} catch (_) {}
|
|
347
|
+
|
|
348
|
+
if (sessionPath === null || awaitingBridgeInit) {
|
|
349
|
+
// Either: session not yet established, or we're mid-replay.
|
|
350
|
+
// In both cases queue the message — it'll be flushed once ready.
|
|
351
|
+
if (awaitingBridgeInit) {
|
|
352
|
+
heldPending.push(line);
|
|
353
|
+
} else {
|
|
354
|
+
pending.push(line);
|
|
355
|
+
}
|
|
262
356
|
} else {
|
|
263
357
|
sendToServer(line);
|
|
264
358
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grove-mcp",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Claude Desktop bridge for the Grove MCP Server (Mattermost + Outline)",
|
|
3
|
+
"version": "1.0.8",
|
|
4
|
+
"description": "Claude Desktop bridge for the Grove MCP Server (Mattermost + Outline + NocoDB)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"grove-mcp": "index.js"
|