Image for post 20% de paquetes no existen: MCP defensivo en Claude Code

20% de paquetes no existen: MCP defensivo en Claude Code


TL;DR

Un MCP defensivo es un servidor que se interpone entre Claude Code y comandos sensibles como pip install o npm i para validar que el paquete existe en el registry oficial antes de ejecutarlos. Sirve para cortar el slopsquatting: ataques que registran nombres de paquetes inventados por LLMs. En este artículo montamos uno en Python con mcp, lo conectamos a Claude Code y revisamos sus límites en producción.

El problema: cuando el agente se inventa un paquete

Has pedido a Claude Code que añada rate limiting a tu FastAPI y sugiere pip install fastapi-throttler. Suena bien, es coherente con la convención de nombres, pero ese paquete no existe en PyPI. Lo instalas sin mirar y, si alguien ha registrado ese nombre en el último minuto, acabas de ejecutar código arbitrario en tu máquina.

Este patrón tiene nombre: slopsquatting. Aprovecha que los LLMs alucinan nombres de paquetes verosímiles. Un estudio reciente sobre recomendaciones de modelos conversacionales cifra la tasa de paquetes inexistentes recomendados cerca del 20% en escenarios típicos. No es teórico: hay casos documentados de paquetes squatter registrados justo después de una alucinación popular.

El agente no tiene contexto del registry real. Si le das acceso a tu terminal, cada sugerencia no verificada es un vector de supply chain. Afecta también a Codex, Cursor y cualquier harness con shell.

¿Qué es un MCP defensivo?

Un servidor MCP defensivo expone herramientas que el agente debe llamar antes de ejecutar acciones sensibles, y devuelve un veredicto (permitir, bloquear, pedir confirmación). A diferencia de un MCP tradicional que añade capacidades, este añade guardrails operativos.

El patrón encaja con la tendencia de tratar el contexto y la seguridad como responsabilidades separadas dentro del workflow, en lugar de depender solo del prompt o de la memoria del modelo.

Comparativa: opciones de validación

EnfoqueCuándo sirveLimitación
Hook de permisos en Claude CodeBloquear comandos concretosNo inspecciona el contenido del paquete
MCP defensivo con consulta al registryValidar existencia y metadatosAñade latencia por cada dependencia
Lockfiles + CIEntornos de equipoNo previene el primer install local

Implementación paso a paso

1. Instalar el SDK de MCP para Python

El SDK oficial mcp expone un servidor con decoradores. Versión recomendada a fecha 22/04/2026: mcp>=1.6.

pip install "mcp[cli]>=1.6" httpx

2. Escribir el servidor que valida paquetes

Este snippet expone dos herramientas: check_pypi y check_npm. Cada una consulta el registry oficial y devuelve si el paquete existe, su última versión y metadatos básicos.

from mcp.server.fastmcp import FastMCP
import httpx

mcp = FastMCP("package-guard")

# Valida un paquete contra PyPI antes de que el agente proponga instalarlo
@mcp.tool()
async def check_pypi(name: str) -> dict:
    url = f"https://pypi.org/pypi/{name}/json"
    async with httpx.AsyncClient(timeout=5.0) as client:
        r = await client.get(url)
    if r.status_code == 404:
        return {"exists": False, "verdict": "BLOCK", "reason": "no existe en PyPI"}
    info = r.json().get("info", {})
    return {"exists": True, "verdict": "ALLOW", "version": info.get("version")}

# Misma validación para el registry de npm
@mcp.tool()
async def check_npm(name: str) -> dict:
    url = f"https://registry.npmjs.org/{name}"
    async with httpx.AsyncClient(timeout=5.0) as client:
        r = await client.get(url)
    if r.status_code == 404:
        return {"exists": False, "verdict": "BLOCK", "reason": "no existe en npm"}
    latest = r.json().get("dist-tags", {}).get("latest")
    return {"exists": True, "verdict": "ALLOW", "latest": latest}

if __name__ == "__main__":
    mcp.run()

3. Registrarlo en Claude Code

Añade el servidor en ~/.claude.json o en el .mcp.json del proyecto.

{
  "mcpServers": {
    "package-guard": {
      "command": "python",
      "args": ["/ruta/absoluta/package_guard.py"]
    }
  }
}

