mlgym-deploy 2.1.0 → 2.3.0

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/index-v2.js +24 -2
  2. package/index.js +479 -26
  3. package/package.json +1 -1
package/index-v2.js CHANGED
@@ -964,25 +964,44 @@ async function deployProject(sessionId, args) {
964
964
  description: `Deployed via MLGym MCP from ${session.framework} project`,
965
965
  visibility: 'private',
966
966
  enable_deployment: true,
967
- deployment_region: region,
968
967
  webhook_secret: crypto.randomBytes(16).toString('hex')
968
+ // Note: deployment_region is not used by backend, it uses random server selection
969
969
  };
970
970
 
971
+ console.error(`Creating project with deployment enabled for ${session.projectName} in region ${region}`);
971
972
  const result = await apiRequest('POST', '/api/v1/projects', projectData);
972
973
 
973
974
  if (!result.success) {
975
+ console.error(`Project creation failed: ${result.error}`);
976
+ // Provide more detailed error information
974
977
  return {
975
978
  content: [{
976
979
  type: 'text',
977
980
  text: JSON.stringify({
978
981
  status: 'error',
979
- message: `Deployment failed: ${result.error}`
982
+ message: `Deployment failed: ${result.error}`,
983
+ details: {
984
+ project_name: session.projectName,
985
+ region: region,
986
+ framework: session.framework,
987
+ suggestion: 'Please ensure the backend and Coolify services are running properly'
988
+ }
980
989
  }, null, 2)
981
990
  }]
982
991
  };
983
992
  }
984
993
 
985
994
  const project = result.data;
995
+ console.error(`Project created successfully: ${project.name} (ID: ${project.id})`);
996
+ console.error(`SSH URL: ${project.ssh_url_to_repo}`);
997
+
998
+ // Check if deployment was actually created
999
+ const deploymentCreated = project.deployment_status || project.coolify_resource_id;
1000
+ if (deploymentCreated) {
1001
+ console.error(`Deployment resource created in Coolify`);
1002
+ } else {
1003
+ console.error(`Warning: Project created but deployment resource might not be set up`);
1004
+ }
986
1005
 
987
1006
  // Initialize git repository
