mlgym-deploy 2.3.5 → 2.4.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.
- package/SECURITY-UPDATE-v2.4.0.md +104 -0
- package/index.js +404 -6269
- package/package.json +1 -1
- package/index-v2.js +0 -1062
- package/index-v3-explicit.js +0 -129
package/index-v2.js
DELETED
|
@@ -1,1062 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
-
import {
|
|
6
|
-
CallToolRequestSchema,
|
|
7
|
-
ListToolsRequestSchema
|
|
8
|
-
} from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
-
import axios from 'axios';
|
|
10
|
-
import fs from 'fs/promises';
|
|
11
|
-
import path from 'path';
|
|
12
|
-
import os from 'os';
|
|
13
|
-
import crypto from 'crypto';
|
|
14
|
-
|
|
15
|
-
// Configuration
|
|
16
|
-
const CONFIG = {
|
|
17
|
-
backend_url: process.env.GITLAB_BACKEND_URL || 'https://backend.eu.ezb.net',
|
|
18
|
-
gitlab_url: process.env.GITLAB_URL || 'https://git.mlgym.io',
|
|
19
|
-
coolify_url: process.env.COOLIFY_URL || 'https://coolify.eu.ezb.net',
|
|
20
|
-
config_file: path.join(os.homedir(), '.mlgym', 'config.json'),
|
|
21
|
-
mlgym_ini: path.join(os.homedir(), '.mlgym', 'mlgym.ini'),
|
|
22
|
-
terms_url: 'https://mlgym.io/terms',
|
|
23
|
-
privacy_url: 'https://mlgym.io/privacy',
|
|
24
|
-
docs_url: 'https://docs.mlgym.io'
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// Available regions for deployment
|
|
28
|
-
const REGIONS = {
|
|
29
|
-
'eu-central': {
|
|
30
|
-
name: 'Europe (Frankfurt)',
|
|
31
|
-
coolify_url: 'https://coolify.eu.ezb.net',
|
|
32
|
-
latency: 'Low for EU users'
|
|
33
|
-
},
|
|
34
|
-
'us-east': {
|
|
35
|
-
name: 'US East (Virginia)',
|
|
36
|
-
coolify_url: 'https://coolify.us-east.mlgym.io',
|
|
37
|
-
latency: 'Low for US East Coast'
|
|
38
|
-
},
|
|
39
|
-
'asia-pacific': {
|
|
40
|
-
name: 'Asia Pacific (Singapore)',
|
|
41
|
-
coolify_url: 'https://coolify.ap.mlgym.io',
|
|
42
|
-
latency: 'Low for APAC users'
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// Session storage for multi-step flows
|
|
47
|
-
const sessions = new Map();
|
|
48
|
-
|
|
49
|
-
// Helper to check if file exists
|
|
50
|
-
async function fileExists(filePath) {
|
|
51
|
-
try {
|
|
52
|
-
await fs.access(filePath);
|
|
53
|
-
return true;
|
|
54
|
-
} catch {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Helper to check if directory exists
|
|
60
|
-
async function dirExists(dirPath) {
|
|
61
|
-
try {
|
|
62
|
-
const stats = await fs.stat(dirPath);
|
|
63
|
-
return stats.isDirectory();
|
|
64
|
-
} catch {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Helper to load user configuration
|
|
70
|
-
async function loadUserConfig() {
|
|
71
|
-
try {
|
|
72
|
-
// First check for mlgym.ini (legacy format)
|
|
73
|
-
if (await fileExists(CONFIG.mlgym_ini)) {
|
|
74
|
-
const iniContent = await fs.readFile(CONFIG.mlgym_ini, 'utf8');
|
|
75
|
-
// Parse simple INI format
|
|
76
|
-
const config = {};
|
|
77
|
-
iniContent.split('\n').forEach(line => {
|
|
78
|
-
const [key, value] = line.split('=');
|
|
79
|
-
if (key && value) {
|
|
80
|
-
config[key.trim()] = value.trim();
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
return config;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Check for JSON config
|
|
87
|
-
if (await fileExists(CONFIG.config_file)) {
|
|
88
|
-
const data = await fs.readFile(CONFIG.config_file, 'utf8');
|
|
89
|
-
return JSON.parse(data);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return null;
|
|
93
|
-
} catch {
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Helper to save user configuration
|
|
99
|
-
async function saveUserConfig(config) {
|
|
100
|
-
const dir = path.dirname(CONFIG.config_file);
|
|
101
|
-
await fs.mkdir(dir, { recursive: true });
|
|
102
|
-
|
|
103
|
-
// Save as JSON
|
|
104
|
-
await fs.writeFile(
|
|
105
|
-
CONFIG.config_file,
|
|
106
|
-
JSON.stringify(config, null, 2)
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
// Also save as INI for backwards compatibility
|
|
110
|
-
const iniContent = Object.entries(config)
|
|
111
|
-
.map(([key, value]) => `${key}=${value}`)
|
|
112
|
-
.join('\n');
|
|
113
|
-
await fs.writeFile(CONFIG.mlgym_ini, iniContent);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Detect framework from project files
|
|
117
|
-
async function detectFramework(projectPath) {
|
|
118
|
-
const checks = [
|
|
119
|
-
{
|
|
120
|
-
file: 'package.json',
|
|
121
|
-
detect: async (content) => {
|
|
122
|
-
const pkg = JSON.parse(content);
|
|
123
|
-
if (pkg.dependencies?.next || pkg.devDependencies?.next) return 'nextjs';
|
|
124
|
-
if (pkg.dependencies?.react || pkg.devDependencies?.react) return 'react';
|
|
125
|
-
if (pkg.dependencies?.vue || pkg.devDependencies?.vue) return 'vue';
|
|
126
|
-
if (pkg.dependencies?.express) return 'express';
|
|
127
|
-
if (pkg.dependencies?.fastify) return 'fastify';
|
|
128
|
-
return 'node';
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
file: 'composer.json',
|
|
133
|
-
detect: async (content) => {
|
|
134
|
-
const composer = JSON.parse(content);
|
|
135
|
-
if (composer.require?.['laravel/framework']) return 'laravel';
|
|
136
|
-
if (composer.require?.['symfony/framework-bundle']) return 'symfony';
|
|
137
|
-
return 'php';
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
file: 'requirements.txt',
|
|
142
|
-
detect: async (content) => {
|
|
143
|
-
if (content.includes('django')) return 'django';
|
|
144
|
-
if (content.includes('flask')) return 'flask';
|
|
145
|
-
if (content.includes('fastapi')) return 'fastapi';
|
|
146
|
-
return 'python';
|
|
147
|
-
}
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
file: 'Cargo.toml',
|
|
151
|
-
detect: () => 'rust'
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
file: 'go.mod',
|
|
155
|
-
detect: () => 'go'
|
|
156
|
-
}
|
|
157
|
-
];
|
|
158
|
-
|
|
159
|
-
for (const check of checks) {
|
|
160
|
-
const filePath = path.join(projectPath, check.file);
|
|
161
|
-
if (await fileExists(filePath)) {
|
|
162
|
-
if (check.detect) {
|
|
163
|
-
const content = await fs.readFile(filePath, 'utf8');
|
|
164
|
-
return await check.detect(content);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return 'static'; // Default for static HTML sites
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Generate Dockerfile based on framework
|
|
173
|
-
function generateDockerfile(framework, projectName) {
|
|
174
|
-
const dockerfiles = {
|
|
175
|
-
nextjs: `# Next.js Production Dockerfile
|
|
176
|
-
FROM node:20-alpine AS builder
|
|
177
|
-
WORKDIR /app
|
|
178
|
-
COPY package*.json ./
|
|
179
|
-
RUN npm ci
|
|
180
|
-
COPY . .
|
|
181
|
-
RUN npm run build
|
|
182
|
-
|
|
183
|
-
FROM node:20-alpine
|
|
184
|
-
WORKDIR /app
|
|
185
|
-
ENV NODE_ENV=production
|
|
186
|
-
COPY --from=builder /app/public ./public
|
|
187
|
-
COPY --from=builder /app/.next ./.next
|
|
188
|
-
COPY --from=builder /app/node_modules ./node_modules
|
|
189
|
-
COPY --from=builder /app/package.json ./package.json
|
|
190
|
-
EXPOSE 3000
|
|
191
|
-
ENV PORT=3000
|
|
192
|
-
CMD ["npm", "start"]`,
|
|
193
|
-
|
|
194
|
-
react: `# React Production Dockerfile
|
|
195
|
-
FROM node:20-alpine AS builder
|
|
196
|
-
WORKDIR /app
|
|
197
|
-
COPY package*.json ./
|
|
198
|
-
RUN npm ci
|
|
199
|
-
COPY . .
|
|
200
|
-
RUN npm run build
|
|
201
|
-
|
|
202
|
-
FROM nginx:alpine
|
|
203
|
-
COPY --from=builder /app/build /usr/share/nginx/html
|
|
204
|
-
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
|
205
|
-
EXPOSE 80
|
|
206
|
-
CMD ["nginx", "-g", "daemon off;"]`,
|
|
207
|
-
|
|
208
|
-
express: `# Express.js Dockerfile
|
|
209
|
-
FROM node:20-alpine
|
|
210
|
-
WORKDIR /app
|
|
211
|
-
COPY package*.json ./
|
|
212
|
-
RUN npm ci
|
|
213
|
-
COPY . .
|
|
214
|
-
EXPOSE 3000
|
|
215
|
-
ENV PORT=3000
|
|
216
|
-
CMD ["node", "index.js"]`,
|
|
217
|
-
|
|
218
|
-
django: `# Django Dockerfile
|
|
219
|
-
FROM python:3.11-slim
|
|
220
|
-
WORKDIR /app
|
|
221
|
-
ENV PYTHONUNBUFFERED=1
|
|
222
|
-
COPY requirements.txt .
|
|
223
|
-
RUN pip install --no-cache-dir -r requirements.txt
|
|
224
|
-
COPY . .
|
|
225
|
-
RUN python manage.py collectstatic --noinput
|
|
226
|
-
EXPOSE 8000
|
|
227
|
-
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "myproject.wsgi:application"]`,
|
|
228
|
-
|
|
229
|
-
php: `# PHP Dockerfile
|
|
230
|
-
FROM php:8.2-apache
|
|
231
|
-
RUN docker-php-ext-install pdo pdo_mysql
|
|
232
|
-
COPY . /var/www/html/
|
|
233
|
-
RUN a2enmod rewrite
|
|
234
|
-
EXPOSE 80`,
|
|
235
|
-
|
|
236
|
-
static: `# Static Site Dockerfile
|
|
237
|
-
FROM nginx:alpine
|
|
238
|
-
COPY . /usr/share/nginx/html
|
|
239
|
-
EXPOSE 80
|
|
240
|
-
CMD ["nginx", "-g", "daemon off;"]`,
|
|
241
|
-
|
|
242
|
-
go: `# Go Dockerfile
|
|
243
|
-
FROM golang:1.21-alpine AS builder
|
|
244
|
-
WORKDIR /app
|
|
245
|
-
COPY go.* ./
|
|
246
|
-
RUN go mod download
|
|
247
|
-
COPY . .
|
|
248
|
-
RUN go build -o main .
|
|
249
|
-
|
|
250
|
-
FROM alpine:latest
|
|
251
|
-
RUN apk --no-cache add ca-certificates
|
|
252
|
-
WORKDIR /root/
|
|
253
|
-
COPY --from=builder /app/main .
|
|
254
|
-
EXPOSE 8080
|
|
255
|
-
CMD ["./main"]`,
|
|
256
|
-
|
|
257
|
-
rust: `# Rust Dockerfile
|
|
258
|
-
FROM rust:1.75 AS builder
|
|
259
|
-
WORKDIR /app
|
|
260
|
-
COPY Cargo.* ./
|
|
261
|
-
COPY src ./src
|
|
262
|
-
RUN cargo build --release
|
|
263
|
-
|
|
264
|
-
FROM debian:bookworm-slim
|
|
265
|
-
WORKDIR /app
|
|
266
|
-
COPY --from=builder /app/target/release/${projectName} .
|
|
267
|
-
EXPOSE 8080
|
|
268
|
-
CMD ["./${projectName}"]`
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
return dockerfiles[framework] || dockerfiles.static;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Generate nginx.conf for React apps
|
|
275
|
-
function generateNginxConf() {
|
|
276
|
-
return `server {
|
|
277
|
-
listen 80;
|
|
278
|
-
location / {
|
|
279
|
-
root /usr/share/nginx/html;
|
|
280
|
-
index index.html index.htm;
|
|
281
|
-
try_files $uri $uri/ /index.html;
|
|
282
|
-
}
|
|
283
|
-
}`;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Detect project name from various sources
|
|
287
|
-
async function detectProjectName(localPath) {
|
|
288
|
-
// Try package.json first
|
|
289
|
-
try {
|
|
290
|
-
const packagePath = path.join(localPath, 'package.json');
|
|
291
|
-
const packageData = await fs.readFile(packagePath, 'utf8');
|
|
292
|
-
const packageJson = JSON.parse(packageData);
|
|
293
|
-
if (packageJson.name) {
|
|
294
|
-
return packageJson.name.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/^-+|-+$/g, '');
|
|
295
|
-
}
|
|
296
|
-
} catch {}
|
|
297
|
-
|
|
298
|
-
// Try composer.json
|
|
299
|
-
try {
|
|
300
|
-
const composerPath = path.join(localPath, 'composer.json');
|
|
301
|
-
const composerData = await fs.readFile(composerPath, 'utf8');
|
|
302
|
-
const composerJson = JSON.parse(composerData);
|
|
303
|
-
if (composerJson.name) {
|
|
304
|
-
const parts = composerJson.name.split('/');
|
|
305
|
-
return parts[parts.length - 1].toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
306
|
-
}
|
|
307
|
-
} catch {}
|
|
308
|
-
|
|
309
|
-
// Fall back to directory name
|
|
310
|
-
const absolutePath = path.resolve(localPath);
|
|
311
|
-
return path.basename(absolutePath).toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/^-+|-+$/g, '');
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// API client helper
|
|
315
|
-
async function apiRequest(method, endpoint, data = null, useAuth = true) {
|
|
316
|
-
const config = {
|
|
317
|
-
method,
|
|
318
|
-
url: `${CONFIG.backend_url}${endpoint}`,
|
|
319
|
-
headers: {
|
|
320
|
-
'Content-Type': 'application/json'
|
|
321
|
-
}
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
if (useAuth) {
|
|
325
|
-
const userConfig = await loadUserConfig();
|
|
326
|
-
// Handle both 'token' and 'auth_token' field names
|
|
327
|
-
const authToken = userConfig?.token || userConfig?.auth_token;
|
|
328
|
-
if (authToken) {
|
|
329
|
-
config.headers['Authorization'] = `Bearer ${authToken}`;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (data) {
|
|
334
|
-
config.data = data;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
try {
|
|
338
|
-
const response = await axios(config);
|
|
339
|
-
return { success: true, data: response.data };
|
|
340
|
-
} catch (error) {
|
|
341
|
-
return {
|
|
342
|
-
success: false,
|
|
343
|
-
error: error.response?.data?.message || error.message,
|
|
344
|
-
status: error.response?.status
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// Create MCP server
|
|
350
|
-
const server = new Server(
|
|
351
|
-
{
|
|
352
|
-
name: 'gitlab-backend-mcp',
|
|
353
|
-
version: '3.0.0',
|
|
354
|
-
},
|
|
355
|
-
{
|
|
356
|
-
capabilities: {
|
|
357
|
-
tools: {}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
);
|
|
361
|
-
|
|
362
|
-
// Tool definitions
|
|
363
|
-
const TOOLS = [
|
|
364
|
-
{
|
|
365
|
-
name: 'mlgym_get_details',
|
|
366
|
-
description: 'Get current MLGym user details, authentication status, and configuration. Use this tool when user asks about their MLGym account, login status, or configuration details.',
|
|
367
|
-
inputSchema: {
|
|
368
|
-
type: 'object',
|
|
369
|
-
properties: {
|
|
370
|
-
verbose: {
|
|
371
|
-
type: 'boolean',
|
|
372
|
-
description: 'Include detailed information about projects and resources',
|
|
373
|
-
default: false
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
},
|
|
378
|
-
{
|
|
379
|
-
name: 'mlgym_deploy',
|
|
380
|
-
description: 'Deploy this project to the cloud. Use this tool when user wants to deploy, publish, or host their project online. Handles all deployment tasks including Docker setup, GitLab integration, and cloud hosting on MLGym platform.',
|
|
381
|
-
inputSchema: {
|
|
382
|
-
type: 'object',
|
|
383
|
-
properties: {
|
|
384
|
-
action: {
|
|
385
|
-
type: 'string',
|
|
386
|
-
enum: ['analyze', 'register', 'configure', 'deploy'],
|
|
387
|
-
description: 'Action to perform: analyze (scan project), register (create account), configure (set up project), deploy (final deployment)'
|
|
388
|
-
},
|
|
389
|
-
session_id: {
|
|
390
|
-
type: 'string',
|
|
391
|
-
description: 'Session ID for multi-step flow (provided after initial analysis)'
|
|
392
|
-
},
|
|
393
|
-
local_path: {
|
|
394
|
-
type: 'string',
|
|
395
|
-
description: 'Local directory path of the project',
|
|
396
|
-
default: '.'
|
|
397
|
-
},
|
|
398
|
-
// For registration step
|
|
399
|
-
user_email: {
|
|
400
|
-
type: 'string',
|
|
401
|
-
description: 'Email for new account registration'
|
|
402
|
-
},
|
|
403
|
-
user_name: {
|
|
404
|
-
type: 'string',
|
|
405
|
-
description: 'Full name for new account registration'
|
|
406
|
-
},
|
|
407
|
-
accept_terms: {
|
|
408
|
-
type: 'boolean',
|
|
409
|
-
description: 'Accept MLGym terms and conditions'
|
|
410
|
-
},
|
|
411
|
-
// For configuration step
|
|
412
|
-
confirm_dockerfile: {
|
|
413
|
-
type: 'boolean',
|
|
414
|
-
description: 'Confirm generated Dockerfile'
|
|
415
|
-
},
|
|
416
|
-
custom_dockerfile: {
|
|
417
|
-
type: 'string',
|
|
418
|
-
description: 'Custom Dockerfile content if not using generated one'
|
|
419
|
-
},
|
|
420
|
-
// For deployment step
|
|
421
|
-
region: {
|
|
422
|
-
type: 'string',
|
|
423
|
-
enum: ['eu-central', 'us-east', 'asia-pacific'],
|
|
424
|
-
description: 'Deployment region'
|
|
425
|
-
},
|
|
426
|
-
confirm_deployment: {
|
|
427
|
-
type: 'boolean',
|
|
428
|
-
description: 'Final confirmation to deploy'
|
|
429
|
-
}
|
|
430
|
-
},
|
|
431
|
-
required: ['action']
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
];
|
|
435
|
-
|
|
436
|
-
// Handle list tools request
|
|
437
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
438
|
-
return {
|
|
439
|
-
tools: TOOLS
|
|
440
|
-
};
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
// Handle tool execution
|
|
444
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
445
|
-
const { name, arguments: args } = request.params;
|
|
446
|
-
|
|
447
|
-
switch (name) {
|
|
448
|
-
case 'mlgym_get_details':
|
|
449
|
-
return await getUserDetails(args);
|
|
450
|
-
case 'mlgym_deploy':
|
|
451
|
-
return await smartDeploy(args);
|
|
452
|
-
default:
|
|
453
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
454
|
-
}
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
// Get user details function
|
|
458
|
-
async function getUserDetails(args = {}) {
|
|
459
|
-
const { verbose = false } = args;
|
|
460
|
-
|
|
461
|
-
try {
|
|
462
|
-
// Check for MLGym configuration
|
|
463
|
-
const config = await loadUserConfig();
|
|
464
|
-
|
|
465
|
-
// Handle both 'token' and 'auth_token' field names
|
|
466
|
-
const authToken = config?.auth_token || config?.token;
|
|
467
|
-
|
|
468
|
-
if (!config || !authToken) {
|
|
469
|
-
return {
|
|
470
|
-
content: [{
|
|
471
|
-
type: 'text',
|
|
472
|
-
text: `❌ No MLGym account found
|
|
473
|
-
|
|
474
|
-
You are not currently logged into MLGym. To get started:
|
|
475
|
-
|
|
476
|
-
1. Deploy a project using the deploy button
|
|
477
|
-
2. Or check your ~/.mlgym/config.json file
|
|
478
|
-
|
|
479
|
-
MLGym provides:
|
|
480
|
-
- Free GitLab repository hosting
|
|
481
|
-
- Automated CI/CD pipelines
|
|
482
|
-
- Cloud deployment with custom domains
|
|
483
|
-
- SSL certificates
|
|
484
|
-
- Container orchestration
|
|
485
|
-
|
|
486
|
-
Visit https://mlgym.io to learn more.`
|
|
487
|
-
}]
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// Get user information from backend
|
|
492
|
-
try {
|
|
493
|
-
const response = await axios.get(`${CONFIG.backend_url}/api/v1/user`, {
|
|
494
|
-
headers: {
|
|
495
|
-
'Authorization': `Bearer ${authToken}`
|
|
496
|
-
}
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
const user = response.data;
|
|
500
|
-
|
|
501
|
-
let details = `✅ MLGym Account Details
|
|
502
|
-
|
|
503
|
-
👤 User Information:
|
|
504
|
-
- Email: ${user.email}
|
|
505
|
-
- Name: ${user.name}
|
|
506
|
-
- User ID: ${user.id}
|
|
507
|
-
- GitLab ID: ${user.gitlab_id || 'Not set'}
|
|
508
|
-
- Admin: ${user.is_admin ? 'Yes' : 'No'}
|
|
509
|
-
- Created: ${new Date(user.created_at).toLocaleDateString()}`;
|
|
510
|
-
|
|
511
|
-
if (verbose) {
|
|
512
|
-
// Get projects
|
|
513
|
-
try {
|
|
514
|
-
const projectsResponse = await axios.get(`${CONFIG.backend_url}/api/v1/projects`, {
|
|
515
|
-
headers: {
|
|
516
|
-
'Authorization': `Bearer ${authToken}`
|
|
517
|
-
}
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
// API returns array directly, not wrapped in an object
|
|
521
|
-
const projects = Array.isArray(projectsResponse.data) ? projectsResponse.data :
|
|
522
|
-
(projectsResponse.data.projects || []);
|
|
523
|
-
|
|
524
|
-
if (projects.length > 0) {
|
|
525
|
-
details += `\n\n📦 Projects (${projects.length}):`;
|
|
526
|
-
projects.forEach(project => {
|
|
527
|
-
details += `\n- ${project.name}`;
|
|
528
|
-
if (project.deployment_url) {
|
|
529
|
-
details += ` → ${project.deployment_url}`;
|
|
530
|
-
}
|
|
531
|
-
});
|
|
532
|
-
} else {
|
|
533
|
-
details += `\n\n📦 No projects yet`;
|
|
534
|
-
}
|
|
535
|
-
} catch (err) {
|
|
536
|
-
// Projects endpoint might not be available
|
|
537
|
-
console.error('Could not fetch projects:', err.message);
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
details += `\n\n🔗 Configuration:
|
|
542
|
-
- Config file: ${CONFIG.config_file}
|
|
543
|
-
- Backend: ${CONFIG.backend_url}
|
|
544
|
-
- GitLab: ${CONFIG.gitlab_url}`;
|
|
545
|
-
|
|
546
|
-
return {
|
|
547
|
-
content: [{
|
|
548
|
-
type: 'text',
|
|
549
|
-
text: details
|
|
550
|
-
}]
|
|
551
|
-
};
|
|
552
|
-
|
|
553
|
-
} catch (apiError) {
|
|
554
|
-
if (apiError.response?.status === 401) {
|
|
555
|
-
return {
|
|
556
|
-
content: [{
|
|
557
|
-
type: 'text',
|
|
558
|
-
text: `⚠️ Authentication expired
|
|
559
|
-
|
|
560
|
-
Your MLGym session has expired. Please:
|
|
561
|
-
1. Deploy a new project to refresh your authentication
|
|
562
|
-
2. Or manually update your token in ~/.mlgym/config.json
|
|
563
|
-
|
|
564
|
-
Error: ${apiError.message}`
|
|
565
|
-
}]
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
return {
|
|
570
|
-
content: [{
|
|
571
|
-
type: 'text',
|
|
572
|
-
text: `❌ Error fetching user details
|
|
573
|
-
|
|
574
|
-
Could not connect to MLGym backend.
|
|
575
|
-
- Backend URL: ${CONFIG.backend_url}
|
|
576
|
-
- Error: ${apiError.message}
|
|
577
|
-
|
|
578
|
-
Your config file exists at: ${CONFIG.config_file}`
|
|
579
|
-
}]
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
} catch (error) {
|
|
584
|
-
return {
|
|
585
|
-
content: [{
|
|
586
|
-
type: 'text',
|
|
587
|
-
text: `❌ Error reading MLGym configuration
|
|
588
|
-
|
|
589
|
-
${error.message}
|
|
590
|
-
|
|
591
|
-
Please check:
|
|
592
|
-
1. ~/.mlgym/config.json exists
|
|
593
|
-
2. You have proper permissions
|
|
594
|
-
3. The file contains valid JSON`
|
|
595
|
-
}]
|
|
596
|
-
};
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// Smart deployment flow
|
|
601
|
-
async function smartDeploy(args) {
|
|
602
|
-
const { action, session_id, local_path = '.' } = args;
|
|
603
|
-
|
|
604
|
-
switch (action) {
|
|
605
|
-
case 'analyze':
|
|
606
|
-
return await analyzeProject(local_path);
|
|
607
|
-
|
|
608
|
-
case 'register':
|
|
609
|
-
return await registerUser(args);
|
|
610
|
-
|
|
611
|
-
case 'configure':
|
|
612
|
-
return await configureProject(session_id, args);
|
|
613
|
-
|
|
614
|
-
case 'deploy':
|
|
615
|
-
return await deployProject(session_id, args);
|
|
616
|
-
|
|
617
|
-
default:
|
|
618
|
-
return {
|
|
619
|
-
content: [{
|
|
620
|
-
type: 'text',
|
|
621
|
-
text: JSON.stringify({
|
|
622
|
-
error: 'Invalid action. Use: analyze, register, configure, or deploy'
|
|
623
|
-
}, null, 2)
|
|
624
|
-
}]
|
|
625
|
-
};
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// Step 1: Analyze project
|
|
630
|
-
async function analyzeProject(localPath) {
|
|
631
|
-
console.error(`Analyzing project at: ${localPath}`);
|
|
632
|
-
|
|
633
|
-
const sessionId = crypto.randomBytes(16).toString('hex');
|
|
634
|
-
|
|
635
|
-
// Detect project details
|
|
636
|
-
const projectName = await detectProjectName(localPath);
|
|
637
|
-
const framework = await detectFramework(localPath);
|
|
638
|
-
const hasDockerfile = await fileExists(path.join(localPath, 'Dockerfile'));
|
|
639
|
-
const hasTests = await dirExists(path.join(localPath, 'tests')) ||
|
|
640
|
-
await dirExists(path.join(localPath, '__tests__')) ||
|
|
641
|
-
await fileExists(path.join(localPath, 'test'));
|
|
642
|
-
|
|
643
|
-
// Check if user is already registered
|
|
644
|
-
const userConfig = await loadUserConfig();
|
|
645
|
-
// Handle both 'token' and 'auth_token' field names
|
|
646
|
-
const hasAccount = !!(userConfig?.token || userConfig?.auth_token);
|
|
647
|
-
|
|
648
|
-
// Store session data
|
|
649
|
-
const sessionData = {
|
|
650
|
-
sessionId,
|
|
651
|
-
localPath,
|
|
652
|
-
projectName,
|
|
653
|
-
framework,
|
|
654
|
-
hasDockerfile,
|
|
655
|
-
hasTests,
|
|
656
|
-
hasAccount,
|
|
657
|
-
userEmail: userConfig?.email
|
|
658
|
-
};
|
|
659
|
-
sessions.set(sessionId, sessionData);
|
|
660
|
-
|
|
661
|
-
// Prepare response
|
|
662
|
-
const response = {
|
|
663
|
-
status: 'analysis_complete',
|
|
664
|
-
session_id: sessionId,
|
|
665
|
-
project: {
|
|
666
|
-
name: projectName,
|
|
667
|
-
path: localPath,
|
|
668
|
-
framework,
|
|
669
|
-
has_dockerfile: hasDockerfile,
|
|
670
|
-
has_tests: hasTests
|
|
671
|
-
}
|
|
672
|
-
};
|
|
673
|
-
|
|
674
|
-
if (!hasAccount) {
|
|
675
|
-
response.next_step = 'registration_required';
|
|
676
|
-
response.mlgym_info = {
|
|
677
|
-
description: 'MLGym is a cloud deployment platform that provides:',
|
|
678
|
-
features: [
|
|
679
|
-
'✅ Automatic CI/CD with GitLab integration',
|
|
680
|
-
'✅ Container-based deployments with Coolify',
|
|
681
|
-
'✅ Global CDN and SSL certificates',
|
|
682
|
-
'✅ Automatic scaling and monitoring',
|
|
683
|
-
'✅ One-click deployments from your IDE'
|
|
684
|
-
],
|
|
685
|
-
pricing: 'Free tier available with 1 project and 1GB storage',
|
|
686
|
-
terms_url: CONFIG.terms_url,
|
|
687
|
-
privacy_url: CONFIG.privacy_url,
|
|
688
|
-
docs_url: CONFIG.docs_url
|
|
689
|
-
};
|
|
690
|
-
response.action_required = {
|
|
691
|
-
message: 'To continue, you need to create a free MLGym account.',
|
|
692
|
-
next_action: 'mlgym_deploy with action: "register"',
|
|
693
|
-
required_fields: ['user_email', 'user_name', 'accept_terms']
|
|
694
|
-
};
|
|
695
|
-
} else {
|
|
696
|
-
response.next_step = 'configuration';
|
|
697
|
-
response.user = {
|
|
698
|
-
email: userConfig.email,
|
|
699
|
-
authenticated: true
|
|
700
|
-
};
|
|
701
|
-
|
|
702
|
-
if (!hasDockerfile) {
|
|
703
|
-
const dockerfile = generateDockerfile(framework, projectName);
|
|
704
|
-
response.suggested_dockerfile = {
|
|
705
|
-
content: dockerfile,
|
|
706
|
-
framework_detected: framework,
|
|
707
|
-
message: 'No Dockerfile found. Generated one based on detected framework.'
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
response.action_required = {
|
|
712
|
-
message: hasDockerfile ?
|
|
713
|
-
'Project has Dockerfile. Ready to configure deployment.' :
|
|
714
|
-
'Review the generated Dockerfile above.',
|
|
715
|
-
next_action: 'mlgym_deploy with action: "configure"',
|
|
716
|
-
required_fields: hasDockerfile ? [] : ['confirm_dockerfile']
|
|
717
|
-
};
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
return {
|
|
721
|
-
content: [{
|
|
722
|
-
type: 'text',
|
|
723
|
-
text: JSON.stringify(response, null, 2)
|
|
724
|
-
}]
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
// Step 2: Register user
|
|
729
|
-
async function registerUser(args) {
|
|
730
|
-
const { user_email, user_name, accept_terms, session_id } = args;
|
|
731
|
-
|
|
732
|
-
if (!accept_terms) {
|
|
733
|
-
return {
|
|
734
|
-
content: [{
|
|
735
|
-
type: 'text',
|
|
736
|
-
text: JSON.stringify({
|
|
737
|
-
status: 'error',
|
|
738
|
-
message: 'You must accept the terms and conditions to continue.',
|
|
739
|
-
terms_url: CONFIG.terms_url
|
|
740
|
-
}, null, 2)
|
|
741
|
-
}]
|
|
742
|
-
};
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
if (!user_email || !user_name) {
|
|
746
|
-
return {
|
|
747
|
-
content: [{
|
|
748
|
-
type: 'text',
|
|
749
|
-
text: JSON.stringify({
|
|
750
|
-
status: 'error',
|
|
751
|
-
message: 'Email and name are required for registration.'
|
|
752
|
-
}, null, 2)
|
|
753
|
-
}]
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
console.error(`Registering user: ${user_email}`);
|
|
758
|
-
|
|
759
|
-
// Generate secure password
|
|
760
|
-
const password = crypto.randomBytes(12).toString('base64').replace(/[^a-zA-Z0-9]/g, '') + '!Aa1';
|
|
761
|
-
|
|
762
|
-
// Create user via API
|
|
763
|
-
const result = await apiRequest('POST', '/api/v1/users', {
|
|
764
|
-
email: user_email,
|
|
765
|
-
name: user_name,
|
|
766
|
-
password
|
|
767
|
-
}, false);
|
|
768
|
-
|
|
769
|
-
if (!result.success) {
|
|
770
|
-
return {
|
|
771
|
-
content: [{
|
|
772
|
-
type: 'text',
|
|
773
|
-
text: JSON.stringify({
|
|
774
|
-
status: 'error',
|
|
775
|
-
message: `Registration failed: ${result.error}`
|
|
776
|
-
}, null, 2)
|
|
777
|
-
}]
|
|
778
|
-
};
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
// Save user configuration
|
|
782
|
-
const userConfig = {
|
|
783
|
-
email: user_email,
|
|
784
|
-
name: user_name,
|
|
785
|
-
token: result.data.token,
|
|
786
|
-
user_id: result.data.id,
|
|
787
|
-
gitlab_id: result.data.gitlab_id,
|
|
788
|
-
created: new Date().toISOString()
|
|
789
|
-
};
|
|
790
|
-
await saveUserConfig(userConfig);
|
|
791
|
-
|
|
792
|
-
// Update session
|
|
793
|
-
if (session_id && sessions.has(session_id)) {
|
|
794
|
-
const session = sessions.get(session_id);
|
|
795
|
-
session.hasAccount = true;
|
|
796
|
-
session.userEmail = user_email;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
// Generate Dockerfile if we have session context
|
|
800
|
-
let dockerfileSection = {};
|
|
801
|
-
if (session_id && sessions.has(session_id)) {
|
|
802
|
-
const session = sessions.get(session_id);
|
|
803
|
-
if (!session.hasDockerfile) {
|
|
804
|
-
const dockerfile = generateDockerfile(session.framework, session.projectName);
|
|
805
|
-
dockerfileSection = {
|
|
806
|
-
suggested_dockerfile: {
|
|
807
|
-
content: dockerfile,
|
|
808
|
-
framework_detected: session.framework,
|
|
809
|
-
message: 'Generated Dockerfile for your project.'
|
|
810
|
-
}
|
|
811
|
-
};
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
return {
|
|
816
|
-
content: [{
|
|
817
|
-
type: 'text',
|
|
818
|
-
text: JSON.stringify({
|
|
819
|
-
status: 'registration_successful',
|
|
820
|
-
message: 'Welcome to MLGym! Your account has been created.',
|
|
821
|
-
user: {
|
|
822
|
-
email: user_email,
|
|
823
|
-
name: user_name,
|
|
824
|
-
gitlab_id: result.data.gitlab_id
|
|
825
|
-
},
|
|
826
|
-
credentials: {
|
|
827
|
-
note: 'Save these credentials securely:',
|
|
828
|
-
email: user_email,
|
|
829
|
-
password,
|
|
830
|
-
gitlab_url: CONFIG.gitlab_url
|
|
831
|
-
},
|
|
832
|
-
...dockerfileSection,
|
|
833
|
-
next_step: 'configuration',
|
|
834
|
-
action_required: {
|
|
835
|
-
message: 'Account created. Now configure your project for deployment.',
|
|
836
|
-
next_action: 'mlgym_deploy with action: "configure"',
|
|
837
|
-
session_id,
|
|
838
|
-
required_fields: dockerfileSection.suggested_dockerfile ? ['confirm_dockerfile'] : []
|
|
839
|
-
}
|
|
840
|
-
}, null, 2)
|
|
841
|
-
}]
|
|
842
|
-
};
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
// Step 3: Configure project
|
|
846
|
-
async function configureProject(sessionId, args) {
|
|
847
|
-
if (!sessionId || !sessions.has(sessionId)) {
|
|
848
|
-
return {
|
|
849
|
-
content: [{
|
|
850
|
-
type: 'text',
|
|
851
|
-
text: JSON.stringify({
|
|
852
|
-
status: 'error',
|
|
853
|
-
message: 'Invalid session. Please start with action: "analyze"'
|
|
854
|
-
}, null, 2)
|
|
855
|
-
}]
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
const session = sessions.get(sessionId);
|
|
860
|
-
const { confirm_dockerfile, custom_dockerfile } = args;
|
|
861
|
-
|
|
862
|
-
// Create/update Dockerfile if needed
|
|
863
|
-
if (!session.hasDockerfile) {
|
|
864
|
-
if (!confirm_dockerfile && !custom_dockerfile) {
|
|
865
|
-
return {
|
|
866
|
-
content: [{
|
|
867
|
-
type: 'text',
|
|
868
|
-
text: JSON.stringify({
|
|
869
|
-
status: 'error',
|
|
870
|
-
message: 'Please confirm the generated Dockerfile or provide a custom one.'
|
|
871
|
-
}, null, 2)
|
|
872
|
-
}]
|
|
873
|
-
};
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
const dockerfilePath = path.join(session.localPath, 'Dockerfile');
|
|
877
|
-
const dockerfileContent = custom_dockerfile || generateDockerfile(session.framework, session.projectName);
|
|
878
|
-
|
|
879
|
-
await fs.writeFile(dockerfilePath, dockerfileContent);
|
|
880
|
-
console.error('Created Dockerfile at:', dockerfilePath);
|
|
881
|
-
|
|
882
|
-
// Create nginx.conf for React apps
|
|
883
|
-
if (session.framework === 'react' && !custom_dockerfile) {
|
|
884
|
-
const nginxPath = path.join(session.localPath, 'nginx.conf');
|
|
885
|
-
await fs.writeFile(nginxPath, generateNginxConf());
|
|
886
|
-
console.error('Created nginx.conf for React app');
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
// Update session
|
|
891
|
-
session.configured = true;
|
|
892
|
-
session.dockerfileCreated = !session.hasDockerfile;
|
|
893
|
-
|
|
894
|
-
return {
|
|
895
|
-
content: [{
|
|
896
|
-
type: 'text',
|
|
897
|
-
text: JSON.stringify({
|
|
898
|
-
status: 'configuration_complete',
|
|
899
|
-
message: 'Project configured successfully.',
|
|
900
|
-
project: {
|
|
901
|
-
name: session.projectName,
|
|
902
|
-
framework: session.framework,
|
|
903
|
-
dockerfile: session.dockerfileCreated ? 'Created' : 'Existing'
|
|
904
|
-
},
|
|
905
|
-
deployment_regions: REGIONS,
|
|
906
|
-
next_step: 'deployment',
|
|
907
|
-
action_required: {
|
|
908
|
-
message: 'Choose deployment region and confirm deployment.',
|
|
909
|
-
next_action: 'mlgym_deploy with action: "deploy"',
|
|
910
|
-
session_id: sessionId,
|
|
911
|
-
required_fields: ['region', 'confirm_deployment']
|
|
912
|
-
}
|
|
913
|
-
}, null, 2)
|
|
914
|
-
}]
|
|
915
|
-
};
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
// Step 4: Deploy project
|
|
919
|
-
async function deployProject(sessionId, args) {
|
|
920
|
-
if (!sessionId || !sessions.has(sessionId)) {
|
|
921
|
-
return {
|
|
922
|
-
content: [{
|
|
923
|
-
type: 'text',
|
|
924
|
-
text: JSON.stringify({
|
|
925
|
-
status: 'error',
|
|
926
|
-
message: 'Invalid session. Please start with action: "analyze"'
|
|
927
|
-
}, null, 2)
|
|
928
|
-
}]
|
|
929
|
-
};
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
const session = sessions.get(sessionId);
|
|
933
|
-
const { region, confirm_deployment } = args;
|
|
934
|
-
|
|
935
|
-
if (!confirm_deployment) {
|
|
936
|
-
return {
|
|
937
|
-
content: [{
|
|
938
|
-
type: 'text',
|
|
939
|
-
text: JSON.stringify({
|
|
940
|
-
status: 'error',
|
|
941
|
-
message: 'Deployment cancelled. Set confirm_deployment: true to proceed.'
|
|
942
|
-
}, null, 2)
|
|
943
|
-
}]
|
|
944
|
-
};
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
if (!region || !REGIONS[region]) {
|
|
948
|
-
return {
|
|
949
|
-
content: [{
|
|
950
|
-
type: 'text',
|
|
951
|
-
text: JSON.stringify({
|
|
952
|
-
status: 'error',
|
|
953
|
-
message: 'Invalid region. Choose from: ' + Object.keys(REGIONS).join(', ')
|
|
954
|
-
}, null, 2)
|
|
955
|
-
}]
|
|
956
|
-
};
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
console.error(`Deploying project ${session.projectName} to ${region}`);
|
|
960
|
-
|
|
961
|
-
// Create project via API
|
|
962
|
-
const projectData = {
|
|
963
|
-
name: session.projectName,
|
|
964
|
-
description: `Deployed via MLGym MCP from ${session.framework} project`,
|
|
965
|
-
visibility: 'private',
|
|
966
|
-
enable_deployment: true,
|
|
967
|
-
webhook_secret: crypto.randomBytes(16).toString('hex')
|
|
968
|
-
// Note: deployment_region is not used by backend, it uses random server selection
|
|
969
|
-
};
|
|
970
|
-
|
|
971
|
-
console.error(`Creating project with deployment enabled for ${session.projectName} in region ${region}`);
|
|
972
|
-
const result = await apiRequest('POST', '/api/v1/projects', projectData);
|
|
973
|
-
|
|
974
|
-
if (!result.success) {
|
|
975
|
-
console.error(`Project creation failed: ${result.error}`);
|
|
976
|
-
// Provide more detailed error information
|
|
977
|
-
return {
|
|
978
|
-
content: [{
|
|
979
|
-
type: 'text',
|
|
980
|
-
text: JSON.stringify({
|
|
981
|
-
status: '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
|
-
}
|
|
989
|
-
}, null, 2)
|
|
990
|
-
}]
|
|
991
|
-
};
|
|
992
|
-
}
|
|
993
|
-
|
|
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
|
-
}
|
|
1005
|
-
|
|
1006
|
-
// Initialize git repository
|
|
1007
|
-
const gitCommands = [
|
|
1008
|
-
'git init',
|
|
1009
|
-
`git remote add origin ${project.ssh_url_to_repo}`,
|
|
1010
|
-
'git add .',
|
|
1011
|
-
'git commit -m "Initial deployment via MLGym"',
|
|
1012
|
-
'# Wait 5-10 seconds for SSH key to propagate in GitLab',
|
|
1013
|
-
'sleep 10',
|
|
1014
|
-
'git push -u origin main'
|
|
1015
|
-
];
|
|
1016
|
-
|
|
1017
|
-
// Clear session
|
|
1018
|
-
sessions.delete(sessionId);
|
|
1019
|
-
|
|
1020
|
-
return {
|
|
1021
|
-
content: [{
|
|
1022
|
-
type: 'text',
|
|
1023
|
-
text: JSON.stringify({
|
|
1024
|
-
status: 'deployment_successful',
|
|
1025
|
-
message: '🚀 Project deployed successfully!',
|
|
1026
|
-
project: {
|
|
1027
|
-
name: project.name,
|
|
1028
|
-
url: project.web_url,
|
|
1029
|
-
ssh_url: project.ssh_url_to_repo,
|
|
1030
|
-
region: REGIONS[region].name
|
|
1031
|
-
},
|
|
1032
|
-
deployment: {
|
|
1033
|
-
domain: `${session.projectName}.${region}.mlgym.app`,
|
|
1034
|
-
status: 'initializing',
|
|
1035
|
-
ssl: 'auto-provisioned',
|
|
1036
|
-
cdn: 'enabled'
|
|
1037
|
-
},
|
|
1038
|
-
important_note: '⚠️ SSH key propagation: Please wait 10 seconds before pushing to allow GitLab to activate your SSH key',
|
|
1039
|
-
next_steps: {
|
|
1040
|
-
message: 'Run these commands in your project directory:',
|
|
1041
|
-
commands: gitCommands
|
|
1042
|
-
},
|
|
1043
|
-
monitoring: {
|
|
1044
|
-
dashboard: `${CONFIG.backend_url}/projects/${project.id}`,
|
|
1045
|
-
logs: `${CONFIG.coolify_url}/projects/${project.id}/logs`
|
|
1046
|
-
}
|
|
1047
|
-
}, null, 2)
|
|
1048
|
-
}]
|
|
1049
|
-
};
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
// Start the server
|
|
1053
|
-
async function main() {
|
|
1054
|
-
const transport = new StdioServerTransport();
|
|
1055
|
-
await server.connect(transport);
|
|
1056
|
-
console.error('MLGym Smart Deploy MCP Server v3.0.0 started');
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
main().catch((error) => {
|
|
1060
|
-
console.error('Server error:', error);
|
|
1061
|
-
process.exit(1);
|
|
1062
|
-
});
|