4. Forzar al agente a usarlo

Un MCP disponible no es un MCP usado. Añade una regla en tu CLAUDE.md para que el agente consulte antes de instalar:

## Reglas de dependencias
- Antes de proponer `pip install X` o `npm i X`, llama a `check_pypi` o `check_npm`.
- Si el verdict es BLOCK, no ejecutes el install y pregunta al usuario.

Caso real: refactor con dependencias nuevas

Sesión típica en la que pides añadir caché con Redis a un servicio FastAPI. El agente propone tres paquetes: fastapi-cache2, redis-asyncio y aiocache-redis. Con el MCP activo, Claude Code llama a check_pypi para cada uno: los dos primeros existen, el tercero devuelve BLOCK. El agente para, te avisa y sugiere aiocache con extras. Te acaba de ahorrar un susto.

Si trabajas con pipelines Python en tiempo real o con proyectos Node con Prisma, el coste de una dependencia fantasma es mucho mayor que los 200 ms de latencia extra por consulta al registry. Y si tu arquitectura de microservicios tiene varios package.json, el ahorro se multiplica.

En Producción

Lo que funciona en tu máquina rompe de formas previsibles en un equipo.

  • Latencia: cada consulta HTTP añade 100-300 ms. Cachea respuestas en memoria con TTL de 10 minutos si validas muchas dependencias seguidas.
  • Falsos positivos: un paquete recién publicado puede no estar indexado. Devuelve WARN en vez de BLOCK cuando no haya metadatos pero el nombre resuelva.
  • Paquetes privados: registries internos (Artifactory, GitHub Packages) requieren auth. Pasa el token por variable de entorno, nunca hardcodeado.
  • Coste: consultas a PyPI y npm son gratuitas, pero respeta rate limits (PyPI recomienda menos de 10 req/s por IP).
  • Alcance: esto valida existencia, no confianza. Un paquete real puede ser malicioso. Complementa con listas de reputación (Socket, Snyk) si el riesgo lo justifica.

Errores comunes

  • Error: el agente ignora el MCP y ejecuta pip install directo. Causa: falta regla explícita en CLAUDE.md. Solución: añade la regla y configura un hook PreToolUse que bloquee Bash con pip install si no hubo llamada previa a check_pypi.
  • Error: httpx.ConnectError en redes corporativas. Causa: proxy no configurado. Solución: respeta HTTPS_PROXY pasándolo al cliente httpx.
  • Error: nombres con guiones bajos vs guiones en Python. Causa: PyPI normaliza pero algunos endpoints no. Solución: normaliza el nombre a minúsculas con guiones antes de consultar.

Preguntas Frecuentes

¿Esto sustituye a un escáner como Socket o Snyk?

No. Un MCP defensivo valida que el paquete existe en el registry oficial, lo que ya elimina la clase de ataques por nombres inventados. Para detectar paquetes maliciosos reales (typosquatting, código ofuscado, post-install hooks sospechosos) necesitas un escáner especializado.

¿Funciona con Codex CLI o Cursor?

Sí, cualquier cliente compatible con MCP puede conectarse al mismo servidor. La regla que obliga a llamarlo se escribe en el archivo de instrucciones del harness (AGENTS.md en Codex, reglas de Cursor).

¿Añade mucho coste por sesión?

Las consultas a PyPI y npm son gratuitas y no consumen tokens del modelo (solo el JSON de respuesta). En una sesión típica con 3-5 dependencias nuevas el overhead es inferior a 2 segundos totales.

Cierre

Hemos visto cómo un servidor MCP de unas 40 líneas corta una clase entera de errores de supply chain en agentes de código. La idea general es simple: cuando el agente tiene shell, cada comando sensible merece una capa de validación que no dependa del prompt. Los guardrails viven mejor en código ejecutable que en instrucciones en lenguaje natural.

Si ya usas MCPs de productividad, este patrón es el siguiente paso lógico. En el próximo artículo voy a montar un hook PreToolUse que bloquea Bash cuando detecta curl | sh u otros patrones peligrosos. ¿Ya tienes guardrails en tu setup de Claude Code? Cuéntamelo en Twitter @sergiomarquezp_.

Compartir X LinkedIn