@aithr-ai/mcp-server 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +22 -7
  2. package/index.js +300 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -14,14 +14,28 @@ claude mcp add --transport stdio aether \
14
14
  ```
15
15
 
16
16
  ### Windows
17
- ```cmd
18
- claude mcp add --transport stdio aether ^
19
- --env AETHER_API_KEY=YOUR_KEY ^
20
- --env AETHER_WORKSPACE_ID=your-workspace ^
21
- --env AETHER_PROJECT_ID=your-project-id ^
22
- -s user -- cmd /c npx -y @aithr-ai/mcp-server
17
+
18
+ Add this to your `~/.claude.json` file (in the `mcpServers` object):
19
+
20
+ ```json
21
+ {
22
+ "mcpServers": {
23
+ "aether": {
24
+ "type": "stdio",
25
+ "command": "cmd",
26
+ "args": ["/c", "npx", "-y", "@aithr-ai/mcp-server"],
27
+ "env": {
28
+ "AETHER_API_KEY": "YOUR_KEY",
29
+ "AETHER_WORKSPACE_ID": "your-workspace",
30
+ "AETHER_PROJECT_ID": "your-project-id"
31
+ }
32
+ }
33
+ }
34
+ }
23
35
  ```
24
36
 
37
+ > **Note**: The `claude mcp add` command has a bug on Windows that drops the command args. Manual config is required.
38
+
25
39
  ## Get Your Credentials
26
40
 
27
41
  Visit [https://www.aithr.ai/settings/mcp-install](https://www.aithr.ai/settings/mcp-install) for personalized install commands with your API key and project ID.
@@ -43,7 +57,8 @@ Visit [https://www.aithr.ai/settings/mcp-install](https://www.aithr.ai/settings/
43
57
  ### GitHub Automation
44
58
  - `aether_automation_status` - Check what's ready to push
45
59
  - `aether_extract_files` - Extract code from artifacts
46
- - `aether_push_artifacts` - Push to GitHub (branch + commit + PR)
60
+ - `aether_push_artifacts` - Push to GitHub via API (branch + commit + PR)
61
+ - `aether_push_local` - **NEW** Push using local git (no rate limits, recommended for large pushes)
47
62
 
48
63
  ### Orchestra (Autonomous Development)
49
64
  - `aether_start_orchestra` - Start orchestration session
package/index.js CHANGED
@@ -434,6 +434,45 @@ class MCPServer {
434
434
  },
435
435
  },
436
436
  },
437
+ {
438
+ name: 'aether_push_local',
439
+ description: 'Push artifacts to GitHub using local git (no rate limits). Requires a local clone of the repository. This is the recommended method for pushing many files.',
440
+ inputSchema: {
441
+ type: 'object',
442
+ properties: {
443
+ repoPath: {
444
+ type: 'string',
445
+ description: 'Path to local git repository (required)',
446
+ },
447
+ artifactIds: {
448
+ type: 'array',
449
+ items: { type: 'string' },
450
+ description: 'Specific artifact IDs to push (optional, uses all parseable if not provided)',
451
+ },
452
+ branchName: {
453
+ type: 'string',
454
+ description: 'Branch name (auto-generated if not provided)',
455
+ },
456
+ commitMessage: {
457
+ type: 'string',
458
+ description: 'Commit message (default: "feat: Add generated code from Aether AI")',
459
+ },
460
+ prTitle: {
461
+ type: 'string',
462
+ description: 'Pull request title (optional, creates PR if provided)',
463
+ },
464
+ prBody: {
465
+ type: 'string',
466
+ description: 'Pull request body/description',
467
+ },
468
+ createPR: {
469
+ type: 'boolean',
470
+ description: 'Create a pull request after pushing (default: true)',
471
+ },
472
+ },
473
+ required: ['repoPath'],
474
+ },
475
+ },
437
476
  // =========================================================================
438
477
  // Canvas Folder Tools
439
478
  // =========================================================================
@@ -934,6 +973,9 @@ class MCPServer {
934
973
  case 'aether_push_artifacts':
935
974
  result = await this.pushArtifacts(args);
936
975
  break;
976
+ case 'aether_push_local':
977
+ result = await this.pushLocal(args);
978
+ break;
937
979
  // Canvas Folder Tools
938
980
  case 'aether_list_folders':
939
981
  result = await this.listFolders(args);
@@ -1644,6 +1686,264 @@ class MCPServer {
1644
1686
  }
1645
1687
  }
1646
1688
 
