@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.
- package/README.md +22 -7
- package/index.js +300 -0
- 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
|
-
|
|
18
|
-
claude
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
// =========================================================================
|