grove-mcp 1.0.0
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 +148 -0
- package/package.json +23 -0
package/index.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Grove MCP Bridge
|
|
6
|
+
*
|
|
7
|
+
* Zero-dependency stdio ↔ SSE bridge for Claude Desktop.
|
|
8
|
+
* Uses only Node.js built-in modules (https, readline).
|
|
9
|
+
* Works on Node 14+ on macOS and Windows.
|
|
10
|
+
*
|
|
11
|
+
* Protocol:
|
|
12
|
+
* 1. Connect to /mcp/sse via GET (server-sent events)
|
|
13
|
+
* 2. Server sends: event: endpoint data: {"uri": "/mcp/messages?session_id=<uuid>"}
|
|
14
|
+
* 3. stdin lines → POST to session endpoint
|
|
15
|
+
* 4. SSE message events → stdout lines
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const https = require('https');
|
|
19
|
+
const readline = require('readline');
|
|
20
|
+
|
|
21
|
+
const HOSTNAME = 'mcp.lyra.tg';
|
|
22
|
+
const SSE_PATH = '/mcp/sse';
|
|
23
|
+
|
|
24
|
+
let sessionPath = null;
|
|
25
|
+
const pending = [];
|
|
26
|
+
|
|
27
|
+
// ── SSE connection ──────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
const sseReq = https.request(
|
|
30
|
+
{
|
|
31
|
+
hostname: HOSTNAME,
|
|
32
|
+
path: SSE_PATH,
|
|
33
|
+
method: 'GET',
|
|
34
|
+
headers: { Accept: 'text/event-stream' },
|
|
35
|
+
},
|
|
36
|
+
(res) => {
|
|
37
|
+
if (res.statusCode !== 200) {
|
|
38
|
+
process.stderr.write(
|
|
39
|
+
`grove-mcp-bridge: SSE connect failed (HTTP ${res.statusCode})\n`
|
|
40
|
+
);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let buf = '';
|
|
45
|
+
let eventType = '';
|
|
46
|
+
|
|
47
|
+
res.on('data', (chunk) => {
|
|
48
|
+
buf += chunk.toString('utf8');
|
|
49
|
+
|
|
50
|
+
let nl;
|
|
51
|
+
while ((nl = buf.indexOf('\n')) !== -1) {
|
|
52
|
+
const line = buf.slice(0, nl).replace(/\r$/, '');
|
|
53
|
+
buf = buf.slice(nl + 1);
|
|
54
|
+
|
|
55
|
+
if (line.startsWith('event:')) {
|
|
56
|
+
eventType = line.slice(6).trim();
|
|
57
|
+
} else if (line.startsWith('data:')) {
|
|
58
|
+
const raw = line.slice(5).trim();
|
|
59
|
+
|
|
60
|
+
if (eventType === 'endpoint') {
|
|
61
|
+
try {
|
|
62
|
+
sessionPath = JSON.parse(raw).uri;
|
|
63
|
+
} catch (_) {
|
|
64
|
+
// Fallback: server sent a plain path instead of JSON
|
|
65
|
+
sessionPath = raw;
|
|
66
|
+
}
|
|
67
|
+
// Flush any stdin lines that arrived before the endpoint was ready
|
|
68
|
+
for (const msg of pending) sendToServer(msg);
|
|
69
|
+
pending.length = 0;
|
|
70
|
+
} else if (eventType === 'message' && raw) {
|
|
71
|
+
// raw is already a serialised JSON-RPC object — write directly
|
|
72
|
+
process.stdout.write(raw + '\n');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
eventType = '';
|
|
76
|
+
} else if (line === '') {
|
|
77
|
+
eventType = '';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
res.on('end', () => {
|
|
83
|
+
process.stderr.write('grove-mcp-bridge: SSE stream ended\n');
|
|
84
|
+
process.exit(0);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
res.on('error', (err) => {
|
|
88
|
+
process.stderr.write(`grove-mcp-bridge: SSE error: ${err.message}\n`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
sseReq.on('error', (err) => {
|
|
95
|
+
process.stderr.write(
|
|
96
|
+
`grove-mcp-bridge: cannot reach ${HOSTNAME}: ${err.message}\n`
|
|
97
|
+
);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
sseReq.end();
|
|
102
|
+
|
|
103
|
+
// ── stdin → POST ────────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
function sendToServer(line) {
|
|
106
|
+
const body = Buffer.from(line, 'utf8');
|
|
107
|
+
|
|
108
|
+
const postReq = https.request(
|
|
109
|
+
{
|
|
110
|
+
hostname: HOSTNAME,
|
|
111
|
+
path: sessionPath,
|
|
112
|
+
method: 'POST',
|
|
113
|
+
headers: {
|
|
114
|
+
'Content-Type': 'application/json',
|
|
115
|
+
'Content-Length': body.length,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
(res) => {
|
|
119
|
+
// 202 Accepted — actual response arrives via SSE; drain body
|
|
120
|
+
res.resume();
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
postReq.on('error', (err) => {
|
|
125
|
+
process.stderr.write(`grove-mcp-bridge: POST error: ${err.message}\n`);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
postReq.write(body);
|
|
129
|
+
postReq.end();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const rl = readline.createInterface({
|
|
133
|
+
input: process.stdin,
|
|
134
|
+
crlfDelay: Infinity, // handles Windows \r\n
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
rl.on('line', (line) => {
|
|
138
|
+
if (!line.trim()) return;
|
|
139
|
+
|
|
140
|
+
if (sessionPath === null) {
|
|
141
|
+
// Session not yet established — queue the message
|
|
142
|
+
pending.push(line);
|
|
143
|
+
} else {
|
|
144
|
+
sendToServer(line);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
rl.on('close', () => process.exit(0));
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "grove-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Claude Desktop bridge for the Grove MCP Server (Mattermost + Outline)",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"grove-mcp": "index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node index.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"mcp",
|
|
14
|
+
"grove",
|
|
15
|
+
"claude",
|
|
16
|
+
"mattermost",
|
|
17
|
+
"outline"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=14"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {}
|
|
23
|
+
}
|