@aithr-ai/mcp-server 1.0.0

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 (3) hide show
  1. package/README.md +137 -0
  2. package/index.js +2178 -0
  3. package/package.json +40 -0
package/index.js ADDED
@@ -0,0 +1,2178 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Aether MCP Server
4
+ *
5
+ * Connects Claude Code to Aether's canvas via Model Context Protocol.
6
+ *
7
+ * QUICK INSTALL (One Command):
8
+ *
9
+ * Mac/Linux:
10
+ * claude mcp add --transport stdio aether \
11
+ * --env AETHER_API_KEY=YOUR_KEY \
12
+ * --env AETHER_WORKSPACE_ID=your-workspace \
13
+ * --env AETHER_PROJECT_ID=your-project-id \
14
+ * -s user -- npx -y @aithr-ai/mcp-server
15
+ *
16
+ * Windows:
17
+ * claude mcp add --transport stdio aether ^
18
+ * --env AETHER_API_KEY=YOUR_KEY ^
19
+ * --env AETHER_WORKSPACE_ID=your-workspace ^
20
+ * --env AETHER_PROJECT_ID=your-project-id ^
21
+ * -s user -- cmd /c npx -y @aithr-ai/mcp-server
22
+ *
23
+ * Or visit: https://www.aithr.ai/settings/mcp-install for personalized commands
24
+ *
25
+ * Tools provided:
26
+ * - aether_canvas_state: Get current canvas state
27
+ * - aether_add_agent: Add an agent to the canvas
28
+ * - aether_remove_node: Remove a node
29
+ * - aether_connect_nodes: Create a connection
30
+ * - aether_trigger_generation: Start AI generation
31
+ * - aether_list_projects: List your projects
32
+ * - aether_list_agents: List agents on the canvas
33
+ * - aether_get_artifacts: Get generated artifacts
34
+ * - aether_shell: Execute shell command locally
35
+ *
36
+ * GitHub Automation (Zero-Intervention Pipeline):
37
+ * - aether_automation_status: Check what's ready to push
38
+ * - aether_extract_files: Extract code files from artifact
39
+ * - aether_push_artifacts: Push to GitHub (branch + commit + PR)
40
+ *
41
+ * Canvas Folder Management:
42
+ * - aether_list_folders: List folders in project
43
+ * - aether_create_folder: Create a new folder
44
+ * - aether_delete_folder: Delete a folder
45
+ * - aether_add_file_to_folder: Add file/artifact to folder
46
+ *
47
+ * Artifact Creation:
48
+ * - aether_create_artifact: Create artifact and place on canvas
49
+ *
50
+ * Orchestra (Autonomous Development):
51
+ * - aether_start_orchestra: Initialize autonomous orchestration session
52
+ * - aether_set_orchestra_plan: Set development plan with phases/tasks
53
+ * - aether_orchestra_approve: User approves plan to begin execution
54
+ * - aether_orchestra_status: Get current progress, running tasks, blockers
55
+ * - aether_orchestra_next: Get next ready tasks based on dependencies
56
+ * - aether_orchestra_trigger_task: Execute a specific task
57
+ * - aether_report_to_orchestrator: Agents report completion/blockers
58
+ * - aether_orchestra_pause: Pause execution
59
+ * - aether_orchestra_resume: Resume execution
60
+ * - aether_complete_orchestra: Mark complete, generate summary
61
+ */
62
+
63
+ const readline = require('readline');
64
+
65
+ // Configuration
66
+ const config = {
67
+ aetherUrl: process.env.AETHER_URL || 'https://www.aithr.ai',
68
+ apiKey: process.env.AETHER_API_KEY || '',
69
+ workspaceSlug: process.env.AETHER_WORKSPACE_ID || '', // Workspace slug (e.g., "my-workspace")
70
+ projectId: process.env.AETHER_PROJECT_ID || '',
71
+ };
72
+
73
+ // Startup validation
74
+ if (!config.apiKey) {
75
+ process.stderr.write('WARNING: AETHER_API_KEY not set. Generate one at Settings > API Keys\n');
76
+ }
77
+ if (!config.workspaceSlug) {
78
+ process.stderr.write('WARNING: AETHER_WORKSPACE_ID not set\n');
79
+ }
80
+ if (!config.projectId) {
81
+ process.stderr.write('WARNING: AETHER_PROJECT_ID not set\n');
82
+ }
83
+
84
+ // MCP Protocol Implementation
85
+ class MCPServer {
86
+ constructor() {
87
+ this.tools = this.defineTools();
88
+ }
89
+
90
+ defineTools() {
91
+ return [
92
+ {
93
+ name: 'aether_canvas_state',
94
+ description: 'Get the current state of the Aether canvas including all nodes and connections',
95
+ inputSchema: {
96
+ type: 'object',
97
+ properties: {
98
+ projectId: {
99
+ type: 'string',
100
+ description: 'Project ID (optional, uses default if not provided)',
101
+ },
102
+ },
103
+ },
104
+ },
105
+ {
106
+ name: 'aether_add_agent',
107
+ description: 'Add an AI agent to the Aether canvas. Agent types: pm, consultant, architect, engineer, ux_designer, ui_engineer, code_auditor, general_chat. NOTE: Custom nodes require Pro tier.',
108
+ inputSchema: {
109
+ type: 'object',
110
+ properties: {
111
+ agentType: {
112
+ type: 'string',
113
+ enum: ['pm', 'consultant', 'architect', 'engineer', 'ux_designer', 'ui_engineer', 'code_auditor', 'general_chat'],
114
+ description: 'Type of agent to add',
115
+ },
116
+ task: {
117
+ type: 'string',
118
+ description: 'Task description for the agent',
119
+ },
120
+ position: {
121
+ type: 'object',
122
+ properties: {
123
+ x: { type: 'number' },
124
+ y: { type: 'number' },
125
+ },
126
+ description: 'Position on canvas (optional)',
127
+ },
128
+ nodeType: {
129
+ type: 'string',
130
+ enum: ['standard', 'parent', 'sub'],
131
+ description: 'Node type: standard (default), parent (orchestrator), or sub (child agent)',
132
+ },
133
+ parentNodeId: {
134
+ type: 'string',
135
+ description: 'UUID of parent node (required if nodeType is "sub")',
136
+ },
137
+ outputArtifactType: {
138
+ type: 'string',
139
+ enum: ['code', 'phase_output', 'design_files', 'prd', 'implementation_plan', 'ux_spec', 'audit_report'],
140
+ description: 'Output artifact type. Use "code" for agents that produce code (enables structured JSON output for GitHub automation). Defaults to "code" for parent/sub agents.',
141
+ },
142
+ systemPrompt: {
143
+ type: 'string',
144
+ description: 'Custom system prompt for the agent (optional, uses default if not provided)',
145
+ },
146
+ },
147
+ required: ['agentType'],
148
+ },
149
+ },
150
+ {
151
+ name: 'aether_remove_node',
152
+ description: 'Remove a node from the Aether canvas',
153
+ inputSchema: {
154
+ type: 'object',
155
+ properties: {
156
+ nodeId: {
157
+ type: 'string',
158
+ description: 'ID of the node to remove',
159
+ },
160
+ },
161
+ required: ['nodeId'],
162
+ },
163
+ },
164
+ {
165
+ name: 'aether_connect_nodes',
166
+ description: 'Create a connection between two nodes on the canvas',
167
+ inputSchema: {
168
+ type: 'object',
169
+ properties: {
170
+ sourceId: {
171
+ type: 'string',
172
+ description: 'Source node ID',
173
+ },
174
+ targetId: {
175
+ type: 'string',
176
+ description: 'Target node ID',
177
+ },
178
+ connectionType: {
179
+ type: 'string',
180
+ enum: ['dependency', 'data_flow', 'handover', 'context'],
181
+ description: 'Type of connection',
182
+ },
183
+ },
184
+ required: ['sourceId', 'targetId'],
185
+ },
186
+ },
187
+ {
188
+ name: 'aether_trigger_generation',
189
+ description: 'Trigger AI generation for an agent on the canvas',
190
+ inputSchema: {
191
+ type: 'object',
192
+ properties: {
193
+ nodeId: {
194
+ type: 'string',
195
+ description: 'ID of the agent node to trigger',
196
+ },
197
+ context: {
198
+ type: 'string',
199
+ description: 'Additional context for generation',
200
+ },
201
+ },
202
+ required: ['nodeId'],
203
+ },
204
+ },
205
+ {
206
+ name: 'aether_list_projects',
207
+ description: 'List all projects in the current workspace',
208
+ inputSchema: {
209
+ type: 'object',
210
+ properties: {},
211
+ },
212
+ },
213
+ {
214
+ name: 'aether_list_agents',
215
+ description: 'List all agents on the current canvas',
216
+ inputSchema: {
217
+ type: 'object',
218
+ properties: {},
219
+ },
220
+ },
221
+ {
222
+ name: 'aether_get_artifacts',
223
+ description: 'Get artifacts (PRDs, code, etc.) from a project',
224
+ inputSchema: {
225
+ type: 'object',
226
+ properties: {
227
+ artifactType: {
228
+ type: 'string',
229
+ enum: ['prd', 'code', 'implementation_plan', 'ux_spec', 'audit_report'],
230
+ description: 'Type of artifact to retrieve',
231
+ },
232
+ },
233
+ },
234
+ },
235
+ {
236
+ name: 'aether_shell',
237
+ description: 'Execute a shell command through Aether (runs on your local machine if bridge is connected)',
238
+ inputSchema: {
239
+ type: 'object',
240
+ properties: {
241
+ command: {
242
+ type: 'string',
243
+ description: 'Shell command to execute',
244
+ },
245
+ cwd: {
246
+ type: 'string',
247
+ description: 'Working directory (optional)',
248
+ },
249
+ },
250
+ required: ['command'],
251
+ },
252
+ },
253
+ // Repository tools - with recommendation for local clone
254
+ {
255
+ name: 'aether_list_repositories',
256
+ description: 'List GitHub repositories synced to this workspace. NOTE: For file access, a local git clone is RECOMMENDED for better performance. Use these tools only if you need to access files through Aether\'s synced data.',
257
+ inputSchema: {
258
+ type: 'object',
259
+ properties: {},
260
+ },
261
+ },
262
+ {
263
+ name: 'aether_list_repo_files',
264
+ description: 'List files in a synced repository. RECOMMENDATION: For faster file access, clone the repo locally with `git clone` and use standard file tools instead.',
265
+ inputSchema: {
266
+ type: 'object',
267
+ properties: {
268
+ repositoryId: {
269
+ type: 'string',
270
+ description: 'Repository ID (from aether_list_repositories)',
271
+ },
272
+ path: {
273
+ type: 'string',
274
+ description: 'Directory path to list (optional, defaults to root)',
275
+ },
276
+ },
277
+ required: ['repositoryId'],
278
+ },
279
+ },
280
+ {
281
+ name: 'aether_read_repo_file',
282
+ description: 'Read a file from a synced repository. RECOMMENDATION: For faster reads, clone the repo locally and use the Read tool directly.',
283
+ inputSchema: {
284
+ type: 'object',
285
+ properties: {
286
+ repositoryId: {
287
+ type: 'string',
288
+ description: 'Repository ID',
289
+ },
290
+ filePath: {
291
+ type: 'string',
292
+ description: 'Path to the file',
293
+ },
294
+ },
295
+ required: ['repositoryId', 'filePath'],
296
+ },
297
+ },
298
+ {
299
+ name: 'aether_search_repo',
300
+ description: 'Search for content across a synced repository. RECOMMENDATION: For faster searches, clone the repo locally and use Grep/Glob tools.',
301
+ inputSchema: {
302
+ type: 'object',
303
+ properties: {
304
+ repositoryId: {
305
+ type: 'string',
306
+ description: 'Repository ID',
307
+ },
308
+ query: {
309
+ type: 'string',
310
+ description: 'Search query',
311
+ },
312
+ fileTypes: {
313
+ type: 'array',
314
+ items: { type: 'string' },
315
+ description: 'File extensions to search (e.g., [".ts", ".tsx"])',
316
+ },
317
+ },
318
+ required: ['repositoryId', 'query'],
319
+ },
320
+ },
321
+ // =========================================================================
322
+ // GitHub Automation Tools (Zero-Intervention Pipeline)
323
+ // =========================================================================
324
+ {
325
+ name: 'aether_automation_status',
326
+ description: 'Check automation readiness - what artifacts are parseable and ready to push to GitHub',
327
+ inputSchema: {
328
+ type: 'object',
329
+ properties: {
330
+ projectId: {
331
+ type: 'string',
332
+ description: 'Project ID (optional, uses default if not provided)',
333
+ },
334
+ },
335
+ },
336
+ },
337
+ {
338
+ name: 'aether_get_artifact_content',
339
+ description: 'Get full content of a specific artifact for inspection',
340
+ inputSchema: {
341
+ type: 'object',
342
+ properties: {
343
+ artifactId: {
344
+ type: 'string',
345
+ description: 'Artifact ID to fetch',
346
+ },
347
+ },
348
+ required: ['artifactId'],
349
+ },
350
+ },
351
+ {
352
+ name: 'aether_extract_files',
353
+ description: 'Extract code files from an artifact. Returns parsed files ready for GitHub push.',
354
+ inputSchema: {
355
+ type: 'object',
356
+ properties: {
357
+ artifactId: {
358
+ type: 'string',
359
+ description: 'ID of the artifact to extract files from',
360
+ },
361
+ saveToGeneratedFiles: {
362
+ type: 'boolean',
363
+ description: 'Save extracted files to database for later push (default: false)',
364
+ },
365
+ },
366
+ required: ['artifactId'],
367
+ },
368
+ },
369
+ {
370
+ name: 'aether_push_artifacts',
371
+ description: 'Push artifacts to GitHub - creates branch, commits files, and opens PR. This is the final step in the automation pipeline.',
372
+ inputSchema: {
373
+ type: 'object',
374
+ properties: {
375
+ artifactIds: {
376
+ type: 'array',
377
+ items: { type: 'string' },
378
+ description: 'Specific artifact IDs to push (optional, uses all parseable if not provided)',
379
+ },
380
+ branchName: {
381
+ type: 'string',
382
+ description: 'Branch name (auto-generated if not provided)',
383
+ },
384
+ commitMessage: {
385
+ type: 'string',
386
+ description: 'Commit message (default: "feat: Add generated code from Aether AI")',
387
+ },
388
+ prTitle: {
389
+ type: 'string',
390
+ description: 'Pull request title',
391
+ },
392
+ prBody: {
393
+ type: 'string',
394
+ description: 'Pull request body/description',
395
+ },
396
+ createPR: {
397
+ type: 'boolean',
398
+ description: 'Create a pull request (default: true)',
399
+ },
400
+ draft: {
401
+ type: 'boolean',
402
+ description: 'Create as draft PR (default: false)',
403
+ },
404
+ dryRun: {
405
+ type: 'boolean',
406
+ description: 'Preview what would be pushed without actually pushing (default: false)',
407
+ },
408
+ },
409
+ },
410
+ },
411
+ // =========================================================================
412
+ // Canvas Folder Tools
413
+ // =========================================================================
414
+ {
415
+ name: 'aether_list_folders',
416
+ description: 'List all canvas folders in the current project with file counts',
417
+ inputSchema: {
418
+ type: 'object',
419
+ properties: {
420
+ projectId: {
421
+ type: 'string',
422
+ description: 'Project ID (optional, uses default if not provided)',
423
+ },
424
+ },
425
+ },
426
+ },
427
+ {
428
+ name: 'aether_create_folder',
429
+ description: 'Create a new canvas folder for organizing files and artifacts',
430
+ inputSchema: {
431
+ type: 'object',
432
+ properties: {
433
+ name: {
434
+ type: 'string',
435
+ description: 'Folder name (required)',
436
+ },
437
+ color: {
438
+ type: 'string',
439
+ enum: ['blue', 'green', 'yellow', 'purple', 'pink', 'orange', 'gray'],
440
+ description: 'Folder color (optional, defaults to blue)',
441
+ },
442
+ position: {
443
+ type: 'object',
444
+ properties: {
445
+ x: { type: 'number' },
446
+ y: { type: 'number' },
447
+ },
448
+ description: 'Position on canvas (optional)',
449
+ },
450
+ },
451
+ required: ['name'],
452
+ },
453
+ },
454
+ {
455
+ name: 'aether_delete_folder',
456
+ description: 'Delete a canvas folder (files inside will be unassigned, not deleted)',
457
+ inputSchema: {
458
+ type: 'object',
459
+ properties: {
460
+ folderId: {
461
+ type: 'string',
462
+ description: 'ID of the folder to delete',
463
+ },
464
+ },
465
+ required: ['folderId'],
466
+ },
467
+ },
468
+ {
469
+ name: 'aether_add_file_to_folder',
470
+ description: 'Add an existing file or artifact to a canvas folder',
471
+ inputSchema: {
472
+ type: 'object',
473
+ properties: {
474
+ folderId: {
475
+ type: 'string',
476
+ description: 'ID of the target folder',
477
+ },
478
+ fileId: {
479
+ type: 'string',
480
+ description: 'ID of the file to add (from workspace files)',
481
+ },
482
+ artifactId: {
483
+ type: 'string',
484
+ description: 'ID of the artifact to add (alternative to fileId)',
485
+ },
486
+ },
487
+ required: ['folderId'],
488
+ },
489
+ },
490
+ // =========================================================================
491
+ // Artifact Creation Tools
492
+ // =========================================================================
493
+ {
494
+ name: 'aether_create_artifact',
495
+ description: 'Create a new artifact and optionally link it to a canvas node. Use this to store generated content (code, specs, docs) on the canvas.',
496
+ inputSchema: {
497
+ type: 'object',
498
+ properties: {
499
+ type: {
500
+ type: 'string',
501
+ enum: ['prd', 'audit_report', 'implementation_plan', 'phase_output', 'code', 'ux_spec', 'design_files', 'code_audit_report'],
502
+ description: 'Artifact type: prd, audit_report, implementation_plan, phase_output, code, ux_spec, design_files, code_audit_report',
503
+ },
504
+ title: {
505
+ type: 'string',
506
+ description: 'Title of the artifact',
507
+ },
508
+ content: {
509
+ type: 'string',
510
+ description: 'The artifact content (markdown, code, JSON structured output, etc.)',
511
+ },
512
+ nodeId: {
513
+ type: 'string',
514
+ description: 'Optional canvas node ID to link this artifact to (marks node as complete)',
515
+ },
516
+ agentId: {
517
+ type: 'string',
518
+ description: 'Optional agent ID that created this (e.g., "ux_designer", "engineer")',
519
+ },
520
+ },
521
+ required: ['type', 'title', 'content'],
522
+ },
523
+ },
524
+ // =========================================================================
525
+ // Orchestra Tools (Autonomous Development Orchestration)
526
+ // =========================================================================
527
+ {
528
+ name: 'aether_start_orchestra',
529
+ description: 'Start a new autonomous development orchestration session. Creates a session in "planning" status. After starting, use aether_set_orchestra_plan to define the development plan.',
530
+ inputSchema: {
531
+ type: 'object',
532
+ properties: {
533
+ maxIterations: {
534
+ type: 'number',
535
+ description: 'Maximum task iterations before stopping (default: 100)',
536
+ },
537
+ maxAgents: {
538
+ type: 'number',
539
+ description: 'Maximum concurrent agents (default: 20)',
540
+ },
541
+ timeoutMinutes: {
542
+ type: 'number',
543
+ description: 'Timeout in minutes (default: 480 = 8 hours)',
544
+ },
545
+ completionCriteria: {
546
+ type: 'array',
547
+ items: { type: 'string' },
548
+ description: 'List of criteria that must be met to auto-complete',
549
+ },
550
+ },
551
+ },
552
+ },
553
+ {
554
+ name: 'aether_set_orchestra_plan',
555
+ description: 'Set the development plan for an orchestra session. The plan should be comprehensive - this is the DEVELOPMENT_PLAN.md that guides the entire orchestra. After setting, status becomes "awaiting_approval".',
556
+ inputSchema: {
557
+ type: 'object',
558
+ properties: {
559
+ sessionId: {
560
+ type: 'string',
561
+ description: 'Orchestra session ID from aether_start_orchestra',
562
+ },
563
+ planContent: {
564
+ type: 'string',
565
+ description: 'Full DEVELOPMENT_PLAN.md content - the comprehensive plan for the entire development cycle',
566
+ },
567
+ phases: {
568
+ type: 'array',
569
+ items: {
570
+ type: 'object',
571
+ properties: {
572
+ phaseNumber: { type: 'number', description: 'Phase order (1-indexed)' },
573
+ title: { type: 'string', description: 'Phase title' },
574
+ description: { type: 'string', description: 'Phase description' },
575
+ tasks: {
576
+ type: 'array',
577
+ items: {
578
+ type: 'object',
579
+ properties: {
580
+ title: { type: 'string', description: 'Task title' },
581
+ description: { type: 'string', description: 'Task details' },
582
+ agentId: { type: 'string', description: 'Agent to execute (pm, architect, engineer, etc.)' },
583
+ contextPrompt: { type: 'string', description: 'Context/prompt for the agent' },
584
+ expectedOutput: { type: 'string', description: 'What output is expected' },
585
+ dependsOnTasks: {
586
+ type: 'array',
587
+ items: { type: 'number' },
588
+ description: 'Task numbers this depends on',
589
+ },
590
+ },
591
+ required: ['title', 'agentId'],
592
+ },
593
+ description: 'Tasks for this phase',
594
+ },
595
+ },
596
+ required: ['phaseNumber', 'title', 'tasks'],
597
+ },
598
+ description: 'Array of phases with their tasks',
599
+ },
600
+ completionCriteria: {
601
+ type: 'array',
602
+ items: { type: 'string' },
603
+ description: 'Updated completion criteria (optional)',
604
+ },
605
+ },
606
+ required: ['sessionId', 'planContent', 'phases'],
607
+ },
608
+ },
609
+ {
610
+ name: 'aether_orchestra_approve',
611
+ description: 'Approve the development plan and start autonomous execution. This is the point of no return - after approval, the orchestra will execute continuously until stopped or completion criteria are met.',
612
+ inputSchema: {
613
+ type: 'object',
614
+ properties: {
615
+ sessionId: {
616
+ type: 'string',
617
+ description: 'Orchestra session ID',
618
+ },
619
+ },
620
+ required: ['sessionId'],
621
+ },
622
+ },
623
+ {
624
+ name: 'aether_orchestra_status',
625
+ description: 'Get the current status of an orchestra session including progress, running tasks, pending reports, and safeguard checks. Call this frequently to monitor progress.',
626
+ inputSchema: {
627
+ type: 'object',
628
+ properties: {
629
+ sessionId: {
630
+ type: 'string',
631
+ description: 'Orchestra session ID (optional - uses latest if not provided)',
632
+ },
633
+ includeReports: {
634
+ type: 'boolean',
635
+ description: 'Include unprocessed agent reports (default: true)',
636
+ },
637
+ includeLogs: {
638
+ type: 'boolean',
639
+ description: 'Include recent event logs (default: false)',
640
+ },
641
+ },
642
+ },
643
+ },
644
+ {
645
+ name: 'aether_orchestra_next',
646
+ description: 'Get the next tasks that are ready to execute (dependencies satisfied). Use this to determine what to trigger next.',
647
+ inputSchema: {
648
+ type: 'object',
649
+ properties: {
650
+ sessionId: {
651
+ type: 'string',
652
+ description: 'Orchestra session ID',
653
+ },
654
+ maxTasks: {
655
+ type: 'number',
656
+ description: 'Maximum number of ready tasks to return (default: 5)',
657
+ },
658
+ },
659
+ required: ['sessionId'],
660
+ },
661
+ },
662
+ {
663
+ name: 'aether_orchestra_trigger_task',
664
+ description: 'Trigger execution of a specific task. This marks the task as "running" and provides the context needed to trigger the agent. After calling this, use aether_trigger_generation with the appropriate nodeId.',
665
+ inputSchema: {
666
+ type: 'object',
667
+ properties: {
668
+ sessionId: {
669
+ type: 'string',
670
+ description: 'Orchestra session ID',
671
+ },
672
+ taskId: {
673
+ type: 'string',
674
+ description: 'Task ID to trigger',
675
+ },
676
+ additionalContext: {
677
+ type: 'string',
678
+ description: 'Additional context to inject into the task prompt',
679
+ },
680
+ },
681
+ required: ['sessionId', 'taskId'],
682
+ },
683
+ },
684
+ {
685
+ name: 'aether_report_to_orchestrator',
686
+ description: 'Report task completion, blockers, or progress from an agent back to the orchestrator. Agents MUST call this when they complete or encounter issues.',
687
+ inputSchema: {
688
+ type: 'object',
689
+ properties: {
690
+ sessionId: {
691
+ type: 'string',
692
+ description: 'Orchestra session ID',
693
+ },
694
+ taskId: {
695
+ type: 'string',
696
+ description: 'Task ID being reported on',
697
+ },
698
+ reportType: {
699
+ type: 'string',
700
+ enum: ['completion', 'blocker', 'progress', 'error', 'needs_clarification'],
701
+ description: 'Type of report',
702
+ },
703
+ summary: {
704
+ type: 'string',
705
+ description: 'Brief summary of the report',
706
+ },
707
+ details: {
708
+ type: 'string',
709
+ description: 'Detailed report content',
710
+ },
711
+ artifactId: {
712
+ type: 'string',
713
+ description: 'ID of artifact produced (for completion reports)',
714
+ },
715
+ blockerDescription: {
716
+ type: 'string',
717
+ description: 'Description of blocker (for blocker reports)',
718
+ },
719
+ suggestedAction: {
720
+ type: 'string',
721
+ description: 'Suggested next action',
722
+ },
723
+ },
724
+ required: ['sessionId', 'taskId', 'reportType', 'summary'],
725
+ },
726
+ },
727
+ {
728
+ name: 'aether_orchestra_pause',
729
+ description: 'Pause orchestra execution. Running tasks will complete but no new tasks will start. Use aether_orchestra_resume to continue.',
730
+ inputSchema: {
731
+ type: 'object',
732
+ properties: {
733
+ sessionId: {
734
+ type: 'string',
735
+ description: 'Orchestra session ID',
736
+ },
737
+ },
738
+ required: ['sessionId'],
739
+ },
740
+ },
741
+ {
742
+ name: 'aether_orchestra_resume',
743
+ description: 'Resume a paused orchestra session.',
744
+ inputSchema: {
745
+ type: 'object',
746
+ properties: {
747
+ sessionId: {
748
+ type: 'string',
749
+ description: 'Orchestra session ID',
750
+ },
751
+ },
752
+ required: ['sessionId'],
753
+ },
754
+ },
755
+ {
756
+ name: 'aether_complete_orchestra',
757
+ description: 'Manually complete an orchestra session. Use this when all work is done or when stopping the session.',
758
+ inputSchema: {
759
+ type: 'object',
760
+ properties: {
761
+ sessionId: {
762
+ type: 'string',
763
+ description: 'Orchestra session ID',
764
+ },
765
+ completionReason: {
766
+ type: 'string',
767
+ enum: ['all_criteria_met', 'all_phases_complete', 'manual_stop', 'timeout', 'max_iterations'],
768
+ description: 'Reason for completion',
769
+ },
770
+ summary: {
771
+ type: 'string',
772
+ description: 'Summary of what was accomplished',
773
+ },
774
+ },
775
+ required: ['sessionId', 'completionReason'],
776
+ },
777
+ },
778
+ ];
779
+ }
780
+
781
+ async handleRequest(request) {
782
+ const { method, params, id } = request;
783
+
784
+ switch (method) {
785
+ case 'initialize':
786
+ return this.handleInitialize(id, params);
787
+ case 'tools/list':
788
+ return this.handleToolsList(id);
789
+ case 'tools/call':
790
+ return this.handleToolCall(id, params);
791
+ default:
792
+ return this.errorResponse(id, -32601, `Method not found: ${method}`);
793
+ }
794
+ }
795
+
796
+ handleInitialize(id, params) {
797
+ return {
798
+ jsonrpc: '2.0',
799
+ id,
800
+ result: {
801
+ protocolVersion: '2024-11-05',
802
+ capabilities: {
803
+ tools: {},
804
+ },
805
+ serverInfo: {
806
+ name: 'aether-mcp-server',
807
+ version: '1.0.0',
808
+ },
809
+ },
810
+ };
811
+ }
812
+
813
+ handleToolsList(id) {
814
+ return {
815
+ jsonrpc: '2.0',
816
+ id,
817
+ result: {
818
+ tools: this.tools,
819
+ },
820
+ };
821
+ }
822
+
823
+ async handleToolCall(id, params) {
824
+ const { name, arguments: args } = params;
825
+
826
+ try {
827
+ let result;
828
+
829
+ switch (name) {
830
+ case 'aether_canvas_state':
831
+ result = await this.getCanvasState(args);
832
+ break;
833
+ case 'aether_add_agent':
834
+ result = await this.addAgent(args);
835
+ break;
836
+ case 'aether_remove_node':
837
+ result = await this.removeNode(args);
838
+ break;
839
+ case 'aether_connect_nodes':
840
+ result = await this.connectNodes(args);
841
+ break;
842
+ case 'aether_trigger_generation':
843
+ result = await this.triggerGeneration(args);
844
+ break;
845
+ case 'aether_list_projects':
846
+ result = await this.listProjects();
847
+ break;
848
+ case 'aether_list_agents':
849
+ result = await this.listAgents();
850
+ break;
851
+ case 'aether_get_artifacts':
852
+ result = await this.getArtifacts(args);
853
+ break;
854
+ case 'aether_shell':
855
+ result = await this.executeShell(args);
856
+ break;
857
+ case 'aether_list_repositories':
858
+ result = await this.listRepositories();
859
+ break;
860
+ case 'aether_list_repo_files':
861
+ result = await this.listRepoFiles(args);
862
+ break;
863
+ case 'aether_read_repo_file':
864
+ result = await this.readRepoFile(args);
865
+ break;
866
+ case 'aether_search_repo':
867
+ result = await this.searchRepo(args);
868
+ break;
869
+ // GitHub Automation Tools
870
+ case 'aether_automation_status':
871
+ result = await this.getAutomationStatus(args);
872
+ break;
873
+ case 'aether_get_artifact_content':
874
+ result = await this.getArtifactContent(args);
875
+ break;
876
+ case 'aether_extract_files':
877
+ result = await this.extractFiles(args);
878
+ break;
879
+ case 'aether_push_artifacts':
880
+ result = await this.pushArtifacts(args);
881
+ break;
882
+ // Canvas Folder Tools
883
+ case 'aether_list_folders':
884
+ result = await this.listFolders(args);
885
+ break;
886
+ case 'aether_create_folder':
887
+ result = await this.createFolder(args);
888
+ break;
889
+ case 'aether_delete_folder':
890
+ result = await this.deleteFolder(args);
891
+ break;
892
+ case 'aether_add_file_to_folder':
893
+ result = await this.addFileToFolder(args);
894
+ break;
895
+ case 'aether_create_artifact':
896
+ result = await this.createArtifact(args);
897
+ break;
898
+ // Orchestra Tools
899
+ case 'aether_start_orchestra':
900
+ result = await this.startOrchestra(args);
901
+ break;
902
+ case 'aether_set_orchestra_plan':
903
+ result = await this.setOrchestraPlan(args);
904
+ break;
905
+ case 'aether_orchestra_approve':
906
+ result = await this.orchestraApprove(args);
907
+ break;
908
+ case 'aether_orchestra_status':
909
+ result = await this.orchestraStatus(args);
910
+ break;
911
+ case 'aether_orchestra_next':
912
+ result = await this.orchestraNext(args);
913
+ break;
914
+ case 'aether_orchestra_trigger_task':
915
+ result = await this.orchestraTriggerTask(args);
916
+ break;
917
+ case 'aether_report_to_orchestrator':
918
+ result = await this.reportToOrchestrator(args);
919
+ break;
920
+ case 'aether_orchestra_pause':
921
+ result = await this.orchestraPause(args);
922
+ break;
923
+ case 'aether_orchestra_resume':
924
+ result = await this.orchestraResume(args);
925
+ break;
926
+ case 'aether_complete_orchestra':
927
+ result = await this.completeOrchestra(args);
928
+ break;
929
+ default:
930
+ return this.errorResponse(id, -32602, `Unknown tool: ${name}`);
931
+ }
932
+
933
+ return {
934
+ jsonrpc: '2.0',
935
+ id,
936
+ result: {
937
+ content: [
938
+ {
939
+ type: 'text',
940
+ text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
941
+ },
942
+ ],
943
+ },
944
+ };
945
+ } catch (error) {
946
+ return {
947
+ jsonrpc: '2.0',
948
+ id,
949
+ result: {
950
+ content: [
951
+ {
952
+ type: 'text',
953
+ text: `Error: ${error.message}`,
954
+ },
955
+ ],
956
+ isError: true,
957
+ },
958
+ };
959
+ }
960
+ }
961
+
962
+ errorResponse(id, code, message) {
963
+ return {
964
+ jsonrpc: '2.0',
965
+ id,
966
+ error: { code, message },
967
+ };
968
+ }
969
+
970
+ // Tool implementations
971
+ async apiCall(endpoint, method = 'GET', body = null) {
972
+ const url = `${config.aetherUrl}/api${endpoint}`;
973
+ const headers = {
974
+ 'Content-Type': 'application/json',
975
+ };
976
+
977
+ if (config.apiKey) {
978
+ headers['Authorization'] = `Bearer ${config.apiKey}`;
979
+ }
980
+
981
+ const options = { method, headers };
982
+ if (body) {
983
+ options.body = JSON.stringify(body);
984
+ }
985
+
986
+ const response = await fetch(url, options);
987
+ if (!response.ok) {
988
+ throw new Error(`API error: ${response.status} ${response.statusText}`);
989
+ }
990
+ return response.json();
991
+ }
992
+
993
+ async getCanvasState(args) {
994
+ const projectId = args.projectId || config.projectId;
995
+ if (!projectId) {
996
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
997
+ }
998
+
999
+ try {
1000
+ const data = await this.apiCall(`/workspaces/${config.workspaceSlug}/projects/${projectId}/canvas`);
1001
+ return {
1002
+ nodes: data.nodes?.length || 0,
1003
+ edges: data.edges?.length || 0,
1004
+ details: data,
1005
+ };
1006
+ } catch (e) {
1007
+ return {
1008
+ message: 'Could not fetch canvas state. Make sure you are logged into Aether.',
1009
+ hint: 'Open https://www.aithr.ai and log in, then try again.',
1010
+ };
1011
+ }
1012
+ }
1013
+
1014
+ async addAgent(args) {
1015
+ const { agentType, task, position, parentNodeId, nodeType, outputArtifactType, systemPrompt } = args;
1016
+ const projectId = config.projectId;
1017
+
1018
+ if (!projectId) {
1019
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID.' };
1020
+ }
1021
+
1022
+ // Determine effective output artifact type
1023
+ // Only set if explicitly provided - let the agent use its natural output type otherwise
1024
+ // Use outputArtifactType: "code" for structured JSON (GitHub automation)
1025
+ // Use outputArtifactType: "ux_spec", "prd", etc. for documents
1026
+ const effectiveNodeType = nodeType || 'standard';
1027
+ const effectiveOutputType = outputArtifactType || undefined;
1028
+
1029
+ // Build the correct payload format for the API
1030
+ const nodeData = {
1031
+ agentId: agentType, // API expects 'agentId' not 'agentType'
1032
+ position: position || { x: 100 + Math.random() * 200, y: 100 + Math.random() * 200 },
1033
+ customConfig: {
1034
+ task: task || '',
1035
+ label: task ? task.split(' - ')[0] : agentType, // Use first part of task as label
1036
+ outputArtifactType: effectiveOutputType, // For structured output injection
1037
+ systemPrompt: systemPrompt || undefined, // Custom system prompt
1038
+ },
1039
+ agentType: effectiveNodeType, // 'standard', 'parent', or 'sub'
1040
+ parentNodeId: parentNodeId || undefined,
1041
+ };
1042
+
1043
+ try {
1044
+ const result = await this.apiCall(
1045
+ `/workspaces/${config.workspaceSlug}/projects/${projectId}/canvas/nodes`,
1046
+ 'POST',
1047
+ nodeData
1048
+ );
1049
+ return { success: true, nodeId: result.id, message: `Added ${agentType} agent`, node: result };
1050
+ } catch (e) {
1051
+ // Check if it's a tier restriction
1052
+ if (e.message.includes('403') || e.message.includes('Pro tier')) {
1053
+ return {
1054
+ success: false,
1055
+ error: 'Custom nodes require Pro tier. Upgrade at Settings > Billing.',
1056
+ hint: 'You can still add default agents through the Aether canvas UI.'
1057
+ };
1058
+ }
1059
+ return { success: false, error: e.message };
1060
+ }
1061
+ }
1062
+
1063
+ async removeNode(args) {
1064
+ const { nodeId } = args;
1065
+ const projectId = config.projectId;
1066
+
1067
+ try {
1068
+ await this.apiCall(
1069
+ `/workspaces/${config.workspaceSlug}/projects/${projectId}/canvas/nodes/${nodeId}`,
1070
+ 'DELETE'
1071
+ );
1072
+ return { success: true, message: `Removed node ${nodeId}` };
1073
+ } catch (e) {
1074
+ return { success: false, error: e.message };
1075
+ }
1076
+ }
1077
+
1078
+ async connectNodes(args) {
1079
+ const { sourceId, targetId, connectionType } = args;
1080
+ const projectId = config.projectId;
1081
+
1082
+ try {
1083
+ // Use MCP endpoint with API key auth
1084
+ const result = await this.mcpApiCall(
1085
+ `/mcp/projects/${projectId}/connections`,
1086
+ 'POST',
1087
+ {
1088
+ sourceNodeId: sourceId,
1089
+ targetNodeId: targetId,
1090
+ connectionType: connectionType || 'dependency'
1091
+ }
1092
+ );
1093
+ return {
1094
+ success: true,
1095
+ connectionId: result.connection?.id,
1096
+ message: result.message
1097
+ };
1098
+ } catch (e) {
1099
+ return { success: false, error: e.message };
1100
+ }
1101
+ }
1102
+
1103
+ async triggerGeneration(args) {
1104
+ const { nodeId, context } = args;
1105
+ const projectId = config.projectId;
1106
+
1107
+ if (!projectId) {
1108
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
1109
+ }
1110
+
1111
+ try {
1112
+ // The generate endpoint returns SSE (Server-Sent Events), so we need to handle it specially
1113
+ const url = `${config.aetherUrl}/api/workspaces/${config.workspaceSlug}/projects/${projectId}/agents/${nodeId}/generate`;
1114
+ const headers = {
1115
+ 'Content-Type': 'application/json',
1116
+ };
1117
+
1118
+ if (config.apiKey) {
1119
+ headers['Authorization'] = `Bearer ${config.apiKey}`;
1120
+ }
1121
+
1122
+ const response = await fetch(url, {
1123
+ method: 'POST',
1124
+ headers,
1125
+ body: JSON.stringify({ context }),
1126
+ });
1127
+
1128
+ if (!response.ok) {
1129
+ const errorText = await response.text();
1130
+ throw new Error(`API error: ${response.status} - ${errorText}`);
1131
+ }
1132
+
1133
+ // Read SSE stream and collect events
1134
+ const reader = response.body.getReader();
1135
+ const decoder = new TextDecoder();
1136
+ let buffer = '';
1137
+ let lastEvent = null;
1138
+ let artifactId = null;
1139
+ let sessionId = null;
1140
+ let error = null;
1141
+
1142
+ while (true) {
1143
+ const { done, value } = await reader.read();
1144
+ if (done) break;
1145
+
1146
+ buffer += decoder.decode(value, { stream: true });
1147
+
1148
+ // Parse SSE events from buffer
1149
+ const lines = buffer.split('\n');
1150
+ buffer = lines.pop() || ''; // Keep incomplete line in buffer
1151
+
1152
+ for (const line of lines) {
1153
+ if (line.startsWith('event: ')) {
1154
+ lastEvent = line.slice(7).trim();
1155
+ } else if (line.startsWith('data: ')) {
1156
+ try {
1157
+ const data = JSON.parse(line.slice(6));
1158
+
1159
+ if (lastEvent === 'generation_complete') {
1160
+ artifactId = data.artifactId;
1161
+ sessionId = data.sessionId;
1162
+ } else if (lastEvent === 'error') {
1163
+ error = data.error || 'Generation failed';
1164
+ }
1165
+ } catch {
1166
+ // Ignore JSON parse errors for partial data
1167
+ }
1168
+ }
1169
+ }
1170
+ }
1171
+
1172
+ if (error) {
1173
+ return { success: false, error };
1174
+ }
1175
+
1176
+ return {
1177
+ success: true,
1178
+ message: 'Generation completed',
1179
+ artifactId,
1180
+ sessionId,
1181
+ hint: artifactId
1182
+ ? 'Use aether_extract_files to extract code from the artifact, then aether_push_artifacts to push to GitHub.'
1183
+ : undefined,
1184
+ };
1185
+ } catch (e) {
1186
+ return { success: false, error: e.message };
1187
+ }
1188
+ }
1189
+
1190
+ async listProjects() {
1191
+ try {
1192
+ const data = await this.apiCall(`/workspaces/${config.workspaceSlug}/projects`);
1193
+ return data.map(p => ({ id: p.id, name: p.name, mode: p.mode }));
1194
+ } catch (e) {
1195
+ return { error: e.message };
1196
+ }
1197
+ }
1198
+
1199
+ async listAgents() {
1200
+ const projectId = config.projectId;
1201
+ if (!projectId) {
1202
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
1203
+ }
1204
+
1205
+ try {
1206
+ const data = await this.apiCall(`/workspaces/${config.workspaceSlug}/projects/${projectId}/canvas`);
1207
+ // Canvas API returns nodes directly - they have agentId, agentType, status, customConfig
1208
+ const nodes = data.nodes || [];
1209
+
1210
+ return {
1211
+ agents: nodes.map(n => ({
1212
+ id: n.id,
1213
+ agentId: n.agentId,
1214
+ agentType: n.agentType || 'standard',
1215
+ status: n.status,
1216
+ name: n.customConfig?.name || n.customConfig?.label || n.agentId,
1217
+ task: n.customConfig?.task,
1218
+ position: n.position,
1219
+ parentNodeId: n.parentNodeId,
1220
+ isCustom: n.isCustom,
1221
+ })),
1222
+ summary: {
1223
+ total: nodes.length,
1224
+ byType: nodes.reduce((acc, n) => {
1225
+ const type = n.agentType || 'standard';
1226
+ acc[type] = (acc[type] || 0) + 1;
1227
+ return acc;
1228
+ }, {}),
1229
+ byStatus: nodes.reduce((acc, n) => {
1230
+ const status = n.status || 'unknown';
1231
+ acc[status] = (acc[status] || 0) + 1;
1232
+ return acc;
1233
+ }, {}),
1234
+ },
1235
+ };
1236
+ } catch (e) {
1237
+ return { error: e.message };
1238
+ }
1239
+ }
1240
+
1241
+ async getArtifactContent(args) {
1242
+ const { artifactId } = args;
1243
+ const projectId = config.projectId;
1244
+
1245
+ if (!projectId) {
1246
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
1247
+ }
1248
+
1249
+ if (!artifactId) {
1250
+ return { error: 'Artifact ID is required' };
1251
+ }
1252
+
1253
+ try {
1254
+ const data = await this.apiCall(
1255
+ `/mcp/projects/${projectId}/artifacts/${artifactId}`
1256
+ );
1257
+ return {
1258
+ artifact: data.artifact,
1259
+ content: data.content,
1260
+ hint: data.content?.contentType === 'json' || data.content?.full?.startsWith('{')
1261
+ ? 'This looks like structured JSON output. Use aether_extract_files to parse it.'
1262
+ : 'This artifact does not appear to have structured JSON output.',
1263
+ };
1264
+ } catch (e) {
1265
+ return { error: e.message };
1266
+ }
1267
+ }
1268
+
1269
+ async getArtifacts(args) {
1270
+ const projectId = config.projectId;
1271
+ try {
1272
+ let url = `/workspaces/${config.workspaceSlug}/projects/${projectId}/artifacts`;
1273
+ if (args.artifactType) {
1274
+ url += `?type=${args.artifactType}`;
1275
+ }
1276
+ const data = await this.apiCall(url);
1277
+ return data.map(a => {
1278
+ // Handle content that might be object, string, or null
1279
+ let preview = '';
1280
+ if (typeof a.content === 'string') {
1281
+ preview = a.content.slice(0, 200);
1282
+ } else if (a.content && typeof a.content === 'object') {
1283
+ preview = JSON.stringify(a.content).slice(0, 200);
1284
+ }
1285
+ return {
1286
+ id: a.id,
1287
+ type: a.type,
1288
+ agentType: a.agentType,
1289
+ createdAt: a.createdAt,
1290
+ preview,
1291
+ };
1292
+ });
1293
+ } catch (e) {
1294
+ return { error: e.message };
1295
+ }
1296
+ }
1297
+
1298
+ async executeShell(args) {
1299
+ const { command, cwd } = args;
1300
+
1301
+ // Execute locally using child_process
1302
+ const { exec } = require('child_process');
1303
+ const { promisify } = require('util');
1304
+ const execAsync = promisify(exec);
1305
+
1306
+ try {
1307
+ const { stdout, stderr } = await execAsync(command, {
1308
+ cwd: cwd || process.cwd(),
1309
+ timeout: 30000,
1310
+ shell: process.platform === 'win32' ? 'powershell.exe' : '/bin/bash',
1311
+ });
1312
+
1313
+ return {
1314
+ success: true,
1315
+ stdout: stdout || '',
1316
+ stderr: stderr || '',
1317
+ };
1318
+ } catch (e) {
1319
+ return {
1320
+ success: false,
1321
+ exitCode: e.code,
1322
+ stdout: e.stdout || '',
1323
+ stderr: e.stderr || e.message,
1324
+ };
1325
+ }
1326
+ }
1327
+
1328
+ // Repository tools with recommendation messaging
1329
+ async listRepositories() {
1330
+ try {
1331
+ const data = await this.apiCall(`/workspaces/${config.workspaceSlug}/repositories`);
1332
+ return {
1333
+ recommendation: '💡 TIP: For faster file access, clone the repo locally: `git clone <url>` and use standard file tools (Read, Grep, Glob).',
1334
+ repositories: data.map(r => ({
1335
+ id: r.id,
1336
+ name: r.name,
1337
+ fullName: r.fullName,
1338
+ defaultBranch: r.defaultBranch,
1339
+ cloneUrl: r.cloneUrl,
1340
+ lastSyncedAt: r.lastSyncedAt,
1341
+ })),
1342
+ };
1343
+ } catch (e) {
1344
+ return { error: e.message };
1345
+ }
1346
+ }
1347
+
1348
+ async listRepoFiles(args) {
1349
+ const { repositoryId, path = '' } = args;
1350
+
1351
+ try {
1352
+ const url = `/workspaces/${config.workspaceSlug}/repositories/${repositoryId}/files${path ? `?path=${encodeURIComponent(path)}` : ''}`;
1353
+ const data = await this.apiCall(url);
1354
+ return {
1355
+ recommendation: '💡 TIP: For faster access, clone this repo locally and use standard file tools.',
1356
+ path: path || '/',
1357
+ files: data,
1358
+ };
1359
+ } catch (e) {
1360
+ return { error: e.message };
1361
+ }
1362
+ }
1363
+
1364
+ async readRepoFile(args) {
1365
+ const { repositoryId, filePath } = args;
1366
+
1367
+ try {
1368
+ // First get file info to get the file ID
1369
+ const filesUrl = `/workspaces/${config.workspaceSlug}/repositories/${repositoryId}/files?path=${encodeURIComponent(filePath)}`;
1370
+ const fileInfo = await this.apiCall(filesUrl);
1371
+
1372
+ if (!fileInfo || fileInfo.length === 0) {
1373
+ return { error: `File not found: ${filePath}` };
1374
+ }
1375
+
1376
+ const file = fileInfo.find(f => f.path === filePath || f.name === filePath.split('/').pop());
1377
+ if (!file) {
1378
+ return { error: `File not found: ${filePath}` };
1379
+ }
1380
+
1381
+ // Get file content
1382
+ const contentUrl = `/workspaces/${config.workspaceSlug}/repositories/${repositoryId}/files/${file.id}`;
1383
+ const content = await this.apiCall(contentUrl);
1384
+
1385
+ return {
1386
+ recommendation: '💡 TIP: For faster reads, clone the repo locally and use: Read tool with the file path.',
1387
+ filePath,
1388
+ content: content.content || content,
1389
+ };
1390
+ } catch (e) {
1391
+ return { error: e.message };
1392
+ }
1393
+ }
1394
+
1395
+ async searchRepo(args) {
1396
+ const { repositoryId, query, fileTypes } = args;
1397
+
1398
+ try {
1399
+ let url = `/workspaces/${config.workspaceSlug}/repositories/${repositoryId}/search?q=${encodeURIComponent(query)}`;
1400
+ if (fileTypes && fileTypes.length > 0) {
1401
+ url += `&fileTypes=${encodeURIComponent(fileTypes.join(','))}`;
1402
+ }
1403
+
1404
+ const data = await this.apiCall(url);
1405
+ return {
1406
+ recommendation: '💡 TIP: For faster searches, clone the repo locally and use: Grep tool for content search, Glob tool for file patterns.',
1407
+ query,
1408
+ results: data,
1409
+ };
1410
+ } catch (e) {
1411
+ return { error: e.message };
1412
+ }
1413
+ }
1414
+
1415
+ // =========================================================================
1416
+ // GitHub Automation Tool Implementations
1417
+ // =========================================================================
1418
+
1419
+ async getAutomationStatus(args) {
1420
+ const projectId = args.projectId || config.projectId;
1421
+ if (!projectId) {
1422
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
1423
+ }
1424
+
1425
+ try {
1426
+ // Use MCP endpoint which doesn't require workspace slug in path
1427
+ const data = await this.mcpApiCall(`/mcp/projects/${projectId}/push-artifacts`, 'GET');
1428
+ return {
1429
+ project: data.project,
1430
+ automation: data.automation,
1431
+ artifacts: {
1432
+ total: data.artifacts?.total || 0,
1433
+ parseable: data.artifacts?.parseable || 0,
1434
+ items: data.artifacts?.items?.map(a => ({
1435
+ id: a.id,
1436
+ title: a.title,
1437
+ parseable: a.parseable,
1438
+ filesCount: a.filesCount,
1439
+ })) || [],
1440
+ },
1441
+ generatedFiles: data.generatedFiles,
1442
+ hints: data.hints,
1443
+ };
1444
+ } catch (e) {
1445
+ return { error: e.message };
1446
+ }
1447
+ }
1448
+
1449
+ async extractFiles(args) {
1450
+ const { artifactId, saveToGeneratedFiles } = args;
1451
+ const projectId = config.projectId;
1452
+
1453
+ if (!projectId) {
1454
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
1455
+ }
1456
+
1457
+ try {
1458
+ // Use POST to get full file contents
1459
+ const data = await this.mcpApiCall(
1460
+ `/mcp/projects/${projectId}/artifacts/${artifactId}/files`,
1461
+ 'POST',
1462
+ { saveToGeneratedFiles: saveToGeneratedFiles || false }
1463
+ );
1464
+
1465
+ return {
1466
+ artifact: data.artifact,
1467
+ extraction: {
1468
+ success: data.extraction?.success,
1469
+ method: data.extraction?.method,
1470
+ filesCount: data.extraction?.files?.length || 0,
1471
+ files: data.extraction?.files?.map(f => ({
1472
+ path: f.path,
1473
+ language: f.language,
1474
+ contentLength: f.content?.length || 0,
1475
+ })) || [],
1476
+ warnings: data.extraction?.warnings,
1477
+ },
1478
+ readyForPush: data.readyForPush,
1479
+ savedFiles: data.savedFiles,
1480
+ };
1481
+ } catch (e) {
1482
+ return { error: e.message };
1483
+ }
1484
+ }
1485
+
1486
+ async pushArtifacts(args) {
1487
+ const projectId = config.projectId;
1488
+
1489
+ if (!projectId) {
1490
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
1491
+ }
1492
+
1493
+ const {
1494
+ artifactIds,
1495
+ branchName,
1496
+ commitMessage,
1497
+ prTitle,
1498
+ prBody,
1499
+ createPR = true,
1500
+ draft = false,
1501
+ dryRun = false,
1502
+ } = args;
1503
+
1504
+ try {
1505
+ const data = await this.mcpApiCall(
1506
+ `/mcp/projects/${projectId}/push-artifacts`,
1507
+ 'POST',
1508
+ {
1509
+ artifactIds,
1510
+ branchName,
1511
+ commitMessage,
1512
+ prTitle,
1513
+ prBody,
1514
+ createPR,
1515
+ draft,
1516
+ dryRun,
1517
+ }
1518
+ );
1519
+
1520
+ if (dryRun) {
1521
+ return {
1522
+ dryRun: true,
1523
+ wouldPush: data.wouldPush,
1524
+ message: `Would push ${data.wouldPush?.filesCount || 0} files to branch ${data.wouldPush?.branch}`,
1525
+ };
1526
+ }
1527
+
1528
+ return {
1529
+ success: data.success,
1530
+ branch: data.branch,
1531
+ commit: data.commit,
1532
+ pullRequest: data.pullRequest,
1533
+ filesCount: data.files?.length || 0,
1534
+ message: data.message,
1535
+ };
1536
+ } catch (e) {
1537
+ return { error: e.message };
1538
+ }
1539
+ }
1540
+
1541
+ // =========================================================================
1542
+ // Canvas Folder Tool Implementations
1543
+ // =========================================================================
1544
+
1545
+ async listFolders(args) {
1546
+ const projectId = args.projectId || config.projectId;
1547
+ if (!projectId) {
1548
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
1549
+ }
1550
+
1551
+ try {
1552
+ // Use MCP endpoint with API key auth
1553
+ const data = await this.mcpApiCall(`/mcp/projects/${projectId}/folders`);
1554
+
1555
+ return {
1556
+ folders: (data.folders || []).map(f => ({
1557
+ id: f.id,
1558
+ name: f.name,
1559
+ color: f.color,
1560
+ fileCount: f.fileCount || 0,
1561
+ position: f.canvasX && f.canvasY ? { x: f.canvasX, y: f.canvasY } : null,
1562
+ createdAt: f.createdAt,
1563
+ })),
1564
+ count: data.count || 0,
1565
+ };
1566
+ } catch (e) {
1567
+ return { error: e.message };
1568
+ }
1569
+ }
1570
+
1571
+ async createFolder(args) {
1572
+ const { name, color, position } = args;
1573
+ const projectId = config.projectId;
1574
+
1575
+ if (!projectId) {
1576
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
1577
+ }
1578
+
1579
+ if (!name) {
1580
+ return { error: 'Folder name is required' };
1581
+ }
1582
+
1583
+ try {
1584
+ // Use MCP endpoint with API key auth
1585
+ const result = await this.mcpApiCall(
1586
+ `/mcp/projects/${projectId}/folders`,
1587
+ 'POST',
1588
+ {
1589
+ name,
1590
+ color: color || 'blue',
1591
+ canvasX: position?.x,
1592
+ canvasY: position?.y,
1593
+ }
1594
+ );
1595
+
1596
+ return {
1597
+ success: true,
1598
+ folder: {
1599
+ id: result.folder?.id,
1600
+ name: result.folder?.name,
1601
+ color: result.folder?.color,
1602
+ },
1603
+ message: result.message || `Created folder "${name}"`,
1604
+ };
1605
+ } catch (e) {
1606
+ return { success: false, error: e.message };
1607
+ }
1608
+ }
1609
+
1610
+ async deleteFolder(args) {
1611
+ const { folderId } = args;
1612
+ const projectId = config.projectId;
1613
+
1614
+ if (!projectId) {
1615
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
1616
+ }
1617
+
1618
+ if (!folderId) {
1619
+ return { error: 'Folder ID is required' };
1620
+ }
1621
+
1622
+ try {
1623
+ // Use MCP endpoint with API key auth
1624
+ const result = await this.mcpApiCall(
1625
+ `/mcp/projects/${projectId}/folders/${folderId}`,
1626
+ 'DELETE'
1627
+ );
1628
+
1629
+ return {
1630
+ success: true,
1631
+ message: `Deleted folder ${folderId}`,
1632
+ };
1633
+ } catch (e) {
1634
+ return { success: false, error: e.message };
1635
+ }
1636
+ }
1637
+
1638
+ async addFileToFolder(args) {
1639
+ const { folderId, fileId, artifactId } = args;
1640
+ const projectId = config.projectId;
1641
+
1642
+ if (!projectId) {
1643
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
1644
+ }
1645
+
1646
+ if (!folderId) {
1647
+ return { error: 'Folder ID is required' };
1648
+ }
1649
+
1650
+ if (!fileId && !artifactId) {
1651
+ return { error: 'Either fileId or artifactId is required' };
1652
+ }
1653
+
1654
+ try {
1655
+ // If artifactId provided, use the artifact push-to-folder endpoint
1656
+ if (artifactId) {
1657
+ const result = await this.apiCall(
1658
+ `/workspaces/${config.workspaceSlug}/projects/${projectId}/artifacts/${artifactId}/push-to-folder`,
1659
+ 'POST',
1660
+ { folderId }
1661
+ );
1662
+
1663
+ return {
1664
+ success: true,
1665
+ message: `Added artifact to folder`,
1666
+ result,
1667
+ };
1668
+ }
1669
+
1670
+ // If fileId provided, update the file's folder assignment
1671
+ const result = await this.apiCall(
1672
+ `/workspaces/${config.workspaceSlug}/projects/${projectId}/workspace-files/${fileId}`,
1673
+ 'PATCH',
1674
+ { folderId }
1675
+ );
1676
+
1677
+ return {
1678
+ success: true,
1679
+ message: `Added file to folder`,
1680
+ result,
1681
+ };
1682
+ } catch (e) {
1683
+ return { success: false, error: e.message };
1684
+ }
1685
+ }
1686
+
1687
+ async createArtifact(args) {
1688
+ const { type, title, content, nodeId, agentId } = args;
1689
+ const projectId = config.projectId;
1690
+
1691
+ if (!projectId) {
1692
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
1693
+ }
1694
+
1695
+ if (!type) {
1696
+ return { error: 'Artifact type is required (e.g., "code", "ux_spec", "prd")' };
1697
+ }
1698
+
1699
+ if (!title) {
1700
+ return { error: 'Title is required' };
1701
+ }
1702
+
1703
+ if (!content) {
1704
+ return { error: 'Content is required' };
1705
+ }
1706
+
1707
+ try {
1708
+ const result = await this.mcpApiCall(
1709
+ `/mcp/projects/${projectId}/artifacts`,
1710
+ 'POST',
1711
+ {
1712
+ type,
1713
+ title,
1714
+ content,
1715
+ nodeId: nodeId || undefined,
1716
+ agentId: agentId || undefined,
1717
+ }
1718
+ );
1719
+
1720
+ return {
1721
+ success: true,
1722
+ message: `Created artifact: ${title}`,
1723
+ artifactId: result.artifact?.id,
1724
+ nodeUpdated: result.nodeUpdated,
1725
+ artifact: result.artifact,
1726
+ };
1727
+ } catch (e) {
1728
+ return { success: false, error: e.message };
1729
+ }
1730
+ }
1731
+
1732
+ // MCP API call helper (uses /api/mcp/* endpoints with API key auth)
1733
+ async mcpApiCall(endpoint, method = 'GET', body = null) {
1734
+ const url = `${config.aetherUrl}/api${endpoint}`;
1735
+ const headers = {
1736
+ 'Content-Type': 'application/json',
1737
+ };
1738
+
1739
+ if (config.apiKey) {
1740
+ headers['Authorization'] = `Bearer ${config.apiKey}`;
1741
+ }
1742
+
1743
+ const options = { method, headers };
1744
+ if (body) {
1745
+ options.body = JSON.stringify(body);
1746
+ }
1747
+
1748
+ const response = await fetch(url, options);
1749
+ const responseData = await response.json();
1750
+
1751
+ if (!response.ok) {
1752
+ // Include hints and code in error message for better debugging
1753
+ let errorMsg = responseData.error || `API error: ${response.status}`;
1754
+ if (responseData.code) {
1755
+ errorMsg += ` [${responseData.code}]`;
1756
+ }
1757
+ if (responseData.hints && responseData.hints.length > 0) {
1758
+ errorMsg += `. Hints: ${responseData.hints.join(' | ')}`;
1759
+ }
1760
+ throw new Error(errorMsg);
1761
+ }
1762
+
1763
+ return responseData;
1764
+ }
1765
+
1766
+ // =========================================================================
1767
+ // Orchestra Tool Implementations
1768
+ // =========================================================================
1769
+
1770
+ async startOrchestra(args) {
1771
+ const projectId = config.projectId;
1772
+
1773
+ if (!projectId) {
1774
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
1775
+ }
1776
+
1777
+ try {
1778
+ const data = await this.mcpApiCall(
1779
+ `/mcp/projects/${projectId}/orchestra`,
1780
+ 'POST',
1781
+ {
1782
+ action: 'start',
1783
+ maxIterations: args.maxIterations,
1784
+ maxAgents: args.maxAgents,
1785
+ timeoutMinutes: args.timeoutMinutes,
1786
+ completionCriteria: args.completionCriteria,
1787
+ }
1788
+ );
1789
+
1790
+ return {
1791
+ success: true,
1792
+ sessionId: data.sessionId,
1793
+ status: data.status,
1794
+ message: data.message,
1795
+ nextStep: 'Use aether_set_orchestra_plan to define the development plan',
1796
+ };
1797
+ } catch (e) {
1798
+ return { success: false, error: e.message };
1799
+ }
1800
+ }
1801
+
1802
+ async setOrchestraPlan(args) {
1803
+ const projectId = config.projectId;
1804
+
1805
+ if (!projectId) {
1806
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
1807
+ }
1808
+
1809
+ const { sessionId, planContent, phases, completionCriteria } = args;
1810
+
1811
+ if (!sessionId || !planContent || !phases) {
1812
+ return { error: 'sessionId, planContent, and phases are required' };
1813
+ }
1814
+
1815
+ try {
1816
+ const data = await this.mcpApiCall(
1817
+ `/mcp/projects/${projectId}/orchestra`,
1818
+ 'POST',
1819
+ {
1820
+ action: 'set-plan',
1821
+ sessionId,
1822
+ planContent,
1823
+ phases,
1824
+ completionCriteria,
1825
+ }
1826
+ );
1827
+
1828
+ return {
1829
+ success: true,
1830
+ sessionId,
1831
+ status: data.status,
1832
+ totalPhases: data.totalPhases,
1833
+ totalTasks: data.totalTasks,
1834
+ message: data.message,
1835
+ nextStep: 'Use aether_orchestra_approve to begin autonomous execution',
1836
+ };
1837
+ } catch (e) {
1838
+ return { success: false, error: e.message };
1839
+ }
1840
+ }
1841
+
1842
+ async orchestraApprove(args) {
1843
+ const projectId = config.projectId;
1844
+
1845
+ if (!projectId) {
1846
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
1847
+ }
1848
+
1849
+ const { sessionId } = args;
1850
+
1851
+ if (!sessionId) {
1852
+ return { error: 'sessionId is required' };
1853
+ }
1854
+
1855
+ try {
1856
+ const data = await this.mcpApiCall(
1857
+ `/mcp/projects/${projectId}/orchestra`,
1858
+ 'POST',
1859
+ {
1860
+ action: 'approve',
1861
+ sessionId,
1862
+ }
1863
+ );
1864
+
1865
+ return {
1866
+ success: true,
1867
+ sessionId,
1868
+ status: data.status,
1869
+ message: data.message,
1870
+ warning: '⚠️ ORCHESTRA STARTED: Claude Code is now in autonomous control. The system will execute tasks until stopped or completion criteria are met.',
1871
+ nextStep: 'Use aether_orchestra_next to get the first batch of ready tasks',
1872
+ };
1873
+ } catch (e) {
1874
+ return { success: false, error: e.message };
1875
+ }
1876
+ }
1877
+
1878
+ async orchestraStatus(args) {
1879
+ const projectId = config.projectId;
1880
+
1881
+ if (!projectId) {
1882
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
1883
+ }
1884
+
1885
+ try {
1886
+ let url = `/mcp/projects/${projectId}/orchestra`;
1887
+ const params = [];
1888
+ if (args.sessionId) params.push(`sessionId=${args.sessionId}`);
1889
+ if (args.includeReports !== undefined) params.push(`includeReports=${args.includeReports}`);
1890
+ if (args.includeLogs !== undefined) params.push(`includeLogs=${args.includeLogs}`);
1891
+ if (params.length > 0) url += `?${params.join('&')}`;
1892
+
1893
+ const data = await this.mcpApiCall(url);
1894
+
1895
+ if (!data.session) {
1896
+ return {
1897
+ hasSession: false,
1898
+ message: 'No orchestra session found. Use aether_start_orchestra to begin.',
1899
+ };
1900
+ }
1901
+
1902
+ return {
1903
+ session: data.session,
1904
+ progress: data.progress,
1905
+ currentPhase: data.currentPhase,
1906
+ phases: data.phases,
1907
+ tasks: data.tasks,
1908
+ reports: data.reports,
1909
+ safeguards: data.safeguards,
1910
+ completionCriteria: data.completionCriteria,
1911
+ };
1912
+ } catch (e) {
1913
+ return { error: e.message };
1914
+ }
1915
+ }
1916
+
1917
+ async orchestraNext(args) {
1918
+ const projectId = config.projectId;
1919
+
1920
+ if (!projectId) {
1921
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
1922
+ }
1923
+
1924
+ const { sessionId, maxTasks = 5 } = args;
1925
+
1926
+ if (!sessionId) {
1927
+ return { error: 'sessionId is required' };
1928
+ }
1929
+
1930
+ try {
1931
+ const data = await this.mcpApiCall(
1932
+ `/mcp/projects/${projectId}/orchestra/next`,
1933
+ 'POST',
1934
+ { sessionId, maxTasks }
1935
+ );
1936
+
1937
+ return {
1938
+ readyTasks: data.tasks || [],
1939
+ count: data.count || 0,
1940
+ hint: data.count > 0
1941
+ ? 'Use aether_orchestra_trigger_task with each taskId to start execution'
1942
+ : 'No tasks ready. Check aether_orchestra_status for blockers or completion.',
1943
+ };
1944
+ } catch (e) {
1945
+ return { error: e.message };
1946
+ }
1947
+ }
1948
+
1949
+ async orchestraTriggerTask(args) {
1950
+ const projectId = config.projectId;
1951
+
1952
+ if (!projectId) {
1953
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
1954
+ }
1955
+
1956
+ const { sessionId, taskId, additionalContext } = args;
1957
+
1958
+ if (!sessionId || !taskId) {
1959
+ return { error: 'sessionId and taskId are required' };
1960
+ }
1961
+
1962
+ try {
1963
+ const data = await this.mcpApiCall(
1964
+ `/mcp/projects/${projectId}/orchestra`,
1965
+ 'POST',
1966
+ {
1967
+ action: 'trigger-task',
1968
+ sessionId,
1969
+ taskId,
1970
+ additionalContext,
1971
+ }
1972
+ );
1973
+
1974
+ return {
1975
+ success: true,
1976
+ taskId: data.taskId,
1977
+ agentId: data.agentId,
1978
+ status: data.status,
1979
+ contextPrompt: data.contextPrompt,
1980
+ expectedOutput: data.expectedOutput,
1981
+ message: data.message,
1982
+ nextStep: `Now trigger the agent with aether_trigger_generation(nodeId: "...", context: "${data.contextPrompt || ''}")`,
1983
+ };
1984
+ } catch (e) {
1985
+ return { success: false, error: e.message };
1986
+ }
1987
+ }
1988
+
1989
+ async reportToOrchestrator(args) {
1990
+ const projectId = config.projectId;
1991
+
1992
+ if (!projectId) {
1993
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
1994
+ }
1995
+
1996
+ const { sessionId, taskId, reportType, summary, details, artifactId, blockerDescription, suggestedAction } = args;
1997
+
1998
+ if (!sessionId || !taskId || !reportType || !summary) {
1999
+ return { error: 'sessionId, taskId, reportType, and summary are required' };
2000
+ }
2001
+
2002
+ try {
2003
+ const data = await this.mcpApiCall(
2004
+ `/mcp/projects/${projectId}/orchestra/report`,
2005
+ 'POST',
2006
+ {
2007
+ sessionId,
2008
+ taskId,
2009
+ reportType,
2010
+ summary,
2011
+ details,
2012
+ artifactId,
2013
+ blockerDescription,
2014
+ suggestedAction,
2015
+ }
2016
+ );
2017
+
2018
+ return {
2019
+ success: true,
2020
+ reportId: data.reportId,
2021
+ taskStatus: data.taskStatus,
2022
+ message: data.message,
2023
+ };
2024
+ } catch (e) {
2025
+ return { success: false, error: e.message };
2026
+ }
2027
+ }
2028
+
2029
+ async orchestraPause(args) {
2030
+ const projectId = config.projectId;
2031
+
2032
+ if (!projectId) {
2033
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
2034
+ }
2035
+
2036
+ const { sessionId } = args;
2037
+
2038
+ if (!sessionId) {
2039
+ return { error: 'sessionId is required' };
2040
+ }
2041
+
2042
+ try {
2043
+ const data = await this.mcpApiCall(
2044
+ `/mcp/projects/${projectId}/orchestra`,
2045
+ 'POST',
2046
+ {
2047
+ action: 'pause',
2048
+ sessionId,
2049
+ }
2050
+ );
2051
+
2052
+ return {
2053
+ success: true,
2054
+ sessionId,
2055
+ status: data.status,
2056
+ message: data.message,
2057
+ };
2058
+ } catch (e) {
2059
+ return { success: false, error: e.message };
2060
+ }
2061
+ }
2062
+
2063
+ async orchestraResume(args) {
2064
+ const projectId = config.projectId;
2065
+
2066
+ if (!projectId) {
2067
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
2068
+ }
2069
+
2070
+ const { sessionId } = args;
2071
+
2072
+ if (!sessionId) {
2073
+ return { error: 'sessionId is required' };
2074
+ }
2075
+
2076
+ try {
2077
+ const data = await this.mcpApiCall(
2078
+ `/mcp/projects/${projectId}/orchestra`,
2079
+ 'POST',
2080
+ {
2081
+ action: 'resume',
2082
+ sessionId,
2083
+ }
2084
+ );
2085
+
2086
+ return {
2087
+ success: true,
2088
+ sessionId,
2089
+ status: data.status,
2090
+ message: data.message,
2091
+ };
2092
+ } catch (e) {
2093
+ return { success: false, error: e.message };
2094
+ }
2095
+ }
2096
+
2097
+ async completeOrchestra(args) {
2098
+ const projectId = config.projectId;
2099
+
2100
+ if (!projectId) {
2101
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
2102
+ }
2103
+
2104
+ const { sessionId, completionReason, summary } = args;
2105
+
2106
+ if (!sessionId || !completionReason) {
2107
+ return { error: 'sessionId and completionReason are required' };
2108
+ }
2109
+
2110
+ try {
2111
+ const data = await this.mcpApiCall(
2112
+ `/mcp/projects/${projectId}/orchestra`,
2113
+ 'POST',
2114
+ {
2115
+ action: 'complete',
2116
+ sessionId,
2117
+ completionReason,
2118
+ summary,
2119
+ }
2120
+ );
2121
+
2122
+ return {
2123
+ success: true,
2124
+ sessionId,
2125
+ status: data.status,
2126
+ completionReason: data.completionReason,
2127
+ summary: data.summary,
2128
+ message: data.message,
2129
+ };
2130
+ } catch (e) {
2131
+ return { success: false, error: e.message };
2132
+ }
2133
+ }
2134
+
2135
+ async run() {
2136
+ const rl = readline.createInterface({
2137
+ input: process.stdin,
2138
+ output: process.stdout,
2139
+ terminal: false,
2140
+ });
2141
+
2142
+ // Send initialized notification
2143
+ process.stderr.write('Aether MCP Server started\n');
2144
+
2145
+ let buffer = '';
2146
+
2147
+ rl.on('line', async (line) => {
2148
+ buffer += line;
2149
+
2150
+ try {
2151
+ const request = JSON.parse(buffer);
2152
+ buffer = '';
2153
+
2154
+ const response = await this.handleRequest(request);
2155
+ console.log(JSON.stringify(response));
2156
+ } catch (e) {
2157
+ // Incomplete JSON, wait for more
2158
+ if (e instanceof SyntaxError) {
2159
+ return;
2160
+ }
2161
+ console.log(JSON.stringify({
2162
+ jsonrpc: '2.0',
2163
+ id: null,
2164
+ error: { code: -32700, message: 'Parse error' },
2165
+ }));
2166
+ buffer = '';
2167
+ }
2168
+ });
2169
+
2170
+ rl.on('close', () => {
2171
+ process.exit(0);
2172
+ });
2173
+ }
2174
+ }
2175
+
2176
+ // Run the server
2177
+ const server = new MCPServer();
2178
+ server.run();