Skip to content

MCP tools reference

This is the tool catalog the MaxoPerf MCP server registers. You can call every entry today over POST https://app.maxoperf.com/mcp. Read tools default to response_format: "concise". Pass "detailed" for the full payload. Tools marked write need a session that isn’t read-only. Loadrigo asks for confirmation before a destructive tool, and any agent that calls one should take the same care.

ToolArgsReturns
whoami(none)Account identity + default workspace.
list_workspaces(none)Workspaces visible to the account.
set_active_workspace{ workspace_id }Sets the active workspace for the rest of the session.
ToolArgsReturns
list_projects{ workspace_id? }Projects visible in the account, with canEdit/canDelete flags.
create_project (write){ workspace_id, name }New project.
ToolArgsReturns
list_tests{ project_id?, workspace_id?, type?: 'single'|'vario'|'all', response_format? }Test list.
get_test{ test_id, response_format? }One test, including its validation summary.
create_test (write){ project_id, name, type?: 'single'|'vario', engine_kind?, failure_criteria?, capture_error_bodies? }An empty new test. Attach the entrypoint script next with upload_test_file. Each failure_criteria row’s label is an exact sample label or "ANY" (all labels, the default). For a single test, pass capture_error_bodies: false to turn off error body capture (on by default). VarioTests take the setting from each member’s source test.
get_test_overview{ test_id }Run-history overview (the console’s Overview tab).
list_test_labels{ test_id }{ testId, lastRunId, labels: [{ label, source: 'last-run'|'definition' }] }. These are the sample labels a failure criterion can target. Use "ANY" for all labels; see ANY label.
get_test_data_bindings{ test_id }The test’s data bindings, each with slug (the File name), relativePath, containerPath, envVar, columnNames[], scriptVariable, plus executionContext.dataDir/dataDirEnvVar. Same shape as GET /v1/tests/:testId/data-bindings; see Use the data in your script. Read-only. To change a File name, send PUT /v1/tests/:testId/data-bindings through call_platform_api. It replaces every binding, so echo each binding’s write fields (datasetId, datasetRevisionId, entityId, variantId, distribution, position, localOverrides, scope, slug): an omitted one resets (distribution back to split, the revision unpinned, overrides and scope cleared). Leave eofMode out to keep each binding’s current end-of-file mode; sending it when executionContext.eofCapability.supported is false returns 422 dataset_eof_unsupported, even with the unchanged value. Response-only fields (id, relativePath, envVar, effectiveEofMode, …) are rejected with 400.
ToolArgsReturns
upload_test_file (write){ test_id, filename, content? | local_path?, role: 'entrypoint'|'test_asset', content_type? }. Pass exactly one of content (inline text) or local_path.{ fileId, uploadState, sizeBytes }. A test has one entrypoint. Every other file is a test_asset.
list_test_files{ test_id }{ files: [{ fileId, filename, fileRole, uploadState, sizeBytes }] }
download_test_file{ test_id, file_id }{ downloadUrl, expiresInSeconds, filename }. You get a short-lived presigned URL, not raw bytes.
ToolArgsReturns
start_run (write){ test_id, engine_kind?, recording_layout?: 'tiled'|'solo' }{ runId, status }. Idempotent: an identical retry reuses the run.
get_run_status{ run_id, response_format? }Lifecycle status: queued | allocating | starting | running | stopping | passed | failed | cancelled. A browser fleet run also returns fleetId and fleetName (its testId is empty).
list_runs{ workspace_id?, test_id?, status?, engine?, q?, from?, to?, page?, response_format? }Paginated run list.
cancel_run (write, destructive){ run_id, mode?: 'graceful'|'kill', reason? }Cancels a run. You can’t undo it. A read-only session’s tool catalog doesn’t list it at all.
rerun_run (write){ run_id, source: 'run_snapshot'|'current_test' }New run derived from an existing one. A browser fleet run returns 409 not_applicable_for_fleet_run; start the fleet again instead.
add_runners (write){ run_id, additions: [{ location_plan_row_id, count }] }Adds runners to a live run at existing location-plan rows. A browser fleet run returns 409 not_applicable_for_fleet_run; scale the fleet instead.