1689
+ /**
1690
+ * Push artifacts using local git (no API rate limits)
1691
+ * This writes files to a local git repo and uses git CLI to push
1692
+ */
1693
+ async pushLocal(args) {
1694
+ const {
1695
+ repoPath,
1696
+ artifactIds,
1697
+ branchName,
1698
+ commitMessage = 'feat: Add generated code from Aether AI',
1699
+ prTitle,
1700
+ prBody,
1701
+ createPR = true,
1702
+ } = args;
1703
+
1704
+ const projectId = config.projectId;
1705
+ if (!projectId) {
1706
+ return { error: 'No project ID configured. Set AETHER_PROJECT_ID environment variable.' };
1707
+ }
1708
+
1709
+ if (!repoPath) {
1710
+ return { error: 'repoPath is required - path to local git repository' };
1711
+ }
1712
+
1713
+ const { execSync, spawnSync } = require('child_process');
1714
+ const fs = require('fs');
1715
+ const path = require('path');
1716
+
1717
+ // Verify repo path exists and is a git repo
1718
+ if (!fs.existsSync(repoPath)) {
1719
+ return { error: `Repository path does not exist: ${repoPath}` };
1720
+ }
1721
+ if (!fs.existsSync(path.join(repoPath, '.git'))) {
1722
+ return { error: `Not a git repository: ${repoPath}` };
1723
+ }
1724
+
1725
+ try {
1726
+ // Get artifacts and extract files via API (dry run to get file list)
1727
+ const dryRunData = await this.mcpApiCall(
1728
+ `/mcp/projects/${projectId}/push-artifacts`,
1729
+ 'POST',
1730
+ {
1731
+ artifactIds,
1732
+ dryRun: true,
1733
+ }
1734
+ );
1735
+
1736
+ if (!dryRunData.wouldPush || !dryRunData.wouldPush.files) {
1737
+ return { error: 'No files to push', details: dryRunData };
1738
+ }
1739
+
1740
+ const files = dryRunData.wouldPush.files;
1741
+ const branch = branchName || dryRunData.wouldPush.branch || `aether-${Date.now().toString(36)}`;
1742
+
1743
+ // Get current branch to return to later
1744
+ const originalBranch = execSync('git rev-parse --abbrev-ref HEAD', {
1745
+ cwd: repoPath,
1746
+ encoding: 'utf-8'
1747
+ }).trim();
1748
+
1749
+ // Fetch latest and create/checkout branch
1750
+ try {
1751
+ execSync('git fetch origin', { cwd: repoPath, stdio: 'pipe' });
1752
+ } catch (e) {
1753
+ // Fetch might fail if no remote, continue anyway
1754
+ }
1755
+
1756
+ // Check if branch exists remotely or locally
1757
+ let branchExists = false;
1758
+ try {
1759
+ execSync(`git rev-parse --verify ${branch}`, { cwd: repoPath, stdio: 'pipe' });
1760
+ branchExists = true;
1761
+ } catch (e) {
1762
+ // Branch doesn't exist locally
1763
+ }
1764
+
1765
+ if (branchExists) {
1766
+ execSync(`git checkout ${branch}`, { cwd: repoPath, stdio: 'pipe' });
1767
+ } else {
1768
+ // Create new branch from current HEAD (or main/master)
1769
+ execSync(`git checkout -b ${branch}`, { cwd: repoPath, stdio: 'pipe' });
1770
+ }
1771
+
1772
+ // Get artifact content and write files
1773
+ // We need to fetch the actual file contents from the artifacts
1774
+ let totalFiles = 0;
1775
+ const writtenFiles = [];
1776
+
1777
+ // Fetch all artifacts to get their content
1778
+ const artifactsData = await this.mcpApiCall(`/mcp/projects/${projectId}/artifacts`);
1779
+ const artifactMap = new Map();
1780
+ for (const a of (artifactsData.artifacts || [])) {
1781
+ if (!artifactIds || artifactIds.length === 0 || artifactIds.includes(a.id)) {
1782
+ // Get full artifact content
1783
+ const fullArtifact = await this.mcpApiCall(`/mcp/projects/${projectId}/artifacts/${a.id}`);
1784
+ if (fullArtifact.content) {
1785
+ artifactMap.set(a.id, fullArtifact);
1786
+ }
1787
+ }
1788
+ }
1789
+
1790
+ // Parse artifacts and write files
1791
+ for (const [artifactId, artifact] of artifactMap) {
1792
+ const parsedFiles = this.parseCodeBlocks(artifact.content);
1793
+
1794
+ for (const file of parsedFiles) {
1795
+ const filePath = path.join(repoPath, file.path);
1796
+ const dirPath = path.dirname(filePath);
1797
+
1798
+ // Create directory if needed
1799
+ if (!fs.existsSync(dirPath)) {
1800
+ fs.mkdirSync(dirPath, { recursive: true });
1801
+ }
1802
+
1803
+ // Write file
1804
+ fs.writeFileSync(filePath, file.content, 'utf-8');
1805
+ writtenFiles.push(file.path);
1806
+ totalFiles++;
1807
+ }
1808
+ }
1809
+
1810
+ if (totalFiles === 0) {
1811
+ // Checkout back to original branch
1812
+ execSync(`git checkout ${originalBranch}`, { cwd: repoPath, stdio: 'pipe' });
1813
+ return { error: 'No files could be extracted from artifacts' };
1814
+ }
1815
+
1816
+ // Stage all changes
1817
+ execSync('git add -A', { cwd: repoPath, stdio: 'pipe' });
1818
+
1819
+ // Check if there are changes to commit
1820
+ const status = execSync('git status --porcelain', { cwd: repoPath, encoding: 'utf-8' });
1821
+ if (!status.trim()) {
1822
+ execSync(`git checkout ${originalBranch}`, { cwd: repoPath, stdio: 'pipe' });
1823
+ return {
1824
+ success: false,
1825
+ message: 'No changes to commit - files may already exist',
1826
+ filesProcessed: totalFiles,
1827
+ };
1828
+ }
1829
+
1830
+ // Commit
1831
+ const commitMsg = `${commitMessage}\n\nšŸ¤– Generated with Aether AI`;
1832
+ execSync(`git commit -m "${commitMsg.replace(/"/g, '\\"')}"`, { cwd: repoPath, stdio: 'pipe' });
1833
+
1834
+ // Get commit SHA
1835
+ const commitSha = execSync('git rev-parse HEAD', { cwd: repoPath, encoding: 'utf-8' }).trim();
1836
+
1837
+ // Push to remote
1838
+ try {
1839
+ execSync(`git push -u origin ${branch}`, { cwd: repoPath, stdio: 'pipe' });
1840
+ } catch (pushError) {
1841
+ return {
1842
+ success: false,
1843
+ error: `Push failed: ${pushError.message}`,
1844
+ hint: 'Make sure you have push access to the repository',
1845
+ branch,
1846
+ commitSha,
1847
+ filesWritten: totalFiles,
1848
+ };
1849
+ }
1850
+
1851
+ // Create PR via API if requested
1852
+ let prResult = null;
1853
+ if (createPR && prTitle) {
1854
+ try {
1855
+ // We need to call GitHub API to create PR
1856
+ // Get repo info from git remote
1857
+ const remoteUrl = execSync('git remote get-url origin', { cwd: repoPath, encoding: 'utf-8' }).trim();
1858
+ const repoMatch = remoteUrl.match(/github\.com[:/](.+?)(?:\.git)?$/);
1859
+
1860
+ if (repoMatch) {
1861
+ const repoFullName = repoMatch[1].replace(/\.git$/, '');
1862
+
1863
+ // Create PR via Aether's API (which has the GitHub token)
1864
+ prResult = await this.mcpApiCall(
1865
+ `/mcp/projects/${projectId}/github/pull-request`,
1866
+ 'POST',
1867
+ {
1868
+ headBranch: branch,
1869
+ title: prTitle,
1870
+ body: prBody || `## Summary\n\nGenerated ${totalFiles} files from Aether AI.\n\nšŸ¤– Generated with Aether`,
1871
+ }
1872
+ );
1873
+ }
1874
+ } catch (prError) {
1875
+ // PR creation failed but push succeeded
1876
+ prResult = { error: prError.message };
1877
+ }
1878
+ }
1879
+
1880
+ // Checkout back to original branch
1881
+ try {
1882
+ execSync(`git checkout ${originalBranch}`, { cwd: repoPath, stdio: 'pipe' });
1883
+ } catch (e) {
1884
+ // Ignore checkout errors
1885
+ }
1886
+
1887
+ return {
1888
+ success: true,
1889
+ method: 'local_git',
1890
+ branch,
1891
+ commit: {
1892
+ sha: commitSha,
1893
+ message: commitMessage,
1894
+ },
1895
+ filesWritten: totalFiles,
1896
+ files: writtenFiles.slice(0, 20), // First 20 files
1897
+ pullRequest: prResult,
1898
+ message: `Successfully pushed ${totalFiles} files to ${branch} using local git`,
1899
+ };
1900
+
1901
+ } catch (e) {
1902
+ return {
1903
+ error: e.message,
1904
+ hint: 'Make sure the repository path is correct and you have git installed',
1905
+ };
1906
+ }
1907
+ }
1908
+
1909
+ /**
1910
+ * Parse code blocks from artifact content
1911
+ */
1912
+ parseCodeBlocks(content) {
1913
+ const files = [];
1914
+ if (!content) return files;
1915
+
1916
+ // Match ```language:path/to/file or ```path/to/file patterns
1917
+ const codeBlockRegex = /```(?:(\w+):)?([^\s`]+)\n([\s\S]*?)```/g;
1918
+ let match;
1919
+
1920
+ while ((match = codeBlockRegex.exec(content)) !== null) {
1921
+ const language = match[1] || '';
1922
+ const filePath = match[2];
1923
+ const code = match[3];
1924
+
1925
+ // Skip if path looks invalid
1926
+ if (!filePath || filePath.includes('`') || filePath.length > 200) {
1927
+ continue;
1928
+ }
1929
+
1930
+ // Clean up path
1931
+ const cleanPath = filePath
1932
+ .replace(/^\//, '') // Remove leading slash
1933
+ .replace(/\.\.\//g, ''); // Remove ../ for security
1934
+
1935
+ if (cleanPath && code) {
1936
+ files.push({
1937
+ path: cleanPath,
1938
+ language,
1939
+ content: code.trim(),
1940
+ });
1941
+ }
1942
+ }
1943
+
1944
+ return files;
1945
+ }
1946
+
1647
1947
  // =========================================================================
1648
1948
  // Canvas Folder Tool Implementations
1649
1949
  // =========================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aithr-ai/mcp-server",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
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": {