Skip to main content
Glama
lucadevv

trazabilidad-mcp

by lucadevv

Trazabilidad

Motor de trazabilidad de código full-stack: construye un grafo de hechos de tu app (componentes → handlers → costuras HTTP → queries DB) y lo expone por web, MCP y CLI desde una sola fuente de verdad. Local-first, sin telemetría, el código nunca sale de tu máquina.

Estado: v1 funcional. El motor completo está implementado y verificado: extractor ts-morph, matcher de costuras HTTP, persistencia SQLite con migraciones, detección de doble-fuente, y las tres superficies vivas (CLI trazar, API REST en :8791, web en :5183, servidor MCP) y soporte multi-proyecto: un dueño apunta la herramienta a N repos reales. Las tres leen la MISMA query layer → una sola fuente de verdad.

Onboarding de un comando

Requisitos: Docker + Docker Compose.

git clone <repo> && cd trazabilidad
docker compose -f docker-compose.local.yml up --build -d

Ambos servicios se publican solo en 127.0.0.1 — nunca expuestos a la LAN (decisión de seguridad de la spec §5).

Para frenar todo:

docker compose -f docker-compose.local.yml down

Ciclo de desarrollo (hot-reload)

El compose usa bind-mount + --watch, no bakea el código:

  • apps/api corre con bun --watch → al guardar un .ts, el proceso reinicia solo.

  • apps/web corre vite con watch.usePolling → HMR cruza el FS del contenedor en macOS.

Editás en el host, el cambio se ve sin reconstruir la imagen. Solo necesitás up --build la primera vez (o tras tocar package.json / lockfile, para reinstalar deps).

Si cambiás dependencias: docker compose -f docker-compose.local.yml up --build -d recrea los contenedores y reinstala dentro de ellos.

Configuración de entorno

Convención de tres archivos en env/:

Archivo

Tracked

Contenido

env/local.env

config no-secreta (puertos, NODE_ENV)

env/local.secret.env.example

todas las keys de secretos, sin valores

env/local.secret.env

no (gitignored)

tus valores reales

v1 no tiene secretos reales (local-only, sin auth, sin servicios externos). El .example existe para fijar la convención de cara a v2.

Estructura (monorepo Bun + Turborepo)

packages/
  domain/    tipos puros del grafo (sin deps)
  core/      FactGraphBuilder + ExtractorPort (HEXAGONAL, no importa ts-morph)
  matcher/   SeamMatcher (STRATEGY, confianza descendente)
  db/        schema SQL + migraciones
  query/     query layer COMPARTIDA (fuente única que leen CLI, API y MCP)
apps/
  api/       Hono REST :8791 (bind 127.0.0.1) — /health, /index/status, /journeys, /seams, /findings
  web/       React + Vite + TanStack Router :5183 — canvas del grafo con polling de estado
  mcp/       servidor MCP stdio (SDK 1.29.0) — trace_flow, list_journeys, get_http_seams, index_status, ...
  cli/       `trazar` (index/add/projects/remove/overview/flow/journeys/seams/findings[+suppress/confirm]/serve)
examples/
  demo-login/  fixture de test: login React + Hono con plantados verificables

CLI trazar

Todos los comandos van sobre la misma query layer que la web y el MCP:

trazar index <ruta>                 # indexa un proyecto y construye el grafo de hechos
trazar add <ruta> [--name=X]        # registra un proyecto en el registro (lo verás en la web)
trazar projects                     # lista los proyectos registrados (id, name, ruta, estado)
trazar remove <id>                  # quita un proyecto del registro
trazar overview [<ruta>]            # salud del proyecto (healthScore, capas, findings por severidad)
trazar journeys [<ruta>]            # lista los journeys (entry points de UI) detectados
trazar flow <journey> [--mermaid]   # camino completo botón→DB de un journey (texto o Mermaid)
trazar seams [<ruta>] [--level=L]   # costuras HTTP con confianza y file:line de ambos lados
trazar findings [<ruta>]            # findings de doble-fuente con evidencia y estado
trazar findings suppress <key> [--reason=...]   # suprime un finding (persiste entre re-index)
trazar findings confirm <key>                   # confirma un finding
trazar serve                        # cómo levantar API + web (modo Docker demo o host)

add y index son cosas distintas: add registra la ruta en el catálogo (para verla en la web/MCP por id), index construye el grafo. index no auto-registra (deja un hint copiable); add no indexa. Mirá la sección Multi-proyecto más abajo.

Honestidad por diseño: sin index → comando copiable; stale tras editar sin re-indexar → ⚠; seams no resueltas → declaradas como huecos, nunca ocultadas. Exit codes: 0 ok · 2 uso · 1 error.

El proyecto objetivo de los comandos de lectura sale de: arg explícito > TRAZABILIDAD_PROJECT > cwd.

Instalar trazar en el PATH

