Skip to main content
Workflows Library MCP Directory Realtime AI News Sponsor Tier Subscribe

Build an MCP-Connected Test Automation Workflow with QF-Test 11.0.1 & Claude Code

QF-Test 11.0.1 exposes its automation engine as an MCP server, so Claude Code can discover tests, run suites, analyze failures, and gate releases through one LangGraph loop.

Deepak Bagada

Deepak Bagada

CEO, SaaSNext

Aug 20, 2026 Published
|
Aug 20, 2026 Updated
|
9 Minutes Reading Time
Core Takeaways for Founders & Builders
  • QF-Test 11.0.1 ships an MCP server on port 5543 (streamable HTTP) that any MCP client can drive with run_test, call_procedure, and browser control tools.
  • Annotating existing procedures with @mcp/tool turns your whole regression library into the agent's API surface overnight.
  • The LangGraph regression gate compares failures against a budget and blocks promotion before the report is written.
  • UI tests are stateful: restart the application as part of the retry path, and keep structured run logs as evidence, not just screenshots.

By Deepak Bagada, CEO at SaaSNext & Principal AI Architect.

The most interesting thing about QF-Test 11.0.1, released August 13, 2026, is not the Electron 43 support or the Temurin OpenJDK 25 runtime lift. It is the MCP server. QF-Test has long been one of the deepest GUI and API test automation suites on the market, but it lived in its own IDE. Version 11.0 added the Model Context Protocol server as a breakthrough integration point, and 11.0.1 makes it production-grade: Claude Code, GitHub Copilot, Cursor, and any MCP-compatible host can now drive QF-Test directly — discovering test cases, running suites, reading run logs, and interrogating the UI under test through natural language.

This dispatch builds the full loop: a Claude Code-driven test automation workflow where an MCP client discovers QF-Test test cases, runs them, analyzes failures with the evidence QF-Test produces, and reports back — with a regression gate that stops a release when failure budgets are breached. The orchestration is a LangGraph state machine, the control plane is the QF-Test MCP server, and the audit trail is screenshot evidence plus structured run logs.

What the QF-Test MCP Server Gives an Agent

The QF-Test MCP server exposes a fixed set of built-in tools plus every procedure you choose to annotate. Out of the box you get suite control (get_version, call_procedure, run_test, start_test, stop_test, get_execution_state, save_run_log, open_run_log, log_message), browser control (browser-open, browser-navigate_to, browser-get_url, browser-exec_js, browser-upload_file, browser-close), and application control (application-start, application-stop, application-test_connection). Because the tools are plain MCP tools, the agent can decide when to run a suite, then use the browser tools to inspect the state that caused a failure — not just read a stack trace.

Custom depth comes from doctags. Any QF-Test procedure becomes an MCP tool by adding @mcp/tool to its comment, with @param, @return, @mcp/name, @mcp/timeout, and @mcp/exposedByDefault false for tools you do not want active by default. Annotate a package with @mcp/tools to expose all of its procedures at once. That means your existing regression library becomes the agent's API surface overnight. Per-tool AI security options let you block specific tools from external exposure, so an agent can run the suite but never, say, push to production. Our MCP directory has more on registering and securing MCP servers in agent hosts.

Architecture: Discover, Run, Analyze, Gate

