# allure-testops-mcp [![PyPI](https://img.shields.io/pypi/v/allure-testops-mcp.svg?logo=pypi&logoColor=white)](https://pypi.org/project/allure-testops-mcp/) [![Python](https://img.shields.io/pypi/pyversions/allure-testops-mcp.svg?logo=python&logoColor=white)](https://pypi.org/project/allure-testops-mcp/) [![License: MIT](https://img.shields.io/pypi/l/allure-testops-mcp.svg)](LICENSE) [![Tests](https://github.com/mshegolev/allure-testops-mcp/actions/workflows/test.yml/badge.svg)](https://github.com/mshegolev/allure-testops-mcp/actions/workflows/test.yml) An [MCP](https://modelcontextprotocol.io) server for [Allure TestOps](https://qameta.io/). It lets an LLM agent (Claude Code, Cursor, OpenCode, …) explore and manage projects, launches, test cases, test results and reference data through the Allure REST API. - **Stack:** Python 3.10+, [FastMCP](https://github.com/modelcontextprotocol/python-sdk), **stdio** transport. - **Compatibility:** any Allure TestOps instance — SaaS `qameta.io` or self-hosted / on-prem (API at `/api/rs`). - **Instance-wide — all projects at once:** one connection serves every project on the instance. The server isn't pinned to a single project — discover them with `allure_list_projects`, then pass any `project_id`. No reconfiguration to switch or compare projects. - **Corporate-friendly:** API-token auth, optional SSL-verify toggle, deliberate proxy bypass. - **Safe by default:** 13 read-only tools; the 7 write tools are off unless you opt in. ## Quick start ```bash claude mcp add allure -s user \ --env ALLURE_URL=https://allure.example.com \ --env ALLURE_TOKEN=your-api-token \ -- uvx --from allure-testops-mcp allure-testops-mcp ``` Then ask your agent: *"List all Allure projects"* or *"Show failed tests in the last launch for project 175"*. Get an API token in Allure TestOps under **Profile → API tokens**. See [Configuration](#configuration) for other clients and [Environment variables](#environment-variables) for all options. ## Tools at a glance 20 tools — 13 read-only (always on) and 7 write tools (opt-in via `ALLURE_ENABLE_WRITE=true`). Every tool carries MCP annotations and returns both a typed `structuredContent` payload and a markdown summary. | Tool | Kind | Purpose | |------|------|---------| | `allure_list_projects` | read | All projects (id, name, abbreviation) | | `allure_get_project_statistics` | read | TC count, automation rate, last-launch summary | | `allure_list_launches` | read | Recent launches with pass/fail stats | | `allure_get_test_results` | read | Test results in a launch (filter by status) | | `allure_search_failed_tests` | read | FAILED/BROKEN tests in the last or a given launch | | `allure_list_test_cases` | read | Test cases (automated/manual + owner filters) | | `allure_get_test_case` | read | One test case's full detail + scenario steps | | `allure_get_test_case_custom_fields` | read | A test case's custom-field values | | `allure_list_statuses` | read | A project's statuses (id, name, color) | | `allure_list_layers` | read | A project's test layers (id, name) | | `allure_list_custom_fields` | read | A project's custom-field schema | | `allure_list_categories` | read | Defect categories (named/coloured buckets) | | `allure_list_category_matchers` | read | Regex automation rules (message/trace → category) | | `allure_create_test_case` | write ⚑ | Create a test case | | `allure_update_test_case` | write ⚑ | Partial update of a test case | | `allure_delete_test_case` | write ⚑ | Permanent delete (destructive — needs `confirm=true`) | | `allure_create_category` | write ⚑ | Create a defect category | | `allure_delete_category` | write ⚑ | Permanent delete (destructive — needs `confirm=true`) | | `allure_create_category_matcher` | write ⚑ | Create + attach a regex automation rule | | `allure_delete_category_matcher` | write ⚑ | Permanent delete (destructive — needs `confirm=true`) | ⚑ Registered only when `ALLURE_ENABLE_WRITE=true`. Without the flag they are never imported, so the agent never sees them — see [Security considerations](#security-considerations). ### Write tools — status & layer by name or id `allure_create_test_case` / `allure_update_test_case` accept status and layer as either a **name** (`status` / `layer`) or a numeric **id** (`status_id` / `layer_id`). Names are auto-resolved to ids against the project's status/layer lists (`GET /api/rs/status`, `GET /api/rs/testlayer`) — an unknown name returns an actionable error listing the valid options. Update is partial (only the fields you pass change), and `allure_delete_test_case` is irreversible: it carries `destructiveHint: True` (compliant clients prompt) and additionally requires an explicit `confirm=true` argument. ## Design highlights - **Full tool annotations** — read tools are `readOnlyHint: True` / `openWorldHint: True` so clients don't prompt; `allure_delete_test_case` is `destructiveHint: True`. - **Structured output on every tool** — each tool declares a `TypedDict` return type, so FastMCP auto-generates an `outputSchema` and every result carries both `structuredContent` and a markdown block. - **Actionable errors** — auth / 400 / 403 / 404 / 409 / 429 / 5xx / missing-env errors are converted to specific, next-step messages (e.g. *"Authentication failed — verify ALLURE_TOKEN has API scope"*). - **Pydantic input validation** — every argument has typed constraints (ranges, lengths, literals), exposed as JSON Schema; usernames are alphabet-restricted to prevent RQL injection. - **Pagination** — list tools return a `pagination` block with `page`, `total`, `has_more`, `next_page`. - **Progress reporting** — multi-call tools emit `ctx.report_progress` + `ctx.info` events. - **Version-agnostic update verb** — `allure_update_test_case` issues `PATCH` and falls back to `PUT` on HTTP 405, so it works across Allure deployments that expose only one verb. - **Single source of truth for version** — `__version__` derives from installed package metadata, and a test asserts `pyproject.toml` matches both `server.json` version fields, so the published version can't drift. ## Installation Requires Python 3.10+. No manual install needed if you use `uvx` (recommended) — your MCP client runs it. ```bash # run on demand via uvx (recommended) uvx --from allure-testops-mcp allure-testops-mcp # or install with pipx pipx install allure-testops-mcp ``` ## Configuration **Claude Code** — one command: ```bash claude mcp add allure -s user \ --env ALLURE_URL=https://allure.example.com \ --env ALLURE_TOKEN=your-api-token \ --env ALLURE_SSL_VERIFY=true \ -- uvx --from allure-testops-mcp allure-testops-mcp ``` **Any MCP client** — add to `~/.claude.json`, a project `.mcp.json`, Cursor's `mcp.json`, etc.: ```json { "mcpServers": { "allure": { "type": "stdio", "command": "uvx", "args": ["--from", "allure-testops-mcp", "allure-testops-mcp"], "env": { "ALLURE_URL": "https://allure.example.com", "ALLURE_TOKEN": "${ALLURE_TOKEN}", "ALLURE_SSL_VERIFY": "true" } } } } ``` See [`.env.example`](./.env.example) for a template. Verify the connection: ```bash claude mcp list # allure: uvx --from allure-testops-mcp allure-testops-mcp - ✓ Connected ``` ## Environment variables | Variable | Required | Default | Description | |---|---|---|---| | `ALLURE_URL` | yes | — | Allure TestOps URL (e.g. `https://allure.example.com`) | | `ALLURE_TOKEN` | yes | — | API token (Allure → Profile → API tokens) | | `ALLURE_SSL_VERIFY` | no | `true` | `true`/`false`. Set `false` for self-signed corp certs | | `ALLURE_ENABLE_WRITE` | no | `false` | `true` registers the 7 write tools; default is a read-only server | `ALLURE_TEST_PROJECT_ID` (plus optional `ALLURE_TEST_STATUS` / `ALLURE_TEST_LAYER`) are used only by the opt-in live integration tests — see [Development](#development). ## Updating The server is a stdio process your client respawns each session, so the running version is decided by the `uvx` invocation. `uvx` caches the resolved environment under `~/.cache/uv`, so an older version sticks until you refresh: ```bash uvx --refresh --from allure-testops-mcp allure-testops-mcp # force latest on next run uv cache clean allure-testops-mcp # or drop the cached env ``` Then reconnect the server (`/mcp` → reconnect, or restart the session). To control the version from config, edit `args` — pin for stability, or always-latest for currency: ```jsonc // Pin a version (deterministic; bump consciously) "args": ["--from", "allure-testops-mcp==0.8.0", "allure-testops-mcp"] // Always latest on every start (adds a PyPI lookup per launch) "args": ["--refresh", "--from", "allure-testops-mcp", "allure-testops-mcp"] ``` ## Example prompts Read-only: - "List all Allure projects" - "Show the last 10 launches for project 63" - "Failed tests in the last launch for project 175" - "What's the automation rate for project 842?" - "Compare the automation rate of project 63 and project 842" — works across projects in one session - "Show me the steps of test case 641012" - "Which custom fields does project 1664 have?" With `ALLURE_ENABLE_WRITE=true`, drive test-case CRUD in natural language: - "Create a Draft manual TC named 'Login flow' in project 63" - "Add an automated smoke TC in project 63 tagged `smoke`, layer `E2E`" - "Rename test case 555 to 'Login (rewritten)' and set its status to Active" - "Delete test case 555" — the agent passes `confirm=true`, and a compliant client prompts you first ## Security considerations - **API token is read from `ALLURE_TOKEN` only** — never passed on the command line, never written to logs. - **Secrets are never echoed back** in tool responses (no header dumps, no auth reflection). - **Self-signed SSL is opt-in** via `ALLURE_SSL_VERIFY=false` (default `true`). Disabling verification on a public network is a risk; use only for trusted corporate instances. - **Proxy discovery is disabled** (`session.trust_env = False`) — the server ignores `HTTP_PROXY` / `HTTPS_PROXY` so it can't be silently routed through an unintended proxy. - **Writes are opt-in and least-privilege** — without `ALLURE_ENABLE_WRITE=true` the server registers only the 13 read-only tools and cannot create, modify, or delete anything, even with a write-scoped token. When enabled, the destructive tools (`allure_delete_test_case` / `allure_delete_category` / `allure_delete_category_matcher`) carry `destructiveHint: True` and require `confirm=true`. - **Input validation via Pydantic** — every argument is typed and bounded; usernames are alphabet-restricted to prevent RQL injection through the search endpoint. - **A token is never more privileged than its account** — Allure `Api-Token` auth inherits the issuing user's role, so a read-only (guest) account yields a read-only server regardless of the `ALLURE_ENABLE_WRITE` flag. ## Rate limits Allure TestOps enforces per-instance rate limits (typically ~60 requests/minute per token). On HTTP 429 the server returns an actionable error suggesting you wait 30–60s, reduce `size`, or paginate with smaller pages. Two tools make multiple API calls internally — `allure_get_project_statistics` (3) and `allure_search_failed_tests` (2–3) — and report per-step progress via MCP `Context`. ## Development ```bash git clone https://github.com/mshegolev/allure-testops-mcp.git cd allure-testops-mcp pip install -e '.[dev]' pytest # unit suite (all HTTP mocked) ruff check src tests && ruff format --check src tests ``` Run the server directly (stdio transport — waits on stdin for MCP messages): ```bash ALLURE_URL=... ALLURE_TOKEN=... allure-testops-mcp ``` ### Live-instance integration tests An opt-in suite runs a real create → update → delete lifecycle against a live Allure project. It is deselected by default and skips itself unless credentials are present, so a normal `pytest` stays green: ```bash export ALLURE_URL=https://allure.example.com export ALLURE_TOKEN=... # token from an account with write access export ALLURE_ENABLE_WRITE=true export ALLURE_TEST_PROJECT_ID=63 # a throwaway project you can write to pytest -m integration tests/integration -v ``` ## Contributing Issues and PRs welcome. Keep the unit suite green (`pytest`) and the linter clean (`ruff check`, `ruff format`); CI runs both on Python 3.10 / 3.11 / 3.12. See [`CHANGELOG.md`](./CHANGELOG.md) for the release history. ## License MIT © Mikhail Shchegolev