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.
- package/index.js +126 -77
- 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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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 (
|
|
35
|
+
if (mcpRequest.platform == null) mcpRequest.platform = 'claude';
|
|
33
36
|
}
|
|
34
37
|
return mcpRequest;
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
|
|
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
|
-
|
|
55
|
-
forwardToAPI(withClaudePlatform(mcpRequest));
|
|
81
|
+
mcpRequest = JSON.parse(line);
|
|
56
82
|
} catch (error) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
error: {
|
|
83
|
+
writeJson(
|
|
84
|
+
rpcError({
|
|
85
|
+
id: null,
|
|
61
86
|
code: -32700,
|
|
62
|
-
message: 'Parse error: ' + error.message
|
|
63
|
-
}
|
|
64
|
-
|
|
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
|
|
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
|
-
|
|
91
|
-
data
|
|
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
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
110
|
-
console.log(data);
|
|
163
|
+
writeJson({ jsonrpc: '2.0', id, result: parsed });
|
|
111
164
|
}
|
|
112
165
|
} else {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
error: {
|
|
166
|
+
writeJson(
|
|
167
|
+
rpcError({
|
|
168
|
+
id,
|
|
117
169
|
code: res.statusCode,
|
|
118
|
-
message: `API error: ${res.statusCode}
|
|
119
|
-
data:
|
|
120
|
-
}
|
|
121
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
error: {
|
|
179
|
+
writeJson(
|
|
180
|
+
rpcError({
|
|
181
|
+
id: rpcId(mcpRequest),
|
|
132
182
|
code: -32603,
|
|
133
|
-
message: '
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
error: {
|
|
191
|
+
writeJson(
|
|
192
|
+
rpcError({
|
|
193
|
+
id: rpcId(mcpRequest),
|
|
145
194
|
code: -32603,
|
|
146
|
-
message: 'Request timeout after 30 seconds'
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
}));
|
|
195
|
+
message: 'Request timeout after 30 seconds',
|
|
196
|
+
})
|
|
197
|
+
);
|
|
150
198
|
});
|
|
151
199
|
|
|
152
|
-
req.write(
|
|
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(
|
|
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) =>
|
|
241
|
+
res.on('data', (chunk) => (data += chunk));
|
|
193
242
|
res.on('end', () => {
|
|
194
243
|
console.log(`Response Status: ${res.statusCode}\n`);
|
|
195
244
|
|