npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ostico/bruno-mcp

v1.2.1

Published

MCP server for generating Bruno API testing files

Readme

Bruno MCP Server

Active fork of macarthy/bruno-mcp (original inactive since Jul 2025). Maintained at Ostico/bruno-mcp — see announcement.

A Model Context Protocol (MCP) server for creating, managing, and executing Bruno API testing collections. Supports both .bru and .yml (opencollection) formats with built-in security hardening.

Why This MCP Server?

Use this when you want an AI agent (Claude, Copilot, etc.) to create, inspect, or execute Bruno API test collections programmatically — without opening the Bruno GUI or installing the Bruno CLI. Typical use cases: AI-assisted test generation, CI pipeline integration, automated API exploration.

Requires Node.js >= 18.0.0.

Features

  • Collection Management: Create and organize Bruno collections
  • Environment Configuration: Manage multiple environments (dev, staging, prod)
  • Request Generation: Generate request files for all HTTP methods
  • Authentication Support: Bearer, Basic, OAuth 2.0, API key, Digest
  • Test Scripts: Add pre/post request scripts and assertions
  • CRUD Operations: Generate complete CRUD request sets
  • Collection Statistics: Analyze existing collections
  • Dual Format Support: .bru (legacy) and .yml (opencollection YAML) with auto-detection
  • Collection Discovery: Discover Bruno collections from workspace with zero config
  • Request Modification: Partial-merge updates to existing request files
  • Variable Chaining: bru.setVar()/bru.getVar() for cross-request variable flow
  • Dependency Ordering: Topological sort for test suite execution order
  • Request Execution: Execute requests and run tests with structured results
  • Security Hardening: SSRF protection, path traversal prevention, VM sandbox for test scripts

Installation

git clone https://github.com/macarthy/bruno-mcp.git
cd bruno-mcp
npm install
npm run build

Client Integration

Quick Setup for Claude Desktop

  1. Edit Claude Desktop config file:

    • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
    • Windows: %APPDATA%/Claude/claude_desktop_config.json
    • Linux: ~/.config/Claude/claude_desktop_config.json
  2. Add Bruno MCP Server:

    {
      "mcpServers": {
        "bruno-mcp": {
          "command": "node",
          "args": ["/absolute/path/to/bruno-mcp/dist/index.js"],
          "env": {}
        }
      }
    }
  3. Restart Claude Desktop

Supported Clients

  • Claude Desktop App - Full support
  • Claude Code (VS Code) - Full support
  • Continue - Tools and resources
  • Cline - Tools and resources
  • LM Studio - Tools support
  • MCP Inspector - Development/testing
  • Custom MCP Clients - via SDK

For detailed integration instructions with all clients, see INTEGRATION.md

Format Detection

The server auto-detects collection format by checking for marker files:

| Marker file | Format | Priority | |---|---|---| | opencollection.yml | YAML (opencollection) | Checked first | | bruno.json | BRU (legacy) | Fallback | | Neither | YAML (default) | — |

New collections default to YAML format. Pass format: "bru" to create_collection for legacy format.

Available MCP Tools

create_collection

Create a new Bruno collection with configuration.

Parameters:

  • name (string): Collection name
  • description (string, optional): Collection description
  • baseUrl (string, optional): Default base URL
  • outputPath (string): Directory to create collection
  • ignore (array, optional): Files to ignore
  • format (string, optional): "yaml" (default) or "bru"

Example:

{
  "name": "my-api-tests",
  "description": "API tests for my application",
  "baseUrl": "https://api.example.com",
  "outputPath": "./collections"
}

create_environment

Create environment configuration files.

Parameters:

  • collectionPath (string): Path to Bruno collection
  • name (string): Environment name
  • variables (object): Environment variables

Example:

{
  "collectionPath": "./collections/my-api-tests",
  "name": "production",
  "variables": {
    "baseUrl": "https://api.example.com",
    "apiKey": "prod-key-123",
    "timeout": 30000
  }
}

create_request

Generate request files (.bru or .yml based on collection format).

Parameters:

  • collectionPath (string): Path to collection
  • name (string): Request name
  • method (string): HTTP method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
  • url (string): Request URL (supports {{variable}} syntax)
  • headers (object, optional): HTTP headers
  • body (object, optional): Request body — see Body Types
  • auth (object, optional): Authentication — see Auth Types
  • query (object, optional): Query parameters as Record<string, string | number | boolean>
  • folder (string, optional): Subfolder within collection
  • sequence (number, optional): Execution order

