cyclecad 0.2.2 → 0.2.3

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 (69) hide show
  1. package/API-BUILD-MANIFEST.txt +339 -0
  2. package/API-SERVER.md +535 -0
  3. package/Architecture-Deck.pptx +0 -0
  4. package/CLAUDE.md +172 -11
  5. package/CLI-BUILD-SUMMARY.md +504 -0
  6. package/CLI-INDEX.md +356 -0
  7. package/CLI-README.md +466 -0
  8. package/COLLABORATION-INTEGRATION-GUIDE.md +325 -0
  9. package/CONNECTED_FABS_GUIDE.md +612 -0
  10. package/CONNECTED_FABS_README.md +310 -0
  11. package/DELIVERABLES.md +343 -0
  12. package/DFM-ANALYZER-INTEGRATION.md +368 -0
  13. package/DFM-QUICK-START.js +253 -0
  14. package/Dockerfile +69 -0
  15. package/IMPLEMENTATION.md +327 -0
  16. package/LICENSE +31 -0
  17. package/MARKETPLACE_QUICK_REFERENCE.txt +294 -0
  18. package/MCP-INDEX.md +264 -0
  19. package/QUICKSTART-API.md +388 -0
  20. package/QUICKSTART-CLI.md +211 -0
  21. package/QUICKSTART-MCP.md +196 -0
  22. package/README-MCP.md +208 -0
  23. package/TEST-TOKEN-ENGINE.md +319 -0
  24. package/TOKEN-ENGINE-SUMMARY.md +266 -0
  25. package/TOKENS-README.md +263 -0
  26. package/TOOLS-REFERENCE.md +254 -0
  27. package/app/index.html +168 -3
  28. package/app/js/TOKEN-INTEGRATION.md +391 -0
  29. package/app/js/agent-api.js +3 -3
  30. package/app/js/ai-copilot.js +1435 -0
  31. package/app/js/cam-pipeline.js +840 -0
  32. package/app/js/collaboration-ui.js +995 -0
  33. package/app/js/collaboration.js +1116 -0
  34. package/app/js/connected-fabs-example.js +404 -0
  35. package/app/js/connected-fabs.js +1449 -0
  36. package/app/js/dfm-analyzer.js +1760 -0
  37. package/app/js/marketplace.js +1994 -0
  38. package/app/js/material-library.js +2115 -0
  39. package/app/js/token-dashboard.js +563 -0
  40. package/app/js/token-engine.js +743 -0
  41. package/app/test-agent.html +1801 -0
  42. package/bin/cyclecad-cli.js +662 -0
  43. package/bin/cyclecad-mcp +2 -0
  44. package/bin/server.js +242 -0
  45. package/cycleCAD-Architecture.pptx +0 -0
  46. package/cycleCAD-Investor-Deck.pptx +0 -0
  47. package/demo-mcp.sh +60 -0
  48. package/docs/API-SERVER-SUMMARY.md +375 -0
  49. package/docs/API-SERVER.md +667 -0
  50. package/docs/CAM-EXAMPLES.md +344 -0
  51. package/docs/CAM-INTEGRATION.md +612 -0
  52. package/docs/CAM-QUICK-REFERENCE.md +199 -0
  53. package/docs/CLI-INTEGRATION.md +510 -0
  54. package/docs/CLI.md +872 -0
  55. package/docs/MARKETPLACE-API-SCHEMA.json +564 -0
  56. package/docs/MARKETPLACE-INTEGRATION.md +467 -0
  57. package/docs/MARKETPLACE-SETUP.html +439 -0
  58. package/docs/MCP-SERVER.md +403 -0
  59. package/examples/api-client-example.js +488 -0
  60. package/examples/api-client-example.py +359 -0
  61. package/examples/batch-manufacturing.txt +28 -0
  62. package/examples/batch-simple.txt +26 -0
  63. package/model-marketplace.html +1273 -0
  64. package/package.json +14 -3
  65. package/server/api-server.js +1120 -0
  66. package/server/mcp-server.js +1161 -0
  67. package/test-api-server.js +432 -0
  68. package/test-mcp.js +198 -0
  69. package/~$cycleCAD-Investor-Deck.pptx +0 -0
