Skip to main content
Glama
RunRequestHandler.ts5.2 kB
/** * Handler for running individual Bruno requests */ import { ErrorCode, McpError, TextContent } from '@modelcontextprotocol/sdk/types.js'; import { z } from 'zod'; import type { IBrunoCLI } from '../../interfaces.js'; import { logSecurityEvent, maskSecretsInError, validateToolParameters } from '../../security.js'; import type { IToolHandler, ToolResponse } from '../IToolHandler.js'; import { RunResultFormatter } from '../formatters/RunResultFormatter.js'; const RunRequestSchema = z.object({ collectionPath: z.string().describe('Path to the Bruno collection'), requestName: z.string().describe('Name of the request to run'), environment: z.string().optional().describe('Name or path of the environment to use'), enviroment: z.string().optional().describe('Alias for environment (to handle common typo)'), envVariables: z.record(z.string()).optional().describe('Environment variables as key-value pairs'), reporterJson: z.string().optional().describe('Path to write JSON report'), reporterJunit: z.string().optional().describe('Path to write JUnit XML report'), reporterHtml: z.string().optional().describe('Path to write HTML report'), dryRun: z.boolean().optional().describe('Validate request without executing HTTP call') }); type RunRequestParams = z.infer<typeof RunRequestSchema>; /** * Handler for bruno_run_request tool * Executes a single request from a Bruno collection */ export class RunRequestHandler implements IToolHandler { private readonly brunoCLI: IBrunoCLI; private readonly formatter: RunResultFormatter; constructor(brunoCLI: IBrunoCLI) { this.brunoCLI = brunoCLI; this.formatter = new RunResultFormatter(); } getName(): string { return 'bruno_run_request'; } async handle(args: unknown): Promise<ToolResponse> { const params = RunRequestSchema.parse(args); // Security validation const validation = await validateToolParameters({ collectionPath: params.collectionPath, requestName: params.requestName, envVariables: params.envVariables }); if (!validation.valid) { logSecurityEvent({ type: 'access_denied', details: `Run request blocked: ${validation.errors.join(', ')}`, severity: 'error' }); throw new McpError( ErrorCode.InvalidRequest, `Security validation failed: ${validation.errors.join(', ')}` ); } // Log warnings if any if (validation.warnings.length > 0) { validation.warnings.forEach(warning => { logSecurityEvent({ type: 'env_var_validation', details: warning, severity: 'warning' }); }); } // Handle dry run mode if (params.dryRun) { return await this.handleDryRun(params); } const result = await this.brunoCLI.runRequest( params.collectionPath, params.requestName, { environment: params.environment || params.enviroment, envVariables: params.envVariables, reporterJson: params.reporterJson, reporterJunit: params.reporterJunit, reporterHtml: params.reporterHtml } ); return { content: [ { type: 'text', text: this.formatter.format(result) } as TextContent ] }; } private async handleDryRun(params: RunRequestParams): Promise<ToolResponse> { try { // Get request details to validate structure const details = await this.brunoCLI.getRequestDetails( params.collectionPath, params.requestName ); const output: string[] = []; output.push('=== DRY RUN: Request Validation ==='); output.push(''); output.push(`✅ Request validated successfully (HTTP call not executed)`); output.push(''); output.push(`Request: ${details.name}`); output.push(`Method: ${details.method}`); output.push(`URL: ${details.url}`); output.push(''); // Show what would be executed output.push('Configuration Summary:'); output.push(` Headers: ${Object.keys(details.headers).length}`); output.push(` Body: ${details.body ? details.body.type : 'none'}`); output.push(` Auth: ${details.auth}`); output.push(` Tests: ${details.tests?.length || 0}`); output.push(''); if (params.environment) { output.push(`Environment: ${params.environment}`); output.push(''); } if (params.envVariables && Object.keys(params.envVariables).length > 0) { output.push(`Environment Variables: ${Object.keys(params.envVariables).length} provided`); output.push(''); } output.push('ℹ️ This was a dry run - no HTTP request was sent.'); output.push(' Remove dryRun parameter to execute the actual request.'); return { content: [ { type: 'text', text: output.join('\n') } as TextContent ] }; } catch (error) { const maskedError = error instanceof Error ? maskSecretsInError(error) : error; throw new McpError( ErrorCode.InternalError, `Dry run validation failed: ${maskedError}` ); } } }

Implementation Reference

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

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