Python Code Validator

Comprueba que el Python generado por IA hace lo que pediste: expresa la intención como afirmaciones y el servidor ejecuta el código en un contenedor aislado, devolviendo una corrección solo cuando todos los ejemplos pasan.

Documentación

Validador de Código Python

Un servidor MCP que valida, repara y ejecuta Python contra los ejemplos que se supone debe satisfacer — validate_python, repair_python y execute_python por HTTP en https://api.statemind.ai/mcp, con una clave gratuita y sin cuenta.

Un servicio alojado que demuestra que el Python generado por IA hace lo que pediste. Indica la intención — aserciones o líneas de doctest — y el código se ejecuta contra ella dentro de un contenedor sin red y con un sistema de archivos de solo lectura; una corrección solo regresa cuando cada ejemplo pasa. En los defectos de QuixBugs, eso es un 41% reparado y un 77% rechazado por no hacer lo que dicen, sin falsas alarmas en los programas corregidos — donde ruff y mypy marcan el defecto en ninguno de ellos (los números).

Las comprobaciones que no necesitan intención vienen incluidas: diagnósticos de sintaxis y lint, una política de seguridad AST que también detecta llamadas ocultas detrás de importaciones dinámicas y búsquedas de atributos en tiempo de ejecución, una pasada de bandit, un escaneo de credenciales y reparación determinista — un veredicto con una puntuación. Hacer la misma pregunta dos veces en diez minutos se responde desde la primera respuesta y no cuesta nada (x-msvc-repeat: 1).

Este repositorio contiene el lado del cliente: la configuración de MCP, el script de CI y el hook de pre-commit. El servicio en sí se ejecuta en https://api.statemind.ai, por lo que no hay nada que instalar ni alojar.

Una clave, sin cuenta

curl -s -X POST https://api.statemind.ai/v1/keys
# {"api_key": "msvc_free_…", "tier": "free", "calls_per_day": 25, "modes": ["static"]}

25 comprobaciones estáticas al día, medidas por día UTC, y unas pocas claves por dirección: suficiente para probarlo y ejecutarlo en un proyecto pequeño, no un suministro. Cada respuesta lleva el estado de la asignación (x-quota-remaining, x-quota-reset), para que un cliente pueda retroceder antes de que se corte.

MCP

Registrado en el registro oficial de MCP como ai.statemind/python-code-validator, un nombre verificado contra el dominio que lo sirve en lugar de una cuenta de GitHub. Cualquier cliente MCP lo añade con un bloque:

{
  "mcpServers": {
    "python-code-validator": {
      "type": "http",
      "url": "https://api.statemind.ai/mcp",
      "headers": { "Authorization": "Bearer msvc_free_…" }
    }
  }
}
  • Claude Code: claude mcp add --transport http python-code-validator https://api.statemind.ai/mcp --header "Authorization: Bearer msvc_free_…"
  • Cursor: ~/.cursor/mcp.json, mismo bloque.
  • VS Code / Copilot: .vscode/mcp.json bajo "servers".

Un cliente que solo lanza un comando usa el puente stdio de este repositorio en su lugar, que reenvía la misma herramienta por HTTPS:

{
  "mcpServers": {
    "python-code-validator": {
      "command": "python3",
      "args": ["/path/to/python-code-validator/mcp_stdio.py"]
    }
  }
}

O como contenedor, que el Dockerfile de aquí construye:

docker build -t python-code-validator .
docker run -i --rm -e VALIDATOR_API_KEY python-code-validator

Gemini CLI instala el mismo puente como extensión, con el archivo de instrucción que hace que se use:

gemini extensions install jkanselaar/python-code-validator

Tres herramientas, nombradas por lo que hacen al código:

herramientaejecuta el códigoclave
validate_pythonnogratuita
repair_python — también devuelve fixed_codenode pago
execute_python — también lo ejecuta en un sandboxsíde pago

La antigua herramienta única python_code_validator, con su argumento mode, sigue respondiendo para clientes que ya la configuraron, pero ya no aparece en la lista.

Decir lo que se suponía que el código debía hacer

Cada comprobación anterior pasa en una función que calcula la respuesta incorrecta. Lo único que lo detecta es la intención, y el agente que pidió el código es el único que la tiene — así que pásala:

