@aithr-ai/mcp-server 1.1.2 ā 1.1.4
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 +35 -0
- package/index.js +101 -23
- package/package.json +1 -1
- package/scripts/postinstall.js +119 -0
- package/skills/orchestra/SKILL.md +2476 -0
package/README.md
CHANGED
|
@@ -79,6 +79,41 @@ Visit [https://www.aithr.ai/settings/mcp-install](https://www.aithr.ai/settings/
|
|
|
79
79
|
- `aether_add_file_to_folder` - Add files to folders
|
|
80
80
|
- `aether_create_artifact` - Create and place artifacts
|
|
81
81
|
|
|
82
|
+
## Orchestra Skill (Auto-Installed)
|
|
83
|
+
|
|
84
|
+
When you install this package, it automatically installs the **Orchestra skill** to your Claude Code skills directory (`~/.claude/skills/orchestra/`).
|
|
85
|
+
|
|
86
|
+
This enables the `/orchestra` command with full v6 capabilities:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
/orchestra # Start planning a new session
|
|
90
|
+
/orchestra approve # Begin autonomous execution
|
|
91
|
+
/orchestra status # Check current progress
|
|
92
|
+
/orchestra push # Push to GitHub
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Orchestra v6 Features
|
|
96
|
+
|
|
97
|
+
- **Contract-First Development** - Define types, schemas, APIs upfront
|
|
98
|
+
- **Orchestrator Brain** - AI-powered decision engine (Claude Opus 4.5)
|
|
99
|
+
- **Dynamic Agent Switching** - Reassign tasks when agents struggle
|
|
100
|
+
- **Structured Reports** - Consistent agent-to-orchestrator communication
|
|
101
|
+
- **File Collision Prevention** - Safe parallel task execution with file locks
|
|
102
|
+
- **Validation Protocol** - TypeScript checks after every code generation
|
|
103
|
+
|
|
104
|
+
### Manual Skill Installation
|
|
105
|
+
|
|
106
|
+
If the auto-install doesn't work, manually copy the skill:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# The skill file is bundled at:
|
|
110
|
+
# node_modules/@aithr-ai/mcp-server/skills/orchestra/SKILL.md
|
|
111
|
+
|
|
112
|
+
# Copy to your Claude skills directory:
|
|
113
|
+
mkdir -p ~/.claude/skills/orchestra
|
|
114
|
+
cp node_modules/@aithr-ai/mcp-server/skills/orchestra/SKILL.md ~/.claude/skills/orchestra/
|
|
115
|
+
```
|
|
116
|
+
|
|
82
117
|
## Documentation
|
|
83
118
|
|
|
84
119
|
Full documentation at [https://www.aithr.ai/docs/orchestra](https://www.aithr.ai/docs/orchestra)
|
package/index.js
CHANGED
|
@@ -234,12 +234,26 @@ class MCPServer {
|
|
|
234
234
|
},
|
|
235
235
|
{
|
|
236
236
|
name: 'aether_list_projects',
|
|
237
|
-
description: 'List all projects in the current workspace',
|
|
237
|
+
description: 'List all projects in the current workspace. Shows which project is currently active.',
|
|
238
238
|
inputSchema: {
|
|
239
239
|
type: 'object',
|
|
240
240
|
properties: {},
|
|
241
241
|
},
|
|
242
242
|
},
|
|
243
|
+
{
|
|
244
|
+
name: 'aether_switch_project',
|
|
245
|
+
description: 'Switch to a different project. All subsequent commands will use this project. Use aether_list_projects to see available projects.',
|
|
246
|
+
inputSchema: {
|
|
247
|
+
type: 'object',
|
|
248
|
+
properties: {
|
|
249
|
+
projectId: {
|
|
250
|
+
type: 'string',
|
|
251
|
+
description: 'Project ID to switch to',
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
required: ['projectId'],
|
|
255
|
+
},
|
|
256
|
+
},
|
|
243
257
|
{
|
|
244
258
|
name: 'aether_list_agents',
|
|
245
259
|
description: 'List all agents on the current canvas',
|
|
@@ -1078,6 +1092,9 @@ class MCPServer {
|
|
|
1078
1092
|
case 'aether_list_projects':
|
|
1079
1093
|
result = await this.listProjects();
|
|
1080
1094
|
break;
|
|
1095
|
+
case 'aether_switch_project':
|
|
1096
|
+
result = await this.switchProject(args);
|
|
1097
|
+
break;
|
|
1081
1098
|
case 'aether_list_agents':
|
|
1082
1099
|
result = await this.listAgents();
|
|
1083
1100
|
break;
|
|
@@ -1253,21 +1270,24 @@ class MCPServer {
|
|
|
1253
1270
|
// Use MCP endpoint which supports API key auth
|
|
1254
1271
|
const data = await this.mcpApiCall(`/mcp/projects/${projectId}/canvas`);
|
|
1255
1272
|
|
|
1273
|
+
// Extract nodes and connections from the correct response structure
|
|
1274
|
+
const nodes = data.canvas?.nodes || [];
|
|
1275
|
+
const connections = data.canvas?.connections || [];
|
|
1276
|
+
|
|
1256
1277
|
// If summary mode, return only essential info (much smaller response)
|
|
1257
1278
|
if (summary) {
|
|
1258
|
-
const nodeSummary =
|
|
1279
|
+
const nodeSummary = nodes.map(node => ({
|
|
1259
1280
|
id: node.id,
|
|
1260
1281
|
agentId: node.agentId,
|
|
1261
1282
|
agentType: node.agentType,
|
|
1262
1283
|
status: node.status,
|
|
1263
|
-
label: node.
|
|
1284
|
+
label: node.customConfig?.label || node.agentId,
|
|
1264
1285
|
}));
|
|
1265
1286
|
|
|
1266
|
-
const edgeSummary =
|
|
1267
|
-
id:
|
|
1268
|
-
source:
|
|
1269
|
-
target:
|
|
1270
|
-
type: edge.data?.connectionType,
|
|
1287
|
+
const edgeSummary = connections.map(conn => ({
|
|
1288
|
+
id: conn.id,
|
|
1289
|
+
source: conn.sourceNodeId,
|
|
1290
|
+
target: conn.targetNodeId,
|
|
1271
1291
|
}));
|
|
1272
1292
|
|
|
1273
1293
|
return {
|
|
@@ -1280,8 +1300,8 @@ class MCPServer {
|
|
|
1280
1300
|
|
|
1281
1301
|
// Full response (can be very large)
|
|
1282
1302
|
return {
|
|
1283
|
-
nodes:
|
|
1284
|
-
edges:
|
|
1303
|
+
nodes: nodes.length,
|
|
1304
|
+
edges: connections.length,
|
|
1285
1305
|
details: data,
|
|
1286
1306
|
};
|
|
1287
1307
|
} catch (e) {
|
|
@@ -1488,7 +1508,53 @@ class MCPServer {
|
|
|
1488
1508
|
async listProjects() {
|
|
1489
1509
|
try {
|
|
1490
1510
|
const data = await this.apiCall(`/workspaces/${config.workspaceSlug}/projects`);
|
|
1491
|
-
|
|
1511
|
+
const projects = data.map(p => ({
|
|
1512
|
+
id: p.id,
|
|
1513
|
+
name: p.name,
|
|
1514
|
+
mode: p.mode,
|
|
1515
|
+
isActive: p.id === config.projectId,
|
|
1516
|
+
}));
|
|
1517
|
+
return {
|
|
1518
|
+
currentProjectId: config.projectId,
|
|
1519
|
+
currentProject: projects.find(p => p.isActive)?.name || 'Unknown',
|
|
1520
|
+
projects,
|
|
1521
|
+
hint: 'Use aether_switch_project(projectId) to switch to a different project.',
|
|
1522
|
+
};
|
|
1523
|
+
} catch (e) {
|
|
1524
|
+
return { error: e.message };
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
async switchProject(args) {
|
|
1529
|
+
const { projectId } = args;
|
|
1530
|
+
|
|
1531
|
+
if (!projectId) {
|
|
1532
|
+
return { error: 'projectId is required' };
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
// Verify the project exists in the workspace
|
|
1536
|
+
try {
|
|
1537
|
+
const data = await this.apiCall(`/workspaces/${config.workspaceSlug}/projects`);
|
|
1538
|
+
const project = data.find(p => p.id === projectId);
|
|
1539
|
+
|
|
1540
|
+
if (!project) {
|
|
1541
|
+
return {
|
|
1542
|
+
error: `Project ${projectId} not found in workspace`,
|
|
1543
|
+
availableProjects: data.map(p => ({ id: p.id, name: p.name })),
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
// Update the config
|
|
1548
|
+
const previousProjectId = config.projectId;
|
|
1549
|
+
config.projectId = projectId;
|
|
1550
|
+
|
|
1551
|
+
return {
|
|
1552
|
+
success: true,
|
|
1553
|
+
previousProject: previousProjectId,
|
|
1554
|
+
currentProject: projectId,
|
|
1555
|
+
projectName: project.name,
|
|
1556
|
+
message: `Switched to project "${project.name}" (${projectId}). All subsequent commands will use this project.`,
|
|
1557
|
+
};
|
|
1492
1558
|
} catch (e) {
|
|
1493
1559
|
return { error: e.message };
|
|
1494
1560
|
}
|
|
@@ -1503,8 +1569,8 @@ class MCPServer {
|
|
|
1503
1569
|
try {
|
|
1504
1570
|
// Use MCP endpoint which supports API key auth
|
|
1505
1571
|
const data = await this.mcpApiCall(`/mcp/projects/${projectId}/canvas`);
|
|
1506
|
-
// Canvas API returns nodes
|
|
1507
|
-
const nodes = data.nodes || [];
|
|
1572
|
+
// Canvas API returns nodes under data.canvas.nodes
|
|
1573
|
+
const nodes = data.canvas?.nodes || [];
|
|
1508
1574
|
|
|
1509
1575
|
return {
|
|
1510
1576
|
agents: nodes.map(n => ({
|
|
@@ -1666,15 +1732,18 @@ class MCPServer {
|
|
|
1666
1732
|
try {
|
|
1667
1733
|
// First get file info to get the file ID
|
|
1668
1734
|
const filesUrl = `/workspaces/${config.workspaceSlug}/repositories/${repositoryId}/files?path=${encodeURIComponent(filePath)}`;
|
|
1669
|
-
const
|
|
1735
|
+
const response = await this.apiCall(filesUrl);
|
|
1670
1736
|
|
|
1671
|
-
|
|
1737
|
+
// API returns { files: [...], totalFiles: N }
|
|
1738
|
+
const fileList = response.files || response || [];
|
|
1739
|
+
|
|
1740
|
+
if (!fileList || fileList.length === 0) {
|
|
1672
1741
|
return { error: `File not found: ${filePath}` };
|
|
1673
1742
|
}
|
|
1674
1743
|
|
|
1675
|
-
const file =
|
|
1744
|
+
const file = fileList.find(f => f.path === filePath || f.name === filePath.split('/').pop());
|
|
1676
1745
|
if (!file) {
|
|
1677
|
-
return { error: `File not found: ${filePath}` };
|
|
1746
|
+
return { error: `File not found: ${filePath}. Available files: ${fileList.slice(0, 5).map(f => f.path).join(', ')}` };
|
|
1678
1747
|
}
|
|
1679
1748
|
|
|
1680
1749
|
// Get file content
|
|
@@ -1695,16 +1764,25 @@ class MCPServer {
|
|
|
1695
1764
|
const { repositoryId, query, fileTypes } = args;
|
|
1696
1765
|
|
|
1697
1766
|
try {
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1767
|
+
const url = `/workspaces/${config.workspaceSlug}/repositories/${repositoryId}/search`;
|
|
1768
|
+
|
|
1769
|
+
// Search API is POST, not GET
|
|
1770
|
+
const data = await this.apiCall(url, {
|
|
1771
|
+
method: 'POST',
|
|
1772
|
+
body: JSON.stringify({
|
|
1773
|
+
query,
|
|
1774
|
+
mode: 'hybrid',
|
|
1775
|
+
limit: 20,
|
|
1776
|
+
languages: fileTypes, // Map fileTypes to languages filter
|
|
1777
|
+
}),
|
|
1778
|
+
});
|
|
1702
1779
|
|
|
1703
|
-
const data = await this.apiCall(url);
|
|
1704
1780
|
return {
|
|
1705
1781
|
recommendation: 'š” TIP: For faster searches, clone the repo locally and use: Grep tool for content search, Glob tool for file patterns.',
|
|
1706
1782
|
query,
|
|
1707
|
-
results: data,
|
|
1783
|
+
results: data.results || data,
|
|
1784
|
+
totalResults: data.totalResults,
|
|
1785
|
+
searchMode: data.searchMode,
|
|
1708
1786
|
};
|
|
1709
1787
|
} catch (e) {
|
|
1710
1788
|
return { error: e.message };
|
package/package.json
CHANGED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Aether MCP Server - Postinstall Script
|
|
5
|
+
*
|
|
6
|
+
* Automatically installs the Orchestra skill to the user's Claude Code skills directory.
|
|
7
|
+
* This enables the /orchestra command with full v6 capabilities.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
|
|
14
|
+
// Determine the Claude Code skills directory based on OS
|
|
15
|
+
function getClaudeSkillsDir() {
|
|
16
|
+
const homeDir = os.homedir();
|
|
17
|
+
|
|
18
|
+
if (process.platform === 'win32') {
|
|
19
|
+
return path.join(homeDir, '.claude', 'skills');
|
|
20
|
+
} else if (process.platform === 'darwin') {
|
|
21
|
+
return path.join(homeDir, '.claude', 'skills');
|
|
22
|
+
} else {
|
|
23
|
+
// Linux and others
|
|
24
|
+
return path.join(homeDir, '.claude', 'skills');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Source skill files (bundled with MCP package)
|
|
29
|
+
const sourceSkillsDir = path.join(__dirname, '..', 'skills');
|
|
30
|
+
|
|
31
|
+
// Target skills directory
|
|
32
|
+
const targetSkillsDir = getClaudeSkillsDir();
|
|
33
|
+
|
|
34
|
+
function copySkills() {
|
|
35
|
+
console.log('\nš¼ Aether MCP Server - Installing Orchestra Skill...\n');
|
|
36
|
+
|
|
37
|
+
// Check if source skills exist
|
|
38
|
+
if (!fs.existsSync(sourceSkillsDir)) {
|
|
39
|
+
console.log('ā ļø No skills to install (skills directory not found in package)');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Create target skills directory if it doesn't exist
|
|
44
|
+
if (!fs.existsSync(targetSkillsDir)) {
|
|
45
|
+
try {
|
|
46
|
+
fs.mkdirSync(targetSkillsDir, { recursive: true });
|
|
47
|
+
console.log(`š Created skills directory: ${targetSkillsDir}`);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.error(`ā Failed to create skills directory: ${err.message}`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Copy each skill folder
|
|
55
|
+
const skills = fs.readdirSync(sourceSkillsDir);
|
|
56
|
+
|
|
57
|
+
for (const skillName of skills) {
|
|
58
|
+
const sourceSkillPath = path.join(sourceSkillsDir, skillName);
|
|
59
|
+
const targetSkillPath = path.join(targetSkillsDir, skillName);
|
|
60
|
+
|
|
61
|
+
// Skip if not a directory
|
|
62
|
+
if (!fs.statSync(sourceSkillPath).isDirectory()) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Create skill directory
|
|
67
|
+
if (!fs.existsSync(targetSkillPath)) {
|
|
68
|
+
fs.mkdirSync(targetSkillPath, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Copy all files in the skill directory
|
|
72
|
+
const files = fs.readdirSync(sourceSkillPath);
|
|
73
|
+
|
|
74
|
+
for (const file of files) {
|
|
75
|
+
const sourceFile = path.join(sourceSkillPath, file);
|
|
76
|
+
const targetFile = path.join(targetSkillPath, file);
|
|
77
|
+
|
|
78
|
+
// Check if target exists and is newer (don't overwrite user modifications)
|
|
79
|
+
let shouldCopy = true;
|
|
80
|
+
if (fs.existsSync(targetFile)) {
|
|
81
|
+
const sourceStats = fs.statSync(sourceFile);
|
|
82
|
+
const targetStats = fs.statSync(targetFile);
|
|
83
|
+
|
|
84
|
+
// If target is newer, ask before overwriting (in non-interactive, skip)
|
|
85
|
+
if (targetStats.mtime > sourceStats.mtime) {
|
|
86
|
+
console.log(`āļø Skipping ${skillName}/${file} (local version is newer)`);
|
|
87
|
+
shouldCopy = false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (shouldCopy) {
|
|
92
|
+
try {
|
|
93
|
+
fs.copyFileSync(sourceFile, targetFile);
|
|
94
|
+
console.log(`ā
Installed: ${skillName}/${file}`);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.error(`ā Failed to copy ${file}: ${err.message}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log('\nš Orchestra skill installed successfully!');
|
|
103
|
+
console.log('\nš Usage:');
|
|
104
|
+
console.log(' /orchestra - Start planning a new orchestra session');
|
|
105
|
+
console.log(' /orchestra approve - Begin autonomous execution');
|
|
106
|
+
console.log(' /orchestra status - Check current progress');
|
|
107
|
+
console.log(' /orchestra push - Push to GitHub');
|
|
108
|
+
console.log('\nš” The skill file is located at:');
|
|
109
|
+
console.log(` ${path.join(targetSkillsDir, 'orchestra', 'SKILL.md')}`);
|
|
110
|
+
console.log('\n');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Run the installation
|
|
114
|
+
try {
|
|
115
|
+
copySkills();
|
|
116
|
+
} catch (err) {
|
|
117
|
+
console.error('ā Postinstall failed:', err.message);
|
|
118
|
+
// Don't exit with error - postinstall failures shouldn't break npm install
|
|
119
|
+
}
|