mlgym-deploy 2.9.0 → 3.0.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/CHANGELOG-v3.0.0.md +285 -0
- package/index.js +380 -158
- package/index.js.backup-atomic +1358 -0
- package/mlgym-deploy-2.10.0.tgz +0 -0
- package/package.json +8 -8
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.0'; // Monolithic workflow: reduced AI complexity, server-side orchestration
|
|
21
21
|
const PACKAGE_NAME = 'mlgym-deploy';
|
|
22
22
|
|
|
23
23
|
// Version check state
|
|
@@ -275,10 +275,16 @@ async function authenticate(args) {
|
|
|
275
275
|
|
|
276
276
|
// Try to create account
|
|
277
277
|
try {
|
|
278
|
+
// Generate SSH key BEFORE creating user
|
|
279
|
+
console.error('Generating SSH key for new user...');
|
|
280
|
+
const { publicKey, privateKeyPath } = await generateSSHKeyPair(email);
|
|
281
|
+
|
|
282
|
+
// Create user with SSH key included
|
|
278
283
|
const createResult = await apiRequest('POST', '/api/v1/users', {
|
|
279
284
|
email,
|
|
280
285
|
name: full_name,
|
|
281
|
-
password
|
|
286
|
+
password,
|
|
287
|
+
ssh_key: publicKey // Include SSH key in user creation
|
|
282
288
|
}, false);
|
|
283
289
|
|
|
284
290
|
if (createResult.success) {
|
|
@@ -291,16 +297,6 @@ async function authenticate(args) {
|
|
|
291
297
|
if (loginResult.success && loginResult.data.token) {
|
|
292
298
|
await saveAuth(email, loginResult.data.token);
|
|
293
299
|
|
|
294
|
-
// Generate and add SSH key for new user
|
|
295
|
-
console.error('Generating SSH key for new user...');
|
|
296
|
-
const { publicKey, privateKeyPath } = await generateSSHKeyPair(email);
|
|
297
|
-
|
|
298
|
-
const keyTitle = `mlgym-${new Date().toISOString().split('T')[0]}`;
|
|
299
|
-
await apiRequest('POST', '/api/v1/keys', {
|
|
300
|
-
title: keyTitle,
|
|
301
|
-
key: publicKey
|
|
302
|
-
}, true);
|
|
303
|
-
|
|
304
300
|
return {
|
|
305
301
|
content: [{
|
|
306
302
|
type: 'text',
|
|
@@ -311,9 +307,11 @@ async function authenticate(args) {
|
|
|
311
307
|
user_id: loginResult.data.user?.user_id,
|
|
312
308
|
gitlab_username: loginResult.data.user?.gitlab_username,
|
|
313
309
|
ssh_key_path: privateKeyPath,
|
|
310
|
+
ssh_key_status: createResult.data.ssh_key_status || 'SSH key configured',
|
|
314
311
|
next_steps: [
|
|
315
312
|
'New account created',
|
|
316
313
|
`SSH key generated at: ${privateKeyPath}`,
|
|
314
|
+
'SSH key automatically uploaded to GitLab',
|
|
317
315
|
'You can now create projects and deploy'
|
|
318
316
|
]
|
|
319
317
|
}, null, 2)
|
|
@@ -1109,6 +1107,342 @@ async function initProject(args) {
|
|
|
1109
1107
|
};
|
|
1110
1108
|
}
|
|
1111
1109
|
|
|
1110
|
+
// ============================================================================
|
|
1111
|
+
// MONOLITHIC DEPLOYMENT WORKFLOW CLASS (v3.0.0)
|
|
1112
|
+
// ============================================================================
|
|
1113
|
+
class DeploymentWorkflow {
|
|
1114
|
+
constructor() {
|
|
1115
|
+
this.currentStep = null;
|
|
1116
|
+
this.authToken = null;
|
|
1117
|
+
this.projectAnalysis = null;
|
|
1118
|
+
this.gitlabProject = null;
|
|
1119
|
+
this.steps = [];
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
addStep(stepName, status = 'pending', result = null) {
|
|
1123
|
+
this.steps.push({ step: stepName, status, result, timestamp: new Date().toISOString() });
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
updateLastStep(status, result = null) {
|
|
1127
|
+
if (this.steps.length > 0) {
|
|
1128
|
+
this.steps[this.steps.length - 1].status = status;
|
|
1129
|
+
if (result) this.steps[this.steps.length - 1].result = result;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
async ensureAuth(email, password) {
|
|
1134
|
+
this.currentStep = 'authentication';
|
|
1135
|
+
this.addStep('authentication', 'running');
|
|
1136
|
+
|
|
1137
|
+
// Check if already authenticated
|
|
1138
|
+
const auth = await loadAuth();
|
|
1139
|
+
if (auth.token) {
|
|
1140
|
+
this.authToken = auth.token;
|
|
1141
|
+
this.updateLastStep('completed', 'Using cached authentication');
|
|
1142
|
+
return { authenticated: true, cached: true };
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// Need credentials
|
|
1146
|
+
if (!email || !password) {
|
|
1147
|
+
this.updateLastStep('failed', 'Email and password required');
|
|
1148
|
+
throw new Error('Authentication required. Please provide email and password.');
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// Authenticate using existing function
|
|
1152
|
+
const authResult = await authenticate({ email, password, create_if_not_exists: false });
|
|
1153
|
+
|
|
1154
|
+
// Parse the result
|
|
1155
|
+
const resultData = JSON.parse(authResult.content[0].text);
|
|
1156
|
+
|
|
1157
|
+
if (resultData.status === 'success' || resultData.authenticated) {
|
|
1158
|
+
this.authToken = (await loadAuth()).token;
|
|
1159
|
+
this.updateLastStep('completed', 'Authentication successful');
|
|
1160
|
+
return { authenticated: true, cached: false };
|
|
1161
|
+
} else {
|
|
1162
|
+
this.updateLastStep('failed', resultData.message);
|
|
1163
|
+
throw new Error(`Authentication failed: ${resultData.message}`);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
async analyzeProject(local_path) {
|
|
1168
|
+
this.currentStep = 'analysis';
|
|
1169
|
+
this.addStep('project_analysis', 'running');
|
|
1170
|
+
|
|
1171
|
+
try {
|
|
1172
|
+
this.projectAnalysis = await analyzeProject(local_path);
|
|
1173
|
+
this.updateLastStep('completed', {
|
|
1174
|
+
type: this.projectAnalysis.project_type,
|
|
1175
|
+
framework: this.projectAnalysis.framework
|
|
1176
|
+
});
|
|
1177
|
+
return this.projectAnalysis;
|
|
1178
|
+
} catch (error) {
|
|
1179
|
+
this.updateLastStep('failed', error.message);
|
|
1180
|
+
throw error;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
async checkExisting(local_path) {
|
|
1185
|
+
this.currentStep = 'check_existing';
|
|
1186
|
+
this.addStep('check_existing_project', 'running');
|
|
1187
|
+
|
|
1188
|
+
try {
|
|
1189
|
+
const existing = await checkExistingProject(local_path);
|
|
1190
|
+
|
|
1191
|
+
if (existing.exists) {
|
|
1192
|
+
this.updateLastStep('completed', 'Project already configured');
|
|
1193
|
+
return existing;
|
|
1194
|
+
} else {
|
|
1195
|
+
this.updateLastStep('completed', 'No existing project found');
|
|
1196
|
+
return null;
|
|
1197
|
+
}
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
this.updateLastStep('failed', error.message);
|
|
1200
|
+
throw error;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
async prepareProject(projectType, framework, packageManager) {
|
|
1205
|
+
this.currentStep = 'preparation';
|
|
1206
|
+
this.addStep('prepare_project', 'running');
|
|
1207
|
+
|
|
1208
|
+
try {
|
|
1209
|
+
// Check if Dockerfile exists
|
|
1210
|
+
const analysis = this.projectAnalysis || await analyzeProject('.');
|
|
1211
|
+
|
|
1212
|
+
if (analysis.has_dockerfile) {
|
|
1213
|
+
this.updateLastStep('skipped', 'Dockerfile already exists');
|
|
1214
|
+
return { skipped: true, reason: 'Dockerfile already exists' };
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
// Generate Dockerfile
|
|
1218
|
+
const prepResult = await prepareProject({
|
|
1219
|
+
local_path: '.',
|
|
1220
|
+
project_type: projectType || analysis.project_type,
|
|
1221
|
+
framework: framework || analysis.framework,
|
|
1222
|
+
package_manager: packageManager || 'npm'
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
this.updateLastStep('completed', prepResult.actions);
|
|
1226
|
+
return prepResult;
|
|
1227
|
+
} catch (error) {
|
|
1228
|
+
this.updateLastStep('failed', error.message);
|
|
1229
|
+
throw error;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
async createAndDeployProject(params) {
|
|
1234
|
+
this.currentStep = 'project_creation';
|
|
1235
|
+
this.addStep('create_gitlab_project', 'running');
|
|
1236
|
+
|
|
1237
|
+
const { name, description, hostname, local_path } = params;
|
|
1238
|
+
|
|
1239
|
+
try {
|
|
1240
|
+
// Create project using existing function
|
|
1241
|
+
const initResult = await initProject({
|
|
1242
|
+
name,
|
|
1243
|
+
description,
|
|
1244
|
+
enable_deployment: true,
|
|
1245
|
+
hostname: hostname || name,
|
|
1246
|
+
local_path: local_path || '.'
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
// Parse result
|
|
1250
|
+
const resultData = JSON.parse(initResult.content[0].text);
|
|
1251
|
+
|
|
1252
|
+
if (resultData.status === 'success') {
|
|
1253
|
+
this.gitlabProject = resultData.project;
|
|
1254
|
+
this.updateLastStep('completed', resultData.project);
|
|
1255
|
+
return resultData;
|
|
1256
|
+
} else {
|
|
1257
|
+
this.updateLastStep('failed', resultData.error || resultData.message);
|
|
1258
|
+
throw new Error(resultData.error || resultData.message);
|
|
1259
|
+
}
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
this.updateLastStep('failed', error.message);
|
|
1262
|
+
throw error;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
getRecoverySuggestion(error) {
|
|
1267
|
+
const errorMsg = error.message.toLowerCase();
|
|
1268
|
+
|
|
1269
|
+
if (errorMsg.includes('authentication') || errorMsg.includes('token')) {
|
|
1270
|
+
return 'Please provide valid email and password credentials';
|
|
1271
|
+
}
|
|
1272
|
+
if (errorMsg.includes('project') && errorMsg.includes('exists')) {
|
|
1273
|
+
return 'Project already exists. Use mlgym_status to check existing configuration';
|
|
1274
|
+
}
|
|
1275
|
+
if (errorMsg.includes('hostname')) {
|
|
1276
|
+
return 'Please provide a valid hostname (lowercase letters, numbers, and hyphens only)';
|
|
1277
|
+
}
|
|
1278
|
+
if (errorMsg.includes('ssh')) {
|
|
1279
|
+
return 'SSH key generation failed. Check SSH configuration';
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
return 'Check the error message and try again with corrected parameters';
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// ============================================================================
|
|
1287
|
+
// MONOLITHIC DEPLOY FUNCTION
|
|
1288
|
+
// ============================================================================
|
|
1289
|
+
async function deployProject(args) {
|
|
1290
|
+
const workflow = new DeploymentWorkflow();
|
|
1291
|
+
|
|
1292
|
+
const {
|
|
1293
|
+
email,
|
|
1294
|
+
password,
|
|
1295
|
+
project_name,
|
|
1296
|
+
project_description,
|
|
1297
|
+
hostname,
|
|
1298
|
+
local_path = '.',
|
|
1299
|
+
project_type,
|
|
1300
|
+
framework,
|
|
1301
|
+
package_manager = 'npm'
|
|
1302
|
+
} = args;
|
|
1303
|
+
|
|
1304
|
+
try {
|
|
1305
|
+
// Step 1: Ensure authenticated
|
|
1306
|
+
await workflow.ensureAuth(email, password);
|
|
1307
|
+
|
|
1308
|
+
// Step 2: Check if project already exists
|
|
1309
|
+
const existing = await workflow.checkExisting(local_path);
|
|
1310
|
+
if (existing && existing.exists) {
|
|
1311
|
+
return {
|
|
1312
|
+
content: [{
|
|
1313
|
+
type: 'text',
|
|
1314
|
+
text: JSON.stringify({
|
|
1315
|
+
status: 'already_exists',
|
|
1316
|
+
message: 'Project already configured in this directory',
|
|
1317
|
+
project: {
|
|
1318
|
+
name: existing.name,
|
|
1319
|
+
namespace: existing.namespace,
|
|
1320
|
+
git_remote: `git@git.mlgym.io:${existing.namespace}/${existing.name}.git`
|
|
1321
|
+
},
|
|
1322
|
+
next_steps: [
|
|
1323
|
+
'Project is already set up',
|
|
1324
|
+
'To update: git push mlgym main',
|
|
1325
|
+
'To check status: use mlgym_status tool'
|
|
1326
|
+
],
|
|
1327
|
+
workflow_steps: workflow.steps
|
|
1328
|
+
}, null, 2)
|
|
1329
|
+
}]
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// Step 3: Analyze project (auto-detect type and framework)
|
|
1334
|
+
const analysis = await workflow.analyzeProject(local_path);
|
|
1335
|
+
|
|
1336
|
+
// Step 4: Prepare project (generate Dockerfile if needed)
|
|
1337
|
+
await workflow.prepareProject(project_type, framework, package_manager);
|
|
1338
|
+
|
|
1339
|
+
// Step 5: Create GitLab project and deploy
|
|
1340
|
+
const deployResult = await workflow.createAndDeployProject({
|
|
1341
|
+
name: project_name,
|
|
1342
|
+
description: project_description,
|
|
1343
|
+
hostname: hostname || project_name,
|
|
1344
|
+
local_path
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
// Success response
|
|
1348
|
+
return {
|
|
1349
|
+
content: [{
|
|
1350
|
+
type: 'text',
|
|
1351
|
+
text: JSON.stringify({
|
|
1352
|
+
status: 'success',
|
|
1353
|
+
message: 'Project deployed successfully',
|
|
1354
|
+
project: deployResult.project,
|
|
1355
|
+
deployment: deployResult.deployment,
|
|
1356
|
+
url: deployResult.project.deployment_url,
|
|
1357
|
+
analysis: {
|
|
1358
|
+
detected_type: analysis.project_type,
|
|
1359
|
+
detected_framework: analysis.framework
|
|
1360
|
+
},
|
|
1361
|
+
next_steps: deployResult.next_steps,
|
|
1362
|
+
workflow_steps: workflow.steps
|
|
1363
|
+
}, null, 2)
|
|
1364
|
+
}]
|
|
1365
|
+
};
|
|
1366
|
+
|
|
1367
|
+
} catch (error) {
|
|
1368
|
+
console.error('Deployment workflow failed:', error);
|
|
1369
|
+
|
|
1370
|
+
return {
|
|
1371
|
+
content: [{
|
|
1372
|
+
type: 'text',
|
|
1373
|
+
text: JSON.stringify({
|
|
1374
|
+
status: 'error',
|
|
1375
|
+
message: 'Deployment failed',
|
|
1376
|
+
error: error.message,
|
|
1377
|
+
failed_at_step: workflow.currentStep,
|
|
1378
|
+
recovery_suggestion: workflow.getRecoverySuggestion(error),
|
|
1379
|
+
workflow_steps: workflow.steps
|
|
1380
|
+
}, null, 2)
|
|
1381
|
+
}]
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// ============================================================================
|
|
1387
|
+
// SIMPLIFIED STATUS CHECK
|
|
1388
|
+
// ============================================================================
|
|
1389
|
+
async function getStatus(args) {
|
|
1390
|
+
const local_path = args.local_path || '.';
|
|
1391
|
+
|
|
1392
|
+
try {
|
|
1393
|
+
// Check authentication
|
|
1394
|
+
const auth = await loadAuth();
|
|
1395
|
+
const authenticated = !!auth.token;
|
|
1396
|
+
|
|
1397
|
+
// Check existing project
|
|
1398
|
+
const projectStatus = await checkExistingProject(local_path);
|
|
1399
|
+
|
|
1400
|
+
// Analyze project if directory exists
|
|
1401
|
+
let analysis = null;
|
|
1402
|
+
try {
|
|
1403
|
+
analysis = await analyzeProject(local_path);
|
|
1404
|
+
} catch (e) {
|
|
1405
|
+
analysis = { error: 'Could not analyze project' };
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
return {
|
|
1409
|
+
content: [{
|
|
1410
|
+
type: 'text',
|
|
1411
|
+
text: JSON.stringify({
|
|
1412
|
+
status: 'ok',
|
|
1413
|
+
authentication: {
|
|
1414
|
+
authenticated,
|
|
1415
|
+
email: auth.email || null
|
|
1416
|
+
},
|
|
1417
|
+
project: projectStatus.exists ? {
|
|
1418
|
+
configured: true,
|
|
1419
|
+
name: projectStatus.name,
|
|
1420
|
+
namespace: projectStatus.namespace,
|
|
1421
|
+
git_remote: `git@git.mlgym.io:${projectStatus.namespace}/${projectStatus.name}.git`
|
|
1422
|
+
} : {
|
|
1423
|
+
configured: false
|
|
1424
|
+
},
|
|
1425
|
+
analysis: analysis.project_type ? {
|
|
1426
|
+
type: analysis.project_type,
|
|
1427
|
+
framework: analysis.framework,
|
|
1428
|
+
has_dockerfile: analysis.has_dockerfile
|
|
1429
|
+
} : null
|
|
1430
|
+
}, null, 2)
|
|
1431
|
+
}]
|
|
1432
|
+
};
|
|
1433
|
+
} catch (error) {
|
|
1434
|
+
return {
|
|
1435
|
+
content: [{
|
|
1436
|
+
type: 'text',
|
|
1437
|
+
text: JSON.stringify({
|
|
1438
|
+
status: 'error',
|
|
1439
|
+
error: error.message
|
|
1440
|
+
}, null, 2)
|
|
1441
|
+
}]
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1112
1446
|
// Create the MCP server
|
|
1113
1447
|
const server = new Server(
|
|
1114
1448
|
{
|
|
@@ -1126,154 +1460,75 @@ const server = new Server(
|
|
|
1126
1460
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1127
1461
|
return {
|
|
1128
1462
|
tools: [
|
|
1463
|
+
// ========== NEW MONOLITHIC TOOLS (v3.0.0) ==========
|
|
1129
1464
|
{
|
|
1130
|
-
name: '
|
|
1131
|
-
description: '
|
|
1132
|
-
inputSchema: {
|
|
1133
|
-
type: 'object',
|
|
1134
|
-
properties: {}
|
|
1135
|
-
}
|
|
1136
|
-
},
|
|
1137
|
-
{
|
|
1138
|
-
name: 'mlgym_authenticate',
|
|
1139
|
-
description: 'PHASE 1: Authentication ONLY. Get email, password, and existing account status in ONE interaction. Never ask for project details here!',
|
|
1465
|
+
name: 'mlgym_deploy',
|
|
1466
|
+
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.',
|
|
1140
1467
|
inputSchema: {
|
|
1141
1468
|
type: 'object',
|
|
1142
1469
|
properties: {
|
|
1143
|
-
|
|
1144
|
-
type: 'string',
|
|
1145
|
-
description: 'Email address',
|
|
1146
|
-
pattern: '^[^@]+@[^@]+\\.[^@]+$'
|
|
1147
|
-
},
|
|
1148
|
-
password: {
|
|
1470
|
+
project_name: {
|
|
1149
1471
|
type: 'string',
|
|
1150
|
-
description: '
|
|
1151
|
-
minLength: 8
|
|
1152
|
-
},
|
|
1153
|
-
create_if_not_exists: {
|
|
1154
|
-
type: 'boolean',
|
|
1155
|
-
description: 'If true and login fails, attempt to create new account',
|
|
1156
|
-
default: false
|
|
1157
|
-
},
|
|
1158
|
-
full_name: {
|
|
1159
|
-
type: 'string',
|
|
1160
|
-
description: 'Full name (required only for new account creation)',
|
|
1161
|
-
minLength: 2
|
|
1162
|
-
},
|
|
1163
|
-
accept_terms: {
|
|
1164
|
-
type: 'boolean',
|
|
1165
|
-
description: 'Accept terms and conditions (required only for new account creation)',
|
|
1166
|
-
default: false
|
|
1167
|
-
}
|
|
1168
|
-
},
|
|
1169
|
-
required: ['email', 'password']
|
|
1170
|
-
}
|
|
1171
|
-
},
|
|
1172
|
-
{
|
|
1173
|
-
name: 'mlgym_project_analyze',
|
|
1174
|
-
description: 'PHASE 2: Analyze project to detect type, framework, and configuration. Call BEFORE creating project.',
|
|
1175
|
-
inputSchema: {
|
|
1176
|
-
type: 'object',
|
|
1177
|
-
properties: {
|
|
1178
|
-
local_path: {
|
|
1179
|
-
type: 'string',
|
|
1180
|
-
description: 'Local directory path (defaults to current directory)',
|
|
1181
|
-
default: '.'
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
},
|
|
1186
|
-
{
|
|
1187
|
-
name: 'mlgym_project_status',
|
|
1188
|
-
description: 'PHASE 2: Check if MLGym project exists in current directory.',
|
|
1189
|
-
inputSchema: {
|
|
1190
|
-
type: 'object',
|
|
1191
|
-
properties: {
|
|
1192
|
-
local_path: {
|
|
1193
|
-
type: 'string',
|
|
1194
|
-
description: 'Local directory path (defaults to current directory)',
|
|
1195
|
-
default: '.'
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
},
|
|
1200
|
-
{
|
|
1201
|
-
name: 'mlgym_project_init',
|
|
1202
|
-
description: 'PHASE 2: Create project ONLY after checking project status. Never ask for email/password here - only project details!',
|
|
1203
|
-
inputSchema: {
|
|
1204
|
-
type: 'object',
|
|
1205
|
-
properties: {
|
|
1206
|
-
name: {
|
|
1207
|
-
type: 'string',
|
|
1208
|
-
description: 'Project name (lowercase alphanumeric with hyphens)',
|
|
1472
|
+
description: 'Project name (lowercase alphanumeric with hyphens, e.g., "my-app")',
|
|
1209
1473
|
pattern: '^[a-z0-9][a-z0-9-]*[a-z0-9]$',
|
|
1210
1474
|
minLength: 3
|
|
1211
1475
|
},
|
|
1212
|
-
|
|
1476
|
+
project_description: {
|
|
1213
1477
|
type: 'string',
|
|
1214
|
-
description: '
|
|
1478
|
+
description: 'Brief project description (min 10 characters)',
|
|
1215
1479
|
minLength: 10
|
|
1216
1480
|
},
|
|
1217
|
-
|
|
1218
|
-
type: '
|
|
1219
|
-
description: '
|
|
1220
|
-
|
|
1481
|
+
email: {
|
|
1482
|
+
type: 'string',
|
|
1483
|
+
description: 'Email for authentication (optional if already authenticated)',
|
|
1484
|
+
pattern: '^[^@]+@[^@]+\\.[^@]+$'
|
|
1485
|
+
},
|
|
1486
|
+
password: {
|
|
1487
|
+
type: 'string',
|
|
1488
|
+
description: 'Password (optional if already authenticated, min 8 characters)',
|
|
1489
|
+
minLength: 8
|
|
1221
1490
|
},
|
|
1222
1491
|
hostname: {
|
|
1223
1492
|
type: 'string',
|
|
1224
|
-
description: '
|
|
1493
|
+
description: 'Deployment hostname/subdomain (optional, defaults to project_name). Will be accessible at https://<hostname>.ezb.net',
|
|
1225
1494
|
pattern: '^[a-z][a-z0-9-]*[a-z0-9]$',
|
|
1226
1495
|
minLength: 3,
|
|
1227
1496
|
maxLength: 63
|
|
1228
1497
|
},
|
|
1229
1498
|
local_path: {
|
|
1230
1499
|
type: 'string',
|
|
1231
|
-
description: 'Local directory path (defaults to current directory)',
|
|
1232
|
-
default: '.'
|
|
1233
|
-
}
|
|
1234
|
-
},
|
|
1235
|
-
required: ['name', 'description']
|
|
1236
|
-
}
|
|
1237
|
-
},
|
|
1238
|
-
{
|
|
1239
|
-
name: 'mlgym_project_prepare',
|
|
1240
|
-
description: 'PHASE 2: Prepare project for deployment by generating Dockerfile and config files.',
|
|
1241
|
-
inputSchema: {
|
|
1242
|
-
type: 'object',
|
|
1243
|
-
properties: {
|
|
1244
|
-
local_path: {
|
|
1245
|
-
type: 'string',
|
|
1246
|
-
description: 'Local directory path (defaults to current directory)',
|
|
1500
|
+
description: 'Local project directory path (optional, defaults to current directory)',
|
|
1247
1501
|
default: '.'
|
|
1248
1502
|
},
|
|
1249
1503
|
project_type: {
|
|
1250
1504
|
type: 'string',
|
|
1251
|
-
description: '
|
|
1252
|
-
enum: ['nodejs', 'python', 'static', 'go'
|
|
1505
|
+
description: 'Override auto-detected project type (optional)',
|
|
1506
|
+
enum: ['nodejs', 'python', 'static', 'go']
|
|
1253
1507
|
},
|
|
1254
1508
|
framework: {
|
|
1255
1509
|
type: 'string',
|
|
1256
|
-
description: '
|
|
1257
|
-
enum: ['nextjs', 'express', 'react', 'vue', 'flask', 'fastapi', 'html'
|
|
1510
|
+
description: 'Override auto-detected framework (optional)',
|
|
1511
|
+
enum: ['nextjs', 'express', 'react', 'vue', 'flask', 'fastapi', 'html']
|
|
1258
1512
|
},
|
|
1259
1513
|
package_manager: {
|
|
1260
1514
|
type: 'string',
|
|
1261
|
-
description: 'Package manager for Node.js projects',
|
|
1515
|
+
description: 'Package manager for Node.js projects (optional, defaults to npm)',
|
|
1262
1516
|
enum: ['npm', 'yarn'],
|
|
1263
1517
|
default: 'npm'
|
|
1264
1518
|
}
|
|
1265
|
-
}
|
|
1519
|
+
},
|
|
1520
|
+
required: ['project_name', 'project_description']
|
|
1266
1521
|
}
|
|
1267
1522
|
},
|
|
1268
1523
|
{
|
|
1269
|
-
name: '
|
|
1270
|
-
description: '
|
|
1524
|
+
name: 'mlgym_status',
|
|
1525
|
+
description: 'Check current authentication status and project configuration. Read-only operation that shows what is already set up.',
|
|
1271
1526
|
inputSchema: {
|
|
1272
1527
|
type: 'object',
|
|
1273
1528
|
properties: {
|
|
1274
1529
|
local_path: {
|
|
1275
1530
|
type: 'string',
|
|
1276
|
-
description: 'Local directory path (defaults to current directory)',
|
|
1531
|
+
description: 'Local directory path to check (defaults to current directory)',
|
|
1277
1532
|
default: '.'
|
|
1278
1533
|
}
|
|
1279
1534
|
}
|
|
@@ -1287,51 +1542,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1287
1542
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1288
1543
|
const { name, arguments: args } = request.params;
|
|
1289
1544
|
|
|
1290
|
-
console.error(`Tool called: ${name}`);
|
|
1545
|
+
console.error(`Tool called: ${name} (MCP v${CURRENT_VERSION})`);
|
|
1291
1546
|
|
|
1292
1547
|
try {
|
|
1293
1548
|
switch (name) {
|
|
1294
|
-
case '
|
|
1295
|
-
return await
|
|
1296
|
-
|
|
1297
|
-
case 'mlgym_authenticate':
|
|
1298
|
-
return await authenticate(args);
|
|
1299
|
-
|
|
1300
|
-
case 'mlgym_project_analyze':
|
|
1301
|
-
const analysis = await analyzeProject(args.local_path);
|
|
1302
|
-
return {
|
|
1303
|
-
content: [{
|
|
1304
|
-
type: 'text',
|
|
1305
|
-
text: JSON.stringify(analysis, null, 2)
|
|
1306
|
-
}]
|
|
1307
|
-
};
|
|
1308
|
-
|
|
1309
|
-
case 'mlgym_project_status':
|
|
1310
|
-
const projectStatus = await checkExistingProject(args.local_path);
|
|
1311
|
-
return {
|
|
1312
|
-
content: [{
|
|
1313
|
-
type: 'text',
|
|
1314
|
-
text: JSON.stringify(projectStatus, null, 2)
|
|
1315
|
-
}]
|
|
1316
|
-
};
|
|
1317
|
-
|
|
1318
|
-
case 'mlgym_project_init':
|
|
1319
|
-
return await initProject(args);
|
|
1320
|
-
|
|
1321
|
-
case 'mlgym_project_prepare':
|
|
1322
|
-
const prepResult = await prepareProject(args);
|
|
1323
|
-
return {
|
|
1324
|
-
content: [{
|
|
1325
|
-
type: 'text',
|
|
1326
|
-
text: JSON.stringify(prepResult, null, 2)
|
|
1327
|
-
}]
|
|
1328
|
-
};
|
|
1549
|
+
case 'mlgym_deploy':
|
|
1550
|
+
return await deployProject(args);
|
|
1329
1551
|
|
|
1330
|
-
case '
|
|
1331
|
-
return await
|
|
1552
|
+
case 'mlgym_status':
|
|
1553
|
+
return await getStatus(args);
|
|
1332
1554
|
|
|
1333
1555
|
default:
|
|
1334
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
1556
|
+
throw new Error(`Unknown tool: ${name}. Available tools: mlgym_deploy, mlgym_status`);
|
|
1335
1557
|
}
|
|
1336
1558
|
} catch (error) {
|
|
1337
1559
|
console.error(`Tool execution failed:`, error);
|