Skip to main content
Glama

MCP API Server

by fikri2992
project-scaffolder.ts19.8 kB
import * as fs from 'fs/promises'; import * as path from 'path'; import { FileWriter, FileWriterConfig, FileWriteResult } from './file-writer.js'; import { GeneratedFile } from './code-generator.js'; import { ProgressInfo } from '../cli/cli-types.js'; /** * Project structure definition */ export interface ProjectStructure { /** Project name */ name: string; /** Project description */ description?: string; /** Base directories to create */ directories: string[]; /** Files to generate */ files: GeneratedFile[]; /** Package.json configuration */ packageConfig?: PackageConfig; /** Additional metadata */ metadata?: ProjectMetadata; } /** * Package.json configuration */ export interface PackageConfig { name: string; version: string; description?: string; author?: string; license?: string; repository?: string; keywords?: string[]; dependencies?: Record<string, string>; devDependencies?: Record<string, string>; scripts?: Record<string, string>; main?: string; types?: string; bin?: Record<string, string>; } /** * Project metadata */ export interface ProjectMetadata { generatedAt: string; generatedBy: string; version: string; sourceFile?: string; templateVersion?: string; } /** * Scaffolding configuration */ export interface ScaffoldingConfig extends Omit<FileWriterConfig, 'outputDir'> { /** Base output directory */ outputDir: string; /** Whether to create git repository */ initGit?: boolean; /** Whether to install dependencies */ installDependencies?: boolean; /** Whether to create example files */ createExamples?: boolean; /** Custom directory structure */ customDirectories?: string[]; /** Progress callback for scaffolding steps */ onScaffoldProgress?: (step: ScaffoldingStep) => void; } /** * Scaffolding step information */ export interface ScaffoldingStep { step: string; current: number; total: number; message?: string; substep?: string; } /** * Scaffolding result */ export interface ScaffoldingResult { success: boolean; projectPath: string; structure: ProjectStructure; fileWriteResult: FileWriteResult; steps: ScaffoldingStep[]; errors: string[]; warnings: string[]; metadata: { totalFiles: number; totalDirectories: number; totalSize: number; duration: number; }; } /** * Project scaffolder for MCP servers */ export class ProjectScaffolder { private config: ScaffoldingConfig; private fileWriter: FileWriter; private steps: ScaffoldingStep[]; private errors: string[]; private warnings: string[]; private startTime: number; constructor(config: ScaffoldingConfig) { this.config = config; this.fileWriter = new FileWriter({ outputDir: config.outputDir, overwrite: config.overwrite, backup: config.backup, debug: config.debug, onProgress: this.handleFileProgress.bind(this), onFileOperation: this.handleFileOperation.bind(this), }); this.steps = []; this.errors = []; this.warnings = []; this.startTime = 0; } /** * Scaffold a complete MCP server project */ async scaffoldProject(structure: ProjectStructure): Promise<ScaffoldingResult> { this.startTime = Date.now(); this.log('Starting project scaffolding', { projectName: structure.name, outputDir: this.config.outputDir, fileCount: structure.files.length, directoryCount: structure.directories.length, }); // Reset state this.resetState(); try { // Step 1: Validate and prepare output directory await this.executeStep({ step: 'Preparing project directory', current: 1, total: 6, message: `Creating project at ${this.config.outputDir}`, }, () => this.prepareOutputDirectory()); // Step 2: Create directory structure const directoriesCreated = await this.executeStep({ step: 'Creating directory structure', current: 2, total: 6, message: `Creating ${structure.directories.length} directories`, }, () => this.createDirectoryStructure(structure.directories)); // Step 3: Generate and write files const fileWriteResult = await this.executeStep({ step: 'Generating project files', current: 3, total: 6, message: `Writing ${structure.files.length} files`, }, () => this.fileWriter.writeFiles(structure.files)); // Step 4: Create additional project files await this.executeStep({ step: 'Creating project configuration', current: 4, total: 6, message: 'Setting up package.json and configuration files', }, () => this.createProjectConfiguration(structure)); // Step 5: Initialize git repository (if requested) if (this.config.initGit) { await this.executeStep({ step: 'Initializing git repository', current: 5, total: 6, message: 'Setting up version control', }, () => this.initializeGitRepository()); } // Step 6: Final validation and cleanup await this.executeStep({ step: 'Finalizing project', current: 6, total: 6, message: 'Validating project structure', }, () => this.finalizeProject(structure)); const duration = Date.now() - this.startTime; const success = this.errors.length === 0; this.log('Project scaffolding completed', { success, duration, stats: fileWriteResult.stats, errorCount: this.errors.length, }); return { success, projectPath: this.config.outputDir, structure, fileWriteResult, steps: [...this.steps], errors: [...this.errors], warnings: [...this.warnings], metadata: { totalFiles: fileWriteResult.stats.created + fileWriteResult.stats.updated, totalDirectories: directoriesCreated + fileWriteResult.stats.directories_created, totalSize: fileWriteResult.stats.total_size, duration, }, }; } catch (error) { const errorMessage = `Project scaffolding failed: ${error instanceof Error ? error.message : 'Unknown error'}`; this.errors.push(errorMessage); this.log('Project scaffolding failed', error); return { success: false, projectPath: this.config.outputDir, structure, fileWriteResult: { success: false, stats: this.fileWriter.getStats(), operations: this.fileWriter.getOperations(), errors: this.fileWriter.getErrors(), warnings: this.fileWriter.getWarnings(), }, steps: [...this.steps], errors: [...this.errors], warnings: [...this.warnings], metadata: { totalFiles: 0, totalDirectories: 0, totalSize: 0, duration: Date.now() - this.startTime, }, }; } } /** * Prepare output directory */ private async prepareOutputDirectory(): Promise<void> { try { // Check if directory exists const exists = await this.directoryExists(this.config.outputDir); if (!exists) { // Create directory await fs.mkdir(this.config.outputDir, { recursive: true }); this.log(`Created output directory: ${this.config.outputDir}`); } else { // Validate existing directory const canWrite = await this.validateWritePermissions(this.config.outputDir); if (!canWrite) { throw new Error(`No write permission to output directory: ${this.config.outputDir}`); } // Check if directory is empty or if overwrite is allowed const files = await fs.readdir(this.config.outputDir); const nonHiddenFiles = files.filter(file => !file.startsWith('.')); if (nonHiddenFiles.length > 0 && !this.config.overwrite) { throw new Error(`Output directory is not empty: ${this.config.outputDir}. Use --overwrite to overwrite existing files.`); } } } catch (error) { throw new Error(`Failed to prepare output directory: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Create directory structure */ private async createDirectoryStructure(directories: string[]): Promise<number> { const allDirectories = [ ...directories, ...(this.config.customDirectories || []), ]; let directoriesCreated = 0; for (const dir of allDirectories) { const fullPath = path.join(this.config.outputDir, dir); try { // Check if directory already exists const exists = await this.directoryExists(fullPath); if (!exists) { await fs.mkdir(fullPath, { recursive: true }); directoriesCreated++; this.log(`Created directory: ${dir}`); } else { this.log(`Directory already exists: ${dir}`); } } catch (error) { const warningMessage = `Failed to create directory ${dir}: ${error instanceof Error ? error.message : 'Unknown error'}`; this.warnings.push(warningMessage); this.log('Directory creation warning', { dir, error }); } } return directoriesCreated; } /** * Create project configuration files */ private async createProjectConfiguration(structure: ProjectStructure): Promise<void> { // Create .mcpbuilder metadata file const metadataFile: GeneratedFile = { path: '.mcpbuilder', content: JSON.stringify({ ...structure.metadata, projectName: structure.name, scaffoldedAt: new Date().toISOString(), directories: structure.directories, fileCount: structure.files.length, }, null, 2), type: 'config', action: 'created', }; await this.fileWriter.writeFile(metadataFile); // Create .gitignore if it doesn't exist const gitignoreExists = await this.fileExists(path.join(this.config.outputDir, '.gitignore')); if (!gitignoreExists) { const gitignoreFile: GeneratedFile = { path: '.gitignore', content: this.generateGitignoreContent(), type: 'config', action: 'created', }; await this.fileWriter.writeFile(gitignoreFile); } // Create README.md if it doesn't exist const readmeExists = await this.fileExists(path.join(this.config.outputDir, 'README.md')); if (!readmeExists) { const readmeFile: GeneratedFile = { path: 'README.md', content: this.generateReadmeContent(structure), type: 'documentation', action: 'created', }; await this.fileWriter.writeFile(readmeFile); } } /** * Initialize git repository */ private async initializeGitRepository(): Promise<void> { try { const { execSync } = await import('child_process'); // Check if git is available try { execSync('git --version', { cwd: this.config.outputDir, stdio: 'ignore' }); } catch { this.warnings.push('Git is not available, skipping repository initialization'); return; } // Check if already a git repository const gitDir = path.join(this.config.outputDir, '.git'); const isGitRepo = await this.directoryExists(gitDir); if (!isGitRepo) { execSync('git init', { cwd: this.config.outputDir, stdio: 'ignore' }); this.log('Initialized git repository'); } else { this.log('Git repository already exists'); } } catch (error) { const warningMessage = `Failed to initialize git repository: ${error instanceof Error ? error.message : 'Unknown error'}`; this.warnings.push(warningMessage); this.log('Git initialization warning', error); } } /** * Finalize project setup */ private async finalizeProject(structure: ProjectStructure): Promise<void> { // Validate that all expected files were created const missingFiles: string[] = []; for (const file of structure.files) { const fullPath = path.join(this.config.outputDir, file.path); const exists = await this.fileExists(fullPath); if (!exists) { missingFiles.push(file.path); } } if (missingFiles.length > 0) { const warningMessage = `Some files were not created: ${missingFiles.join(', ')}`; this.warnings.push(warningMessage); this.log('Missing files warning', { missingFiles }); } // Create examples if requested if (this.config.createExamples) { await this.createExampleFiles(structure); } this.log('Project finalization completed'); } /** * Create example files */ private async createExampleFiles(structure: ProjectStructure): Promise<void> { const exampleDir = path.join(this.config.outputDir, 'examples'); try { await fs.mkdir(exampleDir, { recursive: true }); // Create example usage file const exampleFile: GeneratedFile = { path: 'examples/usage.md', content: this.generateExampleUsage(structure), type: 'documentation', action: 'created', }; await this.fileWriter.writeFile(exampleFile); this.log('Created example files'); } catch (error) { const warningMessage = `Failed to create example files: ${error instanceof Error ? error.message : 'Unknown error'}`; this.warnings.push(warningMessage); this.log('Example creation warning', error); } } /** * Execute a scaffolding step with progress reporting */ private async executeStep<T>( step: ScaffoldingStep, operation: () => Promise<T> ): Promise<T> { this.steps.push(step); this.reportScaffoldProgress(step); try { const result = await operation(); this.log(`Completed step: ${step.step}`); return result; } catch (error) { const errorMessage = `Step failed - ${step.step}: ${error instanceof Error ? error.message : 'Unknown error'}`; this.errors.push(errorMessage); this.log('Step execution error', { step: step.step, error }); throw error; } } /** * Handle file progress from FileWriter */ private handleFileProgress(progress: ProgressInfo): void { // Forward file progress as substep const currentStep = this.steps[this.steps.length - 1]; if (currentStep) { this.reportScaffoldProgress({ ...currentStep, substep: progress.step, }); } } /** * Handle file operations from FileWriter */ private handleFileOperation(operation: any): void { this.log(`File operation: ${operation.type}`, { path: operation.path, size: operation.size, message: operation.message, }); } /** * Report scaffolding progress */ private reportScaffoldProgress(step: ScaffoldingStep): void { if (this.config.onScaffoldProgress) { this.config.onScaffoldProgress(step); } } /** * Generate .gitignore content */ private generateGitignoreContent(): string { return `# Dependencies node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* # Build outputs dist/ build/ *.tsbuildinfo # Environment variables .env .env.local .env.*.local # IDE files .vscode/ .idea/ *.swp *.swo # OS files .DS_Store Thumbs.db # Logs logs/ *.log # Runtime data pids/ *.pid *.seed *.pid.lock # Coverage directory used by tools like istanbul coverage/ *.lcov # Backup files *.backup.* # Temporary files tmp/ temp/ `; } /** * Generate README.md content */ private generateReadmeContent(structure: ProjectStructure): string { return `# ${structure.name} ${structure.description || 'MCP server generated by MCP Builder CLI'} ## Installation \`\`\`bash npm install \`\`\` ## Usage \`\`\`bash npm start \`\`\` ## Development \`\`\`bash npm run dev \`\`\` ## Testing \`\`\`bash npm test \`\`\` ## Generated Files This project was generated using MCP Builder CLI on ${new Date().toISOString()}. - **Source files**: Located in \`src/\` directory - **Tests**: Located in \`tests/\` directory - **Configuration**: Various config files in the root directory ## Project Structure \`\`\` ${structure.directories.map(dir => `${dir}/`).join('\n')} \`\`\` ## License ${structure.packageConfig?.license || 'MIT'} `; } /** * Generate example usage content */ private generateExampleUsage(structure: ProjectStructure): string { return `# ${structure.name} - Usage Examples This file contains examples of how to use the generated MCP server. ## Basic Usage \`\`\`javascript // Example usage code will be generated based on your API specifications \`\`\` ## Configuration \`\`\`javascript // Configuration examples \`\`\` ## Testing \`\`\`bash # Run tests npm test \`\`\` Generated on: ${new Date().toISOString()} `; } /** * Utility methods */ private async directoryExists(dirPath: string): Promise<boolean> { try { const stats = await fs.stat(dirPath); return stats.isDirectory(); } catch { return false; } } private async fileExists(filePath: string): Promise<boolean> { try { await fs.access(filePath, fs.constants.F_OK); return true; } catch { return false; } } private async validateWritePermissions(dirPath: string): Promise<boolean> { try { await fs.access(dirPath, fs.constants.W_OK); return true; } catch { return false; } } private resetState(): void { this.steps = []; this.errors = []; this.warnings = []; } private log(message: string, data?: any): void { if (this.config.debug) { const timestamp = new Date().toISOString(); if (data !== undefined) { console.error(`[${timestamp}] ProjectScaffolder: ${message}`, data); } else { console.error(`[${timestamp}] ProjectScaffolder: ${message}`); } } } } /** * Utility function to create a project scaffolder with common configuration */ export function createProjectScaffolder(config: Partial<ScaffoldingConfig> & { outputDir: string }): ProjectScaffolder { return new ProjectScaffolder({ overwrite: false, backup: true, debug: false, initGit: false, installDependencies: false, createExamples: false, ...config, }); } /** * Create a standard MCP server project structure */ export function createStandardMCPStructure(name: string, description?: string): ProjectStructure { return { name, description, directories: [ 'src', 'tests', 'examples', 'docs', ], files: [], // Will be populated by code generator packageConfig: { name: name.toLowerCase().replace(/\s+/g, '-'), version: '1.0.0', description: description || `MCP server for ${name}`, main: 'dist/index.js', types: 'dist/index.d.ts', scripts: { build: 'tsc', start: 'node dist/index.js', dev: 'ts-node src/index.ts', test: 'jest', 'test:watch': 'jest --watch', lint: 'eslint src/**/*.ts', 'lint:fix': 'eslint src/**/*.ts --fix', }, dependencies: { '@modelcontextprotocol/sdk': '^0.4.0', }, devDependencies: { '@types/node': '^20.0.0', 'typescript': '^5.0.0', 'ts-node': '^10.0.0', 'jest': '^29.0.0', '@types/jest': '^29.0.0', 'eslint': '^8.0.0', '@typescript-eslint/eslint-plugin': '^6.0.0', '@typescript-eslint/parser': '^6.0.0', }, }, metadata: { generatedAt: new Date().toISOString(), generatedBy: 'MCP Builder CLI', version: '1.0.0', }, }; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/fikri2992/mcp0'

If you have feedback or need assistance with the MCP directory API, please join our Discord server