El bin del CLI apunta a apps/cli/src/index.ts, que ya tiene shebang #!/usr/bin/env bun. bun link NO sirve acá: en un monorepo de workspaces solo registra el paquete para enlazarlo en otro proyecto (bun link @trazabilidad/cli), no instala el binario trazar en el PATH (verificado: tras bun link, which trazar → not found). Dos formas que sí funcionan:

Opción A — symlink al bin dir de bun (recomendada; ~/.bun/bin ya está en tu PATH si usás bun):

chmod +x apps/cli/src/index.ts
ln -sf "$(pwd)/apps/cli/src/index.ts" ~/.bun/bin/trazar
trazar journeys examples/demo-login    # ✓ funciona por el shebang

Opción B — alias/función en tu shell (si no querés tocar ~/.bun/bin):

# en ~/.zshrc o ~/.bashrc (ajustá la ruta absoluta del repo):
trazar() { bun "/ruta/al/repo/trazabilidad/apps/cli/src/index.ts" "$@"; }

Sin instalar nada, el CLI siempre corre con bun apps/cli/src/index.ts <comando> desde la raíz del repo (lo que usan los ejemplos de este README).

Multi-proyecto

Trazabilidad apunta a N proyectos desde un solo dueño. El catálogo de rutas vive en ~/.trazabilidad/projects.json (config, separado de la .db de cada proyecto). Cada superficie —web, MCP, CLI— resuelve el proyecto de forma independiente; cuando no se especifica, cae al default (env TRAZABILIDAD_PROJECT), así el demo dockerizado y los tests no se rompen.

Dos modos de arranque

Modo

Comando

Qué ve

Para qué

Demo (Docker)

docker compose -f docker-compose.local.yml up --build -d

solo el fixture montado en /repo

onboarding de un comando, conocer la herramienta

Host (bun)

bun run api:host + bun run web:host

tus repos reales (cualquier path absoluto del host)

trabajar sobre tu código de verdad

La API en Docker está encajonada al volumen montado; para apuntar a tus repos reales usá el modo host (la API corre con bun directo en loopback y ve todo el filesystem). Ambos publican solo en 127.0.0.1.

Registrar y listar proyectos (CLI)

# 1) registrar una o más rutas reales (idempotente; imprime el id estable)
trazar add ~/code/mi-api          # → ✓ registrado "mi-api" — id mi-api-3f9a2c
trazar add ~/code/mi-web --name="Front"

# 2) listar el registro con estado (id, name, ruta, ✓/✗ index, ⚠ si la ruta no existe)
trazar projects

# 3) construir el grafo de cada uno (index ≠ add; corré index aparte)
trazar index ~/code/mi-api

# 4) quitar uno del registro (no borra la .db ni el código)
trazar remove mi-api-3f9a2c

El id es estable y se deriva del path absoluto (slug(basename)-hash6), no del basename: dos repos api en carpetas distintas obtienen ids distintos, sin colisión.

El registro es CLI-only. No hay POST /add ni POST /index en la API: registrar e indexar se hacen por el CLI en el host (decisión v1 — la API es de solo lectura sobre el grafo). La web y el MCP leen el catálogo, pero quien lo escribe es trazar add / trazar remove.

Selector en la web

En modo host (bun run web:host), el sidebar muestra un dropdown que lista GET /projects. El proyecto activo vive en la URL como segmento (/p/<id>/overview, deep-linkeable). Al elegir otro proyecto, todas las vistas (overview/journeys/seams/findings/status) se re-scopean a ese proyecto. Si un proyecto está sin indexar o su ruta ya no existe, aparece con un punto rojo y un estado honesto. Sin proyectos registrados → estado vacío con CTAs copiables (trazar add / trazar index).

El param project del MCP

Cada tool del MCP acepta un project opcional (el id del registro). Con project=<id> responde sobre ese proyecto; sin el param, sobre el default (TRAZABILIDAD_PROJECT). La tool list_projects devuelve el catálogo para que el agente descubra los ids.

// un agente consulta dos proyectos en la MISMA sesión, sin reconectar:
{ "tool": "overview",      "arguments": { "project": "mi-api-3f9a2c" } }
{ "tool": "list_journeys", "arguments": { "project": "mi-web-b71e04" } }
{ "tool": "overview",      "arguments": {} }   // ← default (back-compat)

Las tres superficies pueden operar sobre proyectos distintos a la vez sin interferir: son procesos independientes que leen .dbs distintas, y cada uno cae al default cuando no se especifica.

Flujo completo con el demo

bun install

# 1) indexar el fixture → 22 nodos, 14 aristas, 3 seams (1 finding de doble-fuente)
bun apps/cli/src/index.ts index examples/demo-login

# 2) ver los journeys de UI detectados (onSubmit, signOut)
bun apps/cli/src/index.ts journeys examples/demo-login

# 3) trazar el journey de login botón→DB (con el puente honesto del extractor)
bun apps/cli/src/index.ts flow onSubmit examples/demo-login

