El canal para agentes

Quelva expone un servidor MCP (Model Context Protocol) sobre HTTP. Su agente consulta los datos de SECOP II por herramienta, en lugar de suponerlos o de buscarlos por su cuenta.

El canal es de solo lectura sobre los datos de SECOP: ninguna herramienta escribe en SECOP, ni modifica nada. Dos de las herramientas sí escriben un registro de verificación propio de Quelva, para que exista el rastro de qué se evaluó y con qué versión del motor.

Servidor: secop-mcp, versión 0.2.0. Protocolo: MCP sobre HTTP con streaming (Streamable HTTP). El endpoint es POST /mcp.

Publicar el servidor

Quelva entrega el servidor MCP como un servicio HTTP independiente. Para ponerlo en marcha necesita:

  • Puerto. Por defecto 8787 (variable PORT). El servicio escucha en 0.0.0.0.

  • Base de datos. Una conexión a la base de datos de Quelva (DATABASE_APP_URL). Sin ella el servidor no arranca.

  • Secreto de autenticación. BETTER_AUTH_SECRET, al menos 32 caracteres aleatorios.

  • Lista de hosts permitidos. MCP_ALLOWED_HOSTS con los dominios desde los que se servirá (sin puerto). En producción es obligatoria.

  • Token de Socrata (opcional). SOCRATA_APP_TOKEN sube los límites de consulta a datos.gov.co.

El servicio expone GET /healthz sin autenticación: responde 200 con {status, service, version}. Úselo como prueba de vida en su balanceador o en su orquestador. GET / devuelve una ficha con el protocolo, el endpoint y el método de autenticación.

Cómo conecta su equipo

Una vez publicado, cada empresa obtiene su clave desde el panel (Configuración → Claves) y apunta su agente a https://<su-dominio>/mcp con el encabezado Authorization: Bearer secop_…. Los detalles de conexión, por agente, están en la sección "Conectarse".

Conectarse

El servidor habla MCP sobre HTTP con streaming. Dos detalles que hacen que la primera llamada funcione:

  1. El cliente debe enviar Accept: application/json, text/event-stream. Si envía sólo uno de los dos, el servidor responde 406.

  2. Las respuestas llegan enmarcadas en SSE (data: {…}), no como JSON plano.

curl -s https://<su-instancia>/mcp \
  -H 'Authorization: Bearer secop_...' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

También existen GET /healthz (estado del servicio, sin autenticación) y GET / (una ficha con el protocolo, el endpoint y el método de autenticación).

Configuración por agente

Reemplace https://<su-instancia>/mcp por la URL de su servidor y secop_… por su clave.

Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "quelva-secop": {
      "type": "http",
      "url": "https://<su-instancia>/mcp",
      "headers": {
        "Authorization": "Bearer secop_..."
      }
    }
  }
}

Claude Code (.mcp.json o ~/.claude.json):

{
  "mcpServers": {
    "quelva-secop": {
      "type": "http",
      "url": "https://<su-instancia>/mcp",
      "headers": {
        "Authorization": "Bearer secop_..."
      }
    }
  }
}

O desde la línea de comandos:

claude mcp add --transport http quelva-secop https://<su-instancia>/mcp \
  --header "Authorization: Bearer secop_..."

OpenCode (opencode.json):

{
  "mcp": {
    "quelva-secop": {
      "type": "remote",
      "url": "https://<su-instancia>/mcp",
      "headers": {
        "Authorization": "Bearer secop_..."
      }
    }
  }
}

Codex (~/.codex/config.toml):

[mcp_servers.quelva-secop]
url = "https://<su-instancia>/mcp"

[mcp_servers.quelva-secop.headers]
Authorization = "Bearer secop_..."

Cualquier cliente MCP que hable Streamable HTTP: apunte la URL al endpoint, envíe el encabezado Authorization: Bearer secop_… y los dos tipos en Accept. Eso es todo lo que el servidor exige.

Autenticación

Claves de API por empresa, no por persona.

  • Se emiten desde el panel, en Configuración → Claves. Sólo el propietario de la organización puede emitirlas y revocarlas.

  • El secreto se muestra una sola vez, al crearla. Guárdelo en su gestor de secretos; si se pierde, revóquela y emita otra.

  • El formato es secop_ seguido de 64 caracteres: 70 en total.

  • En la base de datos se guarda el resumen SHA-256 de la clave y sus primeros 20 caracteres (el prefijo secop_ incluido) para poder identificarla en soporte. El resto nunca se guarda.

  • Cada llamada se verifica contra la base de datos y queda registrada con la empresa y la clave que la hizo. La clave lleva el alcance secop:read.

  • Revocar una clave la invalida de inmediato: la siguiente llamada del agente recibe 401.

Cómo obtener una clave

  1. Entre al panel con la cuenta propietaria de su empresa.

  2. Vaya a Configuración → Claves.

  3. Cree una clave con un nombre que identifique la integración (por ejemplo, "Claude de licitaciones").

  4. Copie el secreto que se muestra una sola vez y péguelo en la configuración de su agente.

  5. Una clave por integración: si una se filtra, revóquela sola; las demás siguen funcionando.

Las siete herramientas

Sobre procesos

search_processes — los procesos abiertos.