Example:

{
  "collectionPath": "./collections/my-api-tests",
  "name": "Get User Profile",
  "method": "GET",
  "url": "{{baseUrl}}/users/{{userId}}",
  "headers": {
    "Authorization": "Bearer {{token}}"
  },
  "folder": "users"
}

Auth Types

| auth.type | Required auth.config keys | |---|---| | bearer | token | | basic | username, password | | api-key | key, value, in ("header" or "query") | | digest | username, password | | oauth2 | See Bruno OAuth2 docs | | none | (omit auth entirely) |

Example (bearer):

{ "auth": { "type": "bearer", "config": { "token": "{{token}}" } } }

Example (api-key):

{ "auth": { "type": "api-key", "config": { "key": "X-API-Key", "value": "{{apiKey}}", "in": "header" } } }

Body Types

| body.type | Fields | Description | |---|---|---| | json | content: JSON string | JSON body | | text | content: plain text | Plain text body | | xml | content: XML string | XML body | | form-data | formData: [{name, value, type?}] | Multipart form (type: "text" or "file") | | form-urlencoded | content: URL-encoded string | URL-encoded form | | binary | content: file path | Binary body | | none | (omit body entirely) | No body |

Example (JSON body):

{ "body": { "type": "json", "content": "{\"name\": \"test\"}" } }

Example (form-data):

{ "body": { "type": "form-data", "formData": [{"name": "file", "value": "photo.jpg", "type": "file"}] } }

Note: create_test_suite supports a subset of auth types (bearer, basic, oauth2, api-key) and body types (json, text, xml, form-data, form-urlencoded). digest auth and binary body are only available in create_request.

modify_request

Update an existing Bruno request file with partial-merge semantics. Only provided fields are updated; all other fields are preserved.

Parameters:

  • filePath (string, required): Absolute path to .bru or .yml request file
  • name (string, optional): New request name
  • method (string, optional): New HTTP method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
  • url (string, optional): New URL
  • headers (object, optional): Headers to merge (Record<string, string>)
  • body (object, optional): Request body -- same shape as create_request body
  • auth (object, optional): Authentication -- same shape as create_request auth
  • query (object, optional): Query parameters to merge (Record<string, string | number | boolean>)

Example:

{
  "filePath": "/path/to/collections/my-api-tests/users/get-users.yml",
  "url": "{{baseUrl}}/v2/users",
  "headers": {
    "X-Api-Version": "2"
  },
  "query": {
    "limit": 50
  }
}

create_crud_requests

Generate a complete set of CRUD operations (5 requests: List, Get, Create, Update, Delete).

Parameters:

  • collectionPath (string): Path to collection
  • entityName (string): Entity name (e.g., "Users")
  • baseUrl (string): API base URL
  • folder (string, optional): Folder name

Example:

{
  "collectionPath": "./collections/my-api-tests",
  "entityName": "Products",
  "baseUrl": "{{baseUrl}}/api/v1",
  "folder": "products"
}

create_test_suite

Generate a test suite with multiple related requests and optional dependencies.

Parameters:

  • collectionPath (string): Path to collection
  • suiteName (string): Suite/folder name
  • requests (array): Array of request definitions (same shape as create_request)
  • dependencies (array, optional): Execution ordering constraints as [{from: string, to: string}] — enforces topological order via seq numbers. Circular dependencies return an error.

Example:

{
  "collectionPath": "./collections/my-api-tests",
  "suiteName": "Auth Flow",
  "requests": [
    { "name": "Login", "method": "POST", "url": "{{baseUrl}}/auth/login" },
    { "name": "Get Profile", "method": "GET", "url": "{{baseUrl}}/auth/profile" }
  ],
  "dependencies": [
    { "from": "Login", "to": "Get Profile" }
  ]
}

add_test_script

Add test scripts to existing request files. Format-aware: injects into .bru or .yml automatically.

Parameters:

  • bruFilePath (string): Path to .bru or .yml request file
  • scriptType (string): "pre-request", "post-response", or "tests"
  • script (string): JavaScript code (max 50KB)

list_collections

Discover Bruno collections from the Bruno app's workspace.yml.

Parameters:

  • workspacePath (string, optional): Explicit path to workspace.yml