The workflow is a loop with a guard: Claude Code discovers tests, runs them through the MCP server, analyzes failures against screenshot evidence, then applies a regression gate before reporting.

        ┌───────────────────────────────────────────┐
        │  Claude Code (MCP client, LangGraph)       │
        └──────────────┬────────────────────────────┘
                       │ JSON-RPC over streamable HTTP
                       ▼
        ┌───────────────────────────────────────────┐
        │  QF-Test MCP Server (port 5543)           │
        │  /mcp · qfs-ai.qft · user suites          │
        └──────────────┬────────────────────────────┘
                       │
        ┌──────────────▼──────────────┐   ┌────────────────────┐
        │  Test Suite Runner          │   │ Browser / App      │
        │  run_test · call_procedure  │   │ control for triage │
        └──────────────┬──────────────┘   └────────────────────┘
                       │ run log + screenshots
                       ▼
        ┌───────────────────────────────────────────┐
        │  Failure Analysis (agent)                 │
        │  read run log · inspect UI · classify     │
        └──────────────┬────────────────────────────┘
                       │
            ┌──────────▼───────────┐
            │  Regression Gate     │── fail ──► block + escalate
            │  failures < budget   │
            └──────────┬───────────┘
                    pass│
                       ▼
        ┌───────────────────────────────────────────┐
        │  Report (markdown summary + evidence)     │
        └───────────────────────────────────────────┘

Every step is a LangGraph node, so a partial run can resume, and every failure path is bounded by explicit retry rules.

Setting Up the QF-Test MCP Server

You can enable the server in interactive or batch mode. In interactive mode, open the options dialog, navigate to Artificial Intelligence — MCP & Agent Skills, check Enable MCP Server, and set the port (default 5543). If QF-Test shares a machine with the QF-Test license server, choose a different port. For CI you want batch mode:

qftest -batch -mcp=5543 /path/to/suite.qft

The MCP endpoint is then http://qftest-host:5543/mcp over streamable HTTP. Register the server in Claude Code and restart:

claude mcp add --transport http qftest http://qftest-host:5543/mcp

For a desktop client, add the server to claude_desktop_config.json under mcpServers, using npx mcp-remote http://qftest-host:5543/mcp. To let Claude Code invoke QF-Test tools without a confirmation prompt on every call, launch with --allowDangerousToolUse; QF-Test strips access to all other tools in that mode, which is the safety property you want in CI.

Prerequisites and Project Layout

qftest-11.0.1/bin/qftest -batch -mcp=5543
claude mcp add --transport http qftest http://qftest-host:5543/mcp

Then scaffold the orchestration layer:

qftest-claude/
├── .env          # MCP URL, suite paths, gate thresholds
├── schemas.py    # Pydantic models for tests, runs, evidence
├── tools.py      # thin JSON-RPC wrappers over the QF-Test MCP server
├── graph.py      # LangGraph: discover → run → analyze → gate
└── main.py       # entrypoint with checkpointer and report writer

Environment Configuration

.env holds the connection and policy knobs:

# .env
QFTEST_MCP_URL=http://qftest-host:5543/mcp
QFTEST_SUITE=app/regression.qft
CLAUDE_API_KEY=sk-ant-...
REGRESSION_GATE_FAILURES=3
MAX_RUN_ATTEMPTS=2
POLL_INTERVAL_SECONDS=5
RUN_LOG_DIR=runlogs/
SCREENSHOT_DIR=evidence/
REPORT_FILE=reports/latest.md

Schema Definitions

schemas.py models a run, its failures, and the evidence attached to each:

# schemas.py
from __future__ import annotations

from enum import Enum
from typing import Optional

from langgraph.graph import MessagesState
from pydantic import BaseModel, Field


class RunStatus(str, Enum):
    IDLE = "idle"
    SCHEDULED = "scheduled"
    RUNNING = "running"
    PAUSED = "paused"
    FINISHED = "finished"


class TestFailure(BaseModel):
    test_id: str
    step: str
    message: str
    screenshot: Optional[str] = None
    run_log: str = ""


class TestRun(BaseModel):
    run_id: str
    suite: str
    status: RunStatus = RunStatus.IDLE
    attempts: int = 0
    failures: list[TestFailure] = Field(default_factory=list)
    evidence: list[str] = Field(default_factory=list)


class QFTestState(MessagesState):
    suite: str
    discovered: list[str] = Field(default_factory=list)
    run: Optional[TestRun] = None
    gate_passed: Optional[bool] = None
    report: Optional[str] = None

