reportdash-datastore-mcp-claude-desktop 1.0.9 → 1.0.10

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 +99 -75
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -12,9 +12,8 @@ const API_URL = process.env.REPORTDASH_API_URL || 'https://datastore.reportdash.
12
12
 
13
13
  // Validate configuration
14
14
  if (!API_KEY) {
15
- // NOTE: In MCP stdio mode, prefer stdout only.
16
- // But keeping your original behavior as-is.
17
- console.error(
15
+ // In Claude MCP mode, stdout should be JSON only. This is fatal anyway, so ok.
16
+ process.stdout.write(
18
17
  JSON.stringify({
19
18
  jsonrpc: '2.0',
20
19
  error: {
@@ -22,14 +21,14 @@ if (!API_KEY) {
22
21
  message:
23
22
  'REPORTDASH_API_KEY environment variable is required. Get your API key from ReportDash DataStore app settings.',
24
23
  },
25
- })
24
+ id: null,
25
+ }) + '\n'
26
26
  );
27
27
  process.exit(1);
28
28
  }
29
29
 
30
30
  /**
31
31
  * Ensure every outbound request includes platform="claude"
32
- * without breaking existing requests.
33
32
  */
34
33
  function withClaudePlatform(mcpRequest) {
35
34
  if (mcpRequest && typeof mcpRequest === 'object') {
@@ -39,14 +38,29 @@ function withClaudePlatform(mcpRequest) {
39
38
  }
40
39
 
41
40
  /**
42
- * JSON-RPC id helper:
43
- * Keep 0 as a valid id; only default when null/undefined.
41
+ * Preserve id=0. Only default when null/undefined.
44
42
  */
45
- function rpcId(mcpRequest) {
46
- return mcpRequest?.id ?? null;
43
+ function rpcId(req) {
44
+ return req?.id ?? null;
47
45
  }
48
46
 
49
- // Test mode
47
+ /**
48
+ * Write exactly one JSON object per line to stdout (MCP framing).
49
+ */
50
+ function writeJson(obj) {
51
+ process.stdout.write(JSON.stringify(obj) + '\n');
52
+ }
53
+
54
+ /**
55
+ * Build a JSON-RPC error response
56
+ */
57
+ function rpcError({ id = null, code = -32603, message = 'Internal error', data }) {
58
+ const err = { code, message };
59
+ if (data !== undefined) err.data = data;
60
+ return { jsonrpc: '2.0', id, error: err };
61
+ }
62
+
63
+ // Test mode (prints human logs to stdout; do not run in Claude MCP mode)
50
64
  if (process.argv.includes('--test')) {
51
65
  testConnection();
52
66
  return;
@@ -62,22 +76,21 @@ const rl = readline.createInterface({
62
76
  rl.on('line', (line) => {
63
77
  if (!line.trim()) return;
64
78
 
79
+ let mcpRequest;
65
80
  try {
66
- const mcpRequest = JSON.parse(line);
67
- forwardToAPI(withClaudePlatform(mcpRequest));
81
+ mcpRequest = JSON.parse(line);
68
82
  } catch (error) {
69
- // Only output to stdout, never stderr for MCP protocol
70
- console.log(
71
- JSON.stringify({
72
- jsonrpc: '2.0',
73
- error: {
74
- code: -32700,
75
- message: 'Parse error: ' + error.message,
76
- },
83
+ writeJson(
84
+ rpcError({
77
85
  id: null,
86
+ code: -32700,
87
+ message: 'Parse error: ' + error.message,
78
88
  })
79
89
  );
90
+ return;
80
91
  }
92
+
93
+ forwardToAPI(withClaudePlatform(mcpRequest));
81
94
  });
82
95
 
83
96
  function forwardToAPI(mcpRequest) {
@@ -85,53 +98,77 @@ function forwardToAPI(mcpRequest) {
85
98
  const isHttps = url.protocol === 'https:';
86
99
  const client = isHttps ? https : http;
87
100
 
101
+ const body = JSON.stringify(mcpRequest);
102
+
88
103
  const options = {
89
104
  hostname: url.hostname,
90
105
  port: url.port || (isHttps ? 443 : 80),
91
- path: url.pathname,
106
+ path: url.pathname + url.search,
92
107
  method: 'POST',
93
108
  headers: {
94
109
  'Content-Type': 'application/json',
110
+ 'Content-Length': Buffer.byteLength(body),
95
111
  'X-Api-Key': API_KEY,
96
112
  'User-Agent': 'ReportDash-DataStore-MCP/1.0',
97
113
  },
98
- timeout: 30000, // 30 second timeout
114
+ timeout: 30000,
99
115
  };
100
116
 
101
117
  const req = client.request(options, (res) => {
102
118
  let data = '';
103
-
104
- res.on('data', (chunk) => {
105
- data += chunk;
106
- });
107
-
119
+ res.on('data', (chunk) => (data += chunk));
108
120
  res.on('end', () => {
109
- // Treat 200-299 as success (including 204 No Content)
110
- if (res.statusCode >= 200 && res.statusCode < 300) {
111
- // For 204 No Content or empty response, return empty success
112
- if (res.statusCode === 204 || !data.trim()) {
113
- console.log(
114
- JSON.stringify({
115
- jsonrpc: '2.0',
116
- result: {},
117
- id: rpcId(mcpRequest), // FIX: preserves id=0
121
+ const id = rpcId(mcpRequest);
122
+
123
+ // 204 or empty body => empty result
124
+ if (res.statusCode === 204 || !data.trim()) {
125
+ if (res.statusCode >= 200 && res.statusCode < 300) {
126
+ writeJson({ jsonrpc: '2.0', id, result: {} });
127
+ } else {
128
+ writeJson(
129
+ rpcError({
130
+ id,
131
+ code: res.statusCode,
132
+ message: `API error: ${res.statusCode} (empty body)`,
118
133
  })
119
134
  );
135
+ }
136
+ return;
137
+ }
138
+
139
+ // Always re-serialize to 1-line JSON for MCP framing
140
+ let parsed;
141
+ try {
142
+ parsed = JSON.parse(data);
143
+ } catch (e) {
144
+ writeJson(
145
+ rpcError({
146
+ id,
147
+ code: res.statusCode >= 400 ? res.statusCode : -32603,
148
+ message: 'API returned non-JSON response',
149
+ data: { statusCode: res.statusCode, body: data },
150
+ })
151
+ );
152
+ return;
153
+ }
154
+
155
+ if (res.statusCode >= 200 && res.statusCode < 300) {
156
+ // Ensure JSON-RPC envelope exists; if your API returns raw jsonrpc objects, this is fine.
157
+ // If it returns something else, wrap it.
158
+ if (parsed && typeof parsed === 'object' && parsed.jsonrpc === '2.0') {
159
+ // Some backends might omit id; ensure it's present
160
+ if (parsed.id === undefined) parsed.id = id;
161
+ writeJson(parsed);
120
162
  } else {
121
- // Forward the response as-is
122
- console.log(data);
163
+ writeJson({ jsonrpc: '2.0', id, result: parsed });
123
164
  }
124
165
  } else {
125
- // Send errors to stdout (not stderr!) so Claude can properly handle them
126
- console.log(
127
- JSON.stringify({
128
- jsonrpc: '2.0',
129
- error: {
130
- code: res.statusCode,
131
- message: `API error: ${res.statusCode}${data ? ' - ' + data : ''}`,
132
- data: { statusCode: res.statusCode, body: data },
133
- },
134
- id: rpcId(mcpRequest), // FIX: preserves id=0
166
+ writeJson(
167
+ rpcError({
168
+ id,
169
+ code: res.statusCode,
170
+ message: `API error: ${res.statusCode}`,
171
+ data: parsed,
135
172
  })
136
173
  );
137
174
  }
@@ -139,42 +176,32 @@ function forwardToAPI(mcpRequest) {
139
176
  });
140
177
 
141
178
  req.on('error', (error) => {
142
- // Send errors to stdout (not stderr!)
143
- console.log(
144
- JSON.stringify({
145
- jsonrpc: '2.0',
146
- error: {
147
- code: -32603,
148
- message: 'Network error: ' + error.message,
149
- data: { error: error.message },
150
- },
151
- id: rpcId(mcpRequest), // FIX: preserves id=0
179
+ writeJson(
180
+ rpcError({
181
+ id: rpcId(mcpRequest),
182
+ code: -32603,
183
+ message: 'Network error: ' + error.message,
184
+ data: { error: error.message },
152
185
  })
153
186
  );
154
187
  });
155
188
 
156
189
  req.on('timeout', () => {
157
190
  req.destroy();
158
- // Send errors to stdout (not stderr!)
159
- console.log(
160
- JSON.stringify({
161
- jsonrpc: '2.0',
162
- error: {
163
- code: -32603,
164
- message: 'Request timeout after 30 seconds',
165
- },
166
- id: rpcId(mcpRequest), // FIX: preserves id=0
191
+ writeJson(
192
+ rpcError({
193
+ id: rpcId(mcpRequest),
194
+ code: -32603,
195
+ message: 'Request timeout after 30 seconds',
167
196
  })
168
197
  );
169
198
  });
170
199
 
171
- req.write(JSON.stringify(mcpRequest));
200
+ req.write(body);
172
201
  req.end();
173
202
  }
174
203
 
175
204
  function testConnection() {
176
- // NOTE: This prints non-JSON to stdout, which is fine for --test.
177
- // Do not run --test as an MCP stdio server in Claude Desktop.
178
205
  console.log('🔍 Testing ReportDash DataStore connection...\n');
179
206
  console.log(`API URL: ${API_URL}`);
180
207
  console.log(
@@ -185,7 +212,6 @@ function testConnection() {
185
212
  const isHttps = url.protocol === 'https:';
186
213
  const client = isHttps ? https : http;
187
214
 
188
- // Create MCP tools/list request
189
215
  const mcpRequest = withClaudePlatform({
190
216
  jsonrpc: '2.0',
191
217
  method: 'tools/list',
@@ -197,7 +223,7 @@ function testConnection() {
197
223
  const options = {
198
224
  hostname: url.hostname,
199
225
  port: url.port || (isHttps ? 443 : 80),
200
- path: url.pathname,
226
+ path: url.pathname + url.search,
201
227
  method: 'POST',
202
228
  headers: {
203
229
  'Content-Type': 'application/json',
@@ -212,9 +238,7 @@ function testConnection() {
212
238
 
213
239
  const req = client.request(options, (res) => {
214
240
  let data = '';
215
- res.on('data', (chunk) => {
216
- data += chunk;
217
- });
241
+ res.on('data', (chunk) => (data += chunk));
218
242
  res.on('end', () => {
219
243
  console.log(`Response Status: ${res.statusCode}\n`);
220
244
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reportdash-datastore-mcp-claude-desktop",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "ReportDash DataStore MCP server for Claude Desktop",
5
5
  "main": "index.js",
6
6
  "bin": {