{"code": "def bitcount(n): …", "mode": "execute",
 "options": {"examples": "assert bitcount(127) == 7"}}

Las líneas de doctest (>>> bitcount(127) y luego 7) funcionan de la misma manera, al igual que los ejemplos >>> ya escritos en el código fuente. execute_python los ejecuta en el sandbox: uno que no se cumple es un error python:example-mismatch, y la búsqueda de reparación devuelve una corrección solo cuando cada ejemplo pasa. En el conjunto de defectos de QuixBugs — errores reales, entradas de prueba ocultas que deciden la corrección — eso repara el 41% y rechaza el 77% por no hacer lo que dicen, sin falsas alarmas en los programas corregidos.

Repetir una llamada no cuesta nada: la misma clave preguntando la misma pregunta — mismo modo, mismo código, mismos ejemplos — se responde desde la respuesta que ya obtuvo, marcada x-msvc-repeat: 1, para que un agente que verifica su trabajo en cada paso no pague por veredictos que no pueden haber cambiado.

Plugin de Claude Code

Una instrucción se puede ignorar; un hook no. El plugin comprueba cada archivo Python que Claude Code escribe o edita, en el turno en que se escribió, y devuelve los errores al modelo en lugar de a ti:

/plugin marketplace add jkanselaar/python-code-validator
/plugin install python-code-validator@statemind

Nada que configurar: crea y guarda su propia clave gratuita en el primer uso. Un archivo que vuelve aceptado es silencioso, uno rechazado detiene el turno con las líneas infractoras nombradas, y un archivo idéntico no se pregunta dos veces. Nunca termina una sesión por sus propios problemas — un servicio inalcanzable o una asignación agotada deja que el turno continúe, y la asignación dice cómo aumentarla.

Establece VALIDATOR_API_KEY para usar una clave de pago en lugar del nivel gratuito, y VALIDATOR_URL para apuntar a tu propio despliegue. El plugin también lleva la habilidad validate-python, para la parte que un hook no puede hacer: declarar la intención como ejemplos y ejecutar el código contra ellos.

Hook de Cursor

El mismo script, conectado al postToolUse de Cursor, donde el veredicto vuelve como contexto en la conversación en lugar de como código de salida:

mkdir -p .cursor/hooks
base=https://raw.githubusercontent.com/jkanselaar/python-code-validator/main
curl -sf $base/plugin/hooks/validate_written.py -o .cursor/hooks/validate_written.py
curl -sf $base/cursor/hooks.json -o .cursor/hooks.json

Los hooks de proyecto se ejecutan desde la raíz del proyecto, por eso el comando en cursor/hooks.json es una ruta relativa a ella. Para un hook que se aplica a cada proyecto en su lugar, pon el script en ~/.cursor/hooks/ y el mismo bloque en ~/.cursor/hooks.json con el comando python3 ./hooks/validate_written.py --cursor.

Hacer que el agente lo use

Configurar el servidor no es lo que hace que se llame: el archivo de instrucción lo es. AGENTS.md en este repositorio es ese texto, escrito para dejarse caer en cualquier proyecto bajo el nombre que el cliente lea:

mkdir -p .github
curl -sf https://raw.githubusercontent.com/jkanselaar/python-code-validator/main/AGENTS.md \
  | tee AGENTS.md CLAUDE.md GEMINI.md .github/copilot-instructions.md >/dev/null

Cursor lee reglas con front matter en su lugar, así que esa es un archivo separado — copia .cursor/rules/python-code-validator.mdc en .cursor/rules/ del proyecto.

La versión corta, si prefieres añadir una línea a las instrucciones que ya tienes:

Escribe lo que el código debería hacer como ejemplos assert antes de escribir el código, y pásalos en options.examples. Llama a validate_python después de cada edición y a execute_python una vez que una función esté terminada, no de nuevo hasta que lo que hace haya cambiado. Cuando una llamada devuelva fixed_code, tómala — el servicio lo ejecutó contra tus ejemplos. No presentes código que volvió valid: false.

CI

El servicio entrega el cliente, así que un flujo de trabajo no necesita checkout de este repositorio ni secreto:

