hono-telescope
Depuración estilo Laravel Telescope para aplicaciones Hono cuyo endpoint de panel también es un servidor MCP, de modo que un agente pueda leer solicitudes, excepciones y consultas en vivo.
Documentación
hono-telescope
Una herramienta de depuración para aplicaciones Hono, inspirada en Laravel Telescope: un panel que te muestra cada solicitud con los registros, consultas, excepciones y llamadas salientes que ocurrieron dentro de ella.
El mismo endpoint también es un servidor MCP. Apunta Claude Code, Cursor o cualquier cliente MCP hacia él y tu agente de codificación lee la telemetría de la aplicación en ejecución directamente — la excepción real, la solicitud que la produjo y las consultas que se ejecutaron — en lugar de recibir un stack trace pegado. Nada más en el ecosistema Hono hace eso.
Cero dependencias en tiempo de ejecución. Funciona en Node.js y Bun.

🌐 Demo en Vivo
Una instancia alojada de la aplicación de ejemplo, ejecutando la versión 1.0. Sin necesidad de instalación.
📊 Abrir el panel — Base de API: https://hono-telescope.ilkerbalcilar.com
Golpea algunos endpoints y observa cómo aparecen las entradas:
BASE=https://hono-telescope.ilkerbalcilar.com
curl $BASE/api/users # incoming request + Bun SQLite queries
curl -X POST $BASE/api/import-users # outgoing fetch to JSONPlaceholder, plus inserts
curl -X POST $BASE/api/webhook # outgoing POST whose payload is recorded, `token` redacted
curl -X POST $BASE/api/db-error # UNIQUE violation, recorded as a failed query; 409, no exception
curl $BASE/api/mixed-clients-test # the fetch call is captured, the axios call is not
curl $BASE/api/slow # 2s handler, to see the duration column
curl $BASE/api/error # exception recorded as a child of its request
La demo se ejecuta con memoryStorage({ maxEntries: 500 }) y sin autenticación en el panel, por lo que las entradas son
públicas, limitadas a 500 y desaparecen al reiniciar. No envíes nada que no publicarías.
✨ Características
Actualmente disponibles:
- 📡 Servidor MCP - El endpoint del panel funciona también como servidor del Model Context Protocol, para que un agente de IA pueda leer solicitudes, excepciones y consultas en vivo con cinco herramientas de solo lectura
- 🔍 Monitoreo de Solicitudes HTTP - Rastrea solicitudes entrantes con cabeceras, cargas útiles y cuerpos de respuesta, y llamadas salientes a
fetchcon cabeceras, cargas útiles y respuestas - 🚨 Seguimiento de Excepciones - Captura y monitorea errores de la aplicación con stack traces
- 📝 Monitoreo de Registros - Monitorea registros de consola con diferentes niveles de severidad
- 🗄️ Monitoreo de Consultas de Base de Datos - Instrumentación explícita por cliente para Prisma, Sequelize, MongoDB y Bun SQLite con tiempo de ejecución
- 📊 Panel Hermoso - Interfaz web moderna basada en React con actualizaciones en tiempo real
- 🎯 Cero Configuración - Funciona de inmediato con valores predeterminados sensatos
- 🏷️ Sistema de Etiquetas - Organiza entradas con etiquetas personalizadas y contexto
- 🔧 Soporte TypeScript - Definiciones de tipos completas y seguridad de tipos
- ⚡ Alto Rendimiento - Sobrecarga mínima con gestión eficiente de memoria
- 🌐 Bun y Node.js - Funciona con ambos runtimes sin problemas
- 🗂️ Soporte de Múltiples Bases de Datos - Se integra con bibliotecas de bases de datos populares
- ⚙️ Cero Dependencias en Tiempo de Ejecución - Depende solo de Hono (dependencia par)
Características Planificadas (Hoja de Ruta):
- 💾 Exportación de Datos - Exporta datos monitoreados en múltiples formatos (JSON, CSV)
- 🔔 Alertas y Notificaciones - Alertas en tiempo real para errores y problemas de rendimiento
- 📈 Analítica e Informes - Analítica avanzada y análisis de datos históricos
- 🔐 Autenticación y Autorización - Control de acceso al panel más allá de la autenticación básica
- 🌍 Soporte Multi-Tenant - Soporte para múltiples proyectos aislados
- 🧩 Sistema de Plugins - Arquitectura de plugins extensible para integraciones personalizadas
- 🔄 Persistencia de Datos - Almacenamiento opcional en base de datos para monitoreo a largo plazo
📦 Instalación
# Using npm
npm install hono-telescope
# Using yarn
yarn add hono-telescope
# Using pnpm
pnpm add hono-telescope
# Using bun
bun add hono-telescope
Inicio Rápido
import { Hono } from 'hono';
import { createTelescope, memoryStorage } from 'hono-telescope';
const app = new Hono();
const telescope = createTelescope({ storage: memoryStorage({ maxEntries: 1000 }) });
app.use('*', telescope.middleware());
app.route('/telescope', telescope.dashboard());
export default app;
Visita /telescope. Telescope está activado por defecto fuera de producción y desactivado dentro de ella.
📋 Ejemplo Completo: Consulta src/example/index.ts para un ejemplo funcional completo con todas las características de Telescope, incluido el monitoreo de consultas de base de datos, el seguimiento de solicitudes externas y el manejo de errores.
Servidor MCP
El panel de Telescope funciona también como servidor MCP, para que un agente de codificación de IA pueda leer la telemetría de la aplicación en ejecución en lugar de recibir stack traces pegados. No hay nada adicional que montar — se sirve desde el panel que ya montaste:
app.route('/telescope', telescope.dashboard()); // MCP is at /telescope/mcp
claude mcp add --transport http telescope http://localhost:3000/telescope/mcp
| Herramienta | Qué responde |
|---|---|
recent_exceptions | Qué falló recientemente — cada excepción con su solicitud y los registros y consultas de esa solicitud |
recent_requests | Qué solicitudes se ejecutaron; filtra por minStatus, status, minDuration, uriContains |
request_detail | Una solicitud completa, sin truncar, con cada entrada hija |
slow_queries | Las consultas recientes más lentas y en qué solicitud se ejecutó cada una |
stats | Cuántas entradas de cada tipo existen |
Las cinco son de solo lectura; no hay ninguna herramienta que borre o escriba telemetría. minStatus: 400 es
la que vale la pena recordar — un handler que devuelve un estado de error sin lanzar no registra ninguna
excepción, por lo que ese filtro es la única forma de encontrar esos fallos.
El transporte es la revisión actual de Streamable HTTP (2026-07-28), con 2025-11-25 aún
aceptado para clientes más antiguos. GET y DELETE responden a 405: esta revisión no tiene stream SSE
ni sesiones.
Clientes que solo hablan stdio
Muchos editores no pueden apuntar un cliente MCP a una URL. El paquete incluye un puente para ellos: lee un mensaje JSON-RPC por línea en stdin, lo reenvía al endpoint que tu aplicación ya sirve y escribe la respuesta en stdout.
claude mcp add telescope -- npx -y hono-telescope mcp-stdio \
--url http://localhost:3000/telescope/mcp
{
"mcpServers": {
"telescope": {
"command": "npx",
"args": ["-y", "hono-telescope", "mcp-stdio"],
"env": { "TELESCOPE_URL": "http://localhost:3000/telescope/mcp" }
}
}
}
--url (o TELESCOPE_URL) es la única opción requerida. Para un panel detrás de
dashboard.auth, pasa credenciales como cabecera — --header es repetible, y
TELESCOPE_HEADER acepta una para clientes que solo pueden configurar variables de entorno:
npx -y hono-telescope mcp-stdio --url https://example.com/telescope/mcp \
--header "Authorization: Basic $(printf 'user:pass' | base64)"
El puente reenvía; no implementa el protocolo una segunda vez. Tu aplicación sigue siendo el único lugar que responde a MCP, por lo que el puente no añade herramientas, ni estado de sesión ni ninguna dependencia nueva — y necesita que la aplicación ya esté en ejecución.
El endpoint MCP expone exactamente lo que expone el panel — cuerpos de solicitud y respuesta, cabeceras y SQL — a cualquier agente que conectes. Está cubierto por
dashboard.authy por la misma negativa en producción: conenabled: truebajoNODE_ENV=production, montar sin credenciales lanza un error.
Configuración
import { createTelescope, memoryStorage, alsContext, consoleCollector } from 'hono-telescope';
const telescope = createTelescope({
enabled: process.env.NODE_ENV !== 'production',
storage: memoryStorage({ maxEntries: 1000 }),
context: alsContext(),
collectors: [consoleCollector()],
dashboardPath: '/telescope',
ignorePaths: ['/health'],
ignoreStaticAssets: true,
capture: {
requestBody: true,
responseBody: true,
maxBodySize: 65536,
},
redact: {
headers: ['authorization', 'cookie', 'set-cookie', 'x-api-key', 'proxy-authorization'],
bodyKeys: ['password', 'token', 'secret', 'apikey', 'authorization'],
},
dashboard: {
auth: { username: 'admin', password: 'telescope' },
},
});
Todas las opciones son opcionales — createTelescope() funciona con los valores predeterminados.
| Clave | Tipo | Predeterminado | Notas |
|---|---|---|---|
enabled | boolean | NODE_ENV !== 'production' | Desactivado en producción por defecto |
storage | StorageAdapter | memoryStorage({ maxEntries: 1000 }) | Almacenamiento en memoria con límite de 1000 entradas |
context | ContextStrategy | alsContext() | Seguimiento de contexto de solicitud basado en AsyncLocalStorage |
collectors | Collector[] | [consoleCollector(), exceptionCollector(), fetchCollector()] | Colectores predeterminados para console, excepciones y fetch; pasa [] para desactivar todos |
dashboardPath | string | '/telescope' | Ruta de montaje del panel; debe coincidir con la ruta en app.route() |
ignorePaths | string[] | ['.well-known'] | Rutas a excluir del monitoreo |
ignoreStaticAssets | boolean | true | Omitir monitoreo de solicitudes de archivos estáticos (.js, .css, .svg, etc.) |
capture.requestBody | boolean | true | Capturar cuerpos de solicitudes entrantes |
capture.responseBody | boolean | true | Capturar cuerpos de respuestas salientes |
capture.maxBodySize | number | 65536 | Máximo de bytes a capturar por cuerpo (64 KB) |
redact.headers | string[] | ['authorization', 'cookie', 'set-cookie', 'x-api-key', 'proxy-authorization'] | Nombres de cabeceras a redactar |
redact.bodyKeys | string[] | ['password', 'token', 'secret', 'apikey', 'authorization'] | Claves de objeto a redactar en cuerpos de solicitud/respuesta |
dashboard.auth | DashboardAuth | false | undefined | Autenticación básica opcional para el panel; requerida si enabled: true en producción |
Montaje en una Ruta Personalizada
Si montas el panel en una ruta distinta de /telescope, debes configurar dashboardPath con el mismo valor:
const telescope = createTelescope({ dashboardPath: '/admin/debug' });
app.route('/admin/debug', telescope.dashboard());
El middleware usa dashboardPath para evitar registrar el tráfico propio del panel, y el panel lo usa para construir su URL base.
Consultas de Base de Datos
Pasa tu cliente de base de datos a Telescope para la instrumentación de consultas. Prisma devuelve un nuevo cliente — usa el devuelto:
import { createTelescope } from 'hono-telescope';
import { PrismaClient } from '@prisma/client';
const telescope = createTelescope();
const prisma = telescope.instrumentPrisma(new PrismaClient());
// Use the returned `prisma` client, not the original
Bases de datos soportadas:
const prisma = telescope.instrumentPrisma(new PrismaClient());
telescope.instrumentSequelize(sequelize);
const mongoClient = new MongoClient(url, { monitorCommands: true });
telescope.instrumentMongo(mongoClient);
telescope.instrumentBunSqlite(db);
Nota: La interceptación automática de bases de datos se eliminó en la versión 1.0 porque nunca funcionó bajo Node ESM y capturaba solo SQL crudo donde sí funcionaba. Ahora se requiere instrumentación explícita por cliente.
Una consulta que falla también se registra, marcada como failed con el mensaje de error propio del cliente, para que un
comando fallido se distinga de uno lento en el panel y a través de MCP. Esto cubre
Prisma, MongoDB y Bun SQLite. Sequelize es la excepción: se instrumenta a través del
hook afterQuery, que no parece ejecutarse cuando una consulta falla, por lo que las consultas fallidas de Sequelize
actualmente no se registran en absoluto. Arreglar eso requiere verificación contra un Sequelize real.
Llama a cada método de instrument* una vez por cliente. A diferencia de los colectores, no son
idempotentes (solo instrumentBunSqlite protege contra el doble envoltorio), por lo que instrumentar el
mismo cliente dos veces registra cada consulta dos veces.
instrumentBunSqlite envuelve las fábricas de sentencias query y prepare, por lo que las llamadas de sentencias
(all, get, run, values) se registran. Las consultas emitidas directamente en la base de datos —
db.exec, db.run, db.all, db.get — no se capturan.
Seguridad
El panel expone cuerpos de solicitud y respuesta, cabeceras y SQL. Por lo tanto, Telescope está desactivado cuando NODE_ENV === 'production'. Si lo activas allí de todos modos, debes proporcionar dashboard.auth; montarlo sin ello lanza un error.
Tienes dos opciones para producción:
- Proporciona credenciales para proteger el panel con autenticación básica:
createTelescope({
enabled: true,
dashboard: { auth: { username: 'admin', password: 'secret' } },
});
- Opta explícitamente por no usar autenticación para reconocer la exposición total (sin autenticación, panel completamente abierto):
createTelescope({
enabled: true,
dashboard: { auth: false },
});
Los encabezados sensibles (authorization, cookie, set-cookie, x-api-key, proxy-authorization) y las claves del cuerpo (password, token, secret, apikey, authorization) se redactan por defecto, a cualquier nivel de anidamiento. La redacción es recursiva a través de objetos y arreglos anidados, no distingue entre mayúsculas y minúsculas, y reemplaza los valores con [REDACTED] en lugar de eliminarlos.
Limitaciones
- Los cuerpos de solicitudes salientes solo se capturan cuando ya están en memoria — una cadena,
un
URLSearchParamso unArrayBuffer. Un cuerpoReadableStream,FormDataoBlob, y el cuerpo de un objetoRequestpasado como primer argumento afetch, se omiten y la carga útil permanece vacía. Leerlos consumiría el cuerpo que el llamador está a punto de enviar o forzaría unclone()que puede detenerse en Node. - Las respuestas transmitidas no se capturan. Las respuestas producidas por
streamTextystreamSSEde Hono se registran sin cuerpo, de modo que la grabación nunca almacena en búfer ni retrasa una transmisión. La detección se basa en el encabezadoTransfer-Encoding: chunkedque esos ayudantes establecen (el ayudantestream()simple no establece tipo de contenido, por lo que también se omite); unnew Response(readableStream, { headers: { 'content-type': 'text/plain' } })hecho a mano no establece ningún encabezado, por lo que se lee y se almacena en búfer antes de registrarse. EstablezcaTransfer-Encoding: chunkedo un tipo de contenido no textual en dicha respuesta para excluirla de la captura. - Los cuerpos de solicitudes y respuestas mayores que
capture.maxBodySizese registran solo como metadatos ({ truncated: true, size }), y un cuerpo de solicitudtext/*que no sea JSON se registra como{ body: text }. Un cuerpo de arreglo JSON se envuelve para que un cuerpo registrado sea siempre un objeto:{ body: [...] }para solicitudes,{ response: [...] }para respuestas. La redacción aún llega dentro del arreglo.
Adaptadores de Almacenamiento Personalizados
Implemente StorageAdapter y verifíquelo contra el conjunto de contratos que se incluye con el
paquete:
import { runStorageContract } from 'hono-telescope/testing';
import { myStorage } from './my-storage';
runStorageContract('myStorage', () => myStorage());
El conjunto (un conjunto de Vitest; ejecútelo con su propio ejecutor de pruebas instalado) fija las dos garantías
de orden en las que se basa el panel: list devuelve primero lo más reciente, y findByParent devuelve
primero lo más antiguo.
Actualización desde 0.x
La versión 1.0 introduce una nueva API centrada en createTelescope():
0.x (API Antigua)
import { setupTelescope } from 'hono-telescope';
setupTelescope(app, {
enabled: true,
max_entries: 1000,
sanitize_headers: ['authorization'],
});
1.0 (API Nueva)
import { createTelescope, memoryStorage } from 'hono-telescope';
const telescope = createTelescope({
storage: memoryStorage({ maxEntries: 1000 }),
redact: { headers: ['authorization'] },
});
app.use('*', telescope.middleware());
app.route('/telescope', telescope.dashboard());
Cambios clave:
setupTelescope(app, config)se reemplaza porcreateTelescope(config)con montaje explícito de middleware y panel- Las claves de configuración ahora están en camelCase (por ejemplo,
max_entries→maxEntries,sanitize_headers→redact.headers) - La interceptación de bases de datos ahora es explícita por cliente; la interceptación automática se eliminó
- La interceptación de Axios se eliminó (axios en Node no usa
fetch) - Una solicitud cuyo manejador lanza una excepción se registra con el estado que su propio
onErrordevolvió, y la excepción se registra como una entrada hija de esa solicitud
Desarrollo
Comenzando
Primero, instale las dependencias:
bun install
Luego compile el proyecto por primera vez:
bun run build
Ejecución en Modo de Desarrollo
Inicie el observador de TypeScript y la aplicación de ejemplo:
Terminal 1 - Compilación de TypeScript (Modo Observador)
bun run dev
Esto observa los cambios de TypeScript y los compila a JavaScript.
Terminal 2 - Aplicación de Ejemplo
bun run dev:example
Esto inicia la aplicación de ejemplo de Hono con recarga en caliente en http://localhost:3000
- Puntos finales de API de ejemplo:
http://localhost:3000/api/... - Panel:
http://localhost:3000/telescope
Pruebe todos los puntos finales a la vez con el script de prueba:
bash src/example/test-all-endpoints.sh
Esto probará automáticamente todos los puntos finales y poblará el panel con datos.