Saltar al contenido

Proyecto de portfolio · Adrián Vázquez

Toda la API de Qdrant como 56 herramientas MCP. Cada afirmación, probada contra un servidor real.

Un servidor del Model Context Protocol en Python que permite a un LLM gestionar una base de datos vectorial Qdrant. Su bitácora de ingeniería es pública: qué falló, cómo se descubrió y el test que ahora hace guardia.

  • 56

    herramientas MCP

    31 de ellas de solo lectura

  • 554

    tests

    unitarios y de integración, tal como los recoge pytest

  • 17

    entradas del changelog

    última: v2.6.0

  • 3322

    líneas de Python

    3322 de código y 5717 de tests

  • 46

    KB de schemas de herramientas

    toolset por defecto, cargado en el contexto del modelo

Cada cifra de aquí que parece una medición la genera desde el repositorio un script que CI vuelve a ejecutar. Pasa el cursor por una para ver cuál.

01 · El método

Nada se escribe hasta que se ha ejecutado.

El comportamiento de Qdrant es fácil de suponer y caro de equivocar. Cada afirmación de las descripciones de las herramientas de este proyecto pasa por los mismos tres pasos.

  1. 01 / 03

    Probar

    Ejecutar el escenario contra un servidor Qdrant real, empezando por el camino de fallo. Leer el estado después, no solo la respuesta.

    Una operación de alias válida seguida de otra que falla, y luego listar los alias: la primera sigue aplicada. Por eso la herramienta no dice "atómico".

  2. 02 / 03

    Fijar

    Convertir lo observado en un test de integración, de modo que una versión futura de Qdrant que lo cambie rompa el CI en lugar de dejar la documentación obsoleta en silencio.

    test_alias_batch_is_not_rolled_back_when_a_later_operation_fails

  3. 03 / 03

    Revisar

    A un lector independiente, sin conocer el razonamiento, se le pide reproducir y no especular. Cada hallazgo se reproduce una segunda vez antes de actuar sobre él.

    Una de esas revisiones encontró que un filtro vacío borraba una colección entera. Está en la bitácora.

Leer la bitácora completa

02 · Arquitectura

Cómo viaja una llamada a una herramienta.

Cinco saltos entre un modelo y la base de datos. Cada uno es una decisión, no un valor por defecto.

  1. Recibe 56 definiciones de herramientas. Los schemas que se anuncian son compactos —46 KB para el toolset por defecto en vez de unos 150 KB— porque se cargan en el contexto del modelo en cada conversación.

    tests/unit/test_schemas.py
  2. stdio por defecto. El transporte remoto se niega a arrancar sin un secreto compartido, y no existe ninguna instancia alojada: es un proyecto que se ejecuta, no un servicio.

    src/mcp_qdrant/cli.py
  3. Un único punto de control. Filtra toolsets, elimina toda herramienta que escribe en modo solo lectura (sobreviven 31 de 56) y limita los resultados por llamada. Una herramienta no puede saltárselo registrándose de otra forma.

    src/mcp_qdrant/tools/registry.py
  4. Los fallos de transporte se reintentan con backoff. Un error de Qdrant llega al modelo como el mensaje propio de Qdrant, nunca como un genérico "Error executing tool".

    src/mcp_qdrant/tools/errors.py
  5. El SDK oficial, de modo que las entradas de las herramientas reutilizan sus modelos y siguen su versión. El servidor nunca genera embeddings: tú traes tus vectores.

    ROADMAP.md · principio 1

03 · En acción

Cuatro cosas que un modelo puede hacer con él.

No son maquetas. Cada llamada de abajo se ejecutó a través del servidor MCP contra un Qdrant real, y la respuesta es la que devolvió.

04 · Herramientas

Todas las herramientas, desde el registro vivo.

56 herramientas en 6 toolsets. Solo core (13) está activo por defecto; el resto es opcional, para no entregar todo al cliente de golpe.