ParámetroQué filtra
textbúsqueda de texto libre
entidadentidad, coincidencia sin distinción de mayúsculas
unspsccódigo UNSPSC (usted pasa 43191500; el prefijo lo pone el sistema)
fasefase exacta del proceso
minValueprecio base mínimo
deadlineAfter / deadlineBeforeventana de fecha límite de ofertas
limit25 por defecto, máximo 200
offsetdesplazamiento

Devuelve count, limit, offset y hasMore. hasMore es una señal, no un conteo: significa "la página vino llena, puede haber más".

get_process_detail — un proceso y sus contratos adjudicados. Acepta el identificador de proceso (CO1.REQ.*, CO1.NTC.*) y también el de portafolio (CO1.BDOS.*), en cuyo caso se resuelve el proceso vinculado.

Sobre documentos

list_process_documents — qué documentos publicó el proceso. No descarga nada: es la llamada barata. Devuelve id, nombre, tipo y tamaño, y si ya hay una copia en Markdown disponible.

get_process_document — lee un documento.

ParámetroQué hace
documentel identificador del documento o el nombre exacto de archivo
offsetdónde seguir leyendo; use el next_offset de la respuesta anterior
max_chars50 000 por defecto, máximo 200 000

La respuesta incluye next_offset. Si viene en null, ya leyó todo. Un documento escaneado vuelve marcado como possibly_scanned, nunca vacío: el original queda guardado y se le dice que no hay capa de texto.

Sobre requisitos y verificación

get_process_requirements — los requisitos curados del proceso, con la cita de la página de la que salió cada uno.

Tres respuestas distintas, y la diferencia importa:

RespuestaQué significa
foundEl proceso tiene requisitos publicados. Vienen con su disclaimer.
nothing_curatedEl proceso existe, pero todavía no tiene requisitos. Es una respuesta exitosa, no un error: no hay nada que leer todavía.
errorEl proceso no existe.

validate_company_facts — verifica los datos de una propuesta contra esos requisitos.

Usted envía los hechos de la propuesta como un objeto con rutas JSON (RFC 6901). El motor los compara contra las reglas y devuelve un veredicto por requisito:

VeredictoQué significa
cumpleLa regla corrió y el dato la satisfizo.
no_cumpleLa regla corrió y el dato no la satisfizo.
no_verificableLa regla no se pudo decidir con lo que se recibió.
requiere_criterio_humanoEl requisito no es verificable por máquina tal como está escrito.

check_document_checklist — verifica la lista de documentos exigidos.

Usted envía nombres de archivos, nunca el contenido. El motor compara el conjunto de palabras de cada nombre con el conjunto de palabras del nombre curado. El veredicto solo puede ser cumple o no_verificable: una lista incompleta nunca fabrica un rechazo, y nunca convierte un cumple en un no_cumple.

El veredicto de esta revisión significa "cumple por nombre del archivo". Es evidencia de nombre, no de contenido: que el certificado exista y sea válido lo revisa una persona.

El folio

Las dos herramientas que escriben devuelven un folio: un resumen del contenido de la revisión. El mismo conjunto de requisitos, los mismos hechos y la misma versión del motor producen el mismo folio y los mismos veredictos.

El folio identifica la revisión, no la empresa. Un registro de verificación pertenece a la empresa que lo pidió y nadie más lo alcanza.

Paginación

search_processes pagina con limit (por defecto 25, máximo 200) y offset. Avance con offset += limit mientras hasMore sea verdadero. No hay un total: hasMore es la única señal de que puede haber más filas.

get_process_document pagina por caracteres: use offset con el next_offset de la respuesta anterior hasta que venga null.

Errores que puede encontrar

SituaciónRespuesta
Falta la clave, o la clave no es válida401, con WWW-Authenticate: Bearer
La base de datos no está disponible al verificar la clave503 — no es un 401. El servidor no parece estar rechazando una clave buena.
El Accept no trae los dos tipos406
La ruta no existe404
La herramienta fallóNo es una excepción: la respuesta llega con isError: true y el motivo en el texto
Se alcanzó el límite diario de lecturasisError: true con daily_cap_reached: ...

Un error de herramienta casi nunca significa "el dato no existe". Significa que la herramienta no pudo responder, y el texto dice por qué.

Límites diarios (cuotas)

Las lecturas se miden por empresa y por día, en la zona horaria de Bogotá; el contador se reinicia a medianoche en Bogotá.

CapacidadPlan gratuito
Lecturas de conjunto de requisitos (get_process_requirements)100 por día
Ventanas de documento (get_process_document)50 por día
Validaciones (validate_company_facts, check_document_checklist)0 por mes
Solicitudes de extracción por empresa1 por día

Sólo existe el plan gratuito con los límites anteriores. Cuando se alcanza un límite, la herramienta responde con daily_cap_reached y el contador se reinicia al día siguiente.

Límites que conviene conocer

  • Las fechas límite de ofertas solo existen para una parte de los procesos. Alrededor del 84% de los procesos de SECOP no publican fecha límite. Si usted filtra por ella, deje que el filtro se lleve esa parte.

  • Los valores No Definido, No Adjudicado y UNSPECIFIED no son datos. El sistema los normaliza a "no publicado" y no los presenta como valores.

  • hasMore es una señal, no un conteo. Significa "la página vino llena, puede haber más".

  • Los documentos PDF y TXT se leen; los demás formatos se conservan sin extraer texto.

  • Los precios y las fechas se muestran tal como los publica la entidad. SECOP publica marcas de tiempo sin zona horaria y el sistema no les inventa una.

Continúe