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.
- package/index.js +99 -75
- 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
|
-
//
|
|
16
|
-
|
|
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
|
-
*
|
|
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(
|
|
46
|
-
return
|
|
43
|
+
function rpcId(req) {
|
|
44
|
+
return req?.id ?? null;
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
|
|
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
|
-
|
|
67
|
-
forwardToAPI(withClaudePlatform(mcpRequest));
|
|
81
|
+
mcpRequest = JSON.parse(line);
|
|
68
82
|
} catch (error) {
|
|
69
|
-
|
|
70
|
-
|
|
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,
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
122
|
-
console.log(data);
|
|
163
|
+
writeJson({ jsonrpc: '2.0', id, result: parsed });
|
|
123
164
|
}
|
|
124
165
|
} else {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
error: {
|
|
130
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
error:
|
|
147
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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(
|
|
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
|
|