MCP Rigor logo MCP Rigor

Reuse, Utility Functions, and Data-Driven Testing

This guide is for QA authors first and extension developers second.

Reusable flows

A flow is a named group of ordinary test actions:

Flow: "Verify addition"
  Inputs: a, b, expected

  Call tool "add" with:
    a: "${a}"
    b: "${b}"

  Expect "structuredContent.sum" equals "${expected}"

Use it from any test:

Use flow "Verify addition" with:
  a: 2
  b: 3
  expected: 5

Flows can use other flows. Recursive flow calls are rejected before execution. Each invocation has its own prefixed variables, so reused actions do not overwrite another invocation's inputs.

Flows may contain section markers:

Flow: "Create and remove customer"
  Inputs: email

  Setup:
  Call tool "prepare_tenant"

  Steps:
  Call tool "create_customer" with:
    email: "${email}"

  Cleanup:
  Call tool "remove_customer" with:
    email: "${email}"

Cleanup actions are marked as guaranteed and are attempted after a preceding action fails. Keep cleanup idempotent.

Deterministic built-in utilities

Set "email" using "lowercase" with:
  value: "${row.Email}"

Built-ins:

They have no network or file access and do not use random values or the current time.

Custom utility functions

Declare a reviewed module:

Functions: tests/qa-functions.mjs
export function calculateTax({ amount, rate }) {
  return Math.round(amount * rate * 100) / 100;
}

Use it exactly like a built-in:

Set "expectedTax" using "calculateTax" with:
  amount: "${row.amount}"
  rate: 0.08

Custom code is disabled by default:

mcprigor test tax.mcpr --allow-custom-code

Enabled modules run in constrained workers by default, with manifest checks, explicit permissions, JSON-only boundaries, memory limits, and timeouts. Worker isolation reduces risk but is not a hard sandbox for hostile code. Review, pin, and allowlist every extension before CI use. See Extension SDK.

Inline examples

Test: "Calculator examples"
  For each row:
    | caseId  | a  | b  | expected |
    | basic   | 2  | 3  | 5        |
    | larger  | 20 | 22 | 42       |

  Use flow "Verify addition" with:
    a: "${row.a}"
    b: "${row.b}"
    expected: "${row.expected}"

Each row becomes an isolated test and report entry. caseId or id provides its display ID.

Files

For each row in "data/cases.csv"
For each row in "data/cases.json"
For each row in "data/cases.yaml"
For each row in "data/cases.xlsx" from sheet "Regression"

JSON/YAML files must contain an array of objects. Named source configuration can select a nested path.

Named data sources

Data source: "regression cases"
  provider: json
  file: data/cases.json
  path: cases

Test: "Regression matrix"
  For each row from "regression cases"
  # actions using ${row.column}

Providers:

REST

Data source: "API cases"
  provider: rest
  url: https://qa.example.com/cases
  path: cases
  headers:
    Authorization: "Bearer ${env.CASES_TOKEN}"

Remote sources require explicit permission:

mcprigor test api.mcpr --allow-remote-data

Requests have a ten-second deadline. Responses must be JSON row arrays. Credential-shaped fields are redacted from reports.

Google Sheets

Data source: "Sheet cases"
  provider: google-sheets
  spreadsheetId: "${env.SHEET_ID}"
  range: Regression!A1:F100
  accessToken: "${env.GOOGLE_ACCESS_TOKEN}"

Or provide apiKey for a public sheet. Google Sheets is fetched through the Values REST API and requires --allow-remote-data.

SQL and custom providers

Database drivers are intentionally not bundled. Register a reviewed provider module:

Data source: "Database cases"
  provider: sql
  module: test/providers/postgres-cases.mjs
  query: SELECT case_id, input, expected FROM qa_cases

The module implements:

export default {
  async load(config, context) {
    // Use parameterized, read-only queries and return object rows.
    return rows;
  }
}

Run with --allow-custom-code. The same plugin interface can connect Jira, Xray, TestRail, or internal systems.

Safety and reproducibility