Bruno MCP Studio
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| BRUNO_PROXY_HOSTS | No | Hosts allowed to use a collection's proxy. | |
| BRUNO_UPLOAD_DIRS | No | Extra directories uploads may read from. | |
| BRUNO_DNS_TIMEOUT_MS | No | DNS resolution timeout. | |
| BRUNO_SSRF_ALLOWLIST | No | Comma-separated exact hostnames, IP literals and/or CIDR ranges allowed despite being private. Read once at startup and never influenced by tool arguments; wildcards are rejected. | |
| BRUNO_WORKSPACE_PATH | No | Where to find Bruno's workspace.yml. | |
| BRUNO_INSECURE_TLS_HOSTS | No | Hosts allowed to skip certificate verification. |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| create_collectionA | Create a new Bruno API testing collection with configuration. outputPath is the PARENT directory and name is appended to it: outputPath "/work/apis" with name "Billing" creates the collection at /work/apis/Billing. Do not put the collection name in outputPath as well. The new collection is also added to the workspace registry, because |
| create_environmentA | Create an environment file for a Bruno collection. Writes the WHOLE file, so it REFUSES a name that already exists rather than overwriting it — the error names the existing variables and says which ones a replace would delete, so you can choose between merging with update_environment, picking another name, and retrying with overwrite: true. |
| update_environmentA | Partially update an existing Bruno environment by MERGING the provided variables into the existing ones. Pre-existing variables not listed are preserved (unlike create_environment, which replaces the whole file). |
| set_environment_variableA | Set (add or update) a single variable in an existing Bruno environment. MERGES into the environment — all other variables are preserved. |
| remove_environment_variableA | Remove a single variable from an existing Bruno environment. MERGES into the environment — all other variables are preserved. |
| create_requestA | Generate request files for API testing (supports .bru and .yml formats). Authors HTTP requests by default, WebSocket requests with kind "websocket" (url plus websocket.messages) and gRPC requests with kind "grpc" (url plus grpc.method, grpc.protoPath and grpc.messages); neither takes an HTTP method or a body. Supports multipart/form-data with file uploads and per-part contentType (body.type "form-data" with formData entries of type "file"), and inline scripts (pre-request/post-response/tests) so no separate add_test_script call is needed. Scripts run as async functions: top-level await works, and bru.sleep(ms)/setTimeout/setInterval are available, spending the script timeout (settings.timeout, default 5000ms) — raise it via the settings argument. |
| modify_requestA | Update an existing Bruno request file with partial-merge semantics. Only provided fields are updated; all other fields are preserved. Supports multipart/form-data with file uploads and per-part contentType. Inline scripts REPLACE the existing script of the same type by default (idempotent — repeated calls do not accumulate duplicate blocks); pass scriptMode:"append" to concatenate instead. Use remove_script to clear a script entirely. RENAMING: name and filename are independent, as they are in Bruno itself — name changes the request's name inside the file and filename moves the file. Pass both to keep them in step, and read the new path back from the response. |
| move_requestA | Relocate a request file to another folder, or to another collection entirely. Pass copy:true to duplicate it instead of moving it. The bytes are moved verbatim and nothing is parsed and rewritten, so no part of the request can be lost on the way — which also means seq arrives unchanged and may tie with a request already there; that is reported, and Bruno breaks such a tie by filename. The file keeps its name: use modify_request with filename to rename it. The new path comes back in the response. |
| add_test_scriptA | Add pre-request, post-response, or tests scripts to a Bruno request. Canonical scriptType values are pre-request/post-response/tests; the aliases before-request (→ pre-request) and after-response (→ post-response) are also accepted. Appends to any existing script of that type by default — pass scriptMode:"replace" to overwrite it, or use remove_script to clear it. Assertions must be wrapped in test("name", function() { ... }) to be reported. Scripts run as async functions: top-level await works, and bru.sleep(ms), setTimeout and setInterval are available. Time spent waiting counts against the script timeout (settings.timeout, default 5000ms); raise it with modify_request's settings argument. |
| remove_scriptA | Delete a pre-request, post-response, or tests script from a Bruno request, leaving the rest of the request intact. Use this to undo or clean up a script written by create_request/modify_request/add_test_script — including duplicate blocks accumulated by appending. Canonical scriptType values are pre-request/post-response/tests; the aliases before-request and after-response are accepted. Removing all scripts also drops the now-empty script container. |
| delete_requestA | Permanently delete a Bruno request file from a collection. Use this to remove a request created by mistake; the file is unlinked from disk and cannot be recovered through this server. Only .yml/.bru files inside a detected Bruno collection can be deleted. To clear just a script and keep the request, use remove_script instead. |
| create_test_suiteC | Generate comprehensive test collections with multiple related requests |
| create_crud_requestsA | Generate a complete set of CRUD operations for an entity: list, get by id, create, update, delete. All five inherit the collection or folder auth block unless you pass auth. |
| list_collectionsA | List the Bruno collections REGISTERED IN workspace.yml, with their names and paths. This is a registry listing, not a filesystem scan: a collection that exists on disk but is not registered will NOT appear, and registered entries that no longer exist are returned with "exists": false. If you already know a collection's absolute path, pass it directly to the other tools — it does not need to appear here. Use the returned path as collectionPath in other tools (get_collection_stats, list_requests, run_collection). |
| list_requestsA | List all request files (.yml/.bru) in a Bruno collection. Returns absolute file paths, each usable as an entry in the requests list of run_collection, or of one of its groups. |
| get_collection_statsA | Get statistics about a Bruno collection — request counts by method, folders, environments, and per-request details including each request's file path and URL. environmentDetails lists each environment with the NAMES of the variables it declares (values are withheld), so you can see what an environment already defines before merging into it with set_environment_variable. Use filePath values as entries in the requests list of run_collection. SIZE: the per-request array is the bulk of the response and grows with the collection — a few hundred requests run to tens of kilobytes. Narrow it with folder, method or nameContains, or pass includeRequests: false for counts only. The counts always describe the WHOLE collection; a filtered call adds matchedRequests so the two cannot be confused. |
| run_collectionA | Execute requests in a Bruno collection and run test scripts. HOW TO RUN A SUBSET: requests takes an ORDERED list of entries, each a .yml/.bru request file or a directory (which expands to every request under it, recursively). Absolute, or relative to collectionPath. Order is yours and duplicates are allowed — naming the same request twice runs it twice. Omit requests to run the whole collection. GROUPS: pass groups instead when one call needs more than one identity or configuration. Each group owns its OWN variable store and cookie jar, so nothing a group sets — a bru.setVar, a session cookie — is visible to any other group. That is what makes running the same five requests as alice and as bob in one call safe. Passing both requests and groups is REJECTED rather than resolved for you. ITERATIONS: data or dataFile runs a group once per row of a table, with the row bound as variables — one call to check the same requests against 50 accounts, or one login against 50 credential pairs. Each row is reported as its own group, with the same name, its own index and an iterationIndex, because each row IS a separate group: it gets that isolation, so two rows differing only in a password authenticate separately instead of one silently reusing the other's token. Rows are independent, so a failing row does not stop the rows after it. Ceiling of 1000 rows per scope. PARALLELISM: parallel on the RUN fans the groups out against each other; parallel on a GROUP fans that group's own requests out. A group is serial unless it says otherwise, whatever the run does. seq no longer constrains execution — it is the default order and the reporting order only, so two requests in one parallel group genuinely run at the same time and may contend on the store they share. A group can also WAIT for another: startAfter holds it until the named group has completed a given number of its requests, which is how you get a listener connected before a trigger fires without a bru.sleep tuned to whatever the latency was the day it was written. It needs parallel on the run, chains are allowed, and cycles or gates that could never open are refused before anything runs; if the group being waited on ends early, the waiting group reports that as its error instead of starting. RESULTS are group-shaped: groups[] each carry their own summary, results, capturedVariableNames and capturedVariables, and the top-level summary is the run. Warnings about the run as a whole are top-level, not per group — a captureVariables name is reported as unset only when NO group set it, since a group's store is isolated and lacking a name there says nothing on its own. There is no top-level results array, in the no-groups case either. A group that crashed outright reports error and counts as one failure in the run summary. Each result includes the response body (response_body, response_content_type, response_body_truncated) by default — disable with includeResponseBody=false or cap the size with maxResponseBodyBytes. Each result also carries response_headers by default, no test script needed (set includeResponseHeaders=false to leave them out, and maxResponseHeaderBytes to bound one value): credential-named values are masked, and set-cookie is a LIST — one entry per cookie — whose entries keep every attribute (HttpOnly, Secure, SameSite, Path, Max-Age) and withhold only the cookie value, so a cookie-flag or HSTS check is one call. includeResponseBody=false does not suppress headers; it bounds the body only. A websocket result carries response_headers too, holding the handshake response (the 101) — the only place a session cookie or an agreed sec-websocket-protocol is visible for that transport; a gRPC result reports its own metadata under the grpc detail instead. Within a group, requests run in the order given; a directory expands by seq, scoped to its own folder, with subfolders before that directory's own loose requests, ties broken by filename. A request file that cannot be parsed is skipped when it was DISCOVERED — by running the whole collection, or by expanding a directory: the count is parseErrors and each skipped file is named with its reason in parseFailures. A file you NAMED yourself is different: you asked for that specific request and there is no partial answer to it, so it fails the group that named it, reported as that group's error, and the other groups still run. A named request that does not exist is reported in that group's missingRequests rather than failing anything, so you can see which subset ran. TRANSPORTS: http, graphql, grpc and websocket requests all run; any other kind is refused per-request with status 0 and a named reason, leaving the rest of the group alone. A gRPC result carries a grpc detail — the gRPC status code (0 is OK, and is NOT the refusal sentinel: status 0 with an error and no grpc detail is a refusal), the method, and redacted metadata. A WebSocket result carries a websocket detail — the transcript, stop_reason naming what ended the session (count, timeout, bytes, closed or error), and truncated, which is true for the three that cut a session short. Frame contents are recorded only if you ask for them, via websocket.includePayloads. ASSERTING ON A TRANSPORT: on a websocket result res.getBody() IS the transcript array — the frames, handed over as a structure — and res.getStatus() is ALWAYS 0, because a session has no status. The outcome is res.getStopReason() (equally res.stopReason, and res.statusText, which carries the same string), res.getCloseCode() for the code the peer closed with (null when it sent no close frame — the ordinary case for a session stopped by its own bound), and res.getSessionTruncated() for whether the recording stops short of the session. Each returns exactly what the websocket detail of the same result reports, so an assertion cannot pass against an outcome the result denies. A test written against res.getStatus() on a websocket asserts on a constant and cannot fail. On a gRPC result res.getStatus() is the gRPC code, res.statusText the server details or the code name, and res.getBody() the parsed message. SIMULTANEITY IS PER CALL: groups plus parallel are the only way to make two things happen at the same moment. Two separate run_collection calls are not serialised by this server, but nothing here decides when the second one starts — that is the caller's scheduling, and a client that issues tool calls one at a time produces runs seconds or tens of seconds apart, which no single result shows. If a test needs one identity listening while another triggers, put both in ONE call as parallel groups; the wall-clock gap between separate calls is not something this server can close. Each result carries the path of the request file it came from, so a failure can be read back or re-run by name. STOPPING EARLY: bail stops the run at the first request that fails or whose tests fail, so a chain of dependent requests reports one cause instead of one failure plus every consequence of it. Everything after it comes back with skipped: true and skipReason: "bail", counted in summary.skipped and in neither passed nor failed, and the run carries a bail object naming the reason, the request it stopped at and how many were skipped. Nothing cancels a request already in flight, so with parallel the requests that had already started still finish and are reported normally. REPORT FILES: pass report to also write the run to disk — JUnit XML for CI, HTML for a person — at a path inside the collection; the files written come back under reports. Outbound requests are SSRF-filtered: targets resolving to private, loopback, link-local or otherwise reserved addresses are refused unless the server operator has allowlisted them, and a refusal is reported per-request as an "SSRF blocked" error with status 0. |
| read_requestA | Read a single request file back as structured JSON: method, url, headers, query and path params, body, auth mode, scripts, assertions, vars, settings and docs. Works on both .bru and .yml and returns the same shape for each, so the on-disk format stays invisible. Use this before modify_request to see current state, and after create_request to confirm what was written. A websocket or grpc request also carries its stored messages in full — title, content, and for a websocket the type and whether the runner will send it — under websocket.messages or grpc.messages, keyed as create_request accepts them. A "notes" array reports anything the file declares that the runner will not act on. |
| read_environmentA | Read an environment back as structured JSON: every variable with its value, plus its disabled and secret flags. Omit "name" to list the collection's environments instead. Secret variables are returned by name only — Bruno stores no value for a secret in either file format, so there is none to return. |
| unregister_collectionA | Remove a collection from the workspace registry, leaving every file on disk untouched. list_collections reads that registry, so this is how an entry stops being listed — including a stale entry whose directory is gone, which is what accumulates when collections are created in temporary directories. To delete the collection itself, use delete_collection. |
| delete_collectionA | Permanently delete a Bruno collection: the collection directory and everything inside it is removed from disk, and its workspace registry entry is removed with it. Nothing here can be recovered through this server. Only a directory that is itself a collection root — one holding opencollection.yml or bruno.json — can be deleted, so this cannot be pointed at an arbitrary directory. To keep the files and only stop the collection being listed, use unregister_collection instead. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/Ostico/bruno-mcp-studio'
If you have feedback or need assistance with the MCP directory API, please join our Discord server