@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.
- package/README.md +137 -0
- package/index.js +2178 -0
- 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();
|