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

hono-telescope MCP server Listed on mcpservers.org npm version License: MIT TypeScript Bun Node.js GitHub stars GitHub watchers

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.

The Telescope dashboard: a request list, one request with the queries it ran, the failed query marked with the driver's own error, and an exception with its stack trace


🌐 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 fetch con 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
HerramientaQué responde
recent_exceptionsQué falló recientemente — cada excepción con su solicitud y los registros y consultas de esa solicitud
recent_requestsQué solicitudes se ejecutaron; filtra por minStatus, status, minDuration, uriContains
request_detailUna solicitud completa, sin truncar, con cada entrada hija
slow_queriesLas consultas recientes más lentas y en qué solicitud se ejecutó cada una
statsCuá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.auth y por la misma negativa en producción: con enabled: true bajo NODE_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.

ClaveTipoPredeterminadoNotas
enabledbooleanNODE_ENV !== 'production'Desactivado en producción por defecto
storageStorageAdaptermemoryStorage({ maxEntries: 1000 })Almacenamiento en memoria con límite de 1000 entradas
contextContextStrategyalsContext()Seguimiento de contexto de solicitud basado en AsyncLocalStorage
collectorsCollector[][consoleCollector(), exceptionCollector(), fetchCollector()]Colectores predeterminados para console, excepciones y fetch; pasa [] para desactivar todos
dashboardPathstring'/telescope'Ruta de montaje del panel; debe coincidir con la ruta en app.route()
ignorePathsstring[]['.well-known']Rutas a excluir del monitoreo
ignoreStaticAssetsbooleantrueOmitir monitoreo de solicitudes de archivos estáticos (.js, .css, .svg, etc.)
capture.requestBodybooleantrueCapturar cuerpos de solicitudes entrantes
capture.responseBodybooleantrueCapturar cuerpos de respuestas salientes
capture.maxBodySizenumber65536Máximo de bytes a capturar por cuerpo (64 KB)
redact.headersstring[]['authorization', 'cookie', 'set-cookie', 'x-api-key', 'proxy-authorization']Nombres de cabeceras a redactar
redact.bodyKeysstring[]['password', 'token', 'secret', 'apikey', 'authorization']Claves de objeto a redactar en cuerpos de solicitud/respuesta
dashboard.authDashboardAuth | falseundefinedAutenticació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:

  1. Proporciona credenciales para proteger el panel con autenticación básica:
createTelescope({
  enabled: true,
  dashboard: { auth: { username: 'admin', password: 'secret' } },
});
  1. 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 URLSearchParams o un ArrayBuffer. Un cuerpo ReadableStream, FormData o Blob, y el cuerpo de un objeto Request pasado como primer argumento a fetch, 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 un clone() que puede detenerse en Node.
  • Las respuestas transmitidas no se capturan. Las respuestas producidas por streamText y streamSSE de 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 encabezado Transfer-Encoding: chunked que esos ayudantes establecen (el ayudante stream() simple no establece tipo de contenido, por lo que también se omite); un new 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. Establezca Transfer-Encoding: chunked o un tipo de contenido no textual en dicha respuesta para excluirla de la captura.
  • Los cuerpos de solicitudes y respuestas mayores que capture.maxBodySize se registran solo como metadatos ({ truncated: true, size }), y un cuerpo de solicitud text/* 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 por createTelescope(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 onError devolvió, 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.

Licencia

MIT