Mostrando 13 de 56

core ● activo por defecto
CRUD de colecciones y puntos, qdrant_query y una comprobación de salud. Un servidor completo por sí solo.
search
Consultas por lotes y agrupadas, recommend, discover, matrices de distancia.
payload
Edición e indexado de payload, vectores con nombre, operaciones por lotes ordenadas.
snapshots
Copia y restauración, por colección y de todo el almacenamiento.
admin
Aliases, disposición de shards, progreso del optimizador, copia de colecciones, perfilado de payload.
observability
Telemetría, métricas, cuotas, incidencias autodiagnosticadas.

Pruébalo en dos minutos.

Elige los toolsets y copia la configuración. Se ejecuta en local; no hay que registrarse en nada.

Toolsets

Herramientas registradas 13

.mcp.json

{
  "mcpServers": {
    "qdrant": {
      "command": "uvx",
      "args": [
        "mcp-qdrant"
      ],
      "env": {
        "QDRANT_URL": "http://localhost:6333",
        "QDRANT_MCP_TOOLSETS": "core"
      }
    }
  }
}
  • uvx mcp-qdrant · Claude Code o cualquier cliente MCP: uvx lo ejecuta sin instalar
  • Claude Desktop: descarga mcp-qdrant.mcpb de la última release y haz doble clic
  • Docker, para streamable-http. streamable-http se niega a arrancar sin un secreto compartido. La imagen solo tiene sentido en este modo.
    docker run --rm -p 8000:8000 \
      -e QDRANT_URL=http://host.docker.internal:6333 \
      -e QDRANT_MCP_TRANSPORT=streamable-http \
      -e QDRANT_MCP_HTTP_HOST=0.0.0.0 \
      -e QDRANT_MCP_SHARED_SECRET=<a long random secret> \
      ghcr.io/avaazquezz/qdrant-mcp:latest

05 · La bitácora

La bitácora de ingeniería.