Change a run while it is running. See Change virtual users mid-run for which executors support which control.

ToolArgsReturns
get_live_controls{ run_id }What you can change on this run right now: { writable, liveRunnerCount, controls: [{ kind, support, requires, requirementMet }] }. support: null means the executor has no such control. requirementMet: false means the script lacks what requires names. Call it before any change.
get_live_control_history{ run_id, revision?, limit?, cursor? }Past changes, newest first. With revision, the per-runner acknowledgements for that change.
set_live_virtual_users (write){ run_id, total, scope? }Sets the virtual-user total. MaxoPerf divides it across the runners in scope and returns { revision, runnerCount, matchedRunnerCount, perRunner, dispatchState, delivered }.
set_live_throughput (write){ run_id, requests_per_second, scope? }Sets a requests-per-second cap, divided across runners. 0 removes the cap. On k6 the value is approximate, because MaxoPerf reaches it by changing virtual users.
set_live_properties (write){ run_id, properties, scope? }Sends engine properties to every runner in scope. Keys starting maxoperf_ are reserved.
set_live_config_files (write){ run_id, files: [{ name, content }], scope? }Writes data files your script can read. Send plain text; the tool encodes it.
pause_run / resume_run (write){ run_id, scope? }Stops generating load without ending the run, then resumes where it was. On k6 only with the externally-controlled executor. To end a run, use cancel_run.

scope is optional; without it a change goes to every runner. Use { type: 'locations', locations: [...] } or { type: 'runners', runnerIds: [...] } to target part of the fleet. A location that isn’t a MaxoPerf location returns 400 LIVE_CONTROL_SCOPE_LOCATION_UNKNOWN. A scope that matches no live runner, or names a runner that isn’t live in this run, returns 422 SELECTOR_NO_MATCHING_RUNNERS. Raising load above your plan returns 403 PLAN_LIMIT_EXCEEDED; lowering it always works. A run that isn’t on your account returns 404, on reads as well as writes, and asking for a revision the run never had returns 404 REVISION_NOT_FOUND.

ToolArgsReturns
get_run_results{ run_id, ...optional time-range/location/label/runner/scenario filters }KPI summary (throughput, latency, error rate, duration).
query_run_metrics{ run_id, queries: [{ category, aggregation, alignment?, filters?, group_by? }] } (≤8 queries, ≤5 filters/query, ≤3 group-bys/query)Time series for the requested metrics.
get_run_errors{ run_id, from?, to?, locations?, labels?, runners?, scenario_id?, limit?, response_format? }Grouped error rows: signature, category, label, count, response code and a sample message.
get_run_summary{ run_id, transactions?, response_format? }Summary: the headline outcome and any tripped failure criteria.
get_run_error_bodies{ run_id, row_id?, signature?, response_code?, category?, from?, to?, runner?, label?, scenario?, error_key?, limit?, response_format? }Captured request/response samples with status codes. Filter by signature (from get_run_errors). If that returns nothing, use the error’s label with response_code, then label with category (http_4xx, http_5xx or network, from the error group). Never use label alone: it also returns the bodies of other errors on the same transaction. Body text comes back only with row_id (a snippet by default; the full redacted body with response_format: "detailed").
get_run_logs{ run_id, severity?: 'error'|'warn', stream?: 'all'|'system'|'engine', from?, to?, limit?, cursor?, response_format? }Error-level engine/system log lines.
get_runner_health{ run_id, from?, to?, runners?, response_format? }Runner heartbeat/CPU/mem trend + VU shortfall.
detect_run_anomalies{ run_id, metric_ids?, sensitivity?: 'low'|'normal'|'high', allow_partial? }Robust outlier detection on the run’s charts (terminal runs only).
ToolArgsReturns
get_metrics_catalog(none)The set of chartable metrics.
list_dashboards(none)Dashboards visible in the account.
get_dashboard{ dashboard_id }One dashboard’s panels/config.
create_dashboard (write){ title, ... }New dashboard.
update_dashboard (write){ dashboard_id, title?, ... }Updated dashboard.