Workspace Resolution Cascade (highest priority first):

  1. Explicit workspacePath argument
  2. BRUNO_WORKSPACE_PATH environment variable (set this when running the server in CI or on a machine where Bruno is not installed at the default location)
  3. Platform default:
    • macOS: ~/Library/Application Support/bruno/default-workspace/workspace.yml
    • Linux: ~/.config/bruno/default-workspace/workspace.yml
    • Windows: %APPDATA%/bruno/default-workspace/workspace.yml

Returns:

{
  "collections": [
    { "name": "My API", "path": "/path/to/collection", "exists": true },
    { "name": "Old API", "path": "/missing/path", "exists": false }
  ]
}

get_collection_stats

Get detailed statistics about a collection.

Parameters:

  • collectionPath (string): Path to collection

Returns:

{
  "totalRequests": 12,
  "requestsByMethod": { "GET": 5, "POST": 4, "PUT": 2, "DELETE": 1 },
  "folders": ["auth", "users", "products"],
  "environments": ["dev", "staging", "prod"],
  "requests": [
    { "name": "Get Users", "method": "GET", "seq": 1, "folder": "users", "hasTests": true }
  ]
}

run_collection

Execute all requests in a collection (or a single request) and run test scripts.

Parameters:

  • collectionPath (string): Path to collection or subfolder
  • environment (string, optional): Environment name (loads from environments/<name>.yml)
  • collectionRoot (string, optional): Collection root for environment resolution
  • requestPath (string, optional): Run a single request file instead of the full collection

Execution Flow:

  1. Find all .yml request files, sort by seq field
  2. Load environment variables (if specified)
  3. For each request: substitute {{variables}} (env + runtime) in URL, headers, and body → execute via fetch() → run test scripts → extract bru.setVar() variables for next request
  4. Requests execute serially in sequence order; variables accumulate across the run
  5. On failure: network errors or HTTP errors are recorded in the result — execution continues to the next request (never stops early)
  6. Requests with no test scripts report zero tests (still counted in summary.total)

Returns:

{
  "summary": { "total": 4, "passed": 3, "failed": 1, "duration_ms": 1250 },
  "results": [
    {
      "name": "Get Schema",
      "method": "GET",
      "url": "https://api.example.com/schema",
      "status": 200,
      "duration_ms": 312,
      "tests": [
        { "description": "Status is 200", "status": "pass" },
        { "description": "Body is JSON", "status": "pass" }
      ]
    }
  ]
}

Environment Variables

Environment files define variables that are substituted into requests at execution time.

YAML format (environments/dev.yml):

name: dev
variables:
  - name: baseUrl
    value: https://dev.api.example.com
  - name: apiKey
    value: dev-key-123
  - name: disabled_var
    value: skip-me
    disabled: true

Input vs file format: The create_environment tool accepts variables as a flat object ({"baseUrl": "..."}) and converts them to the YAML array format shown above. You never need to construct the array format yourself when calling the tool.

Substitution: Any {{variableName}} in request URLs, headers, or body content is replaced with the corresponding environment variable value. Variables with disabled: true are skipped. Unresolved references (e.g. {{missing}}) are left as-is.

Test Script API

Test scripts run in a sandboxed VM with these globals:

| Global | Description | |---|---| | test(description, fn) | Define a test case. fn is called synchronously; exceptions mark the test as failed. | | expect(value) | Chai expect — supports .to.equal(), .to.have.property(), .to.be.above(), etc. | | res | Response object (see methods below) | | bru | Variable store for cross-request chaining (see methods below) |

bru methods (variable chaining):

| Method | Description | |---|---| | bru.setVar(name, value) | Store a variable for use by subsequent requests. Run-scoped — lost when execution ends. | | bru.getVar(name) | Retrieve a previously set variable. Returns undefined if not set. |

Variables set via bru.setVar() are merged with environment variables for {{substitution}} in subsequent requests. Runtime variables take precedence over environment variables with the same name.

res methods:

| Method | Returns | Description | |---|---|---| | res.getStatus() | number | HTTP status code | | res.getStatusText() | string | Status text (e.g. "OK") | | res.getHeaders() | object | All response headers | | res.getHeader(name) | string \| null | Single header (case-insensitive) | | res.getBody() | any | Parsed JSON if content-type is application/json, otherwise raw text | | res.getResponseTime() | number | Response time in milliseconds |

Example test script:

