embark-cli 1.1.7
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/.claude/CLAUDE.md +33 -0
- package/.claude/settings.local.json +32 -0
- package/.github/WORKFLOWS.md +147 -0
- package/.github/workflows/ci.yml +49 -0
- package/.github/workflows/publish.yml +109 -0
- package/.idea/embark-remote-mcp.iml +9 -0
- package/.idea/encodings.xml +4 -0
- package/.idea/indexLayout.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/.mcp.json +14 -0
- package/GIT_DISCOVERY.md +231 -0
- package/INTEGRATION_TESTING.md +243 -0
- package/MULTI_REPOSITORY_SEARCH.md +242 -0
- package/README.md +434 -0
- package/dist/auth/auth-helper.d.ts +3 -0
- package/dist/auth/auth-helper.d.ts.map +1 -0
- package/dist/auth/auth-helper.js +171 -0
- package/dist/auth/auth-helper.js.map +1 -0
- package/dist/auth/index.d.ts +4 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +24 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/jba-login.d.ts +17 -0
- package/dist/auth/jba-login.d.ts.map +1 -0
- package/dist/auth/jba-login.js +345 -0
- package/dist/auth/jba-login.js.map +1 -0
- package/dist/auth/types.d.ts +16 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +3 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/config.d.ts +26 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +54 -0
- package/dist/config.js.map +1 -0
- package/dist/embark-client.d.ts +56 -0
- package/dist/embark-client.d.ts.map +1 -0
- package/dist/embark-client.js +543 -0
- package/dist/embark-client.js.map +1 -0
- package/dist/git-utils.d.ts +47 -0
- package/dist/git-utils.d.ts.map +1 -0
- package/dist/git-utils.js +232 -0
- package/dist/git-utils.js.map +1 -0
- package/dist/handlers.d.ts +80 -0
- package/dist/handlers.d.ts.map +1 -0
- package/dist/handlers.js +301 -0
- package/dist/handlers.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +165 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +4 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +92 -0
- package/dist/logger.js.map +1 -0
- package/dist/stats-server.d.ts +3 -0
- package/dist/stats-server.d.ts.map +1 -0
- package/dist/stats-server.js +623 -0
- package/dist/stats-server.js.map +1 -0
- package/dist/stats.d.ts +118 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +206 -0
- package/dist/stats.js.map +1 -0
- package/dist/tools.d.ts +9 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +62 -0
- package/dist/tools.js.map +1 -0
- package/package.json +47 -0
- package/test-git-discovery.mjs +322 -0
- package/test-multi-repo-filters.mjs +151 -0
- package/test-multiple-roots.mjs +436 -0
- package/test-roots.mjs +306 -0
- package/test-snippet-extraction.mjs +136 -0
- package/watch-logs.sh +78 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test script for multiple roots functionality
|
|
5
|
+
*
|
|
6
|
+
* This script tests whether the MCP server properly handles multiple root directories
|
|
7
|
+
* and allows searching across different repositories.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { spawn } from 'child_process';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import { dirname, join } from 'path';
|
|
13
|
+
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = dirname(__filename);
|
|
16
|
+
|
|
17
|
+
// Configuration
|
|
18
|
+
const SERVER_PATH = join(__dirname, 'dist', 'index.js');
|
|
19
|
+
|
|
20
|
+
// Colors for terminal output
|
|
21
|
+
const colors = {
|
|
22
|
+
reset: '\x1b[0m',
|
|
23
|
+
green: '\x1b[32m',
|
|
24
|
+
red: '\x1b[31m',
|
|
25
|
+
yellow: '\x1b[33m',
|
|
26
|
+
blue: '\x1b[34m',
|
|
27
|
+
gray: '\x1b[90m',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function log(message, color = colors.reset) {
|
|
31
|
+
console.log(`${color}${message}${colors.reset}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function logSuccess(message) {
|
|
35
|
+
log(`✓ ${message}`, colors.green);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function logError(message) {
|
|
39
|
+
log(`✗ ${message}`, colors.red);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function logInfo(message) {
|
|
43
|
+
log(`ℹ ${message}`, colors.blue);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function logDebug(message) {
|
|
47
|
+
log(` ${message}`, colors.gray);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Send a JSON-RPC request to the server
|
|
52
|
+
*/
|
|
53
|
+
function sendRequest(server, request) {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
const requestStr = JSON.stringify(request) + '\n';
|
|
56
|
+
logDebug(`→ ${requestStr.trim()}`);
|
|
57
|
+
|
|
58
|
+
let responseBuffer = '';
|
|
59
|
+
let resolved = false;
|
|
60
|
+
|
|
61
|
+
const onData = (data) => {
|
|
62
|
+
responseBuffer += data.toString();
|
|
63
|
+
|
|
64
|
+
// Try to parse complete JSON objects
|
|
65
|
+
const lines = responseBuffer.split('\n');
|
|
66
|
+
responseBuffer = lines.pop(); // Keep incomplete line in buffer
|
|
67
|
+
|
|
68
|
+
for (const line of lines) {
|
|
69
|
+
if (line.trim()) {
|
|
70
|
+
try {
|
|
71
|
+
const response = JSON.parse(line);
|
|
72
|
+
logDebug(`← ${line}`);
|
|
73
|
+
|
|
74
|
+
// Check if this is the response to our request
|
|
75
|
+
if (response.id === request.id) {
|
|
76
|
+
resolved = true;
|
|
77
|
+
server.stdout.off('data', onData);
|
|
78
|
+
resolve(response);
|
|
79
|
+
}
|
|
80
|
+
} catch (e) {
|
|
81
|
+
// Not valid JSON, continue accumulating
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
server.stdout.on('data', onData);
|
|
88
|
+
|
|
89
|
+
// Timeout after 30 seconds (longer for search operations)
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
if (!resolved) {
|
|
92
|
+
server.stdout.off('data', onData);
|
|
93
|
+
reject(new Error('Request timeout'));
|
|
94
|
+
}
|
|
95
|
+
}, 30000);
|
|
96
|
+
|
|
97
|
+
server.stdin.write(requestStr);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Test initialization with multiple roots
|
|
103
|
+
*/
|
|
104
|
+
async function testInitializeWithRoots(server, roots) {
|
|
105
|
+
log('\n--- Testing initialization with multiple roots ---', colors.yellow);
|
|
106
|
+
|
|
107
|
+
const request = {
|
|
108
|
+
jsonrpc: '2.0',
|
|
109
|
+
id: 1,
|
|
110
|
+
method: 'initialize',
|
|
111
|
+
params: {
|
|
112
|
+
protocolVersion: '2024-11-05',
|
|
113
|
+
capabilities: {
|
|
114
|
+
roots: {
|
|
115
|
+
listChanged: true,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
clientInfo: {
|
|
119
|
+
name: 'test-client',
|
|
120
|
+
version: '1.0.0',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const response = await sendRequest(server, request);
|
|
127
|
+
|
|
128
|
+
if (response.error) {
|
|
129
|
+
logError(`Initialization error: ${JSON.stringify(response.error)}`);
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!response.result) {
|
|
134
|
+
logError('Initialization response missing result');
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
logSuccess('Server initialized successfully');
|
|
139
|
+
return true;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
logError(`Failed to initialize: ${error.message}`);
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Send roots/list_changed notification
|
|
148
|
+
*/
|
|
149
|
+
async function sendRootsNotification(server, roots) {
|
|
150
|
+
log('\n--- Sending roots/list_changed notification ---', colors.yellow);
|
|
151
|
+
|
|
152
|
+
logInfo(`Sending ${roots.length} roots:`);
|
|
153
|
+
roots.forEach((root, i) => {
|
|
154
|
+
logInfo(` ${i + 1}. ${root.uri}`);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const notification = {
|
|
158
|
+
jsonrpc: '2.0',
|
|
159
|
+
method: 'notifications/roots/list_changed',
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const notificationStr = JSON.stringify(notification) + '\n';
|
|
163
|
+
logDebug(`→ ${notificationStr.trim()}`);
|
|
164
|
+
|
|
165
|
+
server.stdin.write(notificationStr);
|
|
166
|
+
|
|
167
|
+
// Wait a bit for processing
|
|
168
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
169
|
+
|
|
170
|
+
logSuccess('Notification sent');
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Test tools/list
|
|
176
|
+
*/
|
|
177
|
+
async function testListTools(server) {
|
|
178
|
+
log('\n--- Testing tools/list ---', colors.yellow);
|
|
179
|
+
|
|
180
|
+
const request = {
|
|
181
|
+
jsonrpc: '2.0',
|
|
182
|
+
id: 3,
|
|
183
|
+
method: 'tools/list',
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const response = await sendRequest(server, request);
|
|
188
|
+
|
|
189
|
+
if (response.error) {
|
|
190
|
+
logError(`Server returned error: ${JSON.stringify(response.error)}`);
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (!response.result || !response.result.tools) {
|
|
195
|
+
logError('Response missing tools');
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
logSuccess(`Found ${response.result.tools.length} tool(s)`);
|
|
200
|
+
response.result.tools.forEach(tool => {
|
|
201
|
+
logInfo(` - ${tool.name}`);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
return true;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
logError(`Failed to list tools: ${error.message}`);
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Test semantic_code_search tool call across multiple repositories
|
|
213
|
+
*/
|
|
214
|
+
async function testSemanticSearch(server) {
|
|
215
|
+
log('\n--- Testing semantic_code_search with multiple repositories ---', colors.yellow);
|
|
216
|
+
|
|
217
|
+
const request = {
|
|
218
|
+
jsonrpc: '2.0',
|
|
219
|
+
id: 4,
|
|
220
|
+
method: 'tools/call',
|
|
221
|
+
params: {
|
|
222
|
+
name: 'semantic_code_search',
|
|
223
|
+
arguments: {
|
|
224
|
+
text: 'function that handles logging or configuration',
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
logInfo('Sending search request (this may take a few seconds)...');
|
|
231
|
+
const response = await sendRequest(server, request);
|
|
232
|
+
|
|
233
|
+
if (response.error) {
|
|
234
|
+
logError(`Server returned error: ${JSON.stringify(response.error)}`);
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!response.result || !response.result.content) {
|
|
239
|
+
logError('Response missing content');
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const content = response.result.content[0]?.text || '';
|
|
244
|
+
|
|
245
|
+
// Check if the response mentions multiple repositories
|
|
246
|
+
const hasMultipleRepos = content.includes('## Repository:');
|
|
247
|
+
const repoMatches = content.match(/## Repository:/g);
|
|
248
|
+
const repoCount = repoMatches ? repoMatches.length : 0;
|
|
249
|
+
|
|
250
|
+
if (hasMultipleRepos) {
|
|
251
|
+
logSuccess(`Search executed across ${repoCount} repository/repositories`);
|
|
252
|
+
|
|
253
|
+
// Extract repository names from the response
|
|
254
|
+
const repoNames = content.match(/## Repository: ([^\n]+)/g);
|
|
255
|
+
if (repoNames) {
|
|
256
|
+
repoNames.forEach((name, i) => {
|
|
257
|
+
logInfo(` ${i + 1}. ${name.replace('## Repository: ', '')}`);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (repoCount > 1) {
|
|
262
|
+
logSuccess('Multi-repository search confirmed!');
|
|
263
|
+
} else {
|
|
264
|
+
logInfo('Only one repository was searched (this is expected if only one has a Git remote)');
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
// Single repository response format
|
|
268
|
+
if (content.includes('Found') && content.includes('results')) {
|
|
269
|
+
logSuccess('Search executed successfully (single repository)');
|
|
270
|
+
logInfo('Note: Only one repository was discovered/searched');
|
|
271
|
+
} else {
|
|
272
|
+
logError('Unexpected response format');
|
|
273
|
+
logDebug(content.substring(0, 200));
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return true;
|
|
279
|
+
} catch (error) {
|
|
280
|
+
logError(`Failed to execute search: ${error.message}`);
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Main test runner
|
|
287
|
+
*/
|
|
288
|
+
async function runTests() {
|
|
289
|
+
log('='.repeat(60), colors.blue);
|
|
290
|
+
log('MCP Server Multiple Roots Testing', colors.blue);
|
|
291
|
+
log('='.repeat(60), colors.blue);
|
|
292
|
+
|
|
293
|
+
logInfo(`Server path: ${SERVER_PATH}`);
|
|
294
|
+
|
|
295
|
+
// Define test roots (multiple Git repositories)
|
|
296
|
+
const testRoots = [
|
|
297
|
+
{
|
|
298
|
+
uri: 'file:///Users/potomushto/Projects/temp/embark-mcp',
|
|
299
|
+
name: 'Embark MCP',
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
uri: 'file:///tmp/test-repo-1',
|
|
303
|
+
name: 'Test Repo 1',
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
uri: 'file:///tmp/test-repo-2',
|
|
307
|
+
name: 'Test Repo 2',
|
|
308
|
+
},
|
|
309
|
+
];
|
|
310
|
+
|
|
311
|
+
// Spawn the server
|
|
312
|
+
log('\n--- Starting MCP server ---', colors.yellow);
|
|
313
|
+
const server = spawn('node', [SERVER_PATH], {
|
|
314
|
+
env: {
|
|
315
|
+
...process.env,
|
|
316
|
+
// Don't set REPOSITORY_GIT_REMOTE_URL to test roots discovery
|
|
317
|
+
},
|
|
318
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Set up handler for incoming roots/list requests from the server
|
|
322
|
+
let responseBuffer = '';
|
|
323
|
+
server.stdout.on('data', (data) => {
|
|
324
|
+
responseBuffer += data.toString();
|
|
325
|
+
const lines = responseBuffer.split('\n');
|
|
326
|
+
responseBuffer = lines.pop(); // Keep incomplete line in buffer
|
|
327
|
+
|
|
328
|
+
for (const line of lines) {
|
|
329
|
+
if (line.trim()) {
|
|
330
|
+
try {
|
|
331
|
+
const message = JSON.parse(line);
|
|
332
|
+
|
|
333
|
+
// Handle roots/list request from server
|
|
334
|
+
if (message.method === 'roots/list' && message.id !== undefined) {
|
|
335
|
+
logDebug(`← Server requested roots/list (id: ${message.id})`);
|
|
336
|
+
const response = {
|
|
337
|
+
jsonrpc: '2.0',
|
|
338
|
+
id: message.id,
|
|
339
|
+
result: {
|
|
340
|
+
roots: testRoots
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
const responseStr = JSON.stringify(response) + '\n';
|
|
344
|
+
logDebug(`→ Responding with roots: ${responseStr.trim()}`);
|
|
345
|
+
server.stdin.write(responseStr);
|
|
346
|
+
}
|
|
347
|
+
} catch (e) {
|
|
348
|
+
// Not valid JSON, ignore
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// Log stderr for debugging
|
|
355
|
+
server.stderr.on('data', (data) => {
|
|
356
|
+
logDebug(`[stderr] ${data.toString().trim()}`);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
server.on('error', (error) => {
|
|
360
|
+
logError(`Failed to start server: ${error.message}`);
|
|
361
|
+
process.exit(1);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// Wait a bit for server to start
|
|
365
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
366
|
+
|
|
367
|
+
let allTestsPassed = true;
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
// Test 1: Initialize with roots capability
|
|
371
|
+
const initSuccess = await testInitializeWithRoots(server, testRoots);
|
|
372
|
+
if (!initSuccess) {
|
|
373
|
+
allTestsPassed = false;
|
|
374
|
+
log('\n❌ Initialization test failed', colors.red);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Test 2: Send roots notification
|
|
378
|
+
if (initSuccess) {
|
|
379
|
+
const rootsSuccess = await sendRootsNotification(server, testRoots);
|
|
380
|
+
if (!rootsSuccess) {
|
|
381
|
+
allTestsPassed = false;
|
|
382
|
+
log('\n❌ Roots notification test failed', colors.red);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Test 3: List tools
|
|
387
|
+
if (initSuccess) {
|
|
388
|
+
const toolsSuccess = await testListTools(server);
|
|
389
|
+
if (!toolsSuccess) {
|
|
390
|
+
allTestsPassed = false;
|
|
391
|
+
log('\n❌ Tools list test failed', colors.red);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Test 4: Execute semantic search to verify multi-repo functionality
|
|
396
|
+
if (initSuccess) {
|
|
397
|
+
const searchSuccess = await testSemanticSearch(server);
|
|
398
|
+
if (!searchSuccess) {
|
|
399
|
+
allTestsPassed = false;
|
|
400
|
+
log('\n❌ Semantic search test failed', colors.red);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Summary
|
|
405
|
+
log('\n' + '='.repeat(60), colors.blue);
|
|
406
|
+
if (allTestsPassed) {
|
|
407
|
+
log('✓ All tests passed!', colors.green);
|
|
408
|
+
log('\nTest results:', colors.yellow);
|
|
409
|
+
log('✓ Server initialized with multiple roots capability', colors.green);
|
|
410
|
+
log('✓ Roots notification sent successfully', colors.green);
|
|
411
|
+
log('✓ Tools listed successfully', colors.green);
|
|
412
|
+
log('✓ Semantic search executed and verified', colors.green);
|
|
413
|
+
log('\nNote: Check logs for details on repository discovery', colors.yellow);
|
|
414
|
+
log('='.repeat(60), colors.blue);
|
|
415
|
+
server.kill();
|
|
416
|
+
process.exit(0);
|
|
417
|
+
} else {
|
|
418
|
+
log('✗ Some tests failed', colors.red);
|
|
419
|
+
log('='.repeat(60), colors.blue);
|
|
420
|
+
server.kill();
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
} catch (error) {
|
|
424
|
+
logError(`Test execution failed: ${error.message}`);
|
|
425
|
+
console.error(error);
|
|
426
|
+
server.kill();
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Run tests
|
|
432
|
+
runTests().catch((error) => {
|
|
433
|
+
logError(`Fatal error: ${error.message}`);
|
|
434
|
+
console.error(error);
|
|
435
|
+
process.exit(1);
|
|
436
|
+
});
|