Qué falló, cómo se descubrió y el test que ahora hace guardia. Cada entrada enlaza el pull request y la release que lo corrigió.

  1. 01 / 09

    Una búsqueda híbrida que no podía ejecutarse

    Qué se afirmó
    La búsqueda híbrida sobre vectores densos y dispersos funciona: la propia descripción de la herramienta de consulta traía un ejemplo.
    Qué se ejecutó
    Ejecutar ese ejemplo contra un Qdrant real y luego intentar escribir un punto con un vector disperso a través de la herramienta de upsert.
    Qué ocurre de verdad
    El ejemplo falló ("Conversion between sparse and regular vectors failed"), y la herramienta de upsert rechazaba cualquier vector que no fuera una lista simple de números. La funcionalidad estrella no podía funcionar y ningún test de integración cubría los vectores dispersos.
    Qué cambió
    Un único tipo de vector compartido para escribir y consultar, upserts condicionales (solo insertar, solo actualizar) y tests de integración para puntos con nombre, dispersos y multivector.
  2. 02 / 09

    El lote de alias que no era atómico

    Qué se afirmó
    Las operaciones de alias enviadas en una llamada se aplican de forma atómica (el primer borrador de la descripción de la herramienta).
    Qué se ejecutó
    Una petición con un "crear alias" válido seguido de otro sobre una colección que no existe, y luego listar los alias.
    Qué ocurre de verdad
    La petición falla, pero el primer alias sigue ahí. Un lote no se revierte.
    Qué cambió
    La descripción lo dice y recomienda una sola operación para reapuntar un alias, que es todo o nada por definición. Un test de integración falla si Qdrant cambia esto alguna vez.
  3. 03 / 09

    Una diversidad que no hacía nada

    Qué se afirmó
    MMR reordena los resultados para dar diversidad.
    Qué se ejecutó
    Consultar con MMR, sin límite de candidatos y con límite 2, y compararlo con la consulta normal.
    Qué ocurre de verdad
    Resultados idénticos. El conjunto de candidatos por defecto es el límite, así que MMR no tiene entre qué elegir. Y el offset se aplica dentro de esos resultados: offset 1 con límite 2 devuelve un punto.
    Qué cambió
    Ambos hechos están en la descripción de la herramienta y fijados por un test.
  4. 04 / 09

    Definiciones de herramientas que se comían el contexto

    Qué se afirmó
    Un conjunto compacto de definiciones de herramientas.
    Qué se ejecutó
    Medir los schemas de entrada que un cliente carga en el contexto del modelo.
    Qué ocurre de verdad
    571 KB para el catálogo completo y 150 KB para el toolset por defecto: cada herramienta llevaba su propia copia del schema de filtros de Qdrant, de 16 KB.
    Qué cambió
    Los schemas anunciados se compactaron en torno a dos tercios sin cambiar lo que se acepta, y un test impone ahora un presupuesto de tamaño para que el peso no vuelva a crecer sin que nadie lo note.
  5. 05 / 09descubierto por una revisión independiente

    Un schema compacto que rechazaba llamadas legales

    Qué se afirmó
    El schema compacto de filtros acepta exactamente lo que acepta el modelo de filtros de Qdrant.
    Qué se ejecutó
    Validar el schema anunciado y el modelo real con las mismas entradas válidas e inválidas.
    Qué ocurre de verdad
    El primer borrador rechazaba una condición única legal donde se esperaba una lista. Tras añadir tests de paridad, una revisión independiente encontró que también rechazaba nulls explícitos y una opción "global" legal. Un cliente que validara los argumentos contra el schema habría rechazado llamadas legales.
    Qué cambió
    Ambos corregidos. Los tests de paridad ejecutan ahora el schema y el modelo con las mismas muestras, y otro test falla si el cliente instalado gana una condición que la gramática escrita a mano no menciona.
  6. 06 / 09descubierto por una revisión independiente

    El filtro que lo borraba todo

    Qué se afirmó
    Un borrado necesita exactamente un selector, ids o un filtro, así que no puede ser ambiguo.
    Qué se ejecutó
    Borrar con los filtros {}, {"must": []} y {"should": []} en una colección pequeña.
    Qué ocurre de verdad
    Cada uno borró todos los puntos. Un filtro vacío coincide con todo y nada en la herramienta lo impedía. Un modelo que construya su lista de condiciones dinámicamente y acabe con una vacía borraría los datos.
    Qué cambió
    Un filtro sin condiciones se rechaza en las herramientas de borrado, payload y vectores y dentro de los lotes, y un lote con una operación rechazada no envía nada.
  7. 07 / 09descubierto por una revisión independiente

    Atómico desde la tercera fase, y nunca fue cierto

    Qué se afirmó
    La herramienta de actualización por lotes ejecuta sus operaciones de forma atómica (en su descripción desde la tercera fase).
    Qué se ejecutó
    Un upsert válido seguido de otro con un tamaño de vector incorrecto, y luego contar los puntos.
    Qué ocurre de verdad
    El primero queda aplicado. Nunca fue atómico. Es el mismo error que la entrada de los alias: una propiedad de Qdrant afirmada sin ejecutarla.
    Qué cambió
    La descripción, el título de la herramienta, el README y esta página ya no lo dicen, y un test fija el comportamiento real.
  8. 08 / 09descubierto por una revisión independiente

    Un filtro que callaba y no filtraba

    Qué se afirmó
    Una etapa de prefetch acepta un filtro.
    Qué se ejecutó
    Darle a una etapa la clave propia de Qdrant, "filter", en lugar de "query_filter" de la herramienta.
    Qué ocurre de verdad
    Ningún error, y los resultados no estaban filtrados: las claves desconocidas se ignoraban en silencio. Una opción de MMR mal escrita se ejecutaba con sus valores por defecto de la misma forma, y una errata en la clave del payload de un punto descartaba el payload.
    Qué cambió
    Las entradas anidadas rechazan ahora las claves desconocidas con un error claro.
  9. 09 / 09descubierto por una revisión independiente

    Claves que no se podían pegar en un filtro

    Qué se afirmó
    El perfilado de payload informa de las claves escritas como esperan los filtros de Qdrant.
    Qué se ejecutó
    Perfilar una colección con una clave que contiene un espacio y usar la clave impresa en un filtro.
    Qué ocurre de verdad
    Qdrant responde 400: una clave así necesita comillas dobles. Una clave literal "k[]" se leía como una ruta de array, y un índice creado sobre una clave entre comillas no coincidía nunca con su entrada en el perfil.
    Qué cambió
    Las claves se entrecomillan donde la sintaxis lo exige, que es también la forma en que Qdrant informa de los índices, así que las claves ahora coinciden con su índice.