Managed Selenium-Grid / Playwright-connect() capacity: N browsers behind one endpoint. Your own client drives them directly, so this is not a test run that MaxoPerf orchestrates. See Browser fleets.

ToolArgsReturns
create_browser_fleet (write, idempotent){ browsers: [{ browser: 'chrome'|'firefox'|'edge', count, location }], browsers_per_runner?, ttl_minutes?, idle_timeout_minutes?, name?, step_capture?, workspace_id? }. location is <aws|gcp|azure>:<region> or byoc:<pdcId>. step_capture defaults true — send false to opt sessions out of interaction-step capture. workspace_id defaults to the account’s oldest active workspace when omitted.{ fleetId, runId, status, endpoints, token, expiresAt, supportedPlaywrightVersions }. The token is shown only in this response. Fetch it again later with get_browser_fleet.
get_browser_fleet{ fleet_id, include_token? } (default true)Status, per-location readiness, endpoints, session count, sessionCount (total across every run), stepCapture, the canStart / canStop / terminal flags, and the reconnect token unless you omit it. terminal is not the opposite of canStart: a failed fleet is terminal and restartable.
list_browser_fleets{ q?, lifecycle?: 'created'|'running'|'stopped'|'failed', page?, page_size?, workspace_id? }. q is a name search (max 100 chars).{ fleets, total, page, pageSize, summary: { total, running, stopped, activeSessions } }. summary is unaffected by q/lifecycle/page. It covers the whole account, or only that workspace when you pass workspace_id. Never includes tokens.
list_browser_fleet_sessions{ fleet_id, page?, page_size?, started_from?, started_to? } (default 25, max 100). The two optional bounds are ISO 8601 and keep sessions that started in that window.{ sessions, total }. Every session across ALL of the fleet’s runs, newest first, each with runId/name and a capture summary (stepCount/harStatus/harFailureReason/consoleStatus/consoleFailureReason). total is the fleet’s overall sessionCount, and is left out when a window is set.
list_browser_fleet_runs{ fleet_id }Every run the fleet has ever had, including 0-session runs, newest first: [{ runId, status, startedAt, endedAt, sessionCount }].
scale_browser_fleet (write){ fleet_id, browsers: [...] }Updated fleet detail. Returns 403 when the resulting fleet exceeds your plan’s concurrent VU limit.
release_browser_fleet_browsers (write){ fleet_id, browsers: [...] } (counts to remove)Updated fleet detail. Scales the fleet DOWN — releases whole runner VMs newest-first for the named browser/location slices. Returns 409 when a slice count exceeds the live count.
start_browser_fleet (write){ fleet_id }Updated fleet detail. Mints a fresh run for a created/stopped fleet and provisions it. Returns 409 if already running.
stop_browser_fleet (write){ fleet_id, mode?: 'graceful'|'kill' }Updated fleet detail. Releases the runners but keeps the fleet, its endpoints and its token. Start it again with start_browser_fleet. kill skips the session drain.
update_browser_fleet (write){ fleet_id, name?, browsers?, browsers_per_runner?, ttl_minutes?, idle_timeout_minutes?, step_capture? } — all optionalUpdated fleet detail. A browsers/browsers_per_runner change on a running fleet re-provisions a fresh run; step_capture alone never does — it applies on the fleet’s next Start.
delete_browser_fleet (write, destructive){ fleet_id }{ fleetId, deleted: true }. Tears the runners down, then removes the fleet permanently. After that, get_browser_fleet returns 404 and the endpoints and token stop working. Run history and billing don’t change. Use stop_browser_fleet if you want the fleet back later.

Expose a target that runs locally or isn’t deployed yet at a public https://<name>.maxoperf-tunnel.com URL. See Tunnels.