# 4) ver el finding estrella: doble fuente de verdad de `user`
bun apps/cli/src/index.ts findings examples/demo-login

# 5) levantar API + web (consumen la MISMA verdad que el CLI)
TRAZABILIDAD_PROJECT="$(pwd)/examples/demo-login" bun apps/api/src/index.ts   # :8791
cd apps/web && bun run dev                                                    # :5183

Si editás un .tsx del demo sin re-indexar, todas las superficies (CLI, /index/status, /health, web) reportan stale con ⚠ — la verdad sigue siendo honesta hasta que vuelvas a trazar index.

Conectar el MCP a Claude Code / Claude Desktop / Cursor

El servidor MCP (apps/mcp) expone el grafo de hechos por stdio (trace_flow, list_journeys, get_http_seams, index_status, find_data_providers, list_findings, overview y list_projects) leyendo la MISMA query layer que el CLI y la API. Cada tool acepta un project opcional (el id del registro) para apuntar a un proyecto puntual; sin ese param usa la env TRAZABILIDAD_PROJECT (back-compat). Ver la sección Multi-proyecto arriba. El MCP solo LEE — indexá antes con trazar index <ruta>.

Claude Code — un comando (verificá tu sintaxis con claude mcp add --help):

claude mcp add trazabilidad \
  -e TRAZABILIDAD_PROJECT="$(pwd)/examples/demo-login" \
  -- bun "$(pwd)/apps/mcp/src/index.ts"

Comprobá la conexión con claude mcp get trazabilidad (debe decir Status: ✓ Connected). Quitalo con claude mcp remove trazabilidad. Usá -s project para escribirlo en un .mcp.json versionable del repo, o -s user para que esté disponible en todos tus proyectos.

JSON equivalente (.mcp.json en la raíz del repo, o el bloque mcpServers de la config de Claude Desktop / Cursor). Usá rutas absolutas${PWD} lo expande Claude Code; otros clientes pueden no hacerlo, poné la ruta completa:

{
  "mcpServers": {
    "trazabilidad": {
      "type": "stdio",
      "command": "bun",
      "args": ["/ruta/absoluta/al/repo/apps/mcp/src/index.ts"],
      "env": {
        "TRAZABILIDAD_PROJECT": "/ruta/absoluta/al/repo/examples/demo-login"
      }
    }
  }
}

El fixture: examples/demo-login

App de login completa (React + Hono) que es el contrato de test del producto. Planta a propósito tres situaciones que los lotes 2 y 5 deben detectar (marcadas con // FIXTURE:):

  1. Doble fuente de verdad de user: Dashboard.tsx lo lee de AuthContext, Profile.tsx lo lee directo de localStorage. → Finding double-source (LOTE 5).

  2. Fetch dinámico (fetchProfile, URL template en runtime) → seam no-resuelta visible (LOTE 2).

  3. Fetch literal fetch('/api/auth/login') → costura literal con el handler Hono (LOTE 2).

Typecheck del fixture:

bun install
bun run --filter '@trazabilidad/example-demo-login' typecheck

Limitaciones conocidas v1

Trazabilidad declara sus huecos en vez de ocultarlos. Lo que v1 todavía NO cubre:

  • sessionStorage y claves dinámicas no se detectan como fuente de estado. El extractor reconoce localStorage con clave literal y el Context explícito; una doble-fuente sobre sessionStorage (o localStorage con una clave armada en runtime) no genera finding y find_data_providers devuelve 0 proveedores. Cobertura ampliable en v2.

  • finding_key vs renames. La identidad de un finding (para que la supresión persista entre re-index) se deriva del contenido del hecho; si renombrás el símbolo/archivo involucrado, la key cambia y una supresión previa no se reasocia automáticamente al finding "equivalente".

  • Puente del flujo botón→DB es heurístico. Cuando el extractor no puede unir dos tramos con certeza (p. ej. una URL armada en runtime), declara la costura como no-resuelta (hueco visible) en lugar de inventar una arista — preferimos un hueco honesto a una conexión falsa.

  • La API es de SOLO LECTURA sobre el grafo + supresión/confirmación de findings. El indexado se dispara únicamente por CLI (trazar index); no hay POST /index en v1 (cola + progreso es backlog v2).

  • DB local single-project, single-writer. La base SQLite usa journal_mode=DELETE (no WAL): se comparte host↔contenedor por bind-mount sobre VirtioFS, donde el -shm de WAL no es coherente y causaba "disk I/O error" intermitentes y estados divergentes. DELETE serializa escritor↔lectores, aceptable para un dev local; no es una DB pensada para concurrencia multi-cliente.

  • Sin auth ni multi-tenant. Local-first, bind 127.0.0.1, sin telemetría; multi-usuario/remoto es la visión SaaS de v2, no v1.

Related MCP Connectors