mlgym-deploy 2.5.0 → 2.7.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/index.js +529 -3
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -16,8 +16,8 @@ import crypto from 'crypto';
|
|
|
16
16
|
|
|
17
17
|
const execAsync = promisify(exec);
|
|
18
18
|
|
|
19
|
-
// Current version of this MCP server - INCREMENT FOR WORKFLOW
|
|
20
|
-
const CURRENT_VERSION = '2.
|
|
19
|
+
// Current version of this MCP server - INCREMENT FOR WORKFLOW FIXES
|
|
20
|
+
const CURRENT_VERSION = '2.7.0';
|
|
21
21
|
const PACKAGE_NAME = 'mlgym-deploy';
|
|
22
22
|
|
|
23
23
|
// Version check state
|
|
@@ -353,6 +353,140 @@ async function authenticate(args) {
|
|
|
353
353
|
};
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
+
// Analyze project to detect type, framework, and configuration
|
|
357
|
+
async function analyzeProject(local_path = '.') {
|
|
358
|
+
const absolutePath = path.resolve(local_path);
|
|
359
|
+
const dirName = path.basename(absolutePath);
|
|
360
|
+
|
|
361
|
+
const analysis = {
|
|
362
|
+
project_type: 'unknown',
|
|
363
|
+
detected_files: [],
|
|
364
|
+
suggested_name: dirName.toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
365
|
+
has_dockerfile: false,
|
|
366
|
+
has_git: false,
|
|
367
|
+
framework: null,
|
|
368
|
+
build_command: null,
|
|
369
|
+
start_command: null,
|
|
370
|
+
package_manager: null
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
// Check for git
|
|
375
|
+
try {
|
|
376
|
+
await execAsync('git status', { cwd: absolutePath });
|
|
377
|
+
analysis.has_git = true;
|
|
378
|
+
} catch {}
|
|
379
|
+
|
|
380
|
+
// Check for Dockerfile
|
|
381
|
+
try {
|
|
382
|
+
await fs.access(path.join(absolutePath, 'Dockerfile'));
|
|
383
|
+
analysis.has_dockerfile = true;
|
|
384
|
+
analysis.detected_files.push('Dockerfile');
|
|
385
|
+
} catch {}
|
|
386
|
+
|
|
387
|
+
// Check for Node.js project
|
|
388
|
+
try {
|
|
389
|
+
const packageJsonPath = path.join(absolutePath, 'package.json');
|
|
390
|
+
await fs.access(packageJsonPath);
|
|
391
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
|
392
|
+
|
|
393
|
+
analysis.project_type = 'nodejs';
|
|
394
|
+
analysis.detected_files.push('package.json');
|
|
395
|
+
analysis.suggested_name = packageJson.name || analysis.suggested_name;
|
|
396
|
+
|
|
397
|
+
// Detect package manager
|
|
398
|
+
try {
|
|
399
|
+
await fs.access(path.join(absolutePath, 'package-lock.json'));
|
|
400
|
+
analysis.package_manager = 'npm';
|
|
401
|
+
analysis.detected_files.push('package-lock.json');
|
|
402
|
+
} catch {
|
|
403
|
+
try {
|
|
404
|
+
await fs.access(path.join(absolutePath, 'yarn.lock'));
|
|
405
|
+
analysis.package_manager = 'yarn';
|
|
406
|
+
analysis.detected_files.push('yarn.lock');
|
|
407
|
+
} catch {
|
|
408
|
+
analysis.package_manager = 'npm'; // default
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Detect framework
|
|
413
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
414
|
+
if (deps.next) {
|
|
415
|
+
analysis.framework = 'nextjs';
|
|
416
|
+
analysis.build_command = packageJson.scripts?.build || 'npm run build';
|
|
417
|
+
analysis.start_command = packageJson.scripts?.start || 'npm start';
|
|
418
|
+
} else if (deps.express) {
|
|
419
|
+
analysis.framework = 'express';
|
|
420
|
+
analysis.start_command = packageJson.scripts?.start || 'node index.js';
|
|
421
|
+
} else if (deps.react) {
|
|
422
|
+
analysis.framework = 'react';
|
|
423
|
+
analysis.build_command = packageJson.scripts?.build || 'npm run build';
|
|
424
|
+
} else if (deps.vue) {
|
|
425
|
+
analysis.framework = 'vue';
|
|
426
|
+
analysis.build_command = packageJson.scripts?.build || 'npm run build';
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Use package.json scripts as fallback
|
|
430
|
+
if (!analysis.build_command && packageJson.scripts?.build) {
|
|
431
|
+
analysis.build_command = 'npm run build';
|
|
432
|
+
}
|
|
433
|
+
if (!analysis.start_command && packageJson.scripts?.start) {
|
|
434
|
+
analysis.start_command = 'npm start';
|
|
435
|
+
}
|
|
436
|
+
} catch {}
|
|
437
|
+
|
|
438
|
+
// Check for Python project
|
|
439
|
+
if (analysis.project_type === 'unknown') {
|
|
440
|
+
try {
|
|
441
|
+
await fs.access(path.join(absolutePath, 'requirements.txt'));
|
|
442
|
+
analysis.project_type = 'python';
|
|
443
|
+
analysis.detected_files.push('requirements.txt');
|
|
444
|
+
|
|
445
|
+
// Check for specific Python files
|
|
446
|
+
try {
|
|
447
|
+
await fs.access(path.join(absolutePath, 'app.py'));
|
|
448
|
+
analysis.framework = 'flask';
|
|
449
|
+
analysis.start_command = 'python app.py';
|
|
450
|
+
analysis.detected_files.push('app.py');
|
|
451
|
+
} catch {
|
|
452
|
+
try {
|
|
453
|
+
await fs.access(path.join(absolutePath, 'main.py'));
|
|
454
|
+
analysis.framework = 'fastapi';
|
|
455
|
+
analysis.start_command = 'uvicorn main:app --host 0.0.0.0';
|
|
456
|
+
analysis.detected_files.push('main.py');
|
|
457
|
+
} catch {}
|
|
458
|
+
}
|
|
459
|
+
} catch {}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Check for static HTML project
|
|
463
|
+
if (analysis.project_type === 'unknown') {
|
|
464
|
+
try {
|
|
465
|
+
await fs.access(path.join(absolutePath, 'index.html'));
|
|
466
|
+
analysis.project_type = 'static';
|
|
467
|
+
analysis.framework = 'html';
|
|
468
|
+
analysis.detected_files.push('index.html');
|
|
469
|
+
} catch {}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Check for Go project
|
|
473
|
+
if (analysis.project_type === 'unknown') {
|
|
474
|
+
try {
|
|
475
|
+
await fs.access(path.join(absolutePath, 'go.mod'));
|
|
476
|
+
analysis.project_type = 'go';
|
|
477
|
+
analysis.detected_files.push('go.mod');
|
|
478
|
+
analysis.build_command = 'go build -o app';
|
|
479
|
+
analysis.start_command = './app';
|
|
480
|
+
} catch {}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
} catch (error) {
|
|
484
|
+
console.error('Project analysis error:', error);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return analysis;
|
|
488
|
+
}
|
|
489
|
+
|
|
356
490
|
// Check for existing MLGym project in current directory
|
|
357
491
|
async function checkExistingProject(local_path = '.') {
|
|
358
492
|
const absolutePath = path.resolve(local_path);
|
|
@@ -395,6 +529,203 @@ async function checkExistingProject(local_path = '.') {
|
|
|
395
529
|
}
|
|
396
530
|
}
|
|
397
531
|
|
|
532
|
+
// Generate appropriate Dockerfile based on project type
|
|
533
|
+
function generateDockerfile(projectType, framework, packageManager = 'npm') {
|
|
534
|
+
let dockerfile = '';
|
|
535
|
+
|
|
536
|
+
if (projectType === 'nodejs') {
|
|
537
|
+
if (framework === 'nextjs') {
|
|
538
|
+
dockerfile = `# Build stage
|
|
539
|
+
FROM node:18-alpine AS builder
|
|
540
|
+
WORKDIR /app
|
|
541
|
+
COPY package*.json ./
|
|
542
|
+
RUN ${packageManager} ${packageManager === 'npm' ? 'ci' : 'install --frozen-lockfile'}
|
|
543
|
+
COPY . .
|
|
544
|
+
RUN ${packageManager} run build
|
|
545
|
+
|
|
546
|
+
# Production stage
|
|
547
|
+
FROM node:18-alpine
|
|
548
|
+
WORKDIR /app
|
|
549
|
+
COPY --from=builder /app/.next ./.next
|
|
550
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
551
|
+
COPY --from=builder /app/package.json ./
|
|
552
|
+
COPY --from=builder /app/public ./public
|
|
553
|
+
EXPOSE 3000
|
|
554
|
+
CMD ["${packageManager}", "start"]`;
|
|
555
|
+
} else if (framework === 'express') {
|
|
556
|
+
dockerfile = `FROM node:18-alpine
|
|
557
|
+
WORKDIR /app
|
|
558
|
+
COPY package*.json ./
|
|
559
|
+
RUN ${packageManager} ${packageManager === 'npm' ? 'ci --only=production' : 'install --frozen-lockfile --production'}
|
|
560
|
+
COPY . .
|
|
561
|
+
EXPOSE 3000
|
|
562
|
+
CMD ["node", "index.js"]`;
|
|
563
|
+
} else if (framework === 'react' || framework === 'vue') {
|
|
564
|
+
dockerfile = `# Build stage
|
|
565
|
+
FROM node:18-alpine AS builder
|
|
566
|
+
WORKDIR /app
|
|
567
|
+
COPY package*.json ./
|
|
568
|
+
RUN ${packageManager} ${packageManager === 'npm' ? 'ci' : 'install --frozen-lockfile'}
|
|
569
|
+
COPY . .
|
|
570
|
+
RUN ${packageManager} run build
|
|
571
|
+
|
|
572
|
+
# Production stage
|
|
573
|
+
FROM nginx:alpine
|
|
574
|
+
COPY --from=builder /app/${framework === 'react' ? 'build' : 'dist'} /usr/share/nginx/html
|
|
575
|
+
EXPOSE 80
|
|
576
|
+
CMD ["nginx", "-g", "daemon off;"]`;
|
|
577
|
+
} else {
|
|
578
|
+
// Generic Node.js
|
|
579
|
+
dockerfile = `FROM node:18-alpine
|
|
580
|
+
WORKDIR /app
|
|
581
|
+
COPY package*.json ./
|
|
582
|
+
RUN ${packageManager} ${packageManager === 'npm' ? 'ci --only=production' : 'install --frozen-lockfile --production'}
|
|
583
|
+
COPY . .
|
|
584
|
+
EXPOSE 3000
|
|
585
|
+
CMD ["${packageManager}", "start"]`;
|
|
586
|
+
}
|
|
587
|
+
} else if (projectType === 'python') {
|
|
588
|
+
if (framework === 'flask') {
|
|
589
|
+
dockerfile = `FROM python:3.11-slim
|
|
590
|
+
WORKDIR /app
|
|
591
|
+
COPY requirements.txt .
|
|
592
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
593
|
+
COPY . .
|
|
594
|
+
EXPOSE 5000
|
|
595
|
+
CMD ["python", "app.py"]`;
|
|
596
|
+
} else if (framework === 'fastapi') {
|
|
597
|
+
dockerfile = `FROM python:3.11-slim
|
|
598
|
+
WORKDIR /app
|
|
599
|
+
COPY requirements.txt .
|
|
600
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
601
|
+
COPY . .
|
|
602
|
+
EXPOSE 8000
|
|
603
|
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]`;
|
|
604
|
+
} else {
|
|
605
|
+
// Generic Python
|
|
606
|
+
dockerfile = `FROM python:3.11-slim
|
|
607
|
+
WORKDIR /app
|
|
608
|
+
COPY requirements.txt .
|
|
609
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
610
|
+
COPY . .
|
|
611
|
+
EXPOSE 8000
|
|
612
|
+
CMD ["python", "main.py"]`;
|
|
613
|
+
}
|
|
614
|
+
} else if (projectType === 'static') {
|
|
615
|
+
dockerfile = `FROM nginx:alpine
|
|
616
|
+
COPY . /usr/share/nginx/html
|
|
617
|
+
EXPOSE 80
|
|
618
|
+
CMD ["nginx", "-g", "daemon off;"]`;
|
|
619
|
+
} else if (projectType === 'go') {
|
|
620
|
+
dockerfile = `# Build stage
|
|
621
|
+
FROM golang:1.21-alpine AS builder
|
|
622
|
+
WORKDIR /app
|
|
623
|
+
COPY go.mod go.sum ./
|
|
624
|
+
RUN go mod download
|
|
625
|
+
COPY . .
|
|
626
|
+
RUN go build -o app
|
|
627
|
+
|
|
628
|
+
# Production stage
|
|
629
|
+
FROM alpine:latest
|
|
630
|
+
RUN apk --no-cache add ca-certificates
|
|
631
|
+
WORKDIR /root/
|
|
632
|
+
COPY --from=builder /app/app .
|
|
633
|
+
EXPOSE 8080
|
|
634
|
+
CMD ["./app"]`;
|
|
635
|
+
} else {
|
|
636
|
+
// Unknown type - basic Alpine with shell
|
|
637
|
+
dockerfile = `FROM alpine:latest
|
|
638
|
+
WORKDIR /app
|
|
639
|
+
COPY . .
|
|
640
|
+
RUN echo "Unknown project type - please configure manually"
|
|
641
|
+
CMD ["/bin/sh"]`;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return dockerfile;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Prepare project for deployment
|
|
648
|
+
async function prepareProject(args) {
|
|
649
|
+
const { local_path = '.', project_type, framework, package_manager } = args;
|
|
650
|
+
const absolutePath = path.resolve(local_path);
|
|
651
|
+
|
|
652
|
+
const actions = [];
|
|
653
|
+
|
|
654
|
+
try {
|
|
655
|
+
// Check if Dockerfile exists
|
|
656
|
+
const dockerfilePath = path.join(absolutePath, 'Dockerfile');
|
|
657
|
+
let dockerfileExists = false;
|
|
658
|
+
|
|
659
|
+
try {
|
|
660
|
+
await fs.access(dockerfilePath);
|
|
661
|
+
dockerfileExists = true;
|
|
662
|
+
actions.push('Dockerfile already exists - skipping generation');
|
|
663
|
+
} catch {}
|
|
664
|
+
|
|
665
|
+
// Generate Dockerfile if missing
|
|
666
|
+
if (!dockerfileExists && project_type !== 'unknown') {
|
|
667
|
+
const dockerfile = generateDockerfile(project_type, framework, package_manager);
|
|
668
|
+
await fs.writeFile(dockerfilePath, dockerfile);
|
|
669
|
+
actions.push(`Generated Dockerfile for ${project_type}/${framework || 'generic'}`);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Check/create .gitignore
|
|
673
|
+
const gitignorePath = path.join(absolutePath, '.gitignore');
|
|
674
|
+
let gitignoreExists = false;
|
|
675
|
+
|
|
676
|
+
try {
|
|
677
|
+
await fs.access(gitignorePath);
|
|
678
|
+
gitignoreExists = true;
|
|
679
|
+
} catch {}
|
|
680
|
+
|
|
681
|
+
if (!gitignoreExists) {
|
|
682
|
+
let gitignoreContent = '';
|
|
683
|
+
|
|
684
|
+
if (project_type === 'nodejs') {
|
|
685
|
+
gitignoreContent = `node_modules/
|
|
686
|
+
.env
|
|
687
|
+
.env.local
|
|
688
|
+
dist/
|
|
689
|
+
build/
|
|
690
|
+
.next/
|
|
691
|
+
*.log`;
|
|
692
|
+
} else if (project_type === 'python') {
|
|
693
|
+
gitignoreContent = `__pycache__/
|
|
694
|
+
*.py[cod]
|
|
695
|
+
*$py.class
|
|
696
|
+
.env
|
|
697
|
+
venv/
|
|
698
|
+
env/
|
|
699
|
+
.venv/`;
|
|
700
|
+
} else {
|
|
701
|
+
gitignoreContent = `.env
|
|
702
|
+
*.log
|
|
703
|
+
.DS_Store`;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
await fs.writeFile(gitignorePath, gitignoreContent);
|
|
707
|
+
actions.push('Created .gitignore file');
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
return {
|
|
711
|
+
status: 'success',
|
|
712
|
+
message: 'Project prepared for deployment',
|
|
713
|
+
actions: actions,
|
|
714
|
+
dockerfile_created: !dockerfileExists && project_type !== 'unknown',
|
|
715
|
+
project_type: project_type,
|
|
716
|
+
framework: framework
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
} catch (error) {
|
|
720
|
+
return {
|
|
721
|
+
status: 'error',
|
|
722
|
+
message: 'Failed to prepare project',
|
|
723
|
+
error: error.message,
|
|
724
|
+
actions: actions
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
398
729
|
// Check authentication status
|
|
399
730
|
async function checkAuthStatus() {
|
|
400
731
|
const auth = await loadAuth();
|
|
@@ -446,6 +777,122 @@ async function checkAuthStatus() {
|
|
|
446
777
|
};
|
|
447
778
|
}
|
|
448
779
|
|
|
780
|
+
// Smart deployment initialization that follows the correct workflow
|
|
781
|
+
async function smartDeploy(args) {
|
|
782
|
+
const { local_path = '.' } = args;
|
|
783
|
+
const absolutePath = path.resolve(local_path);
|
|
784
|
+
|
|
785
|
+
const steps = [];
|
|
786
|
+
|
|
787
|
+
try {
|
|
788
|
+
// Step 1: Check authentication
|
|
789
|
+
steps.push({ step: 'auth_check', status: 'running' });
|
|
790
|
+
const auth = await loadAuth();
|
|
791
|
+
if (!auth.token) {
|
|
792
|
+
steps[steps.length - 1].status = 'failed';
|
|
793
|
+
return {
|
|
794
|
+
content: [{
|
|
795
|
+
type: 'text',
|
|
796
|
+
text: JSON.stringify({
|
|
797
|
+
status: 'error',
|
|
798
|
+
message: 'Not authenticated. Please use mlgym_authenticate first',
|
|
799
|
+
workflow_steps: steps
|
|
800
|
+
}, null, 2)
|
|
801
|
+
}]
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
steps[steps.length - 1].status = 'completed';
|
|
805
|
+
|
|
806
|
+
// Step 2: Analyze project
|
|
807
|
+
steps.push({ step: 'project_analysis', status: 'running' });
|
|
808
|
+
const analysis = await analyzeProject(local_path);
|
|
809
|
+
steps[steps.length - 1].status = 'completed';
|
|
810
|
+
steps[steps.length - 1].result = {
|
|
811
|
+
type: analysis.project_type,
|
|
812
|
+
framework: analysis.framework,
|
|
813
|
+
suggested_name: analysis.suggested_name
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
// Step 3: Check existing project
|
|
817
|
+
steps.push({ step: 'check_existing', status: 'running' });
|
|
818
|
+
const projectStatus = await checkExistingProject(local_path);
|
|
819
|
+
steps[steps.length - 1].status = 'completed';
|
|
820
|
+
|
|
821
|
+
if (projectStatus.configured) {
|
|
822
|
+
return {
|
|
823
|
+
content: [{
|
|
824
|
+
type: 'text',
|
|
825
|
+
text: JSON.stringify({
|
|
826
|
+
status: 'info',
|
|
827
|
+
message: projectStatus.message,
|
|
828
|
+
project_name: projectStatus.name,
|
|
829
|
+
git_remote: `git@git.mlgym.io:${projectStatus.namespace}/${projectStatus.name}.git`,
|
|
830
|
+
next_steps: [
|
|
831
|
+
'Project already configured',
|
|
832
|
+
'Run: git push mlgym main'
|
|
833
|
+
],
|
|
834
|
+
workflow_steps: steps
|
|
835
|
+
}, null, 2)
|
|
836
|
+
}]
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Step 4: Prepare project (generate Dockerfile if needed)
|
|
841
|
+
steps.push({ step: 'prepare_project', status: 'running' });
|
|
842
|
+
if (!analysis.has_dockerfile && analysis.project_type !== 'unknown') {
|
|
843
|
+
const prepResult = await prepareProject({
|
|
844
|
+
local_path,
|
|
845
|
+
project_type: analysis.project_type,
|
|
846
|
+
framework: analysis.framework,
|
|
847
|
+
package_manager: analysis.package_manager
|
|
848
|
+
});
|
|
849
|
+
steps[steps.length - 1].status = 'completed';
|
|
850
|
+
steps[steps.length - 1].result = prepResult.actions;
|
|
851
|
+
} else {
|
|
852
|
+
steps[steps.length - 1].status = 'skipped';
|
|
853
|
+
steps[steps.length - 1].result = 'Dockerfile already exists or project type unknown';
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Return analysis and next steps
|
|
857
|
+
return {
|
|
858
|
+
content: [{
|
|
859
|
+
type: 'text',
|
|
860
|
+
text: JSON.stringify({
|
|
861
|
+
status: 'ready',
|
|
862
|
+
message: 'Project analyzed and prepared. Ready for MLGym initialization.',
|
|
863
|
+
analysis: {
|
|
864
|
+
project_type: analysis.project_type,
|
|
865
|
+
framework: analysis.framework,
|
|
866
|
+
suggested_name: analysis.suggested_name,
|
|
867
|
+
has_dockerfile: analysis.has_dockerfile || true
|
|
868
|
+
},
|
|
869
|
+
next_step: 'Use mlgym_project_init with project details to create MLGym project',
|
|
870
|
+
suggested_params: {
|
|
871
|
+
name: analysis.suggested_name,
|
|
872
|
+
description: `${analysis.framework || analysis.project_type} application`,
|
|
873
|
+
enable_deployment: true,
|
|
874
|
+
hostname: analysis.suggested_name
|
|
875
|
+
},
|
|
876
|
+
workflow_steps: steps
|
|
877
|
+
}, null, 2)
|
|
878
|
+
}]
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
} catch (error) {
|
|
882
|
+
return {
|
|
883
|
+
content: [{
|
|
884
|
+
type: 'text',
|
|
885
|
+
text: JSON.stringify({
|
|
886
|
+
status: 'error',
|
|
887
|
+
message: 'Smart deploy failed',
|
|
888
|
+
error: error.message,
|
|
889
|
+
workflow_steps: steps
|
|
890
|
+
}, null, 2)
|
|
891
|
+
}]
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
449
896
|
// Initialize Project (requires authentication)
|
|
450
897
|
async function initProject(args) {
|
|
451
898
|
let { name, description, enable_deployment = true, hostname, local_path = '.' } = args;
|
|
@@ -631,9 +1078,23 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
631
1078
|
required: ['email', 'password']
|
|
632
1079
|
}
|
|
633
1080
|
},
|
|
1081
|
+
{
|
|
1082
|
+
name: 'mlgym_project_analyze',
|
|
1083
|
+
description: 'PHASE 2: Analyze project to detect type, framework, and configuration. Call BEFORE creating project.',
|
|
1084
|
+
inputSchema: {
|
|
1085
|
+
type: 'object',
|
|
1086
|
+
properties: {
|
|
1087
|
+
local_path: {
|
|
1088
|
+
type: 'string',
|
|
1089
|
+
description: 'Local directory path (defaults to current directory)',
|
|
1090
|
+
default: '.'
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
},
|
|
634
1095
|
{
|
|
635
1096
|
name: 'mlgym_project_status',
|
|
636
|
-
description: 'PHASE 2
|
|
1097
|
+
description: 'PHASE 2: Check if MLGym project exists in current directory.',
|
|
637
1098
|
inputSchema: {
|
|
638
1099
|
type: 'object',
|
|
639
1100
|
properties: {
|
|
@@ -682,6 +1143,50 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
682
1143
|
},
|
|
683
1144
|
required: ['name', 'description']
|
|
684
1145
|
}
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
name: 'mlgym_project_prepare',
|
|
1149
|
+
description: 'PHASE 2: Prepare project for deployment by generating Dockerfile and config files.',
|
|
1150
|
+
inputSchema: {
|
|
1151
|
+
type: 'object',
|
|
1152
|
+
properties: {
|
|
1153
|
+
local_path: {
|
|
1154
|
+
type: 'string',
|
|
1155
|
+
description: 'Local directory path (defaults to current directory)',
|
|
1156
|
+
default: '.'
|
|
1157
|
+
},
|
|
1158
|
+
project_type: {
|
|
1159
|
+
type: 'string',
|
|
1160
|
+
description: 'Project type from analysis',
|
|
1161
|
+
enum: ['nodejs', 'python', 'static', 'go', 'unknown']
|
|
1162
|
+
},
|
|
1163
|
+
framework: {
|
|
1164
|
+
type: 'string',
|
|
1165
|
+
description: 'Framework from analysis',
|
|
1166
|
+
enum: ['nextjs', 'express', 'react', 'vue', 'flask', 'fastapi', 'html', null]
|
|
1167
|
+
},
|
|
1168
|
+
package_manager: {
|
|
1169
|
+
type: 'string',
|
|
1170
|
+
description: 'Package manager for Node.js projects',
|
|
1171
|
+
enum: ['npm', 'yarn'],
|
|
1172
|
+
default: 'npm'
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
},
|
|
1177
|
+
{
|
|
1178
|
+
name: 'mlgym_smart_deploy',
|
|
1179
|
+
description: 'RECOMMENDED: Smart deployment workflow that automatically analyzes, prepares, and guides you through the entire deployment process. Use this for new projects!',
|
|
1180
|
+
inputSchema: {
|
|
1181
|
+
type: 'object',
|
|
1182
|
+
properties: {
|
|
1183
|
+
local_path: {
|
|
1184
|
+
type: 'string',
|
|
1185
|
+
description: 'Local directory path (defaults to current directory)',
|
|
1186
|
+
default: '.'
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
685
1190
|
}
|
|
686
1191
|
]
|
|
687
1192
|
};
|
|
@@ -701,6 +1206,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
701
1206
|
case 'mlgym_authenticate':
|
|
702
1207
|
return await authenticate(args);
|
|
703
1208
|
|
|
1209
|
+
case 'mlgym_project_analyze':
|
|
1210
|
+
const analysis = await analyzeProject(args.local_path);
|
|
1211
|
+
return {
|
|
1212
|
+
content: [{
|
|
1213
|
+
type: 'text',
|
|
1214
|
+
text: JSON.stringify(analysis, null, 2)
|
|
1215
|
+
}]
|
|
1216
|
+
};
|
|
1217
|
+
|
|
704
1218
|
case 'mlgym_project_status':
|
|
705
1219
|
const projectStatus = await checkExistingProject(args.local_path);
|
|
706
1220
|
return {
|
|
@@ -713,6 +1227,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
713
1227
|
case 'mlgym_project_init':
|
|
714
1228
|
return await initProject(args);
|
|
715
1229
|
|
|
1230
|
+
case 'mlgym_project_prepare':
|
|
1231
|
+
const prepResult = await prepareProject(args);
|
|
1232
|
+
return {
|
|
1233
|
+
content: [{
|
|
1234
|
+
type: 'text',
|
|
1235
|
+
text: JSON.stringify(prepResult, null, 2)
|
|
1236
|
+
}]
|
|
1237
|
+
};
|
|
1238
|
+
|
|
1239
|
+
case 'mlgym_smart_deploy':
|
|
1240
|
+
return await smartDeploy(args);
|
|
1241
|
+
|
|
716
1242
|
default:
|
|
717
1243
|
throw new Error(`Unknown tool: ${name}`);
|
|
718
1244
|
}
|