mlgym-deploy 2.10.0 → 3.0.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/CHANGELOG-v3.0.0.md +285 -0
- package/index.js +603 -136
- package/index.js.backup-atomic +1358 -0
- package/mlgym-deploy-2.10.0.tgz +0 -0
- package/package.json +15 -7
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 - INCREMENT FOR WORKFLOW FIXES
|
|
20
|
-
const CURRENT_VERSION = '
|
|
20
|
+
const CURRENT_VERSION = '3.0.1'; // Restored legacy user creation and login tools
|
|
21
21
|
const PACKAGE_NAME = 'mlgym-deploy';
|
|
22
22
|
|
|
23
23
|
// Version check state
|
|
@@ -1107,6 +1107,529 @@ async function initProject(args) {
|
|
|
1107
1107
|
};
|
|
1108
1108
|
}
|
|
1109
1109
|
|
|
1110
|
+
// ============================================================================
|
|
1111
|
+
// LEGACY USER MANAGEMENT TOOLS (DISABLED - kept for reference)
|
|
1112
|
+
// ============================================================================
|
|
1113
|
+
|
|
1114
|
+
// Create User (from v2.3.0 - ENABLED)
|
|
1115
|
+
async function createUser(args) {
|
|
1116
|
+
let { email, name, password, accept_terms } = args;
|
|
1117
|
+
|
|
1118
|
+
// Validate all required fields
|
|
1119
|
+
if (!email || !name || !password || accept_terms !== true) {
|
|
1120
|
+
return {
|
|
1121
|
+
content: [{
|
|
1122
|
+
type: 'text',
|
|
1123
|
+
text: JSON.stringify({
|
|
1124
|
+
status: 'error',
|
|
1125
|
+
message: 'All fields are required: email, name, password, and accept_terms must be true',
|
|
1126
|
+
required_fields: {
|
|
1127
|
+
email: email ? '✓ provided' : '✗ missing',
|
|
1128
|
+
name: name ? '✓ provided' : '✗ missing',
|
|
1129
|
+
password: password ? '✓ provided' : '✗ missing',
|
|
1130
|
+
accept_terms: accept_terms === true ? '✓ accepted' : '✗ must explicitly accept terms'
|
|
1131
|
+
}
|
|
1132
|
+
}, null, 2)
|
|
1133
|
+
}]
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
console.error(`Creating user: ${email}`);
|
|
1138
|
+
|
|
1139
|
+
try {
|
|
1140
|
+
// Generate SSH key pair
|
|
1141
|
+
console.error('Generating SSH key pair...');
|
|
1142
|
+
const { publicKey, privateKeyPath } = await generateSSHKeyPair(email);
|
|
1143
|
+
const publicKeyPath = privateKeyPath + '.pub';
|
|
1144
|
+
console.error(`SSH key generated: ${privateKeyPath}`);
|
|
1145
|
+
|
|
1146
|
+
// Create user via backend API with SSH key
|
|
1147
|
+
const result = await apiRequest('POST', '/api/v1/users', {
|
|
1148
|
+
email,
|
|
1149
|
+
name,
|
|
1150
|
+
password,
|
|
1151
|
+
ssh_key: publicKey
|
|
1152
|
+
}, false);
|
|
1153
|
+
|
|
1154
|
+
if (!result.success) {
|
|
1155
|
+
return {
|
|
1156
|
+
content: [{
|
|
1157
|
+
type: 'text',
|
|
1158
|
+
text: `Failed to create user: ${result.error}`
|
|
1159
|
+
}]
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// Save authentication token
|
|
1164
|
+
if (result.data.token) {
|
|
1165
|
+
await saveAuth(email, result.data.token);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
const userData = result.data.user || {};
|
|
1169
|
+
const sshKeyStatus = result.data.ssh_key_status || 'Unknown';
|
|
1170
|
+
const sshKeyUploaded = sshKeyStatus === 'SSH key uploaded successfully';
|
|
1171
|
+
|
|
1172
|
+
const nextSteps = [
|
|
1173
|
+
'Using your provided password',
|
|
1174
|
+
`SSH key generated locally at: ${privateKeyPath}`
|
|
1175
|
+
];
|
|
1176
|
+
|
|
1177
|
+
if (sshKeyUploaded) {
|
|
1178
|
+
nextSteps.push(`✅ SSH key uploaded to GitLab (ID: ${result.data.ssh_key_id || 'unknown'})`);
|
|
1179
|
+
nextSteps.push(`Add to SSH agent: ssh-add "${privateKeyPath}"`);
|
|
1180
|
+
nextSteps.push('You can now push code to GitLab repositories');
|
|
1181
|
+
} else {
|
|
1182
|
+
nextSteps.push(`⚠️ SSH key upload failed: ${sshKeyStatus}`);
|
|
1183
|
+
nextSteps.push('You need to manually add the SSH key to GitLab:');
|
|
1184
|
+
nextSteps.push(`cat ${publicKeyPath} # Copy this key`);
|
|
1185
|
+
nextSteps.push('Then add it at: https://git.mlgym.io/-/user_settings/ssh_keys');
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
const response = {
|
|
1189
|
+
user_id: userData.user_id || userData.UserID,
|
|
1190
|
+
email: userData.email || email,
|
|
1191
|
+
name: userData.name || name,
|
|
1192
|
+
gitlab_username: userData.gitlab_username || userData.GitLabUsername,
|
|
1193
|
+
ssh_key_path: privateKeyPath,
|
|
1194
|
+
ssh_key_status: sshKeyStatus,
|
|
1195
|
+
ssh_key_uploaded: sshKeyUploaded,
|
|
1196
|
+
token: result.data.token,
|
|
1197
|
+
message: result.data.message || 'User created successfully',
|
|
1198
|
+
next_steps: nextSteps
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
return {
|
|
1202
|
+
content: [{
|
|
1203
|
+
type: 'text',
|
|
1204
|
+
text: JSON.stringify(response, null, 2)
|
|
1205
|
+
}]
|
|
1206
|
+
};
|
|
1207
|
+
} catch (error) {
|
|
1208
|
+
return {
|
|
1209
|
+
content: [{
|
|
1210
|
+
type: 'text',
|
|
1211
|
+
text: `Failed to create user: ${error.message}`
|
|
1212
|
+
}]
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
// Login User (from v2.3.0 - ENABLED)
|
|
1218
|
+
async function loginUser(args) {
|
|
1219
|
+
const { email, password } = args;
|
|
1220
|
+
|
|
1221
|
+
if (!email || !password) {
|
|
1222
|
+
return {
|
|
1223
|
+
content: [{
|
|
1224
|
+
type: 'text',
|
|
1225
|
+
text: JSON.stringify({
|
|
1226
|
+
status: 'error',
|
|
1227
|
+
message: 'Email and password are required'
|
|
1228
|
+
}, null, 2)
|
|
1229
|
+
}]
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
console.error(`Logging in user: ${email}`);
|
|
1234
|
+
|
|
1235
|
+
try {
|
|
1236
|
+
const result = await apiRequest('POST', '/api/v1/auth/login', {
|
|
1237
|
+
email,
|
|
1238
|
+
password
|
|
1239
|
+
}, false);
|
|
1240
|
+
|
|
1241
|
+
if (!result.success) {
|
|
1242
|
+
return {
|
|
1243
|
+
content: [{
|
|
1244
|
+
type: 'text',
|
|
1245
|
+
text: JSON.stringify({
|
|
1246
|
+
status: 'error',
|
|
1247
|
+
message: result.error || 'Login failed',
|
|
1248
|
+
details: 'Invalid email or password'
|
|
1249
|
+
}, null, 2)
|
|
1250
|
+
}]
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// Save authentication token
|
|
1255
|
+
if (result.data.token) {
|
|
1256
|
+
await saveAuth(email, result.data.token);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
const userData = result.data.user || {};
|
|
1260
|
+
const response = {
|
|
1261
|
+
status: 'success',
|
|
1262
|
+
token: result.data.token,
|
|
1263
|
+
user: {
|
|
1264
|
+
user_id: userData.user_id || userData.UserID,
|
|
1265
|
+
email: userData.email || email,
|
|
1266
|
+
name: userData.name || userData.Name,
|
|
1267
|
+
gitlab_username: userData.gitlab_username || userData.GitLabUsername
|
|
1268
|
+
},
|
|
1269
|
+
expires_at: result.data.expires_at || new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
|
1270
|
+
message: 'Login successful',
|
|
1271
|
+
next_steps: [
|
|
1272
|
+
'You can now create and manage projects',
|
|
1273
|
+
'Use mlgym_project_init or mlgym_deploy to create a new project',
|
|
1274
|
+
'Token will expire in 24 hours'
|
|
1275
|
+
]
|
|
1276
|
+
};
|
|
1277
|
+
|
|
1278
|
+
return {
|
|
1279
|
+
content: [{
|
|
1280
|
+
type: 'text',
|
|
1281
|
+
text: JSON.stringify(response, null, 2)
|
|
1282
|
+
}]
|
|
1283
|
+
};
|
|
1284
|
+
} catch (error) {
|
|
1285
|
+
return {
|
|
1286
|
+
content: [{
|
|
1287
|
+
type: 'text',
|
|
1288
|
+
text: JSON.stringify({
|
|
1289
|
+
status: 'error',
|
|
1290
|
+
message: `Login failed: ${error.message}`
|
|
1291
|
+
}, null, 2)
|
|
1292
|
+
}]
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// ============================================================================
|
|
1298
|
+
// MONOLITHIC DEPLOYMENT WORKFLOW CLASS (v3.0.0)
|
|
1299
|
+
// ============================================================================
|
|
1300
|
+
class DeploymentWorkflow {
|
|
1301
|
+
constructor() {
|
|
1302
|
+
this.currentStep = null;
|
|
1303
|
+
this.authToken = null;
|
|
1304
|
+
this.projectAnalysis = null;
|
|
1305
|
+
this.gitlabProject = null;
|
|
1306
|
+
this.steps = [];
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
addStep(stepName, status = 'pending', result = null) {
|
|
1310
|
+
this.steps.push({ step: stepName, status, result, timestamp: new Date().toISOString() });
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
updateLastStep(status, result = null) {
|
|
1314
|
+
if (this.steps.length > 0) {
|
|
1315
|
+
this.steps[this.steps.length - 1].status = status;
|
|
1316
|
+
if (result) this.steps[this.steps.length - 1].result = result;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
async ensureAuth(email, password) {
|
|
1321
|
+
this.currentStep = 'authentication';
|
|
1322
|
+
this.addStep('authentication', 'running');
|
|
1323
|
+
|
|
1324
|
+
// Check if already authenticated
|
|
1325
|
+
const auth = await loadAuth();
|
|
1326
|
+
if (auth.token) {
|
|
1327
|
+
this.authToken = auth.token;
|
|
1328
|
+
this.updateLastStep('completed', 'Using cached authentication');
|
|
1329
|
+
return { authenticated: true, cached: true };
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
// Need credentials
|
|
1333
|
+
if (!email || !password) {
|
|
1334
|
+
this.updateLastStep('failed', 'Email and password required');
|
|
1335
|
+
throw new Error('Authentication required. Please provide email and password.');
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
// Authenticate using existing function
|
|
1339
|
+
const authResult = await authenticate({ email, password, create_if_not_exists: false });
|
|
1340
|
+
|
|
1341
|
+
// Parse the result
|
|
1342
|
+
const resultData = JSON.parse(authResult.content[0].text);
|
|
1343
|
+
|
|
1344
|
+
if (resultData.status === 'success' || resultData.authenticated) {
|
|
1345
|
+
this.authToken = (await loadAuth()).token;
|
|
1346
|
+
this.updateLastStep('completed', 'Authentication successful');
|
|
1347
|
+
return { authenticated: true, cached: false };
|
|
1348
|
+
} else {
|
|
1349
|
+
this.updateLastStep('failed', resultData.message);
|
|
1350
|
+
throw new Error(`Authentication failed: ${resultData.message}`);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
async analyzeProject(local_path) {
|
|
1355
|
+
this.currentStep = 'analysis';
|
|
1356
|
+
this.addStep('project_analysis', 'running');
|
|
1357
|
+
|
|
1358
|
+
try {
|
|
1359
|
+
this.projectAnalysis = await analyzeProject(local_path);
|
|
1360
|
+
this.updateLastStep('completed', {
|
|
1361
|
+
type: this.projectAnalysis.project_type,
|
|
1362
|
+
framework: this.projectAnalysis.framework
|
|
1363
|
+
});
|
|
1364
|
+
return this.projectAnalysis;
|
|
1365
|
+
} catch (error) {
|
|
1366
|
+
this.updateLastStep('failed', error.message);
|
|
1367
|
+
throw error;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
async checkExisting(local_path) {
|
|
1372
|
+
this.currentStep = 'check_existing';
|
|
1373
|
+
this.addStep('check_existing_project', 'running');
|
|
1374
|
+
|
|
1375
|
+
try {
|
|
1376
|
+
const existing = await checkExistingProject(local_path);
|
|
1377
|
+
|
|
1378
|
+
if (existing.exists) {
|
|
1379
|
+
this.updateLastStep('completed', 'Project already configured');
|
|
1380
|
+
return existing;
|
|
1381
|
+
} else {
|
|
1382
|
+
this.updateLastStep('completed', 'No existing project found');
|
|
1383
|
+
return null;
|
|
1384
|
+
}
|
|
1385
|
+
} catch (error) {
|
|
1386
|
+
this.updateLastStep('failed', error.message);
|
|
1387
|
+
throw error;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
async prepareProject(projectType, framework, packageManager) {
|
|
1392
|
+
this.currentStep = 'preparation';
|
|
1393
|
+
this.addStep('prepare_project', 'running');
|
|
1394
|
+
|
|
1395
|
+
try {
|
|
1396
|
+
// Check if Dockerfile exists
|
|
1397
|
+
const analysis = this.projectAnalysis || await analyzeProject('.');
|
|
1398
|
+
|
|
1399
|
+
if (analysis.has_dockerfile) {
|
|
1400
|
+
this.updateLastStep('skipped', 'Dockerfile already exists');
|
|
1401
|
+
return { skipped: true, reason: 'Dockerfile already exists' };
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
// Generate Dockerfile
|
|
1405
|
+
const prepResult = await prepareProject({
|
|
1406
|
+
local_path: '.',
|
|
1407
|
+
project_type: projectType || analysis.project_type,
|
|
1408
|
+
framework: framework || analysis.framework,
|
|
1409
|
+
package_manager: packageManager || 'npm'
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
this.updateLastStep('completed', prepResult.actions);
|
|
1413
|
+
return prepResult;
|
|
1414
|
+
} catch (error) {
|
|
1415
|
+
this.updateLastStep('failed', error.message);
|
|
1416
|
+
throw error;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
async createAndDeployProject(params) {
|
|
1421
|
+
this.currentStep = 'project_creation';
|
|
1422
|
+
this.addStep('create_gitlab_project', 'running');
|
|
1423
|
+
|
|
1424
|
+
const { name, description, hostname, local_path } = params;
|
|
1425
|
+
|
|
1426
|
+
try {
|
|
1427
|
+
// Create project using existing function
|
|
1428
|
+
const initResult = await initProject({
|
|
1429
|
+
name,
|
|
1430
|
+
description,
|
|
1431
|
+
enable_deployment: true,
|
|
1432
|
+
hostname: hostname || name,
|
|
1433
|
+
local_path: local_path || '.'
|
|
1434
|
+
});
|
|
1435
|
+
|
|
1436
|
+
// Parse result
|
|
1437
|
+
const resultData = JSON.parse(initResult.content[0].text);
|
|
1438
|
+
|
|
1439
|
+
if (resultData.status === 'success') {
|
|
1440
|
+
this.gitlabProject = resultData.project;
|
|
1441
|
+
this.updateLastStep('completed', resultData.project);
|
|
1442
|
+
return resultData;
|
|
1443
|
+
} else {
|
|
1444
|
+
this.updateLastStep('failed', resultData.error || resultData.message);
|
|
1445
|
+
throw new Error(resultData.error || resultData.message);
|
|
1446
|
+
}
|
|
1447
|
+
} catch (error) {
|
|
1448
|
+
this.updateLastStep('failed', error.message);
|
|
1449
|
+
throw error;
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
getRecoverySuggestion(error) {
|
|
1454
|
+
const errorMsg = error.message.toLowerCase();
|
|
1455
|
+
|
|
1456
|
+
if (errorMsg.includes('authentication') || errorMsg.includes('token')) {
|
|
1457
|
+
return 'Please provide valid email and password credentials';
|
|
1458
|
+
}
|
|
1459
|
+
if (errorMsg.includes('project') && errorMsg.includes('exists')) {
|
|
1460
|
+
return 'Project already exists. Use mlgym_status to check existing configuration';
|
|
1461
|
+
}
|
|
1462
|
+
if (errorMsg.includes('hostname')) {
|
|
1463
|
+
return 'Please provide a valid hostname (lowercase letters, numbers, and hyphens only)';
|
|
1464
|
+
}
|
|
1465
|
+
if (errorMsg.includes('ssh')) {
|
|
1466
|
+
return 'SSH key generation failed. Check SSH configuration';
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
return 'Check the error message and try again with corrected parameters';
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// ============================================================================
|
|
1474
|
+
// MONOLITHIC DEPLOY FUNCTION
|
|
1475
|
+
// ============================================================================
|
|
1476
|
+
async function deployProject(args) {
|
|
1477
|
+
const workflow = new DeploymentWorkflow();
|
|
1478
|
+
|
|
1479
|
+
const {
|
|
1480
|
+
email,
|
|
1481
|
+
password,
|
|
1482
|
+
project_name,
|
|
1483
|
+
project_description,
|
|
1484
|
+
hostname,
|
|
1485
|
+
local_path = '.',
|
|
1486
|
+
project_type,
|
|
1487
|
+
framework,
|
|
1488
|
+
package_manager = 'npm'
|
|
1489
|
+
} = args;
|
|
1490
|
+
|
|
1491
|
+
try {
|
|
1492
|
+
// Step 1: Ensure authenticated
|
|
1493
|
+
await workflow.ensureAuth(email, password);
|
|
1494
|
+
|
|
1495
|
+
// Step 2: Check if project already exists
|
|
1496
|
+
const existing = await workflow.checkExisting(local_path);
|
|
1497
|
+
if (existing && existing.exists) {
|
|
1498
|
+
return {
|
|
1499
|
+
content: [{
|
|
1500
|
+
type: 'text',
|
|
1501
|
+
text: JSON.stringify({
|
|
1502
|
+
status: 'already_exists',
|
|
1503
|
+
message: 'Project already configured in this directory',
|
|
1504
|
+
project: {
|
|
1505
|
+
name: existing.name,
|
|
1506
|
+
namespace: existing.namespace,
|
|
1507
|
+
git_remote: `git@git.mlgym.io:${existing.namespace}/${existing.name}.git`
|
|
1508
|
+
},
|
|
1509
|
+
next_steps: [
|
|
1510
|
+
'Project is already set up',
|
|
1511
|
+
'To update: git push mlgym main',
|
|
1512
|
+
'To check status: use mlgym_status tool'
|
|
1513
|
+
],
|
|
1514
|
+
workflow_steps: workflow.steps
|
|
1515
|
+
}, null, 2)
|
|
1516
|
+
}]
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
// Step 3: Analyze project (auto-detect type and framework)
|
|
1521
|
+
const analysis = await workflow.analyzeProject(local_path);
|
|
1522
|
+
|
|
1523
|
+
// Step 4: Prepare project (generate Dockerfile if needed)
|
|
1524
|
+
await workflow.prepareProject(project_type, framework, package_manager);
|
|
1525
|
+
|
|
1526
|
+
// Step 5: Create GitLab project and deploy
|
|
1527
|
+
const deployResult = await workflow.createAndDeployProject({
|
|
1528
|
+
name: project_name,
|
|
1529
|
+
description: project_description,
|
|
1530
|
+
hostname: hostname || project_name,
|
|
1531
|
+
local_path
|
|
1532
|
+
});
|
|
1533
|
+
|
|
1534
|
+
// Success response
|
|
1535
|
+
return {
|
|
1536
|
+
content: [{
|
|
1537
|
+
type: 'text',
|
|
1538
|
+
text: JSON.stringify({
|
|
1539
|
+
status: 'success',
|
|
1540
|
+
message: 'Project deployed successfully',
|
|
1541
|
+
project: deployResult.project,
|
|
1542
|
+
deployment: deployResult.deployment,
|
|
1543
|
+
url: deployResult.project.deployment_url,
|
|
1544
|
+
analysis: {
|
|
1545
|
+
detected_type: analysis.project_type,
|
|
1546
|
+
detected_framework: analysis.framework
|
|
1547
|
+
},
|
|
1548
|
+
next_steps: deployResult.next_steps,
|
|
1549
|
+
workflow_steps: workflow.steps
|
|
1550
|
+
}, null, 2)
|
|
1551
|
+
}]
|
|
1552
|
+
};
|
|
1553
|
+
|
|
1554
|
+
} catch (error) {
|
|
1555
|
+
console.error('Deployment workflow failed:', error);
|
|
1556
|
+
|
|
1557
|
+
return {
|
|
1558
|
+
content: [{
|
|
1559
|
+
type: 'text',
|
|
1560
|
+
text: JSON.stringify({
|
|
1561
|
+
status: 'error',
|
|
1562
|
+
message: 'Deployment failed',
|
|
1563
|
+
error: error.message,
|
|
1564
|
+
failed_at_step: workflow.currentStep,
|
|
1565
|
+
recovery_suggestion: workflow.getRecoverySuggestion(error),
|
|
1566
|
+
workflow_steps: workflow.steps
|
|
1567
|
+
}, null, 2)
|
|
1568
|
+
}]
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// ============================================================================
|
|
1574
|
+
// SIMPLIFIED STATUS CHECK
|
|
1575
|
+
// ============================================================================
|
|
1576
|
+
async function getStatus(args) {
|
|
1577
|
+
const local_path = args.local_path || '.';
|
|
1578
|
+
|
|
1579
|
+
try {
|
|
1580
|
+
// Check authentication
|
|
1581
|
+
const auth = await loadAuth();
|
|
1582
|
+
const authenticated = !!auth.token;
|
|
1583
|
+
|
|
1584
|
+
// Check existing project
|
|
1585
|
+
const projectStatus = await checkExistingProject(local_path);
|
|
1586
|
+
|
|
1587
|
+
// Analyze project if directory exists
|
|
1588
|
+
let analysis = null;
|
|
1589
|
+
try {
|
|
1590
|
+
analysis = await analyzeProject(local_path);
|
|
1591
|
+
} catch (e) {
|
|
1592
|
+
analysis = { error: 'Could not analyze project' };
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
return {
|
|
1596
|
+
content: [{
|
|
1597
|
+
type: 'text',
|
|
1598
|
+
text: JSON.stringify({
|
|
1599
|
+
status: 'ok',
|
|
1600
|
+
authentication: {
|
|
1601
|
+
authenticated,
|
|
1602
|
+
email: auth.email || null
|
|
1603
|
+
},
|
|
1604
|
+
project: projectStatus.exists ? {
|
|
1605
|
+
configured: true,
|
|
1606
|
+
name: projectStatus.name,
|
|
1607
|
+
namespace: projectStatus.namespace,
|
|
1608
|
+
git_remote: `git@git.mlgym.io:${projectStatus.namespace}/${projectStatus.name}.git`
|
|
1609
|
+
} : {
|
|
1610
|
+
configured: false
|
|
1611
|
+
},
|
|
1612
|
+
analysis: analysis.project_type ? {
|
|
1613
|
+
type: analysis.project_type,
|
|
1614
|
+
framework: analysis.framework,
|
|
1615
|
+
has_dockerfile: analysis.has_dockerfile
|
|
1616
|
+
} : null
|
|
1617
|
+
}, null, 2)
|
|
1618
|
+
}]
|
|
1619
|
+
};
|
|
1620
|
+
} catch (error) {
|
|
1621
|
+
return {
|
|
1622
|
+
content: [{
|
|
1623
|
+
type: 'text',
|
|
1624
|
+
text: JSON.stringify({
|
|
1625
|
+
status: 'error',
|
|
1626
|
+
error: error.message
|
|
1627
|
+
}, null, 2)
|
|
1628
|
+
}]
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1110
1633
|
// Create the MCP server
|
|
1111
1634
|
const server = new Server(
|
|
1112
1635
|
{
|
|
@@ -1124,157 +1647,128 @@ const server = new Server(
|
|
|
1124
1647
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1125
1648
|
return {
|
|
1126
1649
|
tools: [
|
|
1650
|
+
// ========== NEW MONOLITHIC TOOLS (v3.0.0) ==========
|
|
1127
1651
|
{
|
|
1128
|
-
name: '
|
|
1129
|
-
description: '
|
|
1130
|
-
inputSchema: {
|
|
1131
|
-
type: 'object',
|
|
1132
|
-
properties: {}
|
|
1133
|
-
}
|
|
1134
|
-
},
|
|
1135
|
-
{
|
|
1136
|
-
name: 'mlgym_authenticate',
|
|
1137
|
-
description: 'PHASE 1: Authentication ONLY. Get email, password, and existing account status in ONE interaction. Never ask for project details here!',
|
|
1652
|
+
name: 'mlgym_deploy',
|
|
1653
|
+
description: 'RECOMMENDED: Complete deployment workflow in one call. Automatically authenticates (uses cached token if available), analyzes project, creates GitLab repo, and deploys to Coolify. Just provide project details - the server handles everything else internally.',
|
|
1138
1654
|
inputSchema: {
|
|
1139
1655
|
type: 'object',
|
|
1140
1656
|
properties: {
|
|
1657
|
+
project_name: {
|
|
1658
|
+
type: 'string',
|
|
1659
|
+
description: 'Project name (lowercase alphanumeric with hyphens, e.g., "my-app")',
|
|
1660
|
+
pattern: '^[a-z0-9][a-z0-9-]*[a-z0-9]$',
|
|
1661
|
+
minLength: 3
|
|
1662
|
+
},
|
|
1663
|
+
project_description: {
|
|
1664
|
+
type: 'string',
|
|
1665
|
+
description: 'Brief project description (min 10 characters)',
|
|
1666
|
+
minLength: 10
|
|
1667
|
+
},
|
|
1141
1668
|
email: {
|
|
1142
1669
|
type: 'string',
|
|
1143
|
-
description: 'Email
|
|
1670
|
+
description: 'Email for authentication (optional if already authenticated)',
|
|
1144
1671
|
pattern: '^[^@]+@[^@]+\\.[^@]+$'
|
|
1145
1672
|
},
|
|
1146
1673
|
password: {
|
|
1147
1674
|
type: 'string',
|
|
1148
|
-
description: 'Password (min 8 characters)',
|
|
1675
|
+
description: 'Password (optional if already authenticated, min 8 characters)',
|
|
1149
1676
|
minLength: 8
|
|
1150
1677
|
},
|
|
1151
|
-
|
|
1152
|
-
type: 'boolean',
|
|
1153
|
-
description: 'If true and login fails, attempt to create new account',
|
|
1154
|
-
default: false
|
|
1155
|
-
},
|
|
1156
|
-
full_name: {
|
|
1678
|
+
hostname: {
|
|
1157
1679
|
type: 'string',
|
|
1158
|
-
description: '
|
|
1159
|
-
|
|
1680
|
+
description: 'Deployment hostname/subdomain (optional, defaults to project_name). Will be accessible at https://<hostname>.ezb.net',
|
|
1681
|
+
pattern: '^[a-z][a-z0-9-]*[a-z0-9]$',
|
|
1682
|
+
minLength: 3,
|
|
1683
|
+
maxLength: 63
|
|
1160
1684
|
},
|
|
1161
|
-
accept_terms: {
|
|
1162
|
-
type: 'boolean',
|
|
1163
|
-
description: 'Accept terms and conditions (required only for new account creation)',
|
|
1164
|
-
default: false
|
|
1165
|
-
}
|
|
1166
|
-
},
|
|
1167
|
-
required: ['email', 'password']
|
|
1168
|
-
}
|
|
1169
|
-
},
|
|
1170
|
-
{
|
|
1171
|
-
name: 'mlgym_project_analyze',
|
|
1172
|
-
description: 'PHASE 2: Analyze project to detect type, framework, and configuration. Call BEFORE creating project.',
|
|
1173
|
-
inputSchema: {
|
|
1174
|
-
type: 'object',
|
|
1175
|
-
properties: {
|
|
1176
1685
|
local_path: {
|
|
1177
1686
|
type: 'string',
|
|
1178
|
-
description: 'Local directory path (defaults to current directory)',
|
|
1687
|
+
description: 'Local project directory path (optional, defaults to current directory)',
|
|
1179
1688
|
default: '.'
|
|
1689
|
+
},
|
|
1690
|
+
project_type: {
|
|
1691
|
+
type: 'string',
|
|
1692
|
+
description: 'Override auto-detected project type (optional)',
|
|
1693
|
+
enum: ['nodejs', 'python', 'static', 'go']
|
|
1694
|
+
},
|
|
1695
|
+
framework: {
|
|
1696
|
+
type: 'string',
|
|
1697
|
+
description: 'Override auto-detected framework (optional)',
|
|
1698
|
+
enum: ['nextjs', 'express', 'react', 'vue', 'flask', 'fastapi', 'html']
|
|
1699
|
+
},
|
|
1700
|
+
package_manager: {
|
|
1701
|
+
type: 'string',
|
|
1702
|
+
description: 'Package manager for Node.js projects (optional, defaults to npm)',
|
|
1703
|
+
enum: ['npm', 'yarn'],
|
|
1704
|
+
default: 'npm'
|
|
1180
1705
|
}
|
|
1181
|
-
}
|
|
1706
|
+
},
|
|
1707
|
+
required: ['project_name', 'project_description']
|
|
1182
1708
|
}
|
|
1183
1709
|
},
|
|
1184
1710
|
{
|
|
1185
|
-
name: '
|
|
1186
|
-
description: '
|
|
1711
|
+
name: 'mlgym_status',
|
|
1712
|
+
description: 'Check current authentication status and project configuration. Read-only operation that shows what is already set up.',
|
|
1187
1713
|
inputSchema: {
|
|
1188
1714
|
type: 'object',
|
|
1189
1715
|
properties: {
|
|
1190
1716
|
local_path: {
|
|
1191
1717
|
type: 'string',
|
|
1192
|
-
description: 'Local directory path (defaults to current directory)',
|
|
1718
|
+
description: 'Local directory path to check (defaults to current directory)',
|
|
1193
1719
|
default: '.'
|
|
1194
1720
|
}
|
|
1195
1721
|
}
|
|
1196
1722
|
}
|
|
1197
1723
|
},
|
|
1724
|
+
// ========== LEGACY TOOLS (Re-enabled for specific use cases) ==========
|
|
1198
1725
|
{
|
|
1199
|
-
name: '
|
|
1200
|
-
description: '
|
|
1726
|
+
name: 'mlgym_user_create',
|
|
1727
|
+
description: 'Create a new MLGym user account. All fields required: email, name, password, and accept_terms=true.',
|
|
1201
1728
|
inputSchema: {
|
|
1202
1729
|
type: 'object',
|
|
1203
1730
|
properties: {
|
|
1204
|
-
|
|
1731
|
+
email: {
|
|
1205
1732
|
type: 'string',
|
|
1206
|
-
description: '
|
|
1207
|
-
pattern: '^[
|
|
1208
|
-
minLength: 3
|
|
1733
|
+
description: 'User email address',
|
|
1734
|
+
pattern: '^[^@]+@[^@]+\\.[^@]+$'
|
|
1209
1735
|
},
|
|
1210
|
-
|
|
1736
|
+
name: {
|
|
1211
1737
|
type: 'string',
|
|
1212
|
-
description: '
|
|
1213
|
-
minLength:
|
|
1214
|
-
},
|
|
1215
|
-
enable_deployment: {
|
|
1216
|
-
type: 'boolean',
|
|
1217
|
-
description: 'Enable automatic deployment via Coolify',
|
|
1218
|
-
default: true
|
|
1738
|
+
description: 'User full name',
|
|
1739
|
+
minLength: 2
|
|
1219
1740
|
},
|
|
1220
|
-
|
|
1741
|
+
password: {
|
|
1221
1742
|
type: 'string',
|
|
1222
|
-
description: '
|
|
1223
|
-
|
|
1224
|
-
minLength: 3,
|
|
1225
|
-
maxLength: 63
|
|
1743
|
+
description: 'User password (minimum 8 characters)',
|
|
1744
|
+
minLength: 8
|
|
1226
1745
|
},
|
|
1227
|
-
|
|
1228
|
-
type: '
|
|
1229
|
-
description: '
|
|
1230
|
-
default: '.'
|
|
1746
|
+
accept_terms: {
|
|
1747
|
+
type: 'boolean',
|
|
1748
|
+
description: 'User must explicitly accept terms and conditions (must be true)'
|
|
1231
1749
|
}
|
|
1232
1750
|
},
|
|
1233
|
-
required: ['name', '
|
|
1751
|
+
required: ['email', 'name', 'password', 'accept_terms']
|
|
1234
1752
|
}
|
|
1235
1753
|
},
|
|
1236
1754
|
{
|
|
1237
|
-
name: '
|
|
1238
|
-
description: '
|
|
1755
|
+
name: 'mlgym_auth_login',
|
|
1756
|
+
description: 'Login with existing user credentials to get authentication token.',
|
|
1239
1757
|
inputSchema: {
|
|
1240
1758
|
type: 'object',
|
|
1241
1759
|
properties: {
|
|
1242
|
-
|
|
1243
|
-
type: 'string',
|
|
1244
|
-
description: 'Local directory path (defaults to current directory)',
|
|
1245
|
-
default: '.'
|
|
1246
|
-
},
|
|
1247
|
-
project_type: {
|
|
1248
|
-
type: 'string',
|
|
1249
|
-
description: 'Project type from analysis',
|
|
1250
|
-
enum: ['nodejs', 'python', 'static', 'go', 'unknown']
|
|
1251
|
-
},
|
|
1252
|
-
framework: {
|
|
1760
|
+
email: {
|
|
1253
1761
|
type: 'string',
|
|
1254
|
-
description: '
|
|
1255
|
-
|
|
1762
|
+
description: 'Email address',
|
|
1763
|
+
pattern: '^[^@]+@[^@]+\\.[^@]+$'
|
|
1256
1764
|
},
|
|
1257
|
-
|
|
1258
|
-
type: 'string',
|
|
1259
|
-
description: 'Package manager for Node.js projects',
|
|
1260
|
-
enum: ['npm', 'yarn'],
|
|
1261
|
-
default: 'npm'
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
},
|
|
1266
|
-
{
|
|
1267
|
-
name: 'mlgym_smart_deploy',
|
|
1268
|
-
description: 'RECOMMENDED: Smart deployment workflow that automatically analyzes, prepares, and guides you through the entire deployment process. Use this for new projects!',
|
|
1269
|
-
inputSchema: {
|
|
1270
|
-
type: 'object',
|
|
1271
|
-
properties: {
|
|
1272
|
-
local_path: {
|
|
1765
|
+
password: {
|
|
1273
1766
|
type: 'string',
|
|
1274
|
-
description: '
|
|
1275
|
-
|
|
1767
|
+
description: 'Password',
|
|
1768
|
+
minLength: 8
|
|
1276
1769
|
}
|
|
1277
|
-
}
|
|
1770
|
+
},
|
|
1771
|
+
required: ['email', 'password']
|
|
1278
1772
|
}
|
|
1279
1773
|
}
|
|
1280
1774
|
]
|
|
@@ -1285,51 +1779,24 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1285
1779
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1286
1780
|
const { name, arguments: args } = request.params;
|
|
1287
1781
|
|
|
1288
|
-
console.error(`Tool called: ${name}`);
|
|
1782
|
+
console.error(`Tool called: ${name} (MCP v${CURRENT_VERSION})`);
|
|
1289
1783
|
|
|
1290
1784
|
try {
|
|
1291
1785
|
switch (name) {
|
|
1292
|
-
case '
|
|
1293
|
-
return await
|
|
1294
|
-
|
|
1295
|
-
case 'mlgym_authenticate':
|
|
1296
|
-
return await authenticate(args);
|
|
1297
|
-
|
|
1298
|
-
case 'mlgym_project_analyze':
|
|
1299
|
-
const analysis = await analyzeProject(args.local_path);
|
|
1300
|
-
return {
|
|
1301
|
-
content: [{
|
|
1302
|
-
type: 'text',
|
|
1303
|
-
text: JSON.stringify(analysis, null, 2)
|
|
1304
|
-
}]
|
|
1305
|
-
};
|
|
1786
|
+
case 'mlgym_deploy':
|
|
1787
|
+
return await deployProject(args);
|
|
1306
1788
|
|
|
1307
|
-
case '
|
|
1308
|
-
|
|
1309
|
-
return {
|
|
1310
|
-
content: [{
|
|
1311
|
-
type: 'text',
|
|
1312
|
-
text: JSON.stringify(projectStatus, null, 2)
|
|
1313
|
-
}]
|
|
1314
|
-
};
|
|
1315
|
-
|
|
1316
|
-
case 'mlgym_project_init':
|
|
1317
|
-
return await initProject(args);
|
|
1789
|
+
case 'mlgym_status':
|
|
1790
|
+
return await getStatus(args);
|
|
1318
1791
|
|
|
1319
|
-
case '
|
|
1320
|
-
|
|
1321
|
-
return {
|
|
1322
|
-
content: [{
|
|
1323
|
-
type: 'text',
|
|
1324
|
-
text: JSON.stringify(prepResult, null, 2)
|
|
1325
|
-
}]
|
|
1326
|
-
};
|
|
1792
|
+
case 'mlgym_user_create':
|
|
1793
|
+
return await createUser(args);
|
|
1327
1794
|
|
|
1328
|
-
case '
|
|
1329
|
-
return await
|
|
1795
|
+
case 'mlgym_auth_login':
|
|
1796
|
+
return await loginUser(args);
|
|
1330
1797
|
|
|
1331
1798
|
default:
|
|
1332
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
1799
|
+
throw new Error(`Unknown tool: ${name}. Available tools: mlgym_deploy, mlgym_status, mlgym_user_create, mlgym_auth_login`);
|
|
1333
1800
|
}
|
|
1334
1801
|
} catch (error) {
|
|
1335
1802
|
console.error(`Tool execution failed:`, error);
|