ToolArgsReturns
create_tunnel (write){ name, auth_mode?: 'none'|'basic'|'bearer', allow_ips?, rate_limit_rpm?, idle_timeout_seconds?, capture_mode?: 'none'|'headers'|'body', workspace_id? }. name is a globally unique DNS label. workspace_id defaults to the account’s oldest active workspace when omitted.{ id, name, url, status, online, clientToken, runCommand, ... }. This is the only response that shows the clientToken.
list_tunnels{ page?, page_size?, workspace_id? }Tunnel list, without client tokens. workspace_id filters to one workspace.
get_tunnel{ tunnel_id }One tunnel’s detail.
stop_tunnel (write){ tunnel_id }Disconnects the live client. The URL stays reserved but returns 503s until you restart the tunnel.
get_tunnel_stats{ tunnel_id, window_minutes?, step_seconds?, from?, to? }Requests, 4xx/5xx, blocked requests, bytes, and p50/p95/p99 latency over a recent window. from/to (ISO-8601) take precedence over window_minutes — use them to line up with a run’s exact time range (span capped at 7 days).

A mock backend that runs as its own pod on an outpost you choose. See Virtual services.

ToolArgsReturns
list_virtual_services{ workspace_id }{ virtualServices: [{ id, state, placement, hostname, endpointUrl }] }
get_virtual_service{ virtual_service_id }Full detail (state, placement, noMatch policy, transaction selection, idle-shutdown config).
create_virtual_service (write){ workspace_id, service_id, name, placement?: { private_datacenter_id? }, no_match?, think_scale_percent?, transaction_selection?, idle_shutdown_minutes? }New virtual service in draft/stopped state.
deploy_virtual_service (write){ virtual_service_id }State moving toward running. Choose a PDC first.
stop_virtual_service (write){ virtual_service_id }State moving toward stopped.
delete_virtual_service (write, destructive){ virtual_service_id }Deletes the virtual service.

Stateful mocks: State, Capture, Scenarios, Sequences, CRUD resources, backups, Try it

Section titled “Stateful mocks: State, Capture, Scenarios, Sequences, CRUD resources, backups, Try it”

Every virtual service can remember data DURABLY across requests, shared by its replicas — a global bucket plus one bucket per session (keyed by a header/cookie/query value you choose, so each virtual user gets its own cart). Transactions gain a state object (conditions, a scenario step, captures with an optional transform chain, a response sequence), plus top-level cases (response variants) and then_ops (counters). See State & scenarios for the full concept walkthrough. Values whose key name looks like a secret always come back [REDACTED] to every MCP client.

