Skip to main content
Glama
fercarballo

mcp-qa-toolbox

by fercarballo
README.md
# mcp-qa-toolbox

Servidor [MCP](https://modelcontextprotocol.io/) con tres herramientas de QA que un agente de IA puede usar: parsear reportes JUnit, detectar tests flaky y evaluar un quality gate. Todo opera sobre archivos locales, sin red.

**¿Qué es MCP en 3 líneas?** El Model Context Protocol es un protocolo abierto para conectar modelos de lenguaje con herramientas y datos. Un *servidor* MCP expone herramientas tipadas y descriptas; un *cliente* (la app donde corre el agente) las lista y las invoca. Este repo implementa el lado servidor con el SDK oficial de Python (`mcp`, FastMCP) sobre transporte stdio.

## Las tres herramientas

### `parse_junit(path)`

Parsea un reporte JUnit XML y devuelve totales, fallos con mensaje y classname, errores, salteados y tiempos. Salida real del demo (recortada):

```json
{
  "totales": { "tests": 6, "pasados": 2, "fallos": 1, "errores": 1, "salteados": 2 },
  "tiempo_segundos": 5.033,
  "fallos": [
    {
      "test": "test_pago_con_tarjeta",
      "classname": "tests.checkout.test_pago.TestPago",
      "tipo": "AssertionError",
      "mensaje": "assert response.status_code == 200, obtuve 502"
    }
  ],
  "errores": [
    {
      "test": "test_quita_item",
      "tipo": "ConnectionError",
      "mensaje": "no pude conectarme a la base de datos de prueba (timeout 5s)"
    }
  ]
}
```

### `flakiness_report(path_glob, min_runs)`

Dado un glob con N reportes de corridas sucesivas de la misma suite, computa la tasa de fallo por test y clasifica: **estable** (nunca falla), **flaky** (falla intermitente), **roto** (falla siempre) o **datos_insuficientes** (menos de `min_runs` ejecuciones, típico de un test recién agregado). Salida real sobre las 5 corridas de `testdata/corridas/` (recortada):

```json
{
  "corridas_analizadas": 5,
  "tests": [
    { "id": "tests.checkout.test_pago.TestPago::test_cupon_descuento", "fallos": 5, "tasa_fallo": 1.0, "clasificacion": "roto" },
    { "id": "tests.checkout.test_pago.TestPago::test_pago_con_tarjeta", "fallos": 2, "tasa_fallo": 0.4, "clasificacion": "flaky" },
    { "id": "tests.checkout.test_envio.TestEnvio::test_envio_internacional", "corridas_presentes": 3, "tasa_fallo": 0.0, "clasificacion": "estable" }
  ],
  "resumen": { "estables": 3, "flaky": 1, "rotos": 1, "datos_insuficientes": 0 }
}
```

### `quality_gate(path, max_failures, max_flaky_rate)`

Decisión pass/fail con razones explícitas. Con un archivo verifica los fallos de esa corrida; con un glob verifica además la proporción de tests flaky. Salida real (recortada):

```json
{
  "decision": "fail",
  "razones": [
    "FALLA: 1 test(s) fallidos en la última corrida (testdata/corridas/corrida_05.xml), máximo permitido 0. Tests: test_cupon_descuento.",
    "FALLA: 1 de 5 tests clasificados son flaky (tasa 0.2), máximo permitido 0.0. Tests: tests.checkout.test_pago.TestPago::test_pago_con_tarjeta."
  ]
}
```

## Probalo sin un cliente MCP

```bash
python -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]'
python -m mcp_qa demo    # corre las 3 herramientas contra testdata/ e imprime los resultados
pytest -q                # 29 tests
```

## Conectarlo a un cliente MCP

El servidor habla stdio: el cliente lo lanza como subproceso. Configuración genérica (el formato exacto varía según el cliente, pero siempre es `command` + `args`):

```json
{
  "mcpServers": {
    "mcp-qa-toolbox": {
      "command": "/ruta/al/repo/.venv/bin/python",
      "args": ["-m", "mcp_qa", "serve"]
    }
  }
}
```

Las rutas que reciben las herramientas se resuelven relativas al directorio de trabajo del servidor; usá rutas absolutas si el cliente no lo lanza desde la raíz del repo.

## La descripción de una herramienta es prompt engineering

Lo único que el modelo ve de este servidor son los nombres, descripciones y schemas de las tools. Esa descripción decide si el agente elige la herramienta correcta, con qué argumentos, y qué espera de la salida. Por eso las docstrings de `src/mcp_qa/server.py` no dicen solo *qué hace* cada tool: dicen **cuándo usarla** ("cuando tengas VARIOS reportes de corridas sucesivas..."), **cuándo no** ("para comparar varias corridas usá flakiness_report"), qué forma tiene la salida y qué pasa en los casos de error. Escribirlas es el mismo trabajo que escribir un buen prompt.

## Por qué la lógica vive fuera del servidor

La tesis de diseño del repo: **lógica pura + capa de protocolo fina.**

- `src/mcp_qa/junit.py`, `flakiness.py` y `gate.py` son módulos puros: reciben rutas y parámetros, devuelven dicts, levantan `ValueError`/`FileNotFoundError` con mensajes claros. No importan nada de MCP y se testean con pytest a secas (25 de los 29 tests).
- `src/mcp_qa/server.py` solo declara las tools y delega. Los 4 tests de integración usan el cliente in-memory del SDK (`mcp.shared.memory.create_connected_server_and_client_session`), que conecta un `ClientSession` real por streams en memoria: se ejercita el protocolo completo (initialize, `tools/list`, `tools/call`) sin procesos ni red.

Beneficios concretos: los casos borde (XML malformado, suite vacía, glob sin matches, test que aparece en unas corridas y no en otras) se prueban rápido y sin ceremonia; y si mañana estas herramientas se exponen por otra vía (CLI, HTTP), la lógica no se toca — de hecho `python -m mcp_qa demo` ya es esa segunda vía.

## Estructura

```
src/mcp_qa/
  junit.py       # parseo JUnit XML (puro)
  flakiness.py   # clasificación estable/flaky/roto (puro)
  gate.py        # decisión pass/fail con razones (puro)
  server.py      # servidor FastMCP: 3 tools que delegan
  __main__.py    # python -m mcp_qa {serve,demo}
testdata/        # fixtures JUnit escritas a mano: verde, con fallos,
                 # con errores y skips, malformada, suite vacía,
                 # y 5 corridas para flakiness
tests/           # 29 tests: lógica pura + integración MCP in-memory
```

## Qué NO demuestra

- **No incluye un agente ni llama a ningún modelo.** Es solo el lado servidor de MCP; el agente lo pone el cliente que lo conecte.
- **No reemplaza al humano que decide.** Las herramientas resumen evidencia; interpretar por qué un test es flaky y qué hacer con eso sigue siendo trabajo de una persona.
- **El gate es un ejemplo de política, no una recomendación universal.** Umbrales como `max_failures=0` o "flaky = falla intermitente en N corridas" son decisiones de cada equipo; acá son parámetros, no verdades.
- **No es un parser JUnit exhaustivo.** Cubre los reportes que emiten pytest/Surefire/Gradle en sus formas comunes (`<testsuites>` o `<testsuite>` raíz); no cubre extensiones propietarias.

## Licencia

MIT.

TDQS

A4.5/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: flakiness_report analyzes multiple runs for flaky tests, parse_junit details a single run, and quality_gate makes a pass/fail decision. Descriptions are detailed and avoid overlap.

Naming Consistency3/5

Tool names use snake_case but lack a consistent verb_noun pattern: 'flakiness_report' and 'quality_gate' are noun_noun, while 'parse_junit' is verb_noun. This inconsistency could confuse agents expecting uniform conventions.

Tool Count5/5

Three tools is well-scoped for a focused JUnit analysis toolbox. Each tool earns its place, covering core needs without redundancy or excess.

Completeness5/5

The tool set covers the full workflow: parsing a single report, comparing multiple reports for flakiness, and making a decision based on thresholds. No obvious gaps remain for the stated purpose.

Maintenance

ActivityStale
ResponsivenessNo issues