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.
Files changed (2) hide show
  1. package/index.js +266 -25
  2. 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.0';
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
- // Log to stderr if update available (visible in Cursor logs)
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 a new user with GitLab, Coolify, and SSH key setup. IMPORTANT: You must explicitly ask the user for their email, full name, password, and terms acceptance.',
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: must be true)'
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', 'name', 'password', 'accept_terms']
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 and deploy a project with GitLab repository and Coolify deployment. IMPORTANT: You must explicitly ask the user for project name, description, and deployment URL if deployment is enabled.',
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: Create User
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
- // Validate all required fields are provided
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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mlgym-deploy",
3
- "version": "2.2.0",
3
+ "version": "2.3.1",
4
4
  "description": "MCP server for GitLab Backend - User creation and project deployment",
5
5
  "main": "index.js",
6
6
  "type": "module",