ToolArgsReturns
update_virtual_service (write){ virtual_service_id, name?, no_match?, think_scale_percent?, idle_shutdown_minutes?, state_config? }Updated virtual service. state_config ({ session_key, session_idle_ttl_seconds?, variables?, scenario_layout? }) is merged server-side at the top level — send only what changed.
list_vs_transactions{ virtual_service_id }{ transactions: [...] } — the VS’s resolved composition, incl. generated CRUD groups. Disabled transactions are not listed.
get_vs_transaction{ transaction_id }One transaction, incl. its state, cases, then_ops.
create_vs_transaction (write){ virtual_service_id, name, priority?, enabled?, tags?, request, response, latency?, faults?, state?, cases?, then_ops? }New transaction, landed in the VS’s home group.
update_vs_transaction (write)Same shape, all fields optionalPartial update — except state/cases/then_ops, each of which replaces the whole thing as a unit.
delete_vs_transaction (write, destructive){ transaction_id }Deletes one transaction.
sandbox_vs_transaction{ transaction_id, request, state?, session_key_configured? }Dry-run match/capture/sequence preview of ONE transaction — never deploys or mutates real state.
get_virtual_service_state{ virtual_service_id, caller? }Live inspector snapshot: totals, declared variables, and one caller’s data.
create_vs_crud_resource (write){ virtual_service_id, path, scope, seed? }Generates a full in-memory REST resource (list/get/create/update/delete) in one call.
list_vs_state_callers{ virtual_service_id, q?, cursor?, limit? }Paginated, searchable caller list — find a caller id by its keyMask tail.
get_vs_state_value{ virtual_service_id, name, caller? }One full (redacted) stored value.
set_vs_state_value (write){ virtual_service_id, name, caller?, value }Writes one value; never accepts writing back a [REDACTED] value.
delete_vs_state_value (write, destructive){ virtual_service_id, name, caller? }Deletes one value — its declared initial applies again.
forget_vs_state_caller (write, destructive){ virtual_service_id, caller_id }Deletes one caller’s whole bucket.
reset_vs_state_flows (write, destructive){ virtual_service_id, caller_id? }Resets scenario positions; omit caller_id to reset every caller + everyone.
clear_vs_state (write, destructive){ virtual_service_id }Clears every bucket back to starting values.
list_vs_state_backups{ virtual_service_id }Automatic, pre-restore and manual/uploaded backups.
create_vs_state_backup (write){ virtual_service_id, label? }Manual snapshot (20-slot limit).
restore_vs_state_backup (write, destructive){ virtual_service_id, backup_id }Replaces all live state (a safety backup is taken first).
delete_vs_state_backup (write, destructive){ virtual_service_id, backup_id }Manual/uploaded only.
export_vs_state{ virtual_service_id }Current live state in the backup format, redacted.
try_virtual_service{ virtual_service_id, request, live?, caller?, state?, draft_transactions? }Runs the full priority walk across every transaction — scratch (default, mutates nothing) or live (real state, requires caller).
list_transform_snippets{ workspace_id?, virtual_service_id?, q?, include_archived? }Reusable JavaScript transform snippets to pin into a capture’s transform chain.

Every secret, virtual service, tunnel and browser-fleet binding a test has, in one place, plus a read-only summary of its data bindings. Binding one lets you pick it as a live chip in an interactive HTTP or browser step instead of a literal URL, or injects a runtime env var for a scripted test. See Test Dependencies.

ToolArgsReturns
list_test_dependencies{ test_id }{ testId, items: [{ kind, entityId, name, envNames, state, url, autoStart, origin: 'manual'|'inline', source, bindingId? }], warnings }
bind_test_dependency (write){ test_id, kind: 'virtual_service'|'tunnel'|'browser_fleet'|'secret', entity_id, env_var_name?, auto_start? }. env_var_name is required for virtual_service/tunnel/browser_fleet (upper-snake, at most 40 characters); optional for secret (inherits the secret’s name when omitted). auto_start only applies to virtual_service.The created/updated binding. Binding the same test + entity again updates the env var name. For a virtual service it also sets auto_start, which goes back to on if you leave it out.
unbind_test_dependency (write){ test_id, kind, entity_id }Removes the test’s own binding. For a secret, your other manually-bound secrets are kept. A binding the test’s model references inline can’t be removed this way (409 binding_in_use_by_model) — edit the model instead. Bindings a VarioTest member inherits from its master are removed on the master.
get_run_dependencies{ run_id }The virtual services, tunnels and browser fleets a run prepared or held, with lease state and timestamps. Secrets aren’t leased and don’t appear here.
get_dependency_usage{ kind: 'secret'|'virtual_service'|'tunnel'|'browser_fleet', entity_id }Which tests and live runs currently use it — check before deleting or stopping a dependency.
set_eux_probe (write){ test_id, probe_test_id } (null clears it)Points a single performance test at a browser test that runs alongside its load, to measure real-browser UX under load.

One rule set per workspace instead of per test, covering the run-lifecycle triggers plus the ecosystem ones (dependency pre-flight failure, tunnel disconnected during a run, failure criteria breached, virtual-service/browser-fleet failure). See Notifications.

