Back to app

MCP server: let your coding agent test your voice agent

CallQA runs a Model Context Protocol server. Connect your coding agent (Claude Code, Cursor, Codex, VS Code, or any MCP client) and ask it, in plain words:

Test my voice agent on staging with CallQA, tell me what breaks and fix my prompt.

The agent lists your scenarios, starts a run on the right environment, waits for the verdicts, reads the failed calls (transcript, the judge's quotes) and fixes your code. When a test itself is wrong, it can ask the CallQA assistant for a corrected scenario and show you the diff before saving it.

It can also set CallQA up from zero, without the dashboard:

Set up CallQA for the voice agent in this repository: staging and production, tests every weekday morning, and the CI.

See Onboarding with an agent.

Connect

URL https://app.callqa.dev/mcp
Transport Streamable HTTP (protocol 2026-07-28, and the earlier handshake versions)
Authentication An API key of your organization: Authorization: Bearer cqa_...

The quickest way: in the app, open Developer > MCP server. An admin creates a key with the recommended scopes (read, write, run) and the page shows the install command of each client with the key in it, and the prompt to paste in your agent. The same block, with a placeholder for the key, is at the top of the Quickstart.

Any API key works (see Authentication). The examples below read it from the CALLQA_API_KEY environment variable, so no key lands in a file: set it first with export CALLQA_API_KEY="cqa_..." (your own key).

Claude Code

claude mcp add --transport http callqa https://app.callqa.dev/mcp \
  --header "Authorization: Bearer $CALLQA_API_KEY"

Add --scope user to use it in every project. Check with claude mcp list.

Cursor

In ~/.cursor/mcp.json (every project) or .cursor/mcp.json (one project):

{
  "mcpServers": {
    "callqa": {
      "url": "https://app.callqa.dev/mcp",
      "headers": { "Authorization": "Bearer ${env:CALLQA_API_KEY}" }
    }
  }
}

VS Code

In .vscode/mcp.json (VS Code asks for the key once and stores it securely):

{
  "inputs": [
    { "type": "promptString", "id": "callqa-api-key", "description": "CallQA API key", "password": true }
  ],
  "servers": {
    "callqa": {
      "type": "http",
      "url": "https://app.callqa.dev/mcp",
      "headers": { "Authorization": "Bearer ${input:callqa-api-key}" }
    }
  }
}

Codex

In ~/.codex/config.toml (Codex reads the key from CALLQA_API_KEY):

[mcp_servers.callqa]
url = "https://app.callqa.dev/mcp"
bearer_token_env_var = "CALLQA_API_KEY"

Other clients

Any client that speaks Streamable HTTP and lets you set a header works. Clients that only start local (stdio) servers can go through the mcp-remote bridge:

npx mcp-remote https://app.callqa.dev/mcp --header "Authorization: Bearer $CALLQA_API_KEY"

From a script, with the Python SDK (pip install "mcp>=2.2"):

import asyncio, os

import httpx2
from mcp import Client
from mcp.client.streamable_http import streamable_http_client


async def main():
    headers = {"Authorization": f"Bearer {os.environ['CALLQA_API_KEY']}"}
    async with httpx2.AsyncClient(headers=headers, timeout=330) as http:
        async with Client(streamable_http_client("https://app.callqa.dev/mcp", http_client=http)) as c:
            print([t.name for t in (await c.list_tools()).tools])
            print((await c.call_tool("list_scenarios", {})).structured_content)


asyncio.run(main())

To try the tools by hand: npx @modelcontextprotocol/inspector, transport "Streamable HTTP", the URL above and the Authorization header.

Tools

Every tool has typed inputs and outputs (inputSchema, outputSchema) and a description written for the model. Each needs the scope shown, like the matching REST endpoint.

Tool What it does Scope
list_scenarios Scenarios of the organization (filters: q, tag, include_archived) read
get_scenario One scenario: full YAML (variants included), a readable summary and its compliance verdict read
validate_scenario_yaml Checks a YAML against the grammar and the server rules, saves nothing read
create_scenario Creates a scenario from YAML; returns its compliance review (compliance: verdict, reasons to fix) write
update_scenario Replaces a scenario with a complete YAML (variants matched by title); the new content is reviewed again write
list_environments Environments (development, staging, production...), default one, number mapping read
list_versions Versions of your code per environment (label, git_sha, git_ref) read
create_version Records a deployed version (label, environment, git_sha, git_ref) write
run_scenario Starts a run (real calls): environment, optional version, optional wait_s run
get_run_report Verdict, score, one line per test case, failed checks with the judge's quotes read
get_call Readable transcript, verdicts, metrics, links to the call page and the audio read
export_for_llm The "Copy for an LLM" report of a scenario, a run or a call (Markdown or JSON) read
fix_test Runs the assistant on a failed call: proposed YAML and diff, not applied write
apply_fix Saves a proposal of fix_test write
get_usage Quota, wallet credit, forecast of the month read

Setup tools:

Tool What it does Scope
get_setup_status Plan, credit, the key's scopes, and each setup step: done, next tool, what a person must do read
create_environment Creates an environment (name, order, is_default) write
list_numbers Numbers under test, with their verification status read
add_phone_number Registers a number (or returns it when it exists), with the attestation text to show write
get_number_status Verification state, digits heard on a failed attempt, sandbox limits, next step read
start_number_verification A code and the phrase the line must say; signs the attestation when asked (see below) run (+ write to sign)
place_verification_call One real call, free, that listens for the code run
set_schedule Activates a scenario in an environment: schedule (cron or plain words), time zone, call window, pause; returns the month's forecast write
list_schedules Scenario x environment: schedule, next and last run, number dialed read
list_webhooks Webhook endpoints and every event type read
configure_webhook Creates an endpoint, or updates the one with the same URL; the secret is shown once admin
create_alert_channel Slack or email alerts (failure, score, latency) admin
get_ci_setup Ready-to-commit GitHub Actions or GitLab CI files for your scenarios, and the secret to set read

Support tools (see Getting help):

Tool What it does Scope
contact_support Opens a ticket with the CallQA team (bug, question, feedback, billing, abuse report); a person answers, usually within a few hours read
list_support_tickets The organization's tickets, status and unread answers read
get_support_ticket One ticket with every message, to read the team's answer read
reply_support_ticket Adds a message to a ticket (reopens it) read

run_scenario places real phone calls: each one consumes your monthly quota, then wallet credit, under your wallet, spend cap and plan limits, exactly like a run started from the API. wait_s (up to 300 seconds) keeps the request open until the run finishes and streams progress notifications; without it the tool returns at once and the agent polls get_run_report. set_schedule and the CI files lead to more of these calls, on their own: their answers say how many. place_verification_call places one real call that costs nothing. Nothing can be deleted or paid through the MCP server.

Resources and prompts

Resource Content
callqa://grammar/scenario.schema.json JSON Schema of the scenario grammar
callqa://grammar/guide The grammar on one page, with good practice
callqa://examples/level-1-keypad-ivr Example: keypad menu (DTMF IVR), short form
callqa://examples/level-2-voice-ivr Example: voice menu, typed assertions, test data, a variant
callqa://examples/level-3-ai-agent Example: AI agent, latency budget, interruptions, edge cases

Prompts (shown as slash commands by most clients): test-my-voice-agent (arguments environment, scenario), investigate-failing-run (run_id), write-scenario (description, phone) and set-up-callqa (description, phone, environments, schedule).

Onboarding with an agent

The prompt set-up-callqa walks the agent through the setup in this order. Every step is a tool call; the agent stops where a person has to act.

  1. Status. get_setup_status: plan, credit, the key's scopes, what is done.
  2. Environments. create_environment for each deployment of the voice agent (development, staging, production). Every organization starts with one, the default.
  3. Number and verification. add_phone_number, then:
  4. A person signs the attestation: the agent shows the text (get_number_status) and signs with start_number_verification(sign_attestation=true, signer_name=...) only in the name and with the agreement of the person who controls the line. It is a legal statement; the agent never signs on its own.
  5. The line says the code: the agent adds the returned phrase to the voice agent's prompt or greeting when that prompt lives in your repository, and asks you to deploy. Otherwise you add it in your voice platform.
  6. place_verification_call, then get_number_status until verified. Until then only manual calls work (sandbox: 3 a day, no schedule, no CI). See Number verification.
  7. Scenarios from a plain description of the agent, or from its prompt and flows in your repository: validate_scenario_yaml, create_scenario.
  8. A first run: run_scenario on one scenario, get_run_report. Real calls: the agent says how many before starting.
  9. Schedules: set_schedule per scenario and environment, for example schedule="every weekday at 08:30", timezone="Europe/Paris", call_window="08:00-20:00". The answer has the month's forecast.
  10. Webhooks and alerts: configure_webhook, create_alert_channel. They need a key with the admin scope; with the recommended key, an admin sets them up in the app instead.
  11. CI: get_ci_setup returns the files to commit (.github/workflows/callqa.yml and the CallQA action, or .gitlab-ci.callqa.yml), the CLI to download and the secret to set. A person stores a key with the read and run scopes as the CALLQA_API_KEY secret (gh secret set CALLQA_API_KEY): no key is ever written in a file.

What only a person does: sign the attestation of a number, make the line say the verification code when its prompt is not in the repository, choose a plan or top up the wallet (never through an API key), create API keys, and, with a key without the admin scope, webhooks and alerts.

Security

  • Same rules as the REST API. The key's scopes, its IP allow list, its expiry and the rate limit (120 requests per minute per key) apply to every MCP request. Objects of other organizations answer not_found.
  • Journal. Each request is a line of the API journal (Developer > API journal), for example POST /mcp tools/call run_scenario. Tool arguments are not logged. Changes are in the audit log under the key.
  • Least privilege. Give the agent read, write and run, never admin. A read only key is enough for an agent that only reads reports.
  • Browsers. Requests carrying a browser Origin from another site are refused.

Errors

A tool that fails answers an error result whose text is JSON, with the stable codes of the REST API (see Errors) and a hint for the model:

{"error": {"code": "missing_scope", "status": 403,
           "detail": "This API key lacks the 'run' scope (run.trigger).",
           "hint": "Ask an admin of the organization for a key with this scope (...)."}}

Codes you will meet: missing_scope, feature_not_in_plan (with required_plan and upgrade_url), not_found, invalid_scenario (with errors, one per field), run_not_possible, blocked_insufficient_credit (no call of the run could be paid, with run_id and block_reasons), blocked_policy (no call of the run could be placed for a policy reason, same fields), call_not_judged, call_passed (fix_test on a call that passed), not_pending, scenario_changed, rate_limited; for the setup tools, already_exists, invalid_target, attestation_required, no_active_code, invalid_schedule, unknown_event, unsafe_url, no_scenario. Before the protocol starts, the HTTP layer answers 401 unauthorized, 403 ip_not_allowed or 429 rate_limited (with Retry-After).