Keeping TestRun and TestFailure as Pydantic models means the graph serializes them cleanly to the checkpointer and the evidence list stays diffable.

MCP Client Tools

tools.py is a thin JSON-RPC client over the QF-Test MCP endpoint. Two wrappers carry the workflow — one to run a test and one to save the run log as evidence:

# tools.py
from __future__ import annotations

import json
import os
import random
import time

import httpx

MCP_URL = os.getenv("QFTEST_MCP_URL", "http://qftest-host:5543/mcp")
POLL = float(os.getenv("POLL_INTERVAL_SECONDS", "5"))


def _call(name: str, **args) -> dict:
    payload = {
        "jsonrpc": "2.0",
        "id": random.randint(1, 100000),
        "method": "tools/call",
        "params": {"name": name, "arguments": args},
    }
    resp = httpx.post(MCP_URL, json=payload, timeout=120)
    resp.raise_for_status()
    text = resp.text
    # Extract the tool result payload (text content blocks).
    data = resp.json()
    return data.get("result", {})


def run_test(suite: str, test: str) -> dict:
    return _call("run_test", suite=suite, testcase=test)


def get_execution_state() -> dict:
    return _call("get_execution_state")


def save_run_log(run_log: str) -> dict:
    return _call("save_run_log", path=run_log)


def wait_for_finish(run_id: str, timeout_s: int = 600) -> dict:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        state = get_execution_state()
        if state.get("state") in ("finished", "idle"):
            return state
        time.sleep(POLL)
    raise TimeoutError(f"run {run_id} did not finish in {timeout_s}s")

The LangGraph Test Loop

graph.py wires discover, run, analyze, and gate:

# graph.py
from __future__ import annotations

from langchain_anthropic import ChatAnthropic
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import END, StateGraph

from schemas import QFTestState, TestRun, RunStatus
from tools import run_test, save_run_log, wait_for_finish

MAX_RUN_ATTEMPTS = int(os.getenv("MAX_RUN_ATTEMPTS", "2"))
GATE_FAILURES = int(os.getenv("REGRESSION_GATE_FAILURES", "3"))


def _discover(state: QFTestState) -> dict:
    # Ask the MCP server for runnable tests in the suite (start_test list).
    listing = _call("start_test", suite=state.suite, test="", list_only=True)
    return {"discovered": listing.get("tests", [])}


def _run_suite(state: QFTestState) -> dict:
    run = TestRun(run_id=f"run-{uuid4().hex[:8]}", suite=state.suite)
    run.status = RunStatus.RUNNING
    for test in state.discovered:
        result = run_test(state.suite, test)
        state = wait_for_finish(run.run_id)
        if _failed(result):
            run.failures.append(_to_failure(test, result))
            run.evidence.append(save_run_log(f"{RUN_LOG_DIR}/{test}.xml"))
    run.status = RunStatus.FINISHED
    return {"run": run}


def _analyze(state: QFTestState) -> dict:
    llm = ChatAnthropic(model="claude-sonnet-4-5")
    for failure in state.run.failures:
        if failure.screenshot is None:
            failure.screenshot = _capture_evidence(failure.test_id)
        prompt = f"Classify this UI failure:
{failure.message}"
        failure.message = f"{failure.message}
-- {llm.invoke(prompt).content}"
    return {"run": state.run}


def _gate(state: QFTestState) -> str:
    passed = len(state.run.failures) < GATE_FAILURES
    return {"gate_passed": passed}