- run: |
    curl -sf https://api.statemind.ai/v1/client -o validate.py
    python3 validate.py --changed-against "origin/${{ github.base_ref }}"

O como acción, desde el Marketplace:

permissions:
  contents: read
  pull-requests: write   # so the run can comment its result on the pull request
steps:
  - uses: jkanselaar/python-code-validator@v1.22.0
    with:
      api-key: ${{ secrets.VALIDATOR_API_KEY }}   # optional; free tier without it

El Python cambiado se valida y las líneas infractoras se anotan en el diff, fallando el trabajo en errores de sintaxis y patrones inseguros. Los archivos que el servicio rechaza directamente (por su límite de 200 kB) se omiten con una advertencia en lugar de fallar la ejecución.

La ejecución también deja un comentario en la solicitud de extracción, editado en su lugar en empujes posteriores en lugar de repetirse: qué se aceptó, qué se reparó y cuánto de la asignación del día queda. Sin pull-requests: write no se escribe nada y el trabajo no se ve afectado; comment: "false" lo desactiva.

En el nivel gratuito, la acción guarda su clave en la caché del flujo de trabajo, una por repositorio por día, así que la asignación pertenece al repositorio en lugar de a la ejecución. Con api-key establecido, la caché se omite.

Pre-commit

repos:
  - repo: https://github.com/jkanselaar/python-code-validator
    rev: v1.22.0
    hooks:
      - id: python-code-validator

El cliente en sí

validate.py es solo biblioteca estándar, así que también funciona como python validate.py file.py en un Makefile, un hook de git o un contenedor:

$ python3 validate.py service.py
::error file=service.py,line=88,title=SyntaxError::invalid syntax
FAIL service.py score=0.66

0/1 files accepted

VALIDATOR_API_KEY se usa cuando está establecido; de lo contrario, el cliente crea una clave gratuita — guardándola en VALIDATOR_KEY_FILE cuando eso nombra una ruta, que es como una serie de ejecuciones comparte una asignación. VALIDATOR_URL lo apunta a otro despliegue. VALIDATOR_SOURCE nombra al llamante, que solo se cuenta: una ejecución dentro de un flujo de trabajo dice github-action por sí misma.

La insignia

Un repositorio cuyo Python se comprueba en cada solicitud de extracción puede decirlo:

[![Python validated](https://img.shields.io/badge/python-validated-2ea44f?logo=python&logoColor=white)](https://api.statemind.ai/?src=badge)

Python validated

HTTP

curl -s https://api.statemind.ai/v1/validate \
  -H "Authorization: Bearer $VALIDATOR_API_KEY" \
  -H 'content-type: application/json' \
  -d '{"code": "def f(:\n    pass\n", "mode": "static"}'

mode es static, repair o execute; repair y execute necesitan una clave configurada. El código enviado no se registra.

Una llamada rechazada dice qué hacer al respecto, para que un llamante sin operador al que preguntar pueda resolverlo por sí mismo:

{"error": "payment_required",
 "remedy": {"action": "upgrade_key", "hint": "A free key covers static only. …"}}

Pagar por llamadas

Una clave gratuita cubre 25 comprobaciones estáticas al día, y una dirección obtiene unas pocas claves al día, así que la asignación es una prueba en lugar de un suministro. Más allá, una clave lleva créditos: una comprobación estática cuesta 1, una reparación 3 y una ejecución en sandbox 10, y una llamada idéntica repetida dentro de diez minutos se responde desde la primera gratis.

Los créditos se compran con una tarjeta, sin factura ni nadie a quien preguntar:

curl -s -X POST https://api.statemind.ai/v1/keys/checkout \
  -H 'content-type: application/json' \
  -d '{"api_key": "'"$VALIDATOR_API_KEY"'", "credits": 500}'

Eso responde con una página de Stripe Checkout; los créditos están en la clave segundos después de que la tarjeta se liquide (500 créditos son €10). Un agente con una billetera Gnosis puede en su lugar pagar en xDAI sin navegador — GET /v1/pricing indica ambas rutas.

Ejemplos

examples/ contiene tres archivos y el cliente para enviarlos: uno que pasa cada comprobación y aún así devuelve el número incorrecto, uno que la política de seguridad rechaza, y uno que vuelve aceptado desde el sandbox.

Licencia

MIT.