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.
Files changed (2) hide show
  1. package/index.js +148 -0
  2. 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
+ }