Skip to main content
Glama

Bruno MCP Server

by macarthy
collection.ts•9.52 kB
/** * Bruno collection management * Handles creation and management of Bruno collections */ import { promises as fs } from 'fs'; import { join } from 'path'; import { BrunoCollection, CreateCollectionInput, FileOperationResult, BrunoError, BruFileError } from './types.js'; export class CollectionManager { /** * Create a new Bruno collection */ async createCollection(input: CreateCollectionInput): Promise<FileOperationResult> { try { // Validate input this.validateCollectionInput(input); // Create collection directory const collectionPath = join(input.outputPath, input.name); await this.ensureDirectory(collectionPath); // Create bruno.json configuration const brunoConfig: BrunoCollection = { version: '1', name: input.name, type: 'collection', ignore: input.ignore || ['node_modules', '.git', '.env'] }; const configPath = join(collectionPath, 'bruno.json'); await fs.writeFile(configPath, JSON.stringify(brunoConfig, null, 2)); // Create environments directory const envPath = join(collectionPath, 'environments'); await this.ensureDirectory(envPath); // Create .gitignore if it doesn't exist const gitignorePath = join(collectionPath, '.gitignore'); const gitignoreExists = await this.fileExists(gitignorePath); if (!gitignoreExists) { await this.createGitignore(gitignorePath); } // Create README.md with basic collection info const readmePath = join(collectionPath, 'README.md'); await this.createCollectionReadme(readmePath, input); return { success: true, path: collectionPath }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Load an existing Bruno collection */ async loadCollection(collectionPath: string): Promise<BrunoCollection> { try { const configPath = join(collectionPath, 'bruno.json'); const configContent = await fs.readFile(configPath, 'utf-8'); const config = JSON.parse(configContent) as BrunoCollection; this.validateCollectionConfig(config); return config; } catch (error) { throw new BruFileError( `Failed to load collection from ${collectionPath}`, { originalError: error } ); } } /** * Update collection configuration */ async updateCollection( collectionPath: string, updates: Partial<BrunoCollection> ): Promise<FileOperationResult> { try { const existingConfig = await this.loadCollection(collectionPath); const updatedConfig = { ...existingConfig, ...updates }; this.validateCollectionConfig(updatedConfig); const configPath = join(collectionPath, 'bruno.json'); await fs.writeFile(configPath, JSON.stringify(updatedConfig, null, 2)); return { success: true, path: configPath }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * List all .bru files in a collection */ async listRequests(collectionPath: string): Promise<string[]> { try { const bruFiles: string[] = []; await this.findBruFiles(collectionPath, bruFiles); return bruFiles.sort(); } catch (error) { throw new BruFileError( `Failed to list requests in collection ${collectionPath}`, { originalError: error } ); } } /** * Create a folder structure within the collection */ async createFolder(collectionPath: string, folderPath: string): Promise<FileOperationResult> { try { const fullPath = join(collectionPath, folderPath); await this.ensureDirectory(fullPath); return { success: true, path: fullPath }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Get collection statistics */ async getCollectionStats(collectionPath: string): Promise<{ totalRequests: number; requestsByMethod: Record<string, number>; folders: string[]; environments: string[]; }> { try { const requests = await this.listRequests(collectionPath); const folders = await this.listFolders(collectionPath); const environments = await this.listEnvironments(collectionPath); // Count requests by method (would need to parse .bru files) const requestsByMethod: Record<string, number> = {}; // For now, return basic stats return { totalRequests: requests.length, requestsByMethod, folders, environments }; } catch (error) { throw new BruFileError( `Failed to get collection stats for ${collectionPath}`, { originalError: error } ); } } /** * Validate collection input */ private validateCollectionInput(input: CreateCollectionInput): void { if (!input.name || input.name.trim().length === 0) { throw new BrunoError('Collection name is required', 'VALIDATION_ERROR'); } if (!input.outputPath || input.outputPath.trim().length === 0) { throw new BrunoError('Output path is required', 'VALIDATION_ERROR'); } // Check for invalid characters in collection name const invalidChars = /[<>:"/\\|?*]/; if (invalidChars.test(input.name)) { throw new BrunoError( 'Collection name contains invalid characters', 'VALIDATION_ERROR' ); } } /** * Validate collection configuration */ private validateCollectionConfig(config: BrunoCollection): void { if (!config.name || config.name.trim().length === 0) { throw new BrunoError('Collection name is required', 'VALIDATION_ERROR'); } if (!config.version) { throw new BrunoError('Collection version is required', 'VALIDATION_ERROR'); } if (config.type !== 'collection') { throw new BrunoError('Collection type must be "collection"', 'VALIDATION_ERROR'); } } /** * Ensure directory exists, create if it doesn't */ private async ensureDirectory(dirPath: string): Promise<void> { try { await fs.access(dirPath); } catch { await fs.mkdir(dirPath, { recursive: true }); } } /** * Check if file exists */ private async fileExists(filePath: string): Promise<boolean> { try { await fs.access(filePath); return true; } catch { return false; } } /** * Create .gitignore file for Bruno collection */ private async createGitignore(gitignorePath: string): Promise<void> { const gitignoreContent = `# Bruno collection files to ignore *.tmp *.temp .env .env.local .env.*.local # OS generated files .DS_Store Thumbs.db # Editor files .vscode/ .idea/ *.swp *.swo `; await fs.writeFile(gitignorePath, gitignoreContent); } /** * Create README.md for collection */ private async createCollectionReadme( readmePath: string, input: CreateCollectionInput ): Promise<void> { const readmeContent = `# ${input.name} ${input.description || 'Bruno API testing collection'} ## Overview This collection was generated using the Bruno MCP server. ${input.baseUrl ? `**Base URL:** \`${input.baseUrl}\`` : ''} ## Structure - \`environments/\` - Environment configurations - \`*.bru\` - API request files ## Usage Run all tests: \`\`\`bash bruno-cli run \`\`\` Run specific environment: \`\`\`bash bruno-cli run --env production \`\`\` ## Generated Created on: ${new Date().toISOString()} `; await fs.writeFile(readmePath, readmeContent); } /** * Recursively find all .bru files */ private async findBruFiles(dirPath: string, bruFiles: string[]): Promise<void> { const entries = await fs.readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = join(dirPath, entry.name); if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== '.git') { await this.findBruFiles(fullPath, bruFiles); } else if (entry.isFile() && entry.name.endsWith('.bru')) { bruFiles.push(fullPath); } } } /** * List all folders in collection */ private async listFolders(collectionPath: string): Promise<string[]> { const folders: string[] = []; const entries = await fs.readdir(collectionPath, { withFileTypes: true }); for (const entry of entries) { if (entry.isDirectory() && entry.name !== 'environments' && entry.name !== 'node_modules' && entry.name !== '.git') { folders.push(entry.name); } } return folders.sort(); } /** * List all environment files */ private async listEnvironments(collectionPath: string): Promise<string[]> { try { const envPath = join(collectionPath, 'environments'); const entries = await fs.readdir(envPath, { withFileTypes: true }); return entries .filter(entry => entry.isFile() && entry.name.endsWith('.bru')) .map(entry => entry.name.replace('.bru', '')) .sort(); } catch { return []; } } } /** * Create a new collection manager instance */ export function createCollectionManager(): CollectionManager { return new CollectionManager(); }

Latest Blog Posts

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/macarthy/bruno-mcp'

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