test("Status is 200", function() {
  expect(res.getStatus()).to.equal(200);
});

test("Response is JSON array", function() {
  const body = res.getBody();
  expect(body).to.be.an("array");
  expect(body.length).to.be.above(0);
});

test("Response time under 2s", function() {
  expect(res.getResponseTime()).to.be.below(2000);
});

// Chain a variable to subsequent requests
bru.setVar("userId", res.getBody()[0].id);

Variable chaining example (Login → Profile):

// In Login's after-response script:
test("Login returns token", function() {
  expect(res.getBody().access_token).to.be.a("string");
});
bru.setVar("token", res.getBody().access_token);

// In Get Profile's request, {{token}} is now substituted automatically

File Formats

YAML Request (.yml)

info:
  name: Get Users
  type: http
  seq: 1
http:
  method: GET
  url: "{{baseUrl}}/users"
  headers:
    - name: Authorization
      value: "Bearer {{token}}"
  body:
    type: json
    data: '{"limit": 10}'
  auth:
    type: bearer
    token: "{{token}}"
runtime:
  scripts:
    - type: after-response
      code: |
        test("Status is 200", function() {
          expect(res.getStatus()).to.equal(200);
        });
settings:
  timeout: 5000

BRU Request (.bru)

meta {
  name: Get Users
  type: http
  seq: 1
}

get {
  url: {{baseUrl}}/users
  body: none
  auth: none
}

headers {
  Content-Type: application/json
  Authorization: Bearer {{token}}
}

tests {
  test("Status should be 200", function() {
    expect(res.status).to.equal(200);
  });
}

Generated Collection Structure

my-collection/
├── opencollection.yml      # YAML format collection config
├── bruno.json              # BRU format collection config
├── .gitignore
├── README.md
├── environments/
│   ├── dev.yml
│   └── prod.yml
├── auth/
│   ├── login.yml
│   └── get-profile.yml
└── users/
    ├── get-users.yml
    └── create-user.yml

Security

SSRF Protection

All outbound requests from run_collection are validated:

  • Private IP blocking: Requests to 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, and IPv6 equivalents (including IPv4-mapped ::ffff:x.x.x.x) are blocked
  • Scheme validation: Only http: and https: schemes allowed
  • Redirect TOCTOU protection: Each redirect hop is re-validated against SSRF rules (prevents DNS rebinding via redirects to internal IPs)

Path Traversal Prevention

All tool inputs that accept file paths are validated:

  • .. segments rejected
  • Null byte (\0) injection blocked
  • Paths resolved and checked against expected base directory

VM Sandbox (Test Scripts)

Test scripts execute in a hardened node:vm context:

  • Prototype chain isolation: Context created with Object.create(null)
  • Code generation disabled: eval() and new Function() blocked via codeGeneration option
  • Script size limit: 50KB maximum
  • Execution timeout: Default 5 seconds
  • No filesystem/network access: Only test(), expect(), res, and bru are available

Testing

npm test           # Run 591 unit tests (95%+ coverage)

Development

Project Structure

src/
├── index.ts                # Main entry point & exports
├── server.ts               # MCP server (10 tools)
└── bruno/
    ├── types.ts             # TypeScript interfaces
    ├── collection.ts        # Collection management
    ├── environment.ts       # Environment management
    ├── request.ts           # Request builder (dual format)
    ├── bru-parser.ts        # .bru file parser/generator
    ├── yaml-parser.ts       # YAML request parser
    ├── yaml-generator.ts    # YAML file generator
    ├── format-detector.ts   # Auto-detect .bru vs .yml
    ├── format-factory.ts    # Format-aware read/write
    ├── collection-stats.ts  # Collection analysis
    ├── request-executor.ts  # HTTP execution engine
    ├── test-runner.ts       # Sandboxed test runner (node:vm)
    ├── env-loader.ts        # Environment variable loader
    ├── workspace.ts         # Workspace resolver
    ├── variable-store.ts    # Run-scoped variable store for cross-request chaining
    ├── list-collections-handler.ts
    ├── url-validator.ts     # SSRF protection
    ├── path-validator.ts    # Path traversal prevention
    └── response-wrapper.ts  # Response object for test scripts

Building

npm run build      # Build with tsup
npm run typecheck   # TypeScript type checking

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests
  5. Submit a pull request

License

MIT License - see LICENSE file for details.

Links