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