@@ -0,0 +1,432 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Test suite for cycleCAD API Server
5
+ *
6
+ * Usage:
7
+ * npm run test:api
8
+ * node test-api-server.js
9
+ *
10
+ * Verifies all API endpoints and core functionality without external dependencies.
11
+ */
12
+
13
+ const http = require('http');
14
+ const crypto = require('crypto');
15
+
16
+ // ============================================================================
17
+ // TEST FRAMEWORK
18
+ // ============================================================================
19
+
20
+ const colors = {
21
+ reset: '\x1b[0m',
22
+ green: '\x1b[32m',
23
+ red: '\x1b[31m',
24
+ yellow: '\x1b[33m',
25
+ blue: '\x1b[36m',
26
+ dim: '\x1b[2m'
27
+ };
28
+
29
+ let passCount = 0;
30
+ let failCount = 0;
31
+
32
+ function assert(condition, message) {
33
+ if (!condition) {
34
+ console.log(`${colors.red}✗ ${message}${colors.reset}`);
35
+ failCount++;
36
+ throw new Error(`Assertion failed: ${message}`);
37
+ }
38
+ console.log(`${colors.green}✓${colors.reset} ${message}`);
39
+ passCount++;
40
+ }
41
+
42
+ function section(title) {
43
+ console.log(`\n${colors.blue}${title}${colors.reset}`);
44
+ console.log(colors.dim + '='.repeat(60) + colors.reset);
45
+ }
46
+
47
+ // ============================================================================
48
+ // HTTP CLIENT
49
+ // ============================================================================
50
+
51
+ function request(method, path, data = null) {
52
+ return new Promise((resolve, reject) => {
53
+ const options = {
54
+ hostname: 'localhost',
55
+ port: 3000,
56
+ path,
57
+ method,
58
+ headers: {
59
+ 'Content-Type': 'application/json'
60
+ }
61
+ };
62
+
63
+ const req = http.request(options, (res) => {
64
+ let body = '';
65
+ res.on('data', chunk => body += chunk);
66
+ res.on('end', () => {
67
+ try {
68
+ const json = JSON.parse(body);
69
+ resolve({ status: res.statusCode, body: json, headers: res.headers });
70
+ } catch (e) {
71
+ resolve({ status: res.statusCode, body, headers: res.headers });
72
+ }
73
+ });
74
+ });
75
+
76
+ req.on('error', reject);
77
+
78
+ if (data) req.write(JSON.stringify(data));
79
+ req.end();
80
+ });
81
+ }
82
+
83
+ // ============================================================================
84
+ // TESTS
85
+ // ============================================================================
86
+
87
+ async function testHealth() {
88
+ section('1. Health Check');
89
+ const { status, body } = await request('GET', '/api/health');
90
+ assert(status === 200, 'Health endpoint returns 200');
91
+ assert(body.ok === true || body.status === 'ok', 'Server status is OK');
92
+ assert(body.version, 'Version is present');
93
+ assert(body.sessionId, 'Session ID is present');
94
+ }
95
+
96
+ async function testSchema() {
97
+ section('2. API Schema');
98
+ const { status, body } = await request('GET', '/api/schema');
99
+ assert(status === 200, 'Schema endpoint returns 200');
100
+ assert(body.version, 'Schema has version');
101
+ assert(body.namespaces, 'Schema has namespaces');
102
+ assert(Object.keys(body.namespaces).length > 0, 'Schema has at least one namespace');
103
+ assert(body.totalCommands > 0, 'Schema lists commands');
104
+ console.log(` ${colors.dim}(${body.totalCommands} total commands)${colors.reset}`);
105
+ }
106
+
107
+ async function testExecuteSingleCommand() {
108
+ section('3. Execute Single Command');
109
+
110
+ // Valid command
111
+ let { status, body } = await request('POST', '/api/execute', {
112
+ method: 'meta.ping',
113
+ params: {}
114
+ });
115
+ assert(status === 200, 'Execute endpoint returns 200');
116
+ assert(body.ok === true, 'Ping command succeeds');
117
+ assert(body.result.status === 'pong', 'Ping returns pong');
118
+ assert(body.elapsed >= 0, 'Elapsed time is tracked');
119
+
120
+ // Invalid method
121
+ ({ status, body } = await request('POST', '/api/execute', {
122
+ method: 'invalid.method',
123
+ params: {}
124
+ }));
125
+ assert(status === 400, 'Invalid method returns 400');
126
+ assert(body.ok === false, 'Invalid method fails gracefully');
127
+ assert(body.error, 'Error message is provided');
128
+
129
+ // Missing method
130
+ ({ status, body } = await request('POST', '/api/execute', {
131
+ params: {}
132
+ }));
133
+ assert(status === 400, 'Missing method returns 400');
134
+ assert(body.error.includes('method'), 'Error mentions missing method');
135
+ }
136
+
137
+ async function testBatchCommands() {
138
+ section('4. Batch Commands');
139
+ const { status, body } = await request('POST', '/api/batch', {
140
+ commands: [
141
+ { method: 'meta.ping', params: {} },
142
+ { method: 'query.materials', params: {} },
143
+ { method: 'meta.version', params: {} }
144
+ ]
145
+ });
146
+ assert(status === 200, 'Batch endpoint returns 200');
147
+ assert(body.ok === true, 'Batch succeeds');
148
+ assert(body.results.length === 3, 'All 3 commands executed');
149
+ assert(body.executed === 3, 'Executed count is correct');
150
+ assert(body.total === 3, 'Total count is correct');
151
+ assert(body.elapsed >= 0, 'Total elapsed time is tracked');
152
+ }
153
+
154
+ async function testModelManagement() {
155
+ section('5. Model Management');
156
+
157
+ // Get models (empty at start)
158
+ let { status, body } = await request('GET', '/api/models');
159
+ assert(status === 200, 'Get models returns 200');
160
+ assert(body.ok === true, 'Models endpoint succeeds');
161
+ assert(Array.isArray(body.models), 'Models is an array');
162
+ const initialCount = body.count;
163
+
164
+ // Add component
165
+ ({ status, body } = await request('POST', '/api/execute', {
166
+ method: 'assembly.addComponent',
167
+ params: { name: 'TestComponent', position: [0, 0, 0] }
168
+ }));
169
+ assert(status === 200, 'Add component returns 200');
170
+ assert(body.result.id, 'Component has ID');
171
+ assert(body.result.name === 'TestComponent', 'Component name is correct');
172
+ const componentId = body.result.id;
173
+
174
+ // List models
175
+ ({ status, body } = await request('GET', '/api/models'));
176
+ assert(body.count > initialCount, 'Model count increased');
177
+
178
+ // Get specific model
179
+ ({ status, body } = await request('GET', `/api/models/${componentId}`));
180
+ assert(status === 200, 'Get specific model returns 200');
181
+ assert(body.model.id === componentId, 'Model ID matches');
182
+
183
+ // Delete model
184
+ ({ status, body } = await request('DELETE', `/api/models/${componentId}`));
185
+ assert(status === 200, 'Delete model returns 200');
186
+ assert(body.ok === true, 'Delete succeeds');
187
+
188
+ // Verify deletion
189
+ ({ status, body } = await request('GET', `/api/models/${componentId}`));
190
+ assert(status === 404, 'Deleted model returns 404');
191
+ }
192
+
193
+ async function testHistory() {
194
+ section('6. Command History');
195
+
196
+ // Execute some commands first
197
+ await request('POST', '/api/execute', { method: 'meta.ping', params: {} });
198
+ await request('POST', '/api/execute', { method: 'query.materials', params: {} });
199
+
200
+ // Get history
201
+ const { status, body } = await request('GET', '/api/history?count=10');
202
+ assert(status === 200, 'History endpoint returns 200');
203
+ assert(body.sessionId, 'History has session ID');
204
+ assert(body.total >= 2, 'History records previous commands');
205
+ assert(Array.isArray(body.recent), 'Recent is an array');
206
+ }
207
+
208
+ async function testRateLimiting() {
209
+ section('7. Rate Limiting');
210
+
211
+ // Check rate limit headers
212
+ const { headers } = await request('GET', '/api/health');
213
+ assert(headers['ratelimit-limit'], 'Rate limit header present');
214
+ assert(headers['ratelimit-remaining'], 'Rate limit remaining header present');
215
+ assert(headers['ratelimit-reset'], 'Rate limit reset header present');
216
+ const limit = parseInt(headers['ratelimit-limit']);
217
+ const remaining = parseInt(headers['ratelimit-remaining']);
218
+ assert(remaining <= limit, 'Remaining is less than limit');
219
+ }
220
+
221
+ async function testCORS() {
222
+ section('8. CORS Headers');
223
+
224
+ const { headers } = await request('GET', '/api/health');
225
+ assert(headers['access-control-allow-origin'] === '*', 'CORS origin header present');
226
+ assert(headers['access-control-allow-methods'], 'CORS methods header present');
227
+ assert(headers['access-control-allow-headers'], 'CORS headers header present');
228
+ }
229
+
230
+ async function testCOOPCOEP() {
231
+ section('9. COOP/COEP Headers');
232
+
233
+ const { headers } = await request('GET', '/api/health');
234
+ assert(headers['cross-origin-opener-policy'], 'COOP header present');
235
+ assert(headers['cross-origin-embedder-policy'], 'COEP header present');
236
+ }
237
+
238
+ async function testSketchCommands() {
239
+ section('10. Sketch Commands');
240
+
241
+ // Start sketch
242
+ let { status, body } = await request('POST', '/api/execute', {
243
+ method: 'sketch.start',
244
+ params: { plane: 'XY' }
245
+ });
246
+ assert(status === 200, 'Sketch start returns 200');
247
+ assert(body.result.status === 'active', 'Sketch is active');
248
+
249
+ // Draw circle
250
+ ({ status, body } = await request('POST', '/api/execute', {
251
+ method: 'sketch.circle',
252
+ params: { cx: 0, cy: 0, radius: 25 }
253
+ }));
254
+ assert(status === 200, 'Circle command returns 200');
255
+ assert(body.result.radius === 25, 'Circle radius is correct');
256
+
257
+ // Draw line
258
+ ({ status, body } = await request('POST', '/api/execute', {
259
+ method: 'sketch.line',
260
+ params: { x1: 0, y1: 0, x2: 100, y2: 50 }
261
+ }));
262
+ assert(status === 200, 'Line command returns 200');
263
+ assert(body.result.length > 0, 'Line length calculated');
264
+
265
+ // End sketch
266
+ ({ status, body } = await request('POST', '/api/execute', {
267
+ method: 'sketch.end',
268
+ params: {}
269
+ }));
270
+ assert(status === 200, 'Sketch end returns 200');
271
+ assert(body.result.status === 'complete', 'Sketch completed');
272
+ }
273
+
274
+ async function testOperationCommands() {
275
+ section('11. Operation Commands');
276
+
277
+ // Extrude
278
+ let { status, body } = await request('POST', '/api/execute', {
279
+ method: 'ops.extrude',
280
+ params: { height: 50, material: 'steel' }
281
+ });
282
+ assert(status === 200, 'Extrude returns 200');
283
+ assert(body.result.height === 50, 'Extrude height is correct');
284
+
285
+ // Fillet
286
+ ({ status, body } = await request('POST', '/api/execute', {
287
+ method: 'ops.fillet',
288
+ params: { target: 'extrude_1', radius: 5 }
289
+ }));
290
+ assert(status === 200, 'Fillet returns 200');
291
+ assert(body.result.radius === 5, 'Fillet radius is correct');
292
+
293
+ // Chamfer
294
+ ({ status, body } = await request('POST', '/api/execute', {
295
+ method: 'ops.chamfer',
296
+ params: { target: 'extrude_1', distance: 2 }
297
+ }));
298
+ assert(status === 200, 'Chamfer returns 200');
299
+ assert(body.result.distance === 2, 'Chamfer distance is correct');
300
+ }
301
+
302
+ async function testViewCommands() {
303
+ section('12. View Commands');
304
+
305
+ const views = ['isometric', 'top', 'front'];
306
+ for (const view of views) {
307
+ const { status, body } = await request('POST', '/api/execute', {
308
+ method: 'view.set',
309
+ params: { view }
310
+ });
311
+ assert(status === 200, `Setting view to ${view} returns 200`);
312
+ assert(body.result.view === view, `View set to ${view}`);
313
+ }
314
+ }
315
+
316
+ async function testValidationCommands() {
317
+ section('13. Validation Commands');
318
+
319
+ // Mass calculation
320
+ let { status, body } = await request('POST', '/api/execute', {
321
+ method: 'validate.mass',
322
+ params: { target: 'test', material: 'steel' }
323
+ });
324
+ assert(status === 200, 'Mass calculation returns 200');
325
+ assert(typeof body.result.mass === 'number', 'Mass is a number');
326
+
327
+ // Cost estimation
328
+ ({ status, body } = await request('POST', '/api/execute', {
329
+ method: 'validate.cost',
330
+ params: { target: 'test', process: 'FDM', material: 'PLA' }
331
+ }));
332
+ assert(status === 200, 'Cost estimation returns 200');
333
+ assert(body.result.estimatedCost >= 0, 'Cost is non-negative');
334
+ }
335
+
336
+ async function testQueryCommands() {
337
+ section('14. Query Commands');
338
+
339
+ // Query materials
340
+ let { status, body } = await request('POST', '/api/execute', {
341
+ method: 'query.materials',
342
+ params: {}
343
+ });
344
+ assert(status === 200, 'Query materials returns 200');
345
+ assert(Array.isArray(body.result.materials), 'Materials is an array');
346
+ assert(body.result.materials.length > 0, 'Materials list not empty');
347
+
348
+ // Query features
349
+ ({ status, body } = await request('POST', '/api/execute', {
350
+ method: 'query.features',
351
+ params: {}
352
+ }));
353
+ assert(status === 200, 'Query features returns 200');
354
+ assert(Array.isArray(body.result.features), 'Features is an array');
355
+ }
356
+
357
+ async function testErrorHandling() {
358
+ section('15. Error Handling');
359
+
360
+ // Invalid JSON
361
+ let { status } = await request('POST', '/api/execute', 'invalid json');
362
+ assert(status === 400, 'Invalid JSON returns 400');
363
+
364
+ // Method typo (should suggest correction)
365
+ const { body } = await request('POST', '/api/execute', {
366
+ method: 'sketch.circl', // typo
367
+ params: {}
368
+ });
369
+ assert(body.error.includes('Did you mean'), 'Server suggests correction for typo');
370
+
371
+ // Unknown endpoint
372
+ const { status: notFoundStatus } = await request('GET', '/api/unknown');
373
+ assert(notFoundStatus === 404, 'Unknown endpoint returns 404');
374
+ }
375
+
376
+ // ============================================================================
377
+ // RUNNER
378
+ // ============================================================================
379
+
380
+ async function main() {
381
+ console.log('\n' + colors.blue + '█'.repeat(60));
382
+ console.log('█ cycleCAD API Server — Test Suite');
383
+ console.log('█'.repeat(60) + colors.reset);
384
+
385
+ // Check if server is running
386
+ console.log('\n' + colors.dim + 'Checking if server is running on localhost:3000...');
387
+ try {
388
+ await request('GET', '/api/health');
389
+ console.log(colors.reset + '✓ Server is running\n');
390
+ } catch (e) {
391
+ console.log(colors.red + `✗ Server is not running!`);
392
+ console.log(`\nStart the server with: npm run server\n` + colors.reset);
393
+ process.exit(1);
394
+ }
395
+
396
+ // Run all tests
397
+ try {
398
+ await testHealth();
399
+ await testSchema();
400
+ await testExecuteSingleCommand();
401
+ await testBatchCommands();
402
+ await testModelManagement();
403
+ await testHistory();
404
+ await testRateLimiting();
405
+ await testCORS();
406
+ await testCOOPCOEP();
407
+ await testSketchCommands();
408
+ await testOperationCommands();
409
+ await testViewCommands();
410
+ await testValidationCommands();
411
+ await testQueryCommands();
412
+ await testErrorHandling();
413
+ } catch (e) {
414
+ // Error already printed by assert()
415
+ }
416
+
417
+ // Summary
418
+ const total = passCount + failCount;
419
+ const percentage = total > 0 ? Math.round((passCount / total) * 100) : 0;
420
+
421
+ console.log('\n' + colors.blue + '='.repeat(60));
422
+ console.log(`${colors.green}✓ ${passCount} passed ${colors.reset}| ${colors.red}✗ ${failCount} failed${colors.reset}`);
423
+ console.log(`${percentage}% success rate (${passCount}/${total} tests)`);
424
+ console.log(colors.blue + '='.repeat(60) + colors.reset + '\n');
425
+
426
+ process.exit(failCount > 0 ? 1 : 0);
427
+ }
428
+
429
+ main().catch(e => {
430
+ console.log(`${colors.red}✗ Test suite error: ${e.message}${colors.reset}`);
431
+ process.exit(1);
432
+ });
package/test-mcp.js ADDED
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Test utility for cyclecad-mcp server
5
+ * Tests the MCP protocol without needing Claude API
6
+ *
7
+ * Usage:
8
+ * node test-mcp.js --help
9
+ * node test-mcp.js --list-tools
10
+ * node test-mcp.js --test-initialize
11
+ * node test-mcp.js --test-tool sketch_rect --args '{"width": 50, "height": 30}'
12
+ */
13
+
14
+ const { spawn } = require('child_process');
15
+ const path = require('path');
16
+
17
+ const args = process.argv.slice(2);
18
+
19
+ if (args.includes('--help') || args.includes('-h')) {
20
+ console.log(`
21
+ Test utility for cyclecad-mcp server
22
+
23
+ Usage: node test-mcp.js [OPTIONS]
24
+
25
+ OPTIONS:
26
+ --help Show this help message
27
+ --list-tools List all available tools
28
+ --test-initialize Test server initialization
29
+ --test-tool NAME Test a specific tool
30
+ --args JSON Arguments for the tool (as JSON)
31
+ --debug Show debug output
32
+
33
+ EXAMPLES:
34
+ node test-mcp.js --list-tools
35
+ node test-mcp.js --test-initialize
36
+ node test-mcp.js --test-tool sketch_rect --args '{"width": 50, "height": 30}'
37
+ node test-mcp.js --test-tool ops_primitive --args '{"shape": "sphere", "radius": 10}'
38
+ node test-mcp.js --test-tool query_materials
39
+ node test-mcp.js --test-tool validate_dimensions --args '{"target": "extrude_1"}'
40
+
41
+ `);
42
+ process.exit(0);
43
+ }
44
+
45
+ const debug = args.includes('--debug');
46
+
47
+ async function runTest() {
48
+ const mcpProcess = spawn('node', [path.join(__dirname, 'server', 'mcp-server.js')], {
49
+ stdio: ['pipe', 'pipe', 'pipe']
50
+ });
51
+
52
+ let output = '';
53
+ let errorOutput = '';
54
+
55
+ mcpProcess.stdout.on('data', (data) => {
56
+ output += data.toString();
57
+ });
58
+
59
+ mcpProcess.stderr.on('data', (data) => {
60
+ errorOutput += data.toString();
61
+ if (debug) console.error('[MCP]', data.toString().trim());
62
+ });
63
+
64
+ mcpProcess.on('error', (err) => {
65
+ console.error('Failed to start MCP server:', err);
66
+ process.exit(1);
67
+ });
68
+
69
+ // Wait a bit for server to be ready
70
+ await new Promise(resolve => setTimeout(resolve, 200));
71
+
72
+ async function send(req) {
73
+ return new Promise((resolve, reject) => {
74
+ const timeout = setTimeout(() => {
75
+ mcpProcess.kill();
76
+ reject(new Error('No response from MCP server (timeout)'));
77
+ }, 5000);
78
+
79
+ const lines = [];
80
+ const dataHandler = (data) => {
81
+ const text = data.toString();
82
+ lines.push(text);
83
+ try {
84
+ const combined = lines.join('');
85
+ if (combined.includes('{')) {
86
+ const json = JSON.parse(combined.trim());
87
+ clearTimeout(timeout);
88
+ mcpProcess.stdout.removeListener('data', dataHandler);
89
+ resolve(json);
90
+ }
91
+ } catch (e) {
92
+ // Not complete JSON yet
93
+ }
94
+ };
95
+
96
+ mcpProcess.stdout.on('data', dataHandler);
97
+ mcpProcess.stdin.write(JSON.stringify(req) + '\n');
98
+ });
99
+ }
100
+
101
+ try {
102
+ // Initialize
103
+ if (debug) console.log('Sending initialize request...');
104
+ const initResp = await send({ jsonrpc: '2.0', id: 1, method: 'initialize' });
105
+ if (debug) console.log('Initialize response:', JSON.stringify(initResp, null, 2));
106
+
107
+ if (args.includes('--list-tools')) {
108
+ // Get tools list
109
+ if (debug) console.log('\nSending tools/list request...');
110
+ const listResp = await send({ jsonrpc: '2.0', id: 2, method: 'tools/list' });
111
+ const tools = listResp.result.tools;
112
+ console.log(`\nAvailable Tools (${tools.length}):`);
113
+ console.log('=' .repeat(70));
114
+
115
+ // Group by namespace
116
+ const byNamespace = {};
117
+ tools.forEach(t => {
118
+ const ns = t.name.split('_')[0];
119
+ if (!byNamespace[ns]) byNamespace[ns] = [];
120
+ byNamespace[ns].push(t.name);
121
+ });
122
+
123
+ Object.keys(byNamespace).sort().forEach(ns => {
124
+ console.log(`\n${ns.toUpperCase()} (${byNamespace[ns].length} tools)`);
125
+ byNamespace[ns].forEach(name => {
126
+ const tool = tools.find(t => t.name === name);
127
+ const desc = tool.description || 'No description';
128
+ console.log(` • ${name}`);
129
+ console.log(` ${desc}`);
130
+ });
131
+ });
132
+ console.log('\n');
133
+ }
134
+
135
+ if (args.includes('--test-initialize')) {
136
+ console.log('\nInitialization successful!');
137
+ console.log('Server info:', initResp.result.serverInfo);
138
+ }
139
+
140
+ if (args.includes('--test-tool')) {
141
+ const toolIdx = args.indexOf('--test-tool');
142
+ const toolName = args[toolIdx + 1];
143
+ if (!toolName) {
144
+ console.error('Missing tool name after --test-tool');
145
+ process.exit(1);
146
+ }
147
+
148
+ const argsIdx = args.indexOf('--args');
149
+ let toolArgs = {};
150
+ if (argsIdx !== -1 && args[argsIdx + 1]) {
151
+ try {
152
+ toolArgs = JSON.parse(args[argsIdx + 1]);
153
+ } catch (e) {
154
+ console.error('Invalid JSON in --args:', e.message);
155
+ process.exit(1);
156
+ }
157
+ }
158
+
159
+ console.log(`\nTesting tool: ${toolName}`);
160
+ if (Object.keys(toolArgs).length > 0) {
161
+ console.log('Arguments:', JSON.stringify(toolArgs, null, 2));
162
+ } else {
163
+ console.log('Arguments: (none)');
164
+ }
165
+ console.log('');
166
+
167
+ const callResp = await send({
168
+ jsonrpc: '2.0',
169
+ id: 3,
170
+ method: 'tools/call',
171
+ params: {
172
+ name: toolName,
173
+ arguments: toolArgs
174
+ }
175
+ });
176
+
177
+ if (callResp.error) {
178
+ console.error('Tool call failed:', callResp.error.message);
179
+ } else {
180
+ console.log('Tool call successful!');
181
+ console.log('Result:', callResp.result.content[0].text);
182
+ }
183
+ }
184
+
185
+ } catch (e) {
186
+ console.error('Test failed:', e.message);
187
+ process.exit(1);
188
+ } finally {
189
+ mcpProcess.kill();
190
+ }
191
+ }
192
+
193
+ runTest().then(() => {
194
+ process.exit(0);
195
+ }).catch(e => {
196
+ console.error('Unexpected error:', e.message);
197
+ process.exit(1);
198
+ });
Binary file