- GET /status: devuelve ok, dbPath, dims, uptimeSeconds y embedReady (true si EMBED_URL y EMBED_DIMS > 0).
- GET /health: endpoint público de salud. Verifica conectividad a la base de datos y estado del servicio de embeddings cuando está configurado.
- GET /metrics: habilitado cuando ADMIN_ENABLED=1. Si ADMIN_TOKEN está definido, requiere Authorization: Bearer .
- POST /search: búsqueda híbrida (KNN + FTS) por defecto; enviar { hybrid: false } para modo KNN-only.
- GET /resources/:id y GET /resources: acceso a recursos indexados.
- Streamable HTTP en
/mcp: expone herramientas y recursos MCP reales (requiere la misma autenticaci��n que el resto del API) para hosts compatibles con el protocolo. - Herramientas registradas:
search_resources: reutiliza la b��squeda h��brida y devuelveitems,score,sim,bm25junto conlimit/offset.- Recurso
ardf://resource/{id}: descriptor ARDF completo (JSON). - Prompt
ardf/howto: pautas para combinar b��squeda + lectura de descriptores.
- STDIO:
npm run mcp:stdiolanza el servidor MCP sobre STDIO; Ctrl+C realiza un cierre limpio del transporte. - Gating: la herramienta de b��squeda y
resources/readrespetanvisibility == 'public'antes de devolver resultados.
Campos admitidos:
-
query: string (requerido)
-
allowedTypes: ["tool" | "prompt" | "workflow" | "resource"][] (opcional)
-
domain, tag, tier, neededScope, serverUrl: string (opcionales)
-
k: número entero positivo ≤ SEARCH_K_MAX (opcional; se aplica clamp; por defecto SEARCH_K)
-
hybrid: boolean (opcional, default true)
-
wSim, wBm25: números en [0,1] (opcionales). Si ambos se envían, se normalizan para que wSim + wBm25 = 1. Si ambos están presentes, su suma debe ser > 0; si la suma ≤ 0, se responde 400 con
{ error: "invalid_body", details }.
Notas:
- En hybrid=false, se usa sólo KNN. Si el servicio de embeddings no está disponible por fallo de red/timeout, se devuelve 200 con items vacíos.
- Backoff de embeddings: si el servicio responde 429, se respeta Retry-After (segundos o fecha). Si no está presente, se usa 100ms*intento y se aplica jitter 0.5–1.5x.
- Si el k solicitado excede SEARCH_K_MAX, se ajusta automáticamente y se registra un log informativo con kRequested, kMaxEnv y kUsed.
- Validación de pesos: cuando se envían ambos wSim y wBm25, se requiere que wSim + wBm25 > 0; en caso contrario, 400 con detalles de validación.
Ejemplo de respuesta: { ok: true, db: { ok: true }, embeddings: { configured: true, ok: true, dims: 384, model: "dummy-embeddings", tookMs: 12 } } Notas:
- Si embeddings no está configurado (falta EMBED_URL/EMBED_MODEL/EMBED_DIMS), embeddings.error = "missing_env" y ok global considera sólo DB.
- Si está configurado, se realiza una vectorización mínima con input "ok" y se valida que las dimensiones coincidan.
- /health es público (exento de API_TOKEN) al igual que /status.
- EMBED_URL: URL del servicio de embeddings
- EMBED_MODEL: nombre de modelo
- EMBED_DIMS: dimensiones esperadas del vector
- EMBED_TOKEN: Bearer opcional para el servicio de embeddings
- LOG_LEVEL: nivel de log (pino)
- ADMIN_ENABLED=1: habilita /metrics
- ADMIN_TOKEN: protege /metrics con Bearer
- SEARCH_K: K por defecto en KNN
- SEARCH_K_MAX: límite máximo para k (clamp del valor solicitado; por defecto 100)
- HYBRID_WEIGHT_SIM / HYBRID_WEIGHT_BM25: pesos por defecto de fusión (si no se envían en el body)
- DB_PATH: ruta a la base de datos SQLite
- MCP_SERVER_URL: URL por defecto para sincronización MCP
- RATE_LIMIT: límite de peticiones por ventana para rate limiting opcional (ej., 60)
- RATE_WINDOW_MS: tamaño de la ventana de rate limiting en milisegundos (ej., 60000)
- embedding_latency_seconds: histograma de latencia de llamadas al webhook de embeddings
- embedding_retries_total: contador de reintentos de embeddings
- embedding_429_total: contador de códigos 429 del servicio de embeddings
- knn_failures_total: contador de fallos en KNN
- fts_failures_total: contador de fallos en FTS
- search_latency_seconds: latencia de POST /search
- search_requests_total{outcome}: total de POST /search por outcome
- resources_latency_seconds: latencia de GET /resources
- resources_requests_total{outcome}: total de GET /resources por outcome
- sync_latency_seconds{route}: latencia de operaciones de sincronización (route ∈ {refresh, sync, import})
- sync_requests_total{route,outcome}: total de operaciones de sincronización por ruta y outcome
npm i
cp .env.example .env
# PORT=8888 por defecto
npm run dev
curl -s http://127.0.0.1:8888/status | jq .Lanza embeddings, servidor MCP de prueba y el cliente a la vez.
# terminal 1 — embeddings dummy (puerto 8788)
npm run mock:embed
# terminal 2 — MCP ARDF stub (puerto 8001)
npm run mock:mcp
# terminal 3 — cliente (puerto 8888)
# Bash/macOS:
export MCP_SERVER_URL=http://127.0.0.1:8001
export EMBED_URL=http://127.0.0.1:8788/embed
npm run dev
# Windows PowerShell:
$env:MCP_SERVER_URL = 'http://127.0.0.1:8001'
$env:EMBED_URL = 'http://127.0.0.1:8788/embed'
npm run devUn solo comando (requiere concurrently):
MCP_SERVER_URL=http://127.0.0.1:8001 \
EMBED_URL=http://127.0.0.1:8788/embed \
npm run labWindows PowerShell:
$env:MCP_SERVER_URL = 'http://127.0.0.1:8001'
$env:EMBED_URL = 'http://127.0.0.1:8788/embed'
npm run labnpm run lab:start:9001: arranca mock‑embed en:4001(dims=4, model=my-embeddings) y el cliente en:9001(usatsx src/server.ts). Variables:API_TOKEN=labtoken,EMBED_URL=http://127.0.0.1:4001/embed,EMBED_MODEL=my-embeddings,EMBED_DIMS=4.npm run lab:start:9003: arranca mock‑embed en:8788, mock MCP en:8001y el servidor compilado en:9003con métricas admin (ADMIN_ENABLED=1,ADMIN_TOKEN=s3cret). Variables:PORT=9003,API_TOKEN=labtoken,ADMIN_ENABLED=1,ADMIN_TOKEN=s3cret,EMBED_URL=http://127.0.0.1:8788/embed,MCP_SERVER_URL=http://127.0.0.1:8001.
Notas:
- Requiere tener instalados
concurrentlyycross-env. - Asegúrate de liberar los puertos correspondientes (4001/9001 o 8788/8001/9003) antes de ejecutar.
- Para habilitar
/metricstambién en 9001, añadeADMIN_ENABLED=1y opcionalmenteADMIN_TOKEN. - Verificaciones rápidas:
curl -s -H "Authorization: Bearer labtoken" http://127.0.0.1:9001/status | jq .curl -s -H "Authorization: Bearer labtoken" http://127.0.0.1:9003/status | jq .- Métricas admin en 9003:
curl -s -H "Authorization: Bearer s3cret" http://127.0.0.1:9003/metrics | head -n 20
Troubleshooting — puertos ocupados
- Detectar proceso que ocupa un puerto (Windows/Git Bash):
netstat -aon | findstr ":4001"
netstat -aon | findstr ":9001"
netstat -aon | findstr ":8788"
netstat -aon | findstr ":8001"
netstat -aon | findstr ":9003"- Detener proceso por PID con PowerShell desde Git Bash:
powershell.exe -Command "Stop-Process -Id <PID> -Force"- Reintentar el arranque:
npm run lab:start:9001
# o
npm run lab:start:9003- Algunas rutas pueden requerir token. Define
API_TOKENantes de arrancar el cliente:- Bash/macOS:
export API_TOKEN=labtoken - Windows PowerShell:
$env:API_TOKEN = 'labtoken'
- Bash/macOS:
/metricsrequiereADMIN_ENABLED=1y, si existeADMIN_TOKEN, usarAuthorization: Bearer <token>ox-admin-token: <token>.
Más ejemplos: consulta la sección GET /metrics en API.md (API.md → GET /metrics).
- Con
ADMIN_TOKENdefinido (ej.s3cret):
# respuesta 401 sin token o con token incorrecto
curl -i http://localhost:8888/metrics
curl -i -H 'Authorization: Bearer labtoken' http://127.0.0.1:8888/metrics
# respuesta 200 con token correcto
curl -i -H 'Authorization: Bearer s3cret' http://127.0.0.1:8888/metrics | head -n 20- Sin
ADMIN_TOKEN(sóloADMIN_ENABLED=1):
# respuesta 200
curl -i http://localhost:8888/metrics | head -n 20Ejemplos rápidos en laboratorio (puertos 9001/9003): Índice rápido:
- Métricas (/metrics) en 9001 y 9003
- Status (/status) en 9001 y 9003
- Search (KNN-only) en 9001 y 9003
- Resources — listado
- Resources — por id
- Resources — filtros y orden
- Sync/Refresh — POST /refresh y POST /sync/export
- Sync — POST /sync
- Resources — include=payload
# 9001 con ADMIN_TOKEN=s3cret
curl -s -H 'Authorization: Bearer s3cret' http://127.0.0.1:9001/metrics | head -n 20
# sin token -> 401/unauthorized
curl -i http://127.0.0.1:9001/metrics
# 9003 con ADMIN_TOKEN=s3cret
curl -s -H 'Authorization: Bearer s3cret' http://127.0.0.1:9003/metrics | head -n 20
# sin token -> 401/unauthorized
curl -i http://127.0.0.1:9003/metricsStatus:
curl -s -H 'Authorization: Bearer labtoken' http://127.0.0.1:9001/status | jq .
curl -s -H 'Authorization: Bearer labtoken' http://127.0.0.1:9003/status | jq .Search (KNN-only):
# /search KNN-only en 9001
curl -s -H 'Authorization: Bearer labtoken' -H 'content-type: application/json' \
-d '{"query":"demo","k":5,"allowedTypes":["tool","prompt","workflow","resource"],"hybrid":false}' \
http://127.0.0.1:9001/search | jq .
# /search KNN-only en 9003
curl -s -H 'Authorization: Bearer labtoken' -H 'content-type: application/json' \
-d '{"query":"demo","k":5,"allowedTypes":["tool","prompt","workflow","resource"],"hybrid":false}' \
http://127.0.0.1:9003/search | jq .Resources:
# Listar recursos en 9001
curl -s -H 'Authorization: Bearer labtoken' http://127.0.0.1:9001/resources | jq .
# Listar recursos en 9003
curl -s -H 'Authorization: Bearer labtoken' http://127.0.0.1:9003/resources | jq .Resources por id:
# Obtener el primer recurso devuelto en 9001
rid=$(curl -s -H 'Authorization: Bearer labtoken' http://127.0.0.1:9001/resources | jq -r '.items[0].id')
curl -s -H 'Authorization: Bearer labtoken' http://127.0.0.1:9001/resources/$rid | jq .
# Obtener el primer recurso devuelto en 9003
rid=$(curl -s -H 'Authorization: Bearer labtoken' http://127.0.0.1:9003/resources | jq -r '.items[0].id')
curl -s -H 'Authorization: Bearer labtoken' http://127.0.0.1:9003/resources/$rid | jq .Resources con filtros y orden:
# 9001 — incluir payload, filtrar por tipo y tag, ordenar por updatedAt desc
curl -s -H 'Authorization: Bearer labtoken' \
'http://127.0.0.1:9001/resources?limit=50&offset=0&type=tool&tag=beta&sort=updatedAt&order=desc&include=payload' | jq .
# 9003 — filtrar por dominio y scope requerido
curl -s -H 'Authorization: Bearer labtoken' \
'http://127.0.0.1:9003/resources?domain=demo&neededScope=mcp:read' | jq .
# 9003 — filtrar por serverUrl y ordenar por título asc
curl -s -H 'Authorization: Bearer labtoken' \
'http://127.0.0.1:9003/resources?serverUrl=http://127.0.0.1:8001&sort=title&order=asc' | jq .Sync/Refresh en laboratorio:
# POST /refresh — 9001 (requiere serverUrl)
curl -s -X POST \
-H 'content-type: application/json' \
-H 'Authorization: Bearer labtoken' \
-d '{"serverUrl":"http://127.0.0.1:8001"}' \
http://127.0.0.1:9001/refresh | jq .
# POST /refresh — 9003
curl -s -X POST \
-H 'content-type: application/json' \
-H 'Authorization: Bearer labtoken' \
-d '{"serverUrl":"http://127.0.0.1:8001"}' \
http://127.0.0.1:9003/refresh | jq .
# POST /sync/export — 9001 (usando el primer id de /resources)
rid=$(curl -s -H 'Authorization: Bearer labtoken' http://127.0.0.1:9001/resources | jq -r '.items[0].resource_id // .items[0].id')
curl -s -X POST \
-H 'content-type: application/json' \
-H 'Authorization: Bearer labtoken' \
-d '{"ids":["'"$rid"'"]}' \
http://127.0.0.1:9001/sync/export | jq .
# POST /sync/export — 9003
rid=$(curl -s -H 'Authorization: Bearer labtoken' http://127.0.0.1:9003/resources | jq -r '.items[0].resource_id // .items[0].id')
curl -s -X POST \
-H 'content-type: application/json' \
-H 'Authorization: Bearer labtoken' \
-d '{"ids":["'"$rid"'"]}' \
http://127.0.0.1:9003/sync/export | jq .Sync (POST /sync) en laboratorio:
# 9001 — sincronizar contra MCP stub (puerto 8001)
curl -s -X POST \
-H 'content-type: application/json' \
-H 'Authorization: Bearer labtoken' \
-d '{"serverUrl":"http://127.0.0.1:8001"}' \
http://127.0.0.1:9001/sync | jq .
# 9003 — sincronizar contra MCP stub (puerto 8001)
curl -s -X POST \
-H 'content-type: application/json' \
-H 'Authorization: Bearer labtoken' \
-d '{"serverUrl":"http://127.0.0.1:8001"}' \
http://127.0.0.1:9003/sync | jq .Resources con include=payload:
# 9001 — incluir payload y limitar resultados
curl -s -H 'Authorization: Bearer labtoken' \
'http://127.0.0.1:9001/resources?include=payload&limit=5' | jq .
# 9003 — incluir payload
curl -s -H 'Authorization: Bearer labtoken' \
'http://127.0.0.1:9003/resources?include=payload' | jq .- Terminal 1 (embeddings con configuración fija):
npm run stub:normal - Terminal 2 (cliente con configuración fija):
npm run start:normal
# sync
curl -s -XPOST http://127.0.0.1:8888/sync -H 'content-type: application/json' -d '{"serverUrl":"http://127.0.0.1:8001"}' | jq .
# search
curl -s -XPOST http://127.0.0.1:8888/search -H 'content-type: application/json' -d '{"query":"demo","allowedTypes":["tool","prompt","workflow"],"k":5}' | jq .
# descriptor desde DB
curl -s http://127.0.0.1:8888/resources/demo_tool | jq .| Método | Ruta | Descripción | Body (ejemplo) | Response (shape) |
|---|---|---|---|---|
| GET | /status |
Salud del cliente y metadatos | — | { ok, dbPath, dims, uptimeSeconds } |
| POST | /sync |
Sincroniza catálogo desde un MCP | { "serverUrl": "http://127.0.0.1:8001" } |
{ embedded, updated, skipped, total, serverUrl } |
| POST | /refresh |
Refresca catálogo y reindexa | { "serverUrl": "http://127.0.0.1:8001" } |
{ embedded, updated, skipped, total, serverUrl } |
| POST | /search |
Búsqueda híbrida (KNN + FTS) | { "query": "demo", "k": 5 } |
{ items: [...] } |
| GET | /resources/:id |
Descriptor ARDF por ID | — | { payload_json: {...} } |
| GET | /resources |
Listado paginado y filtrado | ?limit=50&offset=0&domain=demo&tag=beta&sort=updatedAt&order=desc&include=payload |
{ items, limit, offset, total } |
| POST | /sync/export |
Exportar IDs por filtros | { "domain": "demo" } |
{ ids: [...] } |
status.json(respuesta de/status)search.json(resultado de/search)lab.log(logs del modo laboratorio)
Para verlos: Actions → CI → última ejecución → Job lab-smoke → Artifacts.
fetchWithTimeoutendurece llamadas al webhook (timeouts + retries 429/502/503/504).- Esquema FTS consolidado (
resource_key,server_url,text). - Gating por
visibility/scopesantes del ranking y filtros (domain,tag). - Preferir
127.0.0.1en ejemplos de curl en lugar delocalhostpara evitar resoluciones mixtas IPv6/IPv4 y asegurar consistencia entre shells/entornos.
Tras el merge a
mainpuedes retirar el badge dehardening/new-roundsi lo prefieres; el badge demainreflejará el estado del pipeline principal.
- Nuevo
POST /refreshcon instrumentación de métricas (sync_requests_total,sync_latency_seconds, labelroute="refresh"). - /resources/:id devuelve el descriptor ARDF completo (
payload_json). - Puerto unificado 8888;
fetchWithTimeoutcon reintentos (429/502/503/504); gating porvisibility/scopes. - Búsqueda híbrida (KNN + BM25) con pesos configurables; FTS consolidado.
- CI (lab-smoke) publica artefactos; nuevo job
refresh-checkvalida/refreshy métricas. - Documentación y topics actualizados; sección de troubleshooting para
npm/registro.
-
Paginación
- limit: entero 1–200 (por defecto 20; máximo 200)
- offset: entero ≥ 0 (por defecto 0)
- include: si es "payload" devuelve el descriptor completo; de lo contrario sólo
{ resource_id }
-
Filtros
- type: coincide exactamente con
resource_type|type|kinden el descriptor - domain: coincide con
domain|namespace - tag: coincide si
tagsincluye el valor o sitag == valor - tier: coincide exactamente con
tier - neededScope: requiere que
visibility.scopesoscopescontenga el valor - serverUrl: coincide con
server_url|serverUrl
- type: coincide exactamente con
-
Ordenamiento
- sort:
resource_id|title|updatedAt/updated_at/updatedat|createdAt/created_at/createdat|serverUrl/server_url/serverurl - order:
asc|desc
- sort:
-
Notas
- Cuando hay filtros, se obtiene un mayor número de filas y se pagina en memoria para no depender de columnas adicionales; límite interno hasta 5000
- Se usa
resources_indexcon fallbackardf_index - El payload proviene de
payload_json; siinclude=payloadse expande en cada item
-
Ejemplos
GET /resources?limit=50&offset=0&type=tool&tag=beta&sort=updatedAt&order=desc&include=payloadGET /resources?domain=demo&neededScope=mcp:readGET /resources?serverUrl=http://127.0.0.1:8001&sort=title
-
Documentación detallada de la API: ver API.md
-
Ejemplos de éxito y errores (curl): ver API.md, secciones “Ejemplos de éxito (curl)” y “Ejemplos de errores (curl)”.