mlgym-deploy 2.2.0 → 2.3.1
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/index.js +266 -25
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import crypto from 'crypto';
|
|
|
17
17
|
const execAsync = promisify(exec);
|
|
18
18
|
|
|
19
19
|
// Current version of this MCP server
|
|
20
|
-
const CURRENT_VERSION = '2.1
|
|
20
|
+
const CURRENT_VERSION = '2.3.1';
|
|
21
21
|
const PACKAGE_NAME = 'mlgym-deploy';
|
|
22
22
|
|
|
23
23
|
// Version check state
|
|
@@ -49,18 +49,7 @@ async function checkForUpdates() {
|
|
|
49
49
|
|
|
50
50
|
lastVersionCheck = Date.now();
|
|
51
51
|
|
|
52
|
-
//
|
|
53
|
-
if (isUpdateAvailable) {
|
|
54
|
-
console.error(`\n╔════════════════════════════════════════════════════════════════╗`);
|
|
55
|
-
console.error(`║ MLGym MCP Server Update Available! ║`);
|
|
56
|
-
console.error(`║ ║`);
|
|
57
|
-
console.error(`║ Current version: ${CURRENT_VERSION.padEnd(49)} ║`);
|
|
58
|
-
console.error(`║ Latest version: ${latestVersion.padEnd(49)} ║`);
|
|
59
|
-
console.error(`║ ║`);
|
|
60
|
-
console.error(`║ To update, run: npm install -g ${PACKAGE_NAME}@latest ║`);
|
|
61
|
-
console.error(`╚════════════════════════════════════════════════════════════════╝\n`);
|
|
62
|
-
}
|
|
63
|
-
|
|
52
|
+
// Don't log here since we'll block execution in main()
|
|
64
53
|
return versionCheckResult;
|
|
65
54
|
} catch (error) {
|
|
66
55
|
// Silent fail - don't disrupt normal operation
|
|
@@ -411,9 +400,29 @@ const TOOLS = [
|
|
|
411
400
|
}
|
|
412
401
|
}
|
|
413
402
|
},
|
|
403
|
+
{
|
|
404
|
+
name: 'mlgym_auth_check',
|
|
405
|
+
description: '🚨 ALWAYS USE THIS FIRST when deploying! Check if user account exists BEFORE asking for other details. Start by ONLY asking for email.',
|
|
406
|
+
inputSchema: {
|
|
407
|
+
type: 'object',
|
|
408
|
+
properties: {
|
|
409
|
+
email: {
|
|
410
|
+
type: 'string',
|
|
411
|
+
description: 'Email address to check (ask for this FIRST)',
|
|
412
|
+
pattern: '^[^@]+@[^@]+\\.[^@]+$'
|
|
413
|
+
},
|
|
414
|
+
password: {
|
|
415
|
+
type: 'string',
|
|
416
|
+
description: 'Password (ONLY ask if account exists)',
|
|
417
|
+
minLength: 8
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
required: ['email']
|
|
421
|
+
}
|
|
422
|
+
},
|
|
414
423
|
{
|
|
415
424
|
name: 'mlgym_user_create',
|
|
416
|
-
description: 'Create
|
|
425
|
+
description: 'Create new user or setup SSH. ⚠️ NEVER use this first! Always check with mlgym_auth_check before asking for full details.',
|
|
417
426
|
inputSchema: {
|
|
418
427
|
type: 'object',
|
|
419
428
|
properties: {
|
|
@@ -434,15 +443,20 @@ const TOOLS = [
|
|
|
434
443
|
},
|
|
435
444
|
accept_terms: {
|
|
436
445
|
type: 'boolean',
|
|
437
|
-
description: 'User must explicitly accept terms and conditions (REQUIRED
|
|
446
|
+
description: 'User must explicitly accept terms and conditions (REQUIRED for new users)'
|
|
447
|
+
},
|
|
448
|
+
setup_ssh_only: {
|
|
449
|
+
type: 'boolean',
|
|
450
|
+
description: 'Set to true to only setup SSH keys for existing user (skip user creation)',
|
|
451
|
+
default: false
|
|
438
452
|
}
|
|
439
453
|
},
|
|
440
|
-
required: ['email', '
|
|
454
|
+
required: ['email', 'password'] // Only email and password are always required
|
|
441
455
|
}
|
|
442
456
|
},
|
|
443
457
|
{
|
|
444
458
|
name: 'mlgym_project_init',
|
|
445
|
-
description: 'Initialize
|
|
459
|
+
description: 'Initialize project deployment. ⚠️ ONLY use AFTER authentication! Ask for project details AFTER user is logged in, not before.',
|
|
446
460
|
inputSchema: {
|
|
447
461
|
type: 'object',
|
|
448
462
|
properties: {
|
|
@@ -1181,6 +1195,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1181
1195
|
case 'mlgym_version_check':
|
|
1182
1196
|
return await versionCheck(args);
|
|
1183
1197
|
|
|
1198
|
+
case 'mlgym_auth_check':
|
|
1199
|
+
return await checkUserExists(args);
|
|
1200
|
+
|
|
1184
1201
|
case 'mlgym_user_create':
|
|
1185
1202
|
return await createUser(args);
|
|
1186
1203
|
|
|
@@ -1700,11 +1717,216 @@ async function configureGit(args) {
|
|
|
1700
1717
|
};
|
|
1701
1718
|
}
|
|
1702
1719
|
|
|
1703
|
-
// Tool implementation:
|
|
1720
|
+
// Tool implementation: Check if User Exists
|
|
1721
|
+
async function checkUserExists(args) {
|
|
1722
|
+
const { email, password } = args;
|
|
1723
|
+
|
|
1724
|
+
if (!email) {
|
|
1725
|
+
return {
|
|
1726
|
+
content: [{
|
|
1727
|
+
type: 'text',
|
|
1728
|
+
text: JSON.stringify({
|
|
1729
|
+
status: 'error',
|
|
1730
|
+
message: 'Email is required'
|
|
1731
|
+
}, null, 2)
|
|
1732
|
+
}]
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
console.error(`Checking if user exists: ${email}`);
|
|
1737
|
+
|
|
1738
|
+
// If password provided, try to login
|
|
1739
|
+
if (password) {
|
|
1740
|
+
try {
|
|
1741
|
+
const loginResult = await apiRequest('POST', '/api/v1/auth/login', {
|
|
1742
|
+
email,
|
|
1743
|
+
password
|
|
1744
|
+
}, false);
|
|
1745
|
+
|
|
1746
|
+
if (loginResult.success) {
|
|
1747
|
+
// Save auth token
|
|
1748
|
+
if (loginResult.data.token) {
|
|
1749
|
+
await saveAuth(email, loginResult.data.token);
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
// Check SSH keys
|
|
1753
|
+
const keysResult = await apiRequest('GET', '/api/v1/keys', null, true);
|
|
1754
|
+
const sshKeys = keysResult.success ? keysResult.data : [];
|
|
1755
|
+
|
|
1756
|
+
return {
|
|
1757
|
+
content: [{
|
|
1758
|
+
type: 'text',
|
|
1759
|
+
text: JSON.stringify({
|
|
1760
|
+
status: 'success',
|
|
1761
|
+
exists: true,
|
|
1762
|
+
authenticated: true,
|
|
1763
|
+
email: email,
|
|
1764
|
+
user_id: loginResult.data.user?.user_id,
|
|
1765
|
+
gitlab_username: loginResult.data.user?.gitlab_username,
|
|
1766
|
+
ssh_keys_count: sshKeys.length,
|
|
1767
|
+
has_ssh_keys: sshKeys.length > 0,
|
|
1768
|
+
ssh_keys: sshKeys.map(key => ({
|
|
1769
|
+
id: key.id,
|
|
1770
|
+
title: key.title,
|
|
1771
|
+
created_at: key.created_at
|
|
1772
|
+
})),
|
|
1773
|
+
message: 'User exists and authenticated successfully'
|
|
1774
|
+
}, null, 2)
|
|
1775
|
+
}]
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
} catch (err) {
|
|
1779
|
+
// Login failed, but user might exist
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
// Try to check if email exists without password (will fail but gives info)
|
|
1784
|
+
try {
|
|
1785
|
+
const result = await apiRequest('POST', '/api/v1/auth/login', {
|
|
1786
|
+
email,
|
|
1787
|
+
password: 'dummy_check'
|
|
1788
|
+
}, false);
|
|
1789
|
+
|
|
1790
|
+
// If we get here, it means the endpoint is accessible
|
|
1791
|
+
// The error message will tell us if user exists
|
|
1792
|
+
if (result.error && result.error.includes('Invalid credentials')) {
|
|
1793
|
+
return {
|
|
1794
|
+
content: [{
|
|
1795
|
+
type: 'text',
|
|
1796
|
+
text: JSON.stringify({
|
|
1797
|
+
status: 'success',
|
|
1798
|
+
exists: true,
|
|
1799
|
+
authenticated: false,
|
|
1800
|
+
email: email,
|
|
1801
|
+
message: 'User exists but needs password to authenticate',
|
|
1802
|
+
next_step: 'Please provide password to check SSH keys'
|
|
1803
|
+
}, null, 2)
|
|
1804
|
+
}]
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
} catch (err) {
|
|
1808
|
+
// Network or other error
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
// User doesn't exist
|
|
1812
|
+
return {
|
|
1813
|
+
content: [{
|
|
1814
|
+
type: 'text',
|
|
1815
|
+
text: JSON.stringify({
|
|
1816
|
+
status: 'success',
|
|
1817
|
+
exists: false,
|
|
1818
|
+
email: email,
|
|
1819
|
+
message: 'User does not exist. You can create a new account.',
|
|
1820
|
+
next_step: 'Use mlgym_user_create to create a new account'
|
|
1821
|
+
}, null, 2)
|
|
1822
|
+
}]
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
// Tool implementation: Create User or Setup SSH Keys
|
|
1704
1827
|
async function createUser(args) {
|
|
1705
|
-
let { email, name, password, accept_terms } = args;
|
|
1828
|
+
let { email, name, password, accept_terms, setup_ssh_only = false } = args;
|
|
1829
|
+
|
|
1830
|
+
// Check if this is SSH setup for existing user
|
|
1831
|
+
if (setup_ssh_only) {
|
|
1832
|
+
if (!email || !password) {
|
|
1833
|
+
return {
|
|
1834
|
+
content: [{
|
|
1835
|
+
type: 'text',
|
|
1836
|
+
text: JSON.stringify({
|
|
1837
|
+
status: 'error',
|
|
1838
|
+
message: 'Email and password required for SSH key setup',
|
|
1839
|
+
required_fields: {
|
|
1840
|
+
email: email ? '✓ provided' : '✗ missing',
|
|
1841
|
+
password: password ? '✓ provided' : '✗ missing'
|
|
1842
|
+
}
|
|
1843
|
+
}, null, 2)
|
|
1844
|
+
}]
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
console.error(`Setting up SSH keys for existing user: ${email}`);
|
|
1849
|
+
|
|
1850
|
+
// Login first
|
|
1851
|
+
try {
|
|
1852
|
+
const loginResult = await apiRequest('POST', '/api/v1/auth/login', {
|
|
1853
|
+
email,
|
|
1854
|
+
password
|
|
1855
|
+
}, false);
|
|
1856
|
+
|
|
1857
|
+
if (!loginResult.success) {
|
|
1858
|
+
return {
|
|
1859
|
+
content: [{
|
|
1860
|
+
type: 'text',
|
|
1861
|
+
text: `Failed to authenticate: ${loginResult.error}`
|
|
1862
|
+
}]
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1706
1865
|
|
|
1707
|
-
|
|
1866
|
+
// Save auth token
|
|
1867
|
+
if (loginResult.data.token) {
|
|
1868
|
+
await saveAuth(email, loginResult.data.token);
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
// Generate SSH key
|
|
1872
|
+
console.error('Generating SSH key pair...');
|
|
1873
|
+
const { publicKey, privateKeyPath } = await generateSSHKeyPair(email);
|
|
1874
|
+
const publicKeyPath = privateKeyPath + '.pub';
|
|
1875
|
+
console.error(`SSH key generated: ${privateKeyPath}`);
|
|
1876
|
+
|
|
1877
|
+
// Add SSH key
|
|
1878
|
+
const keyTitle = `mlgym-${new Date().toISOString().split('T')[0]}`;
|
|
1879
|
+
const keyResult = await apiRequest('POST', '/api/v1/keys', {
|
|
1880
|
+
title: keyTitle,
|
|
1881
|
+
key: publicKey
|
|
1882
|
+
}, true);
|
|
1883
|
+
|
|
1884
|
+
if (keyResult.success) {
|
|
1885
|
+
return {
|
|
1886
|
+
content: [{
|
|
1887
|
+
type: 'text',
|
|
1888
|
+
text: JSON.stringify({
|
|
1889
|
+
status: 'success',
|
|
1890
|
+
message: 'SSH key added successfully',
|
|
1891
|
+
email: email,
|
|
1892
|
+
ssh_key_path: privateKeyPath,
|
|
1893
|
+
ssh_key_id: keyResult.data.id,
|
|
1894
|
+
next_steps: [
|
|
1895
|
+
`SSH key generated at: ${privateKeyPath}`,
|
|
1896
|
+
`Add to SSH agent: ssh-add "${privateKeyPath}"`,
|
|
1897
|
+
'Wait 10-15 seconds for SSH key to propagate to GitLab',
|
|
1898
|
+
'You can now push code to GitLab repositories'
|
|
1899
|
+
]
|
|
1900
|
+
}, null, 2)
|
|
1901
|
+
}]
|
|
1902
|
+
};
|
|
1903
|
+
} else {
|
|
1904
|
+
return {
|
|
1905
|
+
content: [{
|
|
1906
|
+
type: 'text',
|
|
1907
|
+
text: JSON.stringify({
|
|
1908
|
+
status: 'error',
|
|
1909
|
+
message: 'Failed to add SSH key',
|
|
1910
|
+
error: keyResult.error,
|
|
1911
|
+
manual_steps: [
|
|
1912
|
+
`SSH key generated at: ${publicKeyPath}`,
|
|
1913
|
+
'Manually add it at: https://git.mlgym.io/-/user_settings/ssh_keys'
|
|
1914
|
+
]
|
|
1915
|
+
}, null, 2)
|
|
1916
|
+
}]
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
} catch (err) {
|
|
1920
|
+
return {
|
|
1921
|
+
content: [{
|
|
1922
|
+
type: 'text',
|
|
1923
|
+
text: `Error setting up SSH keys: ${err.message}`
|
|
1924
|
+
}]
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
// Regular user creation - validate all required fields
|
|
1708
1930
|
if (!email || !name || !password || accept_terms !== true) {
|
|
1709
1931
|
return {
|
|
1710
1932
|
content: [{
|
|
@@ -6105,14 +6327,33 @@ async function replicateProject(args) {
|
|
|
6105
6327
|
|
|
6106
6328
|
// Start the server
|
|
6107
6329
|
async function main() {
|
|
6330
|
+
// Check for updates BEFORE starting the server
|
|
6331
|
+
const updateCheck = await checkForUpdates();
|
|
6332
|
+
|
|
6333
|
+
if (updateCheck.updateAvailable) {
|
|
6334
|
+
// Block execution and show update required message
|
|
6335
|
+
console.error(`\n╔════════════════════════════════════════════════════════════════════╗`);
|
|
6336
|
+
console.error(`║ 🚫 UPDATE REQUIRED 🚫 ║`);
|
|
6337
|
+
console.error(`║ ║`);
|
|
6338
|
+
console.error(`║ The MLGym MCP Server is out of date and cannot run. ║`);
|
|
6339
|
+
console.error(`║ ║`);
|
|
6340
|
+
console.error(`║ Current version: ${CURRENT_VERSION.padEnd(52)} ║`);
|
|
6341
|
+
console.error(`║ Required version: ${updateCheck.latest.padEnd(51)} ║`);
|
|
6342
|
+
console.error(`║ ║`);
|
|
6343
|
+
console.error(`║ Please update before continuing: ║`);
|
|
6344
|
+
console.error(`║ ║`);
|
|
6345
|
+
console.error(`║ npm install -g ${PACKAGE_NAME}@latest ║`);
|
|
6346
|
+
console.error(`║ ║`);
|
|
6347
|
+
console.error(`║ Then restart your IDE or MCP client. ║`);
|
|
6348
|
+
console.error(`╚════════════════════════════════════════════════════════════════════════╝\n`);
|
|
6349
|
+
|
|
6350
|
+
// Exit with error code to prevent MCP from starting
|
|
6351
|
+
process.exit(1);
|
|
6352
|
+
}
|
|
6353
|
+
|
|
6108
6354
|
const transport = new StdioServerTransport();
|
|
6109
6355
|
await server.connect(transport);
|
|
6110
6356
|
console.error(`GitLab Backend MCP Server v${CURRENT_VERSION} started`);
|
|
6111
|
-
|
|
6112
|
-
// Check for updates in the background (don't block startup)
|
|
6113
|
-
setTimeout(async () => {
|
|
6114
|
-
await checkForUpdates();
|
|
6115
|
-
}, 2000); // Check after 2 seconds
|
|
6116
6357
|
}
|
|
6117
6358
|
|
|
6118
6359
|
main().catch((error) => {
|