Skip to main content
Glama

Bruno MCP Server

by macarthy
generator.ts•12.5 kB
/** * BRU file generator with proper syntax * Generates Bruno API testing files in the correct BRU format */ import { BruFile, BruMeta, BruHttpRequest, BruAuth, BruHeaders, BruQuery, BruBody, BruVars, BruPreRequestScript, BruPostResponseScript, BruTests, BruGeneratorOptions, BruValidationError, AuthType, BodyType } from './types.js'; export class BruGenerator { private options: Required<BruGeneratorOptions>; constructor(options: BruGeneratorOptions = {}) { this.options = { indentSize: options.indentSize ?? 2, useSpaces: options.useSpaces ?? true, addTimestamp: options.addTimestamp ?? false, validateSyntax: options.validateSyntax ?? true }; } /** * Generate a complete .bru file from a BruFile object */ generateBruFile(bruFile: BruFile): string { if (this.options.validateSyntax) { this.validateBruFile(bruFile); } const sections: string[] = []; // Add timestamp comment if requested if (this.options.addTimestamp) { sections.push(`# Generated on ${new Date().toISOString()}`); sections.push(''); } // Generate meta block sections.push(this.generateMetaBlock(bruFile.meta)); sections.push(''); // Generate HTTP block sections.push(this.generateHttpBlock(bruFile.http)); sections.push(''); // Generate auth block if present if (bruFile.auth && bruFile.auth.type !== 'none') { sections.push(this.generateAuthBlock(bruFile.auth)); sections.push(''); } // Generate headers block if present if (bruFile.headers && Object.keys(bruFile.headers).length > 0) { sections.push(this.generateHeadersBlock(bruFile.headers)); sections.push(''); } // Generate query block if present if (bruFile.query && Object.keys(bruFile.query).length > 0) { sections.push(this.generateQueryBlock(bruFile.query)); sections.push(''); } // Generate body block if present if (bruFile.body && bruFile.body.type !== 'none') { sections.push(this.generateBodyBlock(bruFile.body)); sections.push(''); } // Generate vars block if present if (bruFile.vars && Object.keys(bruFile.vars).length > 0) { sections.push(this.generateVarsBlock(bruFile.vars)); sections.push(''); } // Generate script blocks if present if (bruFile.script) { if (bruFile.script['pre-request']) { sections.push(this.generatePreRequestScript(bruFile.script['pre-request'])); sections.push(''); } if (bruFile.script['post-response']) { sections.push(this.generatePostResponseScript(bruFile.script['post-response'])); sections.push(''); } } // Generate tests block if present if (bruFile.tests) { sections.push(this.generateTestsBlock(bruFile.tests)); sections.push(''); } // Generate docs if present if (bruFile.docs) { sections.push('docs {'); sections.push(this.indent(bruFile.docs)); sections.push('}'); sections.push(''); } return sections.join('\n').trim() + '\n'; } /** * Generate meta block */ private generateMetaBlock(meta: BruMeta): string { const lines = ['meta {']; lines.push(this.indent(`name: ${this.escapeString(meta.name)}`)); lines.push(this.indent(`type: ${meta.type}`)); if (meta.seq !== undefined) { lines.push(this.indent(`seq: ${meta.seq}`)); } lines.push('}'); return lines.join('\n'); } /** * Generate HTTP request block */ private generateHttpBlock(http: BruHttpRequest): string { const lines = [`${http.method.toLowerCase()} {`]; lines.push(this.indent(`url: ${this.escapeString(http.url)}`)); lines.push(this.indent(`body: ${http.body}`)); lines.push(this.indent(`auth: ${http.auth}`)); lines.push('}'); return lines.join('\n'); } /** * Generate auth block */ private generateAuthBlock(auth: BruAuth): string { const lines = [`auth:${auth.type} {`]; switch (auth.type) { case 'bearer': if (auth.bearer) { lines.push(this.indent(`token: ${this.escapeString(auth.bearer.token)}`)); } break; case 'basic': if (auth.basic) { lines.push(this.indent(`username: ${this.escapeString(auth.basic.username)}`)); lines.push(this.indent(`password: ${this.escapeString(auth.basic.password)}`)); } break; case 'oauth2': if (auth.oauth2) { lines.push(this.indent(`grant_type: ${auth.oauth2.grantType}`)); if (auth.oauth2.accessTokenUrl) { lines.push(this.indent(`access_token_url: ${this.escapeString(auth.oauth2.accessTokenUrl)}`)); } if (auth.oauth2.authorizationUrl) { lines.push(this.indent(`authorization_url: ${this.escapeString(auth.oauth2.authorizationUrl)}`)); } if (auth.oauth2.clientId) { lines.push(this.indent(`client_id: ${this.escapeString(auth.oauth2.clientId)}`)); } if (auth.oauth2.clientSecret) { lines.push(this.indent(`client_secret: ${this.escapeString(auth.oauth2.clientSecret)}`)); } if (auth.oauth2.scope) { lines.push(this.indent(`scope: ${this.escapeString(auth.oauth2.scope)}`)); } if (auth.oauth2.username) { lines.push(this.indent(`username: ${this.escapeString(auth.oauth2.username)}`)); } if (auth.oauth2.password) { lines.push(this.indent(`password: ${this.escapeString(auth.oauth2.password)}`)); } } break; case 'api-key': if (auth.apikey) { lines.push(this.indent(`key: ${this.escapeString(auth.apikey.key)}`)); lines.push(this.indent(`value: ${this.escapeString(auth.apikey.value)}`)); lines.push(this.indent(`in: ${auth.apikey.in}`)); } break; case 'digest': if (auth.digest) { lines.push(this.indent(`username: ${this.escapeString(auth.digest.username)}`)); lines.push(this.indent(`password: ${this.escapeString(auth.digest.password)}`)); } break; } lines.push('}'); return lines.join('\n'); } /** * Generate headers block */ private generateHeadersBlock(headers: BruHeaders): string { const lines = ['headers {']; Object.entries(headers).forEach(([key, value]) => { lines.push(this.indent(`${key}: ${this.escapeString(value)}`)); }); lines.push('}'); return lines.join('\n'); } /** * Generate query parameters block */ private generateQueryBlock(query: BruQuery): string { const lines = ['query {']; Object.entries(query).forEach(([key, value]) => { lines.push(this.indent(`${key}: ${this.formatValue(value)}`)); }); lines.push('}'); return lines.join('\n'); } /** * Generate body block */ private generateBodyBlock(body: BruBody): string { if (body.type === 'none') { return ''; } if (body.type === 'json' || body.type === 'text' || body.type === 'xml') { const lines = [`body:${body.type} {`]; if (body.content) { lines.push(this.indent(body.content)); } lines.push('}'); return lines.join('\n'); } if (body.type === 'form-data' && body.formData) { const lines = ['body:multipart-form {']; body.formData.forEach(field => { if (field.enabled !== false) { lines.push(this.indent(`${field.name}: ${this.escapeString(field.value)}`)); } }); lines.push('}'); return lines.join('\n'); } if (body.type === 'form-urlencoded' && body.formUrlEncoded) { const lines = ['body:form-urlencoded {']; body.formUrlEncoded.forEach(field => { if (field.enabled !== false) { lines.push(this.indent(`${field.name}: ${this.escapeString(field.value)}`)); } }); lines.push('}'); return lines.join('\n'); } return ''; } /** * Generate variables block */ private generateVarsBlock(vars: BruVars): string { const lines = ['vars {']; Object.entries(vars).forEach(([key, value]) => { lines.push(this.indent(`${key}: ${this.formatValue(value)}`)); }); lines.push('}'); return lines.join('\n'); } /** * Generate pre-request script block */ private generatePreRequestScript(script: BruPreRequestScript): string { const lines = ['script:pre-request {']; script.exec.forEach(line => { lines.push(this.indent(line)); }); lines.push('}'); return lines.join('\n'); } /** * Generate post-response script block */ private generatePostResponseScript(script: BruPostResponseScript): string { const lines = ['script:post-response {']; script.exec.forEach(line => { lines.push(this.indent(line)); }); lines.push('}'); return lines.join('\n'); } /** * Generate tests block */ private generateTestsBlock(tests: BruTests): string { const lines = ['tests {']; tests.exec.forEach(line => { lines.push(this.indent(line)); }); lines.push('}'); return lines.join('\n'); } /** * Validate BRU file structure */ private validateBruFile(bruFile: BruFile): void { if (!bruFile.meta || !bruFile.meta.name) { throw new BruValidationError('Meta block with name is required'); } if (!bruFile.http || !bruFile.http.method || !bruFile.http.url) { throw new BruValidationError('HTTP block with method and URL is required'); } // Validate URL format (basic check) if (!this.isValidUrl(bruFile.http.url)) { throw new BruValidationError(`Invalid URL format: ${bruFile.http.url}`); } // Validate auth configuration if present if (bruFile.auth && bruFile.auth.type !== 'none') { this.validateAuthConfig(bruFile.auth); } } /** * Validate authentication configuration */ private validateAuthConfig(auth: BruAuth): void { switch (auth.type) { case 'bearer': if (!auth.bearer?.token) { throw new BruValidationError('Bearer token is required for bearer auth'); } break; case 'basic': if (!auth.basic?.username || !auth.basic?.password) { throw new BruValidationError('Username and password are required for basic auth'); } break; case 'api-key': if (!auth.apikey?.key || !auth.apikey?.value) { throw new BruValidationError('Key and value are required for API key auth'); } break; } } /** * Basic URL validation */ private isValidUrl(url: string): boolean { try { new URL(url); return true; } catch { // Check if it's a relative URL or contains variables return url.startsWith('/') || url.includes('{{') || url.startsWith('http'); } } /** * Escape string values for BRU format */ private escapeString(value: string): string { // BRU uses single quotes for strings if (value.includes("'") || value.includes('\n') || value.includes('\r')) { // Use multiline string format for complex strings return `'''${value}'''`; } return `'${value}'`; } /** * Format various value types */ private formatValue(value: string | number | boolean): string { if (typeof value === 'string') { return this.escapeString(value); } return String(value); } /** * Add indentation to a line */ private indent(text: string): string { const indentChar = this.options.useSpaces ? ' ' : '\t'; const indentString = this.options.useSpaces ? indentChar.repeat(this.options.indentSize) : indentChar; return text.split('\n').map(line => line.trim() ? indentString + line : line ).join('\n'); } } /** * Convenience function to generate a BRU file */ export function generateBruFile(bruFile: BruFile, options?: BruGeneratorOptions): string { const generator = new BruGenerator(options); return generator.generateBruFile(bruFile); } /** * Create a basic BRU file structure */ export function createBasicBruFile( name: string, method: string, url: string, sequence?: number ): BruFile { return { meta: { name, type: 'http', seq: sequence }, http: { method: method.toUpperCase() as any, url, body: 'none', auth: 'none' } }; }

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