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,1161 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * cycleCAD MCP Server
5
+ *
6
+ * Implements the Model Context Protocol (MCP) for the cycleCAD Agent API.
7
+ * Exposes all 55+ commands from the Agent API as MCP tools.
8
+ *
9
+ * Protocol: JSON-RPC 2.0 over stdio
10
+ * Methods: initialize, tools/list, tools/call
11
+ *
12
+ * Usage:
13
+ * npx cyclecad-mcp [--help] [--port 3000] [--ws-url ws://localhost:3000/api/ws]
14
+ */
15
+
16
+ const readline = require('readline');
17
+ const http = require('http');
18
+
19
+ // Try to load WebSocket, but make it optional
20
+ let WebSocket = null;
21
+ try {
22
+ WebSocket = require('ws');
23
+ } catch (e) {
24
+ if (config && config.debug) {
25
+ console.error('[MCP] WebSocket module not available, using HTTP only');
26
+ }
27
+ }
28
+
29
+ // =============================================================================
30
+ // Configuration
31
+ // =============================================================================
32
+
33
+ const config = {
34
+ wsUrl: process.env.CYCLECAD_WS_URL || 'ws://localhost:3000/api/ws',
35
+ httpUrl: process.env.CYCLECAD_HTTP_URL || 'http://localhost:3000/api/execute',
36
+ timeout: 30000,
37
+ debug: process.env.DEBUG_MCP === '1'
38
+ };
39
+
40
+ // =============================================================================
41
+ // MCP Protocol Implementation
42
+ // =============================================================================
43
+
44
+ class MCPServer {
45
+ constructor() {
46
+ this.messageId = 0;
47
+ this.pendingRequests = new Map();
48
+ this.wsConnection = null;
49
+ this.isInitialized = false;
50
+ this.commandQueue = [];
51
+ this.sessionId = null;
52
+ }
53
+
54
+ /**
55
+ * Initialize the MCP server and optionally connect to cycleCAD
56
+ */
57
+ async initialize() {
58
+ this.isInitialized = true;
59
+ this.sessionId = this.generateId('session');
60
+
61
+ // Try to connect to cycleCAD WebSocket (non-blocking)
62
+ this.connectToWebSocket().catch(err => {
63
+ if (config.debug) console.error('[MCP] WebSocket connection failed:', err.message);
64
+ });
65
+
66
+ return {
67
+ protocolVersion: '2024-11-05',
68
+ capabilities: {
69
+ tools: {
70
+ listChanged: false
71
+ }
72
+ },
73
+ serverInfo: {
74
+ name: 'cyclecad-mcp',
75
+ version: '1.0.0'
76
+ }
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Connect to cycleCAD via WebSocket
82
+ */
83
+ async connectToWebSocket() {
84
+ if (!WebSocket) {
85
+ if (config.debug) console.error('[MCP] WebSocket not available, skipping connection');
86
+ return;
87
+ }
88
+
89
+ try {
90
+ this.wsConnection = new WebSocket(config.wsUrl);
91
+
92
+ this.wsConnection.on('open', () => {
93
+ if (config.debug) console.error('[MCP] WebSocket connected');
94
+ // Process queued commands
95
+ while (this.commandQueue.length > 0) {
96
+ const cmd = this.commandQueue.shift();
97
+ this.sendCommand(cmd.method, cmd.params, cmd.resolve, cmd.reject);
98
+ }
99
+ });
100
+
101
+ this.wsConnection.on('message', (data) => {
102
+ try {
103
+ const msg = JSON.parse(data);
104
+ if (msg.id && this.pendingRequests.has(msg.id)) {
105
+ const { resolve, reject, timeout } = this.pendingRequests.get(msg.id);
106
+ clearTimeout(timeout);
107
+ this.pendingRequests.delete(msg.id);
108
+ if (msg.error) {
109
+ reject(new Error(msg.error));
110
+ } else {
111
+ resolve(msg.result);
112
+ }
113
+ }
114
+ } catch (e) {
115
+ if (config.debug) console.error('[MCP] WebSocket message parse error:', e);
116
+ }
117
+ });
118
+
119
+ this.wsConnection.on('error', (err) => {
120
+ if (config.debug) console.error('[MCP] WebSocket error:', err.message);
121
+ });
122
+
123
+ this.wsConnection.on('close', () => {
124
+ if (config.debug) console.error('[MCP] WebSocket closed');
125
+ this.wsConnection = null;
126
+ });
127
+ } catch (e) {
128
+ if (config.debug) console.error('[MCP] WebSocket connection error:', e.message);
129
+ throw e;
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Execute a cycleCAD command via WebSocket or HTTP
135
+ */
136
+ async executeCommand(method, params = {}) {
137
+ const cmd = { method, params };
138
+
139
+ if (WebSocket && this.wsConnection && this.wsConnection.readyState === WebSocket.OPEN) {
140
+ // Use WebSocket
141
+ return this.sendCommandViaWS(cmd);
142
+ } else if (this.isInitialized && WebSocket) {
143
+ // Queue for when connected
144
+ return new Promise((resolve, reject) => {
145
+ this.commandQueue.push({ method, params, resolve, reject });
146
+ // Try HTTP as fallback immediately
147
+ this.sendCommandViaHTTP(cmd).then(resolve).catch(() => {
148
+ // Keep queued
149
+ });
150
+ });
151
+ } else {
152
+ // Use HTTP
153
+ return this.sendCommandViaHTTP(cmd);
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Send command via WebSocket
159
+ */
160
+ sendCommandViaWS(cmd) {
161
+ return new Promise((resolve, reject) => {
162
+ const id = ++this.messageId;
163
+ const timeout = setTimeout(() => {
164
+ this.pendingRequests.delete(id);
165
+ reject(new Error(`Command timeout: ${cmd.method}`));
166
+ }, config.timeout);
167
+
168
+ this.pendingRequests.set(id, { resolve, reject, timeout });
169
+
170
+ try {
171
+ this.wsConnection.send(JSON.stringify({ id, ...cmd }));
172
+ } catch (e) {
173
+ clearTimeout(timeout);
174
+ this.pendingRequests.delete(id);
175
+ reject(e);
176
+ }
177
+ });
178
+ }
179
+
180
+ /**
181
+ * Send command via HTTP POST
182
+ */
183
+ sendCommandViaHTTP(cmd) {
184
+ return new Promise((resolve, reject) => {
185
+ const payload = JSON.stringify(cmd);
186
+ const options = {
187
+ method: 'POST',
188
+ headers: {
189
+ 'Content-Type': 'application/json',
190
+ 'Content-Length': Buffer.byteLength(payload)
191
+ }
192
+ };
193
+
194
+ const req = http.request(config.httpUrl, options, (res) => {
195
+ let data = '';
196
+ res.on('data', chunk => data += chunk);
197
+ res.on('end', () => {
198
+ try {
199
+ const result = JSON.parse(data);
200
+ if (result.ok) {
201
+ resolve(result.result);
202
+ } else {
203
+ reject(new Error(result.error || 'Command failed'));
204
+ }
205
+ } catch (e) {
206
+ reject(e);
207
+ }
208
+ });
209
+ });
210
+
211
+ req.on('error', reject);
212
+ req.setTimeout(config.timeout, () => {
213
+ req.destroy();
214
+ reject(new Error(`HTTP timeout: ${cmd.method}`));
215
+ });
216
+
217
+ req.write(payload);
218
+ req.end();
219
+ });
220
+ }
221
+
222
+ /**
223
+ * Get all available tools (MCP tools/list)
224
+ */
225
+ getTools() {
226
+ return TOOL_DEFINITIONS;
227
+ }
228
+
229
+ /**
230
+ * Call a tool (MCP tools/call)
231
+ */
232
+ async callTool(name, args) {
233
+ const tool = TOOL_DEFINITIONS.find(t => t.name === name);
234
+ if (!tool) {
235
+ throw new Error(`Unknown tool: ${name}`);
236
+ }
237
+
238
+ // Extract command method from tool name
239
+ // Tool name format: "sketch_start" -> method: "sketch.start"
240
+ const method = name.replace(/_/g, '.');
241
+
242
+ try {
243
+ const result = await this.executeCommand(method, args);
244
+ return {
245
+ content: [
246
+ {
247
+ type: 'text',
248
+ text: JSON.stringify(result, null, 2)
249
+ }
250
+ ]
251
+ };
252
+ } catch (e) {
253
+ return {
254
+ content: [
255
+ {
256
+ type: 'text',
257
+ text: JSON.stringify({
258
+ ok: false,
259
+ error: e.message,
260
+ method,
261
+ args
262
+ }, null, 2)
263
+ }
264
+ ],
265
+ isError: true
266
+ };
267
+ }
268
+ }
269
+
270
+ generateId(prefix) {
271
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
272
+ }
273
+ }
274
+
275
+ // =============================================================================
276
+ // MCP Tool Definitions
277
+ // =============================================================================
278
+
279
+ /**
280
+ * Convert Agent API schema to MCP tool definitions
281
+ */
282
+ const TOOL_DEFINITIONS = [
283
+ // SKETCH tools
284
+ {
285
+ name: 'sketch_start',
286
+ description: 'Start sketch mode on XY, XZ, or YZ plane',
287
+ inputSchema: {
288
+ type: 'object',
289
+ properties: {
290
+ plane: { type: 'string', enum: ['XY', 'XZ', 'YZ'], description: 'Construction plane' }
291
+ },
292
+ required: []
293
+ }
294
+ },
295
+ {
296
+ name: 'sketch_end',
297
+ description: 'End sketch and return all entities drawn',
298
+ inputSchema: { type: 'object', properties: {} }
299
+ },
300
+ {
301
+ name: 'sketch_line',
302
+ description: 'Draw a line segment in the sketch',
303
+ inputSchema: {
304
+ type: 'object',
305
+ properties: {
306
+ x1: { type: 'number', description: 'Start X coordinate' },
307
+ y1: { type: 'number', description: 'Start Y coordinate' },
308
+ x2: { type: 'number', description: 'End X coordinate' },
309
+ y2: { type: 'number', description: 'End Y coordinate' }
310
+ },
311
+ required: ['x1', 'y1', 'x2', 'y2']
312
+ }
313
+ },
314
+ {
315
+ name: 'sketch_rect',
316
+ description: 'Draw a rectangle in the sketch',
317
+ inputSchema: {
318
+ type: 'object',
319
+ properties: {
320
+ x: { type: 'number', description: 'Origin X (default: 0)' },
321
+ y: { type: 'number', description: 'Origin Y (default: 0)' },
322
+ width: { type: 'number', description: 'Rectangle width' },
323
+ height: { type: 'number', description: 'Rectangle height' }
324
+ },
325
+ required: ['width', 'height']
326
+ }
327
+ },
328
+ {
329
+ name: 'sketch_circle',
330
+ description: 'Draw a circle in the sketch',
331
+ inputSchema: {
332
+ type: 'object',
333
+ properties: {
334
+ cx: { type: 'number', description: 'Center X (default: 0)' },
335
+ cy: { type: 'number', description: 'Center Y (default: 0)' },
336
+ radius: { type: 'number', description: 'Circle radius' }
337
+ },
338
+ required: ['radius']
339
+ }
340
+ },
341
+ {
342
+ name: 'sketch_arc',
343
+ description: 'Draw an arc in the sketch',
344
+ inputSchema: {
345
+ type: 'object',
346
+ properties: {
347
+ cx: { type: 'number', description: 'Center X (default: 0)' },
348
+ cy: { type: 'number', description: 'Center Y (default: 0)' },
349
+ radius: { type: 'number', description: 'Arc radius' },
350
+ startAngle: { type: 'number', description: 'Start angle in radians (default: 0)' },
351
+ endAngle: { type: 'number', description: 'End angle in radians (default: π)' }
352
+ },
353
+ required: ['radius']
354
+ }
355
+ },
356
+ {
357
+ name: 'sketch_clear',
358
+ description: 'Clear all sketch entities',
359
+ inputSchema: { type: 'object', properties: {} }
360
+ },
361
+ {
362
+ name: 'sketch_entities',
363
+ description: 'List all entities in the current sketch',
364
+ inputSchema: { type: 'object', properties: {} }
365
+ },
366
+
367
+ // OPERATIONS tools
368
+ {
369
+ name: 'ops_extrude',
370
+ description: 'Extrude sketch profile into 3D solid',
371
+ inputSchema: {
372
+ type: 'object',
373
+ properties: {
374
+ height: { type: 'number', description: 'Extrusion height' },
375
+ symmetric: { type: 'boolean', description: 'Extrude symmetrically' },
376
+ material: { type: 'string', description: 'Material name (steel, aluminum, etc)' }
377
+ },
378
+ required: ['height']
379
+ }
380
+ },
381
+ {
382
+ name: 'ops_revolve',
383
+ description: 'Revolve sketch profile around an axis',
384
+ inputSchema: {
385
+ type: 'object',
386
+ properties: {
387
+ axis: { type: 'string', enum: ['X', 'Y', 'Z'], description: 'Rotation axis' },
388
+ angle: { type: 'number', description: 'Revolution angle in degrees' },
389
+ material: { type: 'string', description: 'Material name' }
390
+ },
391
+ required: []
392
+ }
393
+ },
394
+ {
395
+ name: 'ops_primitive',
396
+ description: 'Create a primitive 3D shape (box, sphere, cylinder, etc)',
397
+ inputSchema: {
398
+ type: 'object',
399
+ properties: {
400
+ shape: { type: 'string', enum: ['box', 'sphere', 'cylinder', 'cone', 'torus', 'capsule'] },
401
+ width: { type: 'number', description: 'Width dimension' },
402
+ height: { type: 'number', description: 'Height dimension' },
403
+ depth: { type: 'number', description: 'Depth dimension' },
404
+ radius: { type: 'number', description: 'Radius' },
405
+ segments: { type: 'number', description: 'Tessellation segments' },
406
+ material: { type: 'string', description: 'Material name' }
407
+ },
408
+ required: ['shape']
409
+ }
410
+ },
411
+ {
412
+ name: 'ops_fillet',
413
+ description: 'Apply fillet radius to edges of a feature',
414
+ inputSchema: {
415
+ type: 'object',
416
+ properties: {
417
+ target: { type: 'string', description: 'Feature ID to fillet' },
418
+ radius: { type: 'number', description: 'Fillet radius' }
419
+ },
420
+ required: ['target']
421
+ }
422
+ },
423
+ {
424
+ name: 'ops_chamfer',
425
+ description: 'Apply chamfer to edges of a feature',
426
+ inputSchema: {
427
+ type: 'object',
428
+ properties: {
429
+ target: { type: 'string', description: 'Feature ID to chamfer' },
430
+ distance: { type: 'number', description: 'Chamfer distance' }
431
+ },
432
+ required: ['target']
433
+ }
434
+ },
435
+ {
436
+ name: 'ops_boolean',
437
+ description: 'Perform boolean operation (union, cut, intersect)',
438
+ inputSchema: {
439
+ type: 'object',
440
+ properties: {
441
+ operation: { type: 'string', enum: ['union', 'cut', 'intersect'] },
442
+ targetA: { type: 'string', description: 'First feature ID' },
443
+ targetB: { type: 'string', description: 'Second feature ID' }
444
+ },
445
+ required: ['operation', 'targetA', 'targetB']
446
+ }
447
+ },
448
+ {
449
+ name: 'ops_shell',
450
+ description: 'Create a hollow shell from a solid',
451
+ inputSchema: {
452
+ type: 'object',
453
+ properties: {
454
+ target: { type: 'string', description: 'Feature ID to shell' },
455
+ thickness: { type: 'number', description: 'Shell wall thickness' }
456
+ },
457
+ required: ['target']
458
+ }
459
+ },
460
+ {
461
+ name: 'ops_pattern',
462
+ description: 'Create array pattern of a feature',
463
+ inputSchema: {
464
+ type: 'object',
465
+ properties: {
466
+ target: { type: 'string', description: 'Feature ID to pattern' },
467
+ type: { type: 'string', enum: ['rect', 'circular'], description: 'Pattern type' },
468
+ count: { type: 'number', description: 'Number of copies' },
469
+ spacing: { type: 'number', description: 'Space between copies' }
470
+ },
471
+ required: ['target']
472
+ }
473
+ },
474
+ {
475
+ name: 'ops_material',
476
+ description: 'Change material of a feature',
477
+ inputSchema: {
478
+ type: 'object',
479
+ properties: {
480
+ target: { type: 'string', description: 'Feature ID' },
481
+ material: { type: 'string', description: 'Material name' }
482
+ },
483
+ required: ['target', 'material']
484
+ }
485
+ },
486
+ {
487
+ name: 'ops_sweep',
488
+ description: 'Sweep a profile along a path',
489
+ inputSchema: {
490
+ type: 'object',
491
+ properties: {
492
+ profile: { type: 'object', description: 'Profile to sweep' },
493
+ path: { type: 'object', description: 'Path to follow' },
494
+ twist: { type: 'number', description: 'Twist angle' },
495
+ scale: { type: 'number', description: 'Scale interpolation' }
496
+ },
497
+ required: ['profile', 'path']
498
+ }
499
+ },
500
+ {
501
+ name: 'ops_loft',
502
+ description: 'Loft between multiple profiles',
503
+ inputSchema: {
504
+ type: 'object',
505
+ properties: {
506
+ profiles: { type: 'array', description: 'Profiles to loft between' }
507
+ },
508
+ required: ['profiles']
509
+ }
510
+ },
511
+ {
512
+ name: 'ops_spring',
513
+ description: 'Generate a helical spring',
514
+ inputSchema: {
515
+ type: 'object',
516
+ properties: {
517
+ radius: { type: 'number', description: 'Spring radius' },
518
+ wireRadius: { type: 'number', description: 'Wire radius' },
519
+ height: { type: 'number', description: 'Spring height' },
520
+ turns: { type: 'number', description: 'Number of turns' },
521
+ material: { type: 'string', description: 'Material name' }
522
+ },
523
+ required: []
524
+ }
525
+ },
526
+ {
527
+ name: 'ops_thread',
528
+ description: 'Generate a screw thread',
529
+ inputSchema: {
530
+ type: 'object',
531
+ properties: {
532
+ outerRadius: { type: 'number', description: 'Outer radius' },
533
+ innerRadius: { type: 'number', description: 'Inner radius' },
534
+ pitch: { type: 'number', description: 'Thread pitch' },
535
+ length: { type: 'number', description: 'Thread length' },
536
+ material: { type: 'string', description: 'Material name' }
537
+ },
538
+ required: []
539
+ }
540
+ },
541
+ {
542
+ name: 'ops_bend',
543
+ description: 'Bend sheet metal',
544
+ inputSchema: {
545
+ type: 'object',
546
+ properties: {
547
+ target: { type: 'string', description: 'Feature ID to bend' },
548
+ angle: { type: 'number', description: 'Bend angle in degrees' },
549
+ radius: { type: 'number', description: 'Bend radius' }
550
+ },
551
+ required: ['target']
552
+ }
553
+ },
554
+
555
+ // TRANSFORM tools
556
+ {
557
+ name: 'transform_move',
558
+ description: 'Translate a feature by X, Y, Z offset',
559
+ inputSchema: {
560
+ type: 'object',
561
+ properties: {
562
+ target: { type: 'string', description: 'Feature ID' },
563
+ x: { type: 'number', description: 'X offset' },
564
+ y: { type: 'number', description: 'Y offset' },
565
+ z: { type: 'number', description: 'Z offset' }
566
+ },
567
+ required: ['target']
568
+ }
569
+ },
570
+ {
571
+ name: 'transform_rotate',
572
+ description: 'Rotate a feature around X, Y, Z axes',
573
+ inputSchema: {
574
+ type: 'object',
575
+ properties: {
576
+ target: { type: 'string', description: 'Feature ID' },
577
+ x: { type: 'number', description: 'Rotation around X axis (degrees)' },
578
+ y: { type: 'number', description: 'Rotation around Y axis (degrees)' },
579
+ z: { type: 'number', description: 'Rotation around Z axis (degrees)' }
580
+ },
581
+ required: ['target']
582
+ }
583
+ },
584
+ {
585
+ name: 'transform_scale',
586
+ description: 'Scale a feature along X, Y, Z axes',
587
+ inputSchema: {
588
+ type: 'object',
589
+ properties: {
590
+ target: { type: 'string', description: 'Feature ID' },
591
+ x: { type: 'number', description: 'X scale factor' },
592
+ y: { type: 'number', description: 'Y scale factor' },
593
+ z: { type: 'number', description: 'Z scale factor' }
594
+ },
595
+ required: ['target']
596
+ }
597
+ },
598
+
599
+ // VIEW tools
600
+ {
601
+ name: 'view_set',
602
+ description: 'Set camera to a standard view',
603
+ inputSchema: {
604
+ type: 'object',
605
+ properties: {
606
+ view: {
607
+ type: 'string',
608
+ enum: ['front', 'back', 'left', 'right', 'top', 'bottom', 'isometric'],
609
+ description: 'View direction'
610
+ }
611
+ },
612
+ required: []
613
+ }
614
+ },
615
+ {
616
+ name: 'view_fit',
617
+ description: 'Fit view to a feature or all features',
618
+ inputSchema: {
619
+ type: 'object',
620
+ properties: {
621
+ target: { type: 'string', description: 'Feature ID (optional, fits all if omitted)' }
622
+ },
623
+ required: []
624
+ }
625
+ },
626
+ {
627
+ name: 'view_wireframe',
628
+ description: 'Toggle wireframe rendering',
629
+ inputSchema: {
630
+ type: 'object',
631
+ properties: {
632
+ enabled: { type: 'boolean', description: 'Enable or disable wireframe' }
633
+ },
634
+ required: []
635
+ }
636
+ },
637
+ {
638
+ name: 'view_grid',
639
+ description: 'Toggle grid visibility',
640
+ inputSchema: {
641
+ type: 'object',
642
+ properties: {
643
+ visible: { type: 'boolean', description: 'Show or hide grid' }
644
+ },
645
+ required: []
646
+ }
647
+ },
648
+
649
+ // EXPORT tools
650
+ {
651
+ name: 'export_stl',
652
+ description: 'Export model as STL file',
653
+ inputSchema: {
654
+ type: 'object',
655
+ properties: {
656
+ filename: { type: 'string', description: 'Output filename' },
657
+ binary: { type: 'boolean', description: 'Use binary format (default: true)' }
658
+ },
659
+ required: []
660
+ }
661
+ },
662
+ {
663
+ name: 'export_obj',
664
+ description: 'Export model as OBJ file',
665
+ inputSchema: {
666
+ type: 'object',
667
+ properties: {
668
+ filename: { type: 'string', description: 'Output filename' }
669
+ },
670
+ required: []
671
+ }
672
+ },
673
+ {
674
+ name: 'export_gltf',
675
+ description: 'Export model as glTF 2.0 file',
676
+ inputSchema: {
677
+ type: 'object',
678
+ properties: {
679
+ filename: { type: 'string', description: 'Output filename' }
680
+ },
681
+ required: []
682
+ }
683
+ },
684
+ {
685
+ name: 'export_json',
686
+ description: 'Export model as cycleCAD JSON',
687
+ inputSchema: {
688
+ type: 'object',
689
+ properties: {
690
+ filename: { type: 'string', description: 'Output filename' }
691
+ },
692
+ required: []
693
+ }
694
+ },
695
+
696
+ // VALIDATE tools
697
+ {
698
+ name: 'validate_dimensions',
699
+ description: 'Get dimensions and bounding box of a feature',
700
+ inputSchema: {
701
+ type: 'object',
702
+ properties: {
703
+ target: { type: 'string', description: 'Feature ID' }
704
+ },
705
+ required: ['target']
706
+ }
707
+ },
708
+ {
709
+ name: 'validate_wallThickness',
710
+ description: 'Check minimum wall thickness of a feature',
711
+ inputSchema: {
712
+ type: 'object',
713
+ properties: {
714
+ target: { type: 'string', description: 'Feature ID' },
715
+ minWall: { type: 'number', description: 'Minimum wall thickness in mm' }
716
+ },
717
+ required: ['target']
718
+ }
719
+ },
720
+ {
721
+ name: 'validate_printability',
722
+ description: 'Check if part is printable via FDM, SLA, or CNC',
723
+ inputSchema: {
724
+ type: 'object',
725
+ properties: {
726
+ target: { type: 'string', description: 'Feature ID' },
727
+ process: { type: 'string', enum: ['FDM', 'SLA', 'CNC'], description: 'Manufacturing process' }
728
+ },
729
+ required: ['target']
730
+ }
731
+ },
732
+ {
733
+ name: 'validate_cost',
734
+ description: 'Estimate manufacturing cost',
735
+ inputSchema: {
736
+ type: 'object',
737
+ properties: {
738
+ target: { type: 'string', description: 'Feature ID' },
739
+ process: { type: 'string', enum: ['FDM', 'SLA', 'CNC', 'injection'], description: 'Manufacturing process' },
740
+ material: { type: 'string', description: 'Material name' }
741
+ },
742
+ required: ['target']
743
+ }
744
+ },
745
+ {
746
+ name: 'validate_mass',
747
+ description: 'Estimate part mass (weight)',
748
+ inputSchema: {
749
+ type: 'object',
750
+ properties: {
751
+ target: { type: 'string', description: 'Feature ID' },
752
+ material: { type: 'string', description: 'Material name' }
753
+ },
754
+ required: ['target']
755
+ }
756
+ },
757
+ {
758
+ name: 'validate_surfaceArea',
759
+ description: 'Calculate surface area of a feature',
760
+ inputSchema: {
761
+ type: 'object',
762
+ properties: {
763
+ target: { type: 'string', description: 'Feature ID' }
764
+ },
765
+ required: ['target']
766
+ }
767
+ },
768
+ {
769
+ name: 'validate_centerOfMass',
770
+ description: 'Get geometric centroid of a feature',
771
+ inputSchema: {
772
+ type: 'object',
773
+ properties: {
774
+ target: { type: 'string', description: 'Feature ID' }
775
+ },
776
+ required: ['target']
777
+ }
778
+ },
779
+ {
780
+ name: 'validate_designReview',
781
+ description: 'Auto-analyze feature for manufacturing issues (scored A/B/C/F)',
782
+ inputSchema: {
783
+ type: 'object',
784
+ properties: {
785
+ target: { type: 'string', description: 'Feature ID' }
786
+ },
787
+ required: ['target']
788
+ }
789
+ },
790
+
791
+ // RENDER tools
792
+ {
793
+ name: 'render_snapshot',
794
+ description: 'Render current viewport as PNG image',
795
+ inputSchema: {
796
+ type: 'object',
797
+ properties: {
798
+ width: { type: 'number', description: 'Image width in pixels' },
799
+ height: { type: 'number', description: 'Image height in pixels' }
800
+ },
801
+ required: []
802
+ }
803
+ },
804
+ {
805
+ name: 'render_multiview',
806
+ description: 'Render 6 standard views (front/back/left/right/top/isometric) as PNGs',
807
+ inputSchema: {
808
+ type: 'object',
809
+ properties: {
810
+ width: { type: 'number', description: 'Image width in pixels' },
811
+ height: { type: 'number', description: 'Image height in pixels' }
812
+ },
813
+ required: []
814
+ }
815
+ },
816
+ {
817
+ name: 'render_highlight',
818
+ description: 'Highlight a component with color',
819
+ inputSchema: {
820
+ type: 'object',
821
+ properties: {
822
+ target: { type: 'string', description: 'Feature ID' },
823
+ color: { type: 'string', description: 'Hex color code (e.g., #FF0000)' },
824
+ duration: { type: 'number', description: 'Duration in milliseconds' }
825
+ },
826
+ required: ['target']
827
+ }
828
+ },
829
+ {
830
+ name: 'render_hide',
831
+ description: 'Hide or show a component',
832
+ inputSchema: {
833
+ type: 'object',
834
+ properties: {
835
+ target: { type: 'string', description: 'Feature ID' },
836
+ hidden: { type: 'boolean', description: 'Hide or show' }
837
+ },
838
+ required: ['target']
839
+ }
840
+ },
841
+ {
842
+ name: 'render_section',
843
+ description: 'Enable section cutting (cross-section)',
844
+ inputSchema: {
845
+ type: 'object',
846
+ properties: {
847
+ enabled: { type: 'boolean', description: 'Enable or disable section cut' },
848
+ axis: { type: 'string', enum: ['X', 'Y', 'Z'], description: 'Cut axis' },
849
+ position: { type: 'number', description: 'Cut position along axis' },
850
+ mode: { type: 'string', enum: ['single', 'clip'], description: 'Section mode' }
851
+ },
852
+ required: []
853
+ }
854
+ },
855
+
856
+ // QUERY tools
857
+ {
858
+ name: 'query_features',
859
+ description: 'List all features in the model',
860
+ inputSchema: { type: 'object', properties: {} }
861
+ },
862
+ {
863
+ name: 'query_bbox',
864
+ description: 'Get bounding box of a feature',
865
+ inputSchema: {
866
+ type: 'object',
867
+ properties: {
868
+ target: { type: 'string', description: 'Feature ID' }
869
+ },
870
+ required: ['target']
871
+ }
872
+ },
873
+ {
874
+ name: 'query_materials',
875
+ description: 'List available materials',
876
+ inputSchema: { type: 'object', properties: {} }
877
+ },
878
+ {
879
+ name: 'query_session',
880
+ description: 'Get session info',
881
+ inputSchema: { type: 'object', properties: {} }
882
+ },
883
+ {
884
+ name: 'query_log',
885
+ description: 'Get recent command log',
886
+ inputSchema: {
887
+ type: 'object',
888
+ properties: {
889
+ last: { type: 'number', description: 'Number of recent commands' }
890
+ },
891
+ required: []
892
+ }
893
+ },
894
+
895
+ // ASSEMBLY tools
896
+ {
897
+ name: 'assembly_addComponent',
898
+ description: 'Add component to assembly',
899
+ inputSchema: {
900
+ type: 'object',
901
+ properties: {
902
+ name: { type: 'string', description: 'Component name' },
903
+ meshOrFile: { type: 'string', description: 'Feature ID or file path' },
904
+ position: { type: 'array', description: '[x, y, z] position' },
905
+ material: { type: 'string', description: 'Material name' }
906
+ },
907
+ required: ['name']
908
+ }
909
+ },
910
+ {
911
+ name: 'assembly_removeComponent',
912
+ description: 'Remove component from assembly',
913
+ inputSchema: {
914
+ type: 'object',
915
+ properties: {
916
+ target: { type: 'string', description: 'Component ID' }
917
+ },
918
+ required: ['target']
919
+ }
920
+ },
921
+ {
922
+ name: 'assembly_mate',
923
+ description: 'Define mate constraint between components',
924
+ inputSchema: {
925
+ type: 'object',
926
+ properties: {
927
+ target1: { type: 'string', description: 'First component ID' },
928
+ target2: { type: 'string', description: 'Second component ID' },
929
+ type: { type: 'string', enum: ['coincident', 'concentric', 'parallel', 'tangent'], description: 'Mate type' },
930
+ offset: { type: 'number', description: 'Mate offset' }
931
+ },
932
+ required: ['target1', 'target2']
933
+ }
934
+ },
935
+ {
936
+ name: 'assembly_explode',
937
+ description: 'Explode component or assembly',
938
+ inputSchema: {
939
+ type: 'object',
940
+ properties: {
941
+ target: { type: 'string', description: 'Component ID or "*" for all' },
942
+ distance: { type: 'number', description: 'Explode distance' }
943
+ },
944
+ required: ['target']
945
+ }
946
+ },
947
+
948
+ // SCENE tools
949
+ {
950
+ name: 'scene_clear',
951
+ description: 'Clear all features from the scene',
952
+ inputSchema: { type: 'object', properties: {} }
953
+ },
954
+ {
955
+ name: 'scene_snapshot',
956
+ description: 'Capture viewport as PNG (legacy)',
957
+ inputSchema: { type: 'object', properties: {} }
958
+ },
959
+
960
+ // AI tools
961
+ {
962
+ name: 'ai_identifyPart',
963
+ description: 'Identify part using Gemini Vision API',
964
+ inputSchema: {
965
+ type: 'object',
966
+ properties: {
967
+ target: { type: 'string', description: 'Feature ID' },
968
+ imageData: { type: 'string', description: 'Base64 image data (optional)' }
969
+ },
970
+ required: ['target']
971
+ }
972
+ },
973
+ {
974
+ name: 'ai_suggestImprovements',
975
+ description: 'Get AI-generated design improvement suggestions',
976
+ inputSchema: {
977
+ type: 'object',
978
+ properties: {
979
+ target: { type: 'string', description: 'Feature ID' }
980
+ },
981
+ required: ['target']
982
+ }
983
+ },
984
+ {
985
+ name: 'ai_estimateCostAI',
986
+ description: 'AI-powered cost estimation with recommendations',
987
+ inputSchema: {
988
+ type: 'object',
989
+ properties: {
990
+ target: { type: 'string', description: 'Feature ID' },
991
+ process: { type: 'string', enum: ['FDM', 'SLA', 'CNC', 'auto'], description: 'Manufacturing process' },
992
+ material: { type: 'string', description: 'Material name or "auto"' },
993
+ quantity: { type: 'number', description: 'Production quantity' }
994
+ },
995
+ required: ['target']
996
+ }
997
+ },
998
+
999
+ // META tools
1000
+ {
1001
+ name: 'meta_ping',
1002
+ description: 'Health check and session uptime',
1003
+ inputSchema: { type: 'object', properties: {} }
1004
+ },
1005
+ {
1006
+ name: 'meta_version',
1007
+ description: 'Get version info and feature flags',
1008
+ inputSchema: { type: 'object', properties: {} }
1009
+ },
1010
+ {
1011
+ name: 'meta_schema',
1012
+ description: 'Get full API schema',
1013
+ inputSchema: { type: 'object', properties: {} }
1014
+ },
1015
+ {
1016
+ name: 'meta_modules',
1017
+ description: 'Check which modules are available',
1018
+ inputSchema: { type: 'object', properties: {} }
1019
+ },
1020
+ {
1021
+ name: 'meta_history',
1022
+ description: 'Get undo/redo history stack',
1023
+ inputSchema: { type: 'object', properties: {} }
1024
+ }
1025
+ ];
1026
+
1027
+ // =============================================================================
1028
+ // Main Server Loop
1029
+ // =============================================================================
1030
+
1031
+ async function main() {
1032
+ const args = process.argv.slice(2);
1033
+
1034
+ if (args.includes('--help') || args.includes('-h')) {
1035
+ console.log(`
1036
+ cycleCAD MCP Server
1037
+ Usage: cyclecad-mcp [OPTIONS]
1038
+
1039
+ OPTIONS:
1040
+ --help Show this help message
1041
+ --ws-url URL WebSocket URL (default: ws://localhost:3000/api/ws)
1042
+ --http-url URL HTTP URL (default: http://localhost:3000/api/execute)
1043
+ --debug Enable debug logging
1044
+ --version Show version
1045
+
1046
+ ENVIRONMENT:
1047
+ CYCLECAD_WS_URL WebSocket URL
1048
+ CYCLECAD_HTTP_URL HTTP URL
1049
+ DEBUG_MCP Enable debug logging
1050
+
1051
+ EXAMPLES:
1052
+ npx cyclecad-mcp
1053
+ npx cyclecad-mcp --debug
1054
+ npx cyclecad-mcp --ws-url ws://10.0.0.1:3000/api/ws
1055
+
1056
+ `);
1057
+ process.exit(0);
1058
+ }
1059
+
1060
+ if (args.includes('--version') || args.includes('-v')) {
1061
+ console.log('cyclecad-mcp 1.0.0');
1062
+ process.exit(0);
1063
+ }
1064
+
1065
+ // Parse environment and CLI args
1066
+ if (args.includes('--debug')) config.debug = true;
1067
+
1068
+ const wsIdx = args.indexOf('--ws-url');
1069
+ if (wsIdx !== -1 && args[wsIdx + 1]) {
1070
+ config.wsUrl = args[wsIdx + 1];
1071
+ }
1072
+
1073
+ const httpIdx = args.indexOf('--http-url');
1074
+ if (httpIdx !== -1 && args[httpIdx + 1]) {
1075
+ config.httpUrl = args[httpIdx + 1];
1076
+ }
1077
+
1078
+ const server = new MCPServer();
1079
+
1080
+ // Setup stdio transport
1081
+ const rl = readline.createInterface({
1082
+ input: process.stdin,
1083
+ output: process.stdout,
1084
+ terminal: false
1085
+ });
1086
+
1087
+ let isInitialized = false;
1088
+
1089
+ rl.on('line', async (line) => {
1090
+ try {
1091
+ const request = JSON.parse(line);
1092
+ let response = null;
1093
+
1094
+ if (config.debug) {
1095
+ console.error(`[MCP] ← ${request.method}`);
1096
+ }
1097
+
1098
+ if (request.method === 'initialize') {
1099
+ const result = await server.initialize();
1100
+ isInitialized = true;
1101
+ response = {
1102
+ jsonrpc: '2.0',
1103
+ id: request.id,
1104
+ result
1105
+ };
1106
+ } else if (request.method === 'tools/list') {
1107
+ response = {
1108
+ jsonrpc: '2.0',
1109
+ id: request.id,
1110
+ result: {
1111
+ tools: server.getTools()
1112
+ }
1113
+ };
1114
+ } else if (request.method === 'tools/call') {
1115
+ const result = await server.callTool(request.params.name, request.params.arguments);
1116
+ response = {
1117
+ jsonrpc: '2.0',
1118
+ id: request.id,
1119
+ result
1120
+ };
1121
+ } else {
1122
+ response = {
1123
+ jsonrpc: '2.0',
1124
+ id: request.id,
1125
+ error: {
1126
+ code: -32601,
1127
+ message: `Unknown method: ${request.method}`
1128
+ }
1129
+ };
1130
+ }
1131
+
1132
+ if (config.debug) {
1133
+ console.error(`[MCP] → ${response.result ? 'OK' : 'ERROR'}`);
1134
+ }
1135
+
1136
+ console.log(JSON.stringify(response));
1137
+ } catch (e) {
1138
+ console.error(`[MCP Server Error] ${e.message}`);
1139
+ console.log(JSON.stringify({
1140
+ jsonrpc: '2.0',
1141
+ error: {
1142
+ code: -32603,
1143
+ message: e.message
1144
+ }
1145
+ }));
1146
+ }
1147
+ });
1148
+
1149
+ rl.on('close', () => {
1150
+ process.exit(0);
1151
+ });
1152
+
1153
+ if (config.debug) {
1154
+ console.error('[MCP] Server ready, waiting for requests...');
1155
+ }
1156
+ }
1157
+
1158
+ main().catch(e => {
1159
+ console.error('Fatal error:', e);
1160
+ process.exit(1);
1161
+ });