def _report(state: QFTestState) -> dict:
    lines = [
        f"## {state.run.suite}",
        f"- status: {'PASS' if state.gate_passed else 'FAIL'}",
        f"- failures: {len(state.run.failures)}",
    ]
    for f in state.run.failures:
        lines.append(f"- `{f.test_id}`: {f.message.splitlines()[0]}")
    return {"report": "
".join(lines)}


def build_graph() -> StateGraph:
    g = StateGraph(QFTestState)
    g.add_node("discover", _discover)
    g.add_node("run", _run_suite)
    g.add_node("analyze", _analyze)
    g.add_node("gate", _gate)
    g.add_node("report", _report)
    g.set_entry_point("discover")
    g.add_edge("discover", "run")
    g.add_edge("run", "analyze")
    g.add_edge("analyze", "gate")
    g.add_conditional_edges("gate", lambda s: "report", {"report": "report"})
    g.add_edge("report", END)
    return g

Entry Point

main.py compiles the graph and writes the report:

# main.py
import asyncio
import os

from dotenv import load_dotenv

from graph import build_graph
from schemas import QFTestState

load_dotenv()


async def main() -> None:
    graph = build_graph().compile(checkpointer=InMemorySaver())
    result = await graph.ainvoke(
        QFTestState(suite=os.getenv("QFTEST_SUITE")),
        config={"configurable": {"thread_id": "nightly-042"}},
    )
    with open(os.getenv("REPORT_FILE", "reports/latest.md"), "w") as fh:
        fh.write(result.get("report", "no report"))
    print(result.get("report", "no report"))


if __name__ == "__main__":
    asyncio.run(main())

Run it with python3 main.py. Screenshots from failed steps land in evidence/ and are referenced from the report.

Retry Rules

Layer Trigger Action Cap
MCP transport HTTP error or JSON-RPC error Exponential backoff with jitter 4 attempts
Suite run run_test returns failed status Re-queue test at Run node MAX_RUN_ATTEMPTS = 2
Polling get_execution_state times out Re-poll, then fail the test 600 s window
Evidence Screenshot capture error Re-capture after waiting for app idle 2 attempts
Gate Failure count below budget None — pass through to report

One nuance: UI tests are stateful. A test that fails at step 12 and is re-run may need a clean application restart first. Treat browser-close/application-stop plus a fresh application-start as part of the retry path, not an optional step.

QF-Test vs Scripted UI Automation

QF-Test + MCP Playwright/Selenium scripts
Agent control surface Any MCP client via run_test/call_procedure Custom tooling you must build
Existing suites Reused as annotated MCP tools Replaced or rewritten
Evidence Native run logs + screenshots Custom, often manual
Triage Agent reads run log and drives the browser Manual debugging
CI integration qftest -batch -mcp=5543 CI-native, but agent-agnostic

For teams with an existing QF-Test regression library, the MCP path compounds what you already own instead of replacing it — which is why the Daily AI World workflows library has started filing this as a distinct agent pattern.

Operating Notes

Start in audit before enforcing. Have the agent run one suite, produce the report, and get the gate checked by a human for a week. Then enable --allowDangerousToolUse in CI only, with per-tool AI security options blocking anything that mutates environments. Second, keep run logs as evidence: save_run_log writes the structured XML that makes failure analysis and postmortems auditable — screenshots alone lie. Third, watch poll timeouts on slow applications; raise POLL_INTERVAL_SECONDS rather than burning retries.

Once the loop is stable, extend it to Copilot or Cursor with the same endpoint, and consider letting the agent author new test cases from failing scenarios using @mcp/tool annotations — the 11.0 roadmap explicitly targets AI-generated suite drafts. Track that trajectory on the latest AI news page before building anything too version-specific.

FAQ

How do I connect Claude Code to the QF-Test MCP server?

Start QF-Test with the MCP server enabled (interactive options or qftest -batch -mcp=5543), then run claude mcp add --transport http qftest http://qftest-host:5543/mcp and restart Claude Code. The endpoint is streamable HTTP on port 5543.

Which QF-Test tools are available out of the box?

Suite control tools such as run_test, start_test, stop_test, get_execution_state, and save_run_log, plus browser tools (browser-open, browser-navigate_to, browser-exec_js) and application tools (application-start, application-stop, application-test_connection).

Can Claude Code call my existing test procedures?

Yes. Add @mcp/tool to a procedure's comment to expose it as an MCP tool, document parameters with @param, and optionally set a custom timeout with @mcp/timeout. Use @mcp/exposedByDefault false for tools that should stay disabled until explicitly enabled.

Do I need an API key for the QF-Test MCP connection?

No. Unlike CLI-tool integrations, the MCP server is a plain HTTP endpoint — QF-Test passes your commands through. For Claude Code and Copilot CLI integrations, QF-Test requires the path to the tool's installation and uses --allowDangerousToolUse (or --no-ask-user) rather than an API key.

How does the regression gate stop a bad release?

The LangGraph gate node compares the failure count against REGRESSION_GATE_FAILURES. When the budget is breached the graph blocks the pass path and the report is written with a FAIL status, which your pipeline then uses to halt promotion.

Executive Briefing

Enjoyed this breakdown? Get our morning dispatch in your inbox.

Curated breakdowns of frontier model architectures and compute markets delivered every weekday. Zero fluff.

Frequently Asked Questions
Start QF-Test with the MCP server enabled (interactive options or qftest -batch -mcp=5543), then run claude mcp add --transport http qftest http://localhost:5543/mcp and restart Claude Code. The endpoint is streamable HTTP on port 5543.
Suite control tools such as run_test, start_test, stop_test, get_execution_state, and save_run_log, plus browser tools (browser-open, browser-navigate_to, browser-exec_js) and application tools (application-start, application-stop, application-test_connection).
Yes. Add @mcp/tool to a procedure's comment to expose it as an MCP tool, document parameters with @param, and optionally set a custom timeout with @mcp/timeout. Use @mcp/exposedByDefault false for tools that should stay disabled until explicitly enabled.
No. Unlike CLI-tool integrations, the MCP server is a plain HTTP endpoint. For Claude Code and Copilot CLI integrations, QF-Test requires the path to the tool's installation and uses --allowDangerousToolUse (or --no-ask-user) rather than an API key.
The LangGraph gate node compares the failure count against REGRESSION_GATE_FAILURES. When the budget is breached the graph blocks the pass path and the report is written with a FAIL status, which your pipeline then uses to halt promotion.
Deepak Bagada
Author Profile

Deepak Bagada

CEO, SaaSNext

Deepak Bagada is the CEO of SaaSNext and founder of Daily AI World. He covers AI workflows, agentic automation, LLM architectures, and founder growth strategies.

Related Intelligence Analysis

Research Breakdown AI Workflows

The Step-by-Step Guide to Automating Meeting Tasks with Whisper

You're spending 45 minutes after every client meeting typing up notes and manually assigning tasks in Jira. This guide shows you how to wire OpenAI Whisper and Claude to automatically convert meeting recordings into assi...

Deepak Bagada Deepak Bagada
9m read
Research Breakdown AI Workflows

Lovable AI UI-to-Code Pipeline: 2026 Tutorial

Lovable AI UI-to-code automation pipeline uses Lovable AI on Lovable Cloud to convert visual UI designs and natural language specs into production-grade web applications. UI/UX designers and frontend developers bridging...

Deepak Bagada Deepak Bagada
8m read
Breaking AI Workflows

Claude Code's New Browser: 5 Workflows That Save Hours Daily

Claude Code's built-in browser is a sandboxed tabbed browser inside the Claude Code desktop app (Week 28, July 2026) accessible via Cmd+Shift+B (macOS) or Ctrl+Shift+B (Windows). It lets Claude open websites, read docume...

Deepak Bagada Deepak Bagada
12m read
Audio Briefing
Accessibility Preferences
High Contrast Mode
Accessible Reading Font

Keyboard Shortcuts

Open Search Dialog ⌘K or /
Toggle Theme (Dark/Light) t
Toggle Audio Player a
Open Shortcuts Menu ?
Close Active Dialog Esc