@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 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 = (data.nodes || []).map(node => ({
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.data?.label || node.agentId,
1284
+ label: node.customConfig?.label || node.agentId,
1264
1285
  }));
1265
1286
 
1266
- const edgeSummary = (data.edges || []).map(edge => ({
1267
- id: edge.id,
1268
- source: edge.source,
1269
- target: edge.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: data.nodes?.length || 0,
1284
- edges: data.edges?.length || 0,
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
- return data.map(p => ({ id: p.id, name: p.name, mode: p.mode }));
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 directly - they have agentId, agentType, status, customConfig
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 fileInfo = await this.apiCall(filesUrl);
1735
+ const response = await this.apiCall(filesUrl);
1670
1736
 
1671
- if (!fileInfo || fileInfo.length === 0) {
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 = fileInfo.find(f => f.path === filePath || f.name === filePath.split('/').pop());
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
- let url = `/workspaces/${config.workspaceSlug}/repositories/${repositoryId}/search?q=${encodeURIComponent(query)}`;
1699
- if (fileTypes && fileTypes.length > 0) {
1700
- url += `&fileTypes=${encodeURIComponent(fileTypes.join(','))}`;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aithr-ai/mcp-server",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "MCP server to connect Claude Code to Aether canvas - AI-powered team workspace for software development",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -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
+ }