988
1007
  const gitCommands = [
@@ -990,6 +1009,8 @@ async function deployProject(sessionId, args) {
990
1009
  `git remote add origin ${project.ssh_url_to_repo}`,
991
1010
  'git add .',
992
1011
  'git commit -m "Initial deployment via MLGym"',
1012
+ '# Wait 5-10 seconds for SSH key to propagate in GitLab',
1013
+ 'sleep 10',
993
1014
  'git push -u origin main'
994
1015
  ];
995
1016
 
@@ -1014,6 +1035,7 @@ async function deployProject(sessionId, args) {
1014
1035
  ssl: 'auto-provisioned',
1015
1036
  cdn: 'enabled'
1016
1037
  },
1038
+ important_note: '⚠️ SSH key propagation: Please wait 10 seconds before pushing to allow GitLab to activate your SSH key',
1017
1039
  next_steps: {
1018
1040
  message: 'Run these commands in your project directory:',
1019
1041
  commands: gitCommands
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.0';
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: 'Check if a user account exists and whether SSH keys are configured. Use this BEFORE creating a new account to avoid duplicates.',
406
+ inputSchema: {
407
+ type: 'object',
408
+ properties: {
409
+ email: {
410
+ type: 'string',
411
+ description: 'Email address to check',
412
+ pattern: '^[^@]+@[^@]+\\.[^@]+$'
413
+ },
414
+ password: {
415
+ type: 'string',
416
+ description: 'Password (optional - only needed 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 a new user OR setup SSH keys for existing user. First use mlgym_auth_check to verify if account exists.',
417
426
  inputSchema: {
418
427
  type: 'object',
419
428
  properties: {
@@ -434,10 +443,15 @@ 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
  {
@@ -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}`);
1706
1849
 
1707
- // Validate all required fields are provided
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
+ }
1865
+
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: [{
@@ -2019,10 +2241,47 @@ async function initProject(args) {
2019
2241
  namespace: project.namespace?.path || project.namespace || auth.email.split('@')[0]
2020
2242
  };
2021
2243
 
2022
- // If deployment was enabled, the backend has already created webhook and Coolify resources
2244
+ // If deployment was enabled, create Dockerfile if not exists
2023
2245
  if (enable_deployment) {
2024
2246
  console.error('Deployment enabled - backend has set up webhook and Coolify resources automatically');
2025
2247
 
2248
+ // Check if Dockerfile already exists
2249
+ const dockerfilePath = path.join(local_path, 'Dockerfile');
2250
+ let dockerfileCreated = false;
2251
+
2252
+ try {
2253
+ await fs.access(dockerfilePath);
2254
+ console.error('Dockerfile already exists in project');
2255
+ } catch {
2256
+ // Dockerfile doesn't exist, create one based on detected framework
2257
+ console.error('No Dockerfile found, generating one...');
2258
+
2259
+ try {
2260
+ const framework = await detectFramework(local_path);
2261
+ console.error(`Detected framework: ${framework}`);
2262
+
2263
+ const dockerfileContent = generateDockerfile(framework, name);
2264
+ await fs.writeFile(dockerfilePath, dockerfileContent, 'utf8');
2265
+ dockerfileCreated = true;
2266
+
2267
+ console.error(`Created Dockerfile for ${framework} framework`);
2268
+
2269
+ // If React app, also create nginx.conf
2270
+ if (framework === 'react') {
2271
+ const nginxPath = path.join(local_path, 'nginx.conf');
2272
+ try {
2273
+ await fs.access(nginxPath);
2274
+ } catch {
2275
+ const nginxContent = generateNginxConf();
2276
+ await fs.writeFile(nginxPath, nginxContent, 'utf8');
2277
+ console.error('Created nginx.conf for React app');
2278
+ }
2279
+ }
2280
+ } catch (err) {
2281
+ console.error(`Failed to create Dockerfile: ${err.message}`);
2282
+ }
2283
+ }
2284
+
2026
2285
  response.webhook = {
2027
2286
  secret: webhookSecret,
2028
2287
  status: 'created',
@@ -2032,7 +2291,8 @@ async function initProject(args) {
2032
2291
  response.deployment = {
2033
2292
  status: 'initialized',
2034
2293
  domain: `${name}.eu-central.mlgym.app`,
2035
- message: 'Coolify deployment ready - push to main branch to trigger deployment'
2294
+ message: 'Coolify deployment ready - push to main branch to trigger deployment',
2295
+ dockerfile: dockerfileCreated ? 'Generated automatically based on framework detection' : 'Using existing Dockerfile'
2036
2296
  };
2037
2297
  }
2038
2298
 
@@ -2075,6 +2335,180 @@ function generateWebhookSecret() {
2075
2335
  return secret;
2076
2336
  }
2077
2337
 
2338
+ // Generate Dockerfile based on detected framework
2339
+ function generateDockerfile(framework, projectName) {
2340
+ const dockerfiles = {
2341
+ nextjs: `# Next.js Production Dockerfile
2342
+ FROM node:20-alpine AS builder
2343
+ WORKDIR /app
2344
+ COPY package*.json ./
2345
+ RUN npm ci
2346
+ COPY . .
2347
+ RUN npm run build
2348
+
2349
+ FROM node:20-alpine
2350
+ WORKDIR /app
2351
+ ENV NODE_ENV=production
2352
+ COPY --from=builder /app/public ./public
2353
+ COPY --from=builder /app/.next ./.next
2354
+ COPY --from=builder /app/node_modules ./node_modules
2355
+ COPY --from=builder /app/package.json ./package.json
2356
+ EXPOSE 3000
2357
+ ENV PORT=3000
2358
+ CMD ["npm", "start"]`,
2359
+
2360
+ react: `# React Production Dockerfile
2361
+ FROM node:20-alpine AS builder
2362
+ WORKDIR /app
2363
+ COPY package*.json ./
2364
+ RUN npm ci
2365
+ COPY . .
2366
+ RUN npm run build
2367
+
2368
+ FROM nginx:alpine
2369
+ COPY --from=builder /app/build /usr/share/nginx/html
2370
+ COPY nginx.conf /etc/nginx/conf.d/default.conf
2371
+ EXPOSE 80
2372
+ CMD ["nginx", "-g", "daemon off;"]`,
2373
+
2374
+ express: `# Express.js Dockerfile
2375
+ FROM node:20-alpine
2376
+ WORKDIR /app
2377
+ COPY package*.json ./
2378
+ RUN npm ci
2379
+ COPY . .
2380
+ EXPOSE 3000
2381
+ ENV PORT=3000
2382
+ CMD ["node", "index.js"]`,
2383
+
2384
+ django: `# Django Dockerfile
2385
+ FROM python:3.11-slim
2386
+ WORKDIR /app
2387
+ ENV PYTHONUNBUFFERED=1
2388
+ COPY requirements.txt .
2389
+ RUN pip install --no-cache-dir -r requirements.txt
2390
+ COPY . .
2391
+ RUN python manage.py collectstatic --noinput
2392
+ EXPOSE 8000
2393
+ CMD ["gunicorn", "--bind", "0.0.0.0:8000", "myproject.wsgi:application"]`,
2394
+
2395
+ php: `# PHP Dockerfile
2396
+ FROM php:8.2-apache
2397
+ RUN docker-php-ext-install pdo pdo_mysql
2398
+ COPY . /var/www/html/
2399
+ RUN a2enmod rewrite
2400
+ EXPOSE 80`,
2401
+
2402
+ static: `# Static Site Dockerfile
2403
+ FROM nginx:alpine
2404
+ COPY . /usr/share/nginx/html
2405
+ EXPOSE 80
2406
+ CMD ["nginx", "-g", "daemon off;"]`,
2407
+
2408
+ go: `# Go Dockerfile
2409
+ FROM golang:1.21-alpine AS builder
2410
+ WORKDIR /app
2411
+ COPY go.* ./
2412
+ RUN go mod download
2413
+ COPY . .
2414
+ RUN go build -o main .
2415
+
2416
+ FROM alpine:latest
2417
+ RUN apk --no-cache add ca-certificates
2418
+ WORKDIR /root/
2419
+ COPY --from=builder /app/main .
2420
+ EXPOSE 8080
2421
+ CMD ["./main"]`,
2422
+
2423
+ rust: `# Rust Dockerfile
2424
+ FROM rust:1.75 AS builder
2425
+ WORKDIR /app
2426
+ COPY Cargo.* ./
2427
+ COPY src ./src
2428
+ RUN cargo build --release
2429
+
2430
+ FROM debian:bookworm-slim
2431
+ WORKDIR /app
2432
+ COPY --from=builder /app/target/release/${projectName} .
2433
+ EXPOSE 8080
2434
+ CMD ["./${projectName}"]`
2435
+ };
2436
+
2437
+ return dockerfiles[framework] || dockerfiles.static;
2438
+ }
2439
+
2440
+ // Generate nginx.conf for React apps
2441
+ function generateNginxConf() {
2442
+ return `server {
2443
+ listen 80;
2444
+ location / {
2445
+ root /usr/share/nginx/html;
2446
+ index index.html index.htm;
2447
+ try_files $uri $uri/ /index.html;
2448
+ }
2449
+ }`;
2450
+ }
2451
+
2452
+ // Detect project framework by analyzing files
2453
+ async function detectFramework(localPath) {
2454
+ try {
2455
+ // Check for package.json
2456
+ const packagePath = path.join(localPath, 'package.json');
2457
+ const packageData = await fs.readFile(packagePath, 'utf8');
2458
+ const packageJson = JSON.parse(packageData);
2459
+
2460
+ // Check dependencies
2461
+ const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
2462
+
2463
+ if (deps.next) return 'nextjs';
2464
+ if (deps.react && !deps.next) return 'react';
2465
+ if (deps.express) return 'express';
2466
+ if (deps.vue) return 'vue';
2467
+ if (deps.angular) return 'angular';
2468
+
2469
+ // If has package.json but no recognized framework
2470
+ return 'node';
2471
+ } catch {
2472
+ // No package.json, check for other files
2473
+ }
2474
+
2475
+ // Check for Python
2476
+ try {
2477
+ await fs.access(path.join(localPath, 'requirements.txt'));
2478
+ const content = await fs.readFile(path.join(localPath, 'requirements.txt'), 'utf8');
2479
+ if (content.includes('django')) return 'django';
2480
+ if (content.includes('flask')) return 'flask';
2481
+ return 'python';
2482
+ } catch {}
2483
+
2484
+ // Check for Go
2485
+ try {
2486
+ await fs.access(path.join(localPath, 'go.mod'));
2487
+ return 'go';
2488
+ } catch {}
2489
+
2490
+ // Check for Rust
2491
+ try {
2492
+ await fs.access(path.join(localPath, 'Cargo.toml'));
2493
+ return 'rust';
2494
+ } catch {}
2495
+
2496
+ // Check for PHP
2497
+ try {
2498
+ await fs.access(path.join(localPath, 'composer.json'));
2499
+ return 'php';
2500
+ } catch {}
2501
+
2502
+ // Check for index.php
2503
+ try {
2504
+ await fs.access(path.join(localPath, 'index.php'));
2505
+ return 'php';
2506
+ } catch {}
2507
+
2508
+ // Default to static site
2509
+ return 'static';
2510
+ }
2511
+
2078
2512
  // Tool implementation: Recover User
2079
2513
  async function recoverUser(args) {
2080
2514
  const { email, pin, new_password } = args;
@@ -5893,14 +6327,33 @@ async function replicateProject(args) {
5893
6327
 
5894
6328
  // Start the server
5895
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
+
5896
6354
  const transport = new StdioServerTransport();
5897
6355
  await server.connect(transport);
5898
6356
  console.error(`GitLab Backend MCP Server v${CURRENT_VERSION} started`);
5899
-
5900
- // Check for updates in the background (don't block startup)
5901
- setTimeout(async () => {
5902
- await checkForUpdates();
5903
- }, 2000); // Check after 2 seconds
5904
6357
  }
5905
6358
 
5906
6359
  main().catch((error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mlgym-deploy",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "MCP server for GitLab Backend - User creation and project deployment",
5
5
  "main": "index.js",
6
6
  "type": "module",