06 · Calidad

Lo que lo mantiene honesto.

Nada de esto se promete en la página. Lo impone el repositorio.

  1. 01

    push

  2. 02

    lint · tipos · generadores

  3. 03

    tests · Qdrant real

  4. 04

    merge

  5. 05

    tag

  6. 06

    PyPI · GHCR · .mcpb

Integración continua

Cada pull request ejecuta lint, formato, comprobación de tipos estricta y toda la suite de tests, con los de integración contra un servicio Qdrant 1.19.0 real. Tres generadores (la tabla del README, los datos de esta página, las transcripciones de las demos) rompen la build si se desincronizan.

Releases

Un tag publica en PyPI con Trusted Publishing (sin token guardado), sube la imagen de Docker a GHCR y adjunta el bundle .mcpb a la release. Tres workflows, comprobados tras cada tag.

Roadmap

Cada fase tiene su propia rama y su pull request. ROADMAP.md guarda las decisiones y los hallazgos verificados, incluidas las funcionalidades descartadas y por qué.

07 · Preguntas

Preguntas que hace un revisor.

¿Está afiliado a Qdrant?

No. Es un proyecto independiente con licencia MIT que envuelve el SDK qdrant-client de la propia Qdrant.

¿Por qué no crea embeddings?

Es un wrapper, no un sistema RAG, a propósito: es el primer principio del roadmap. Nunca incrusta, parsea ni trocea nada; el cliente trae sus vectores. Eso mantiene el servidor pequeño y hace que cada herramienta sea un reflejo fiel de una operación de Qdrant.

¿Hay una versión alojada?

No, por diseño. Una fase anterior añadió un modo público multiinquilino y se retiró: es un proyecto que se ejecuta (uvx mcp-qdrant), no un servicio que alguien tenga que operar.

¿Cómo se construyó?

Python 3.12 con uv, sobre el SDK oficial de Python de MCP y qdrant-client, con Pydantic validando cada entrada y tenacity reintentando los fallos de transporte. La calidad la imponen pytest contra un Qdrant real en CI, mypy estricto, ruff y pre-commit; GitHub Actions publica en PyPI (Trusted Publishing), en GHCR y como bundle para Claude Desktop. Claude Code ayudó en el trabajo y los commits llevan su trailer Co-Authored-By; lo que decide qué es verdad es la cadena de herramientas, no el asistente. Esta página es Nuxt, Tailwind, GSAP y Lenis, servida por nginx en Docker.

¿Qué versiones de Qdrant soporta?

La v1.19.0 y posteriores son las únicas contra las que se prueba. Dos herramientas necesitan un endpoint al que los servidores antiguos responden con 404.

¿Qué no cubre?

La administración de clústeres distribuidos: las shard keys y el resharding necesitan un clúster, y el proyecto apunta a un único nodo. El roadmap recoge esa decisión y lo que se verificó.

08 · Contacto

Mira el código.

El repositorio tiene toda la historia: cada fase, cada pull request, cada hallazgo.