ToolArgsReturns
list_workspace_notification_rules{ workspace_id }{ rules: [{ id, workspaceId, integrationId, trigger, tagFilter, enabled, createdBy, createdAt, updatedAt }] }
upsert_workspace_notification_rule (write){ workspace_id, rule_id?, integration_id, trigger, tag_filter?, enabled? }. Omit rule_id to create a rule; send one to replace its full config (an omitted tag_filter or enabled resets to no filter / enabled).The created/updated rule.
delete_workspace_notification_rule (write){ workspace_id, rule_id }Deletes the rule.
ToolArgsReturns
list_execution_locations{ workspace_id? }{ managed: { providers }, byoc: { privateDatacenters } }. byoc.privateDatacenters lists the Shared Sandbox outpost, which MaxoPerf operates and shares, together with any self-hosted outpost your workspace registered. Pass the id as location: "byoc:<id>" (fleets) or placement.privateDatacenterId (virtual services).
inspect_url (write){ url }Starts (or reuses) a real browser, opens url, and returns the DOM, text and outline after JavaScript runs. Use it instead of fetch_url/read_page_dom when the target is a single-page app.

For Platform API endpoints that no named tool above covers, such as billing, secrets, notifications, and schedules.

ToolArgsReturns
call_platform_api{ method: 'GET'|'POST'|'PATCH'|'PUT'|'DELETE'|'OPTIONS'|'HEAD', path, body? }Calls any public endpoint. Admin, internal and key-management paths are always rejected before any upstream call. Read-only sessions can only use GET, HEAD, and OPTIONS.
get_openapi(none)The public OpenAPI document. It is large, so try search_endpoints first.
search_endpoints{ query }Keyword search over the OpenAPI document.
ToolArgsReturns
search_docs{ query }Keyword search over this documentation site’s build-time index.
get_agent_skill(varies)The MaxoPerf agent skill’s instructions. See MaxoPerf agents.
list_agent_skill_sections(none)The skill bundle’s section index.

These tools only read, and every one goes through a single SSRF-guarded fetch. If the deployment has no search provider configured, web_search disables itself and leaves the tool catalog.

ToolArgsReturns
web_search(query args)Web search results, when a provider is configured.
fetch_url{ url, ... }Static page fetch: HTML only, no JavaScript execution.
read_openapi{ url, ... }Fetches and parses a third-party OpenAPI document.
read_page_dom{ url, ... }Static DOM extraction, with the same JavaScript limit as fetch_url.

Prompts are multi-step recipes. A client can offer them as one-click flows (prompts/list), so an agent doesn’t have to work out the right sequence of tool calls on its own.

PromptArgsWhat it guides
run-baseline-load-test{ test_id }Start a run and report pass/fail, duration, throughput, p95 latency, and error rate once it’s terminal.
diagnose-latency-regression{ baseline_run_id, candidate_run_id }Compare p95 latency between two runs of the same test and correlate with errors.
summarize-run{ run_id }A short, plain-language summary of one run.
plan-and-build-test{ goal, target? }Turn a plain-language goal into the project → test → upload → run sequence.
choose-executor{ test_kind }Recommend a test engine/executor and explain why.
scan-endpoints-for-hotspots{ openapi_or_paste }Rank likely load-testing hotspots from an OpenAPI spec or pasted endpoint list.
setup-secrets-and-envs{ test_id }Set up secrets and per-environment values before the first run.
diagnose-run-failure{ run_id }Find the root cause of a failed or slow run from tripped criteria, error bodies, logs, runner health, and anomalies.
explain-run-anomalies{ run_id }Run the anomaly scan on a finished run and explain flagged outliers.

Two read-only MCP resources for clients that prefer resources over tool calls:

Resource URIContents
maxoperf://openapiThe public Platform API OpenAPI document (same source as get_openapi).
maxoperf://run/{id}A run’s current status and dashboard overview.

Every tool error has the same shape: { isError: true, content: [{ type: "text", text: "<status> <code>: <detail>" }], structuredContent: { status, code, detail } }. Common codes: READ_ONLY_MODE, FORBIDDEN_PATH (call_platform_api deny-listed path), TOO_MANY_QUERIES / TOO_MANY_FILTERS / TOO_MANY_SERIES (query_run_metrics limits), UPLOAD_FAILED, PATH_NOT_ALLOWED (upload_test_file local-path traversal), INVALID_ARGUMENTS.