hono-telescope

Depuração no estilo Laravel Telescope para aplicativos Hono cujo endpoint de dashboard também é um servidor MCP, permitindo que um agente leia requisições, exceções e consultas em tempo real.

Documentação

hono-telescope

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

Uma ferramenta de depuração para aplicações Hono, inspirada no Laravel Telescope: um painel que mostra cada requisição com os logs, consultas, exceções e chamadas de saída que ocorreram dentro dela.

O mesmo endpoint também é um servidor MCP. Aponte o Claude Code, Cursor ou qualquer cliente MCP para ele e seu agente de codificação lê a telemetria da aplicação em execução diretamente — a exceção real, a requisição que a produziu e as consultas que foram executadas — em vez de receber um stack trace colado. Nada mais no ecossistema Hono faz isso.

Zero dependências em tempo de execução. Funciona em Node.js e 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


🌐 Demonstração ao Vivo

Uma instância hospedada de o aplicativo de exemplo, executando a versão 1.0. Sem necessidade de instalação.

📊 Abrir o painel — Base da API: https://hono-telescope.ilkerbalcilar.com

Acesse alguns endpoints e veja as entradas aparecerem:

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

A demonstração roda com memoryStorage({ maxEntries: 500 }) e sem autenticação no painel, então as entradas são públicas, limitadas a 500 e desaparecem ao reiniciar. Não envie nada que você não publicaria.


✨ Recursos

Disponíveis Atualmente:

  • 📡 Servidor MCP - O endpoint do painel também funciona como um servidor Model Context Protocol, permitindo que um agente de IA leia requisições, exceções e consultas ao vivo com cinco ferramentas somente leitura
  • 🔍 Monitoramento de Requisições HTTP - Rastreie requisições recebidas com cabeçalhos, payloads e corpos de resposta, e chamadas de saída fetch com cabeçalhos, payloads e respostas
  • 🚨 Rastreamento de Exceções - Capture e monitore erros da aplicação com stack traces
  • 📝 Monitoramento de Logs - Monitore logs de console com diferentes níveis de severidade
  • 🗄️ Monitoramento de Consultas de Banco de Dados - Instrumentação explícita por cliente para Prisma, Sequelize, MongoDB e Bun SQLite com tempo de execução
  • 📊 Painel Bonito - Interface web moderna baseada em React com atualizações em tempo real
  • 🎯 Zero Configuração - Funciona imediatamente com padrões sensatos
  • 🏷️ Sistema de Etiquetas - Organize entradas com etiquetas personalizadas e contexto
  • 🔧 Suporte a TypeScript - Definições de tipos completas e segurança de tipos
  • ⚡ Alto Desempenho - Sobrecarga mínima com gerenciamento eficiente de memória
  • 🌐 Bun e Node.js - Funciona perfeitamente com ambos os runtimes
  • 🗂️ Suporte a Múltiplos Bancos de Dados - Integra-se com bibliotecas de banco de dados populares
  • ⚙️ Zero Dependências em Tempo de Execução - Depende apenas de Hono (dependência par)

Recursos Planejados (Roadmap):

  • 💾 Exportação de Dados - Exporte dados monitorados em múltiplos formatos (JSON, CSV)
  • 🔔 Alertas e Notificações - Alertas em tempo real para erros e problemas de desempenho
  • 📈 Análises e Relatórios - Análises avançadas e análise de dados históricos
  • 🔐 Autenticação e Autorização - Controle de acesso ao painel além da autenticação básica
  • 🌍 Suporte a Multi-Tenancy - Suporte para múltiplos projetos isolados
  • 🧩 Sistema de Plugins - Arquitetura de plugins extensível para integrações personalizadas
  • 🔄 Persistência de Dados - Armazenamento opcional em banco de dados para monitoramento de longo prazo

📦 Instalação

# Using npm
npm install hono-telescope

# Using yarn
yarn add hono-telescope

# Using pnpm
pnpm add hono-telescope

# Using bun
bun add hono-telescope

Início 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;

Visite /telescope. O Telescope está ativado por padrão fora do ambiente de produção e desativado dentro dele.

📋 Exemplo Completo: Veja src/example/index.ts para um exemplo funcional completo com todos os recursos do Telescope, incluindo monitoramento de consultas de banco de dados, rastreamento de requisições externas e tratamento de erros.

Servidor MCP

O painel do Telescope também funciona como um servidor MCP, permitindo que um agente de codificação de IA leia a telemetria da aplicação em execução em vez de receber stack traces colados. Não há nada extra para montar — ele é servido pelo painel que você já montou:

app.route('/telescope', telescope.dashboard()); // MCP is at /telescope/mcp
claude mcp add --transport http telescope http://localhost:3000/telescope/mcp
FerramentaO que responde
recent_exceptionsO que acabou de falhar — cada exceção com sua requisição e os logs e consultas dessa requisição
recent_requestsQuais requisições foram executadas; filtre por minStatus, status, minDuration, uriContains
request_detailUma requisição completa, sem truncamento, com todas as entradas filhas
slow_queriesAs consultas recentes mais lentas e em qual requisição cada uma foi executada
statsQuantas entradas de cada tipo existem

Todas as cinco são somente leitura; não há ferramenta que limpe ou escreva telemetria. minStatus: 400 é a que vale lembrar — um handler que retorna um status de erro sem lançar exceção não registra nenhuma exceção, então esse filtro é a única maneira de encontrar essas falhas.

O transporte é a revisão atual do Streamable HTTP (2026-07-28), com 2025-11-25 ainda aceito para clientes mais antigos. GET e DELETE respondem a 405: esta revisão não tem stream SSE e não tem sessões.

Clientes que só falam stdio

Muitos editores não conseguem apontar um cliente MCP para uma URL. O pacote inclui uma ponte para eles: ela lê uma mensagem JSON-RPC por linha no stdin, encaminha para o endpoint que sua aplicação já serve e escreve a resposta de volta no 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 (ou TELESCOPE_URL) é a única opção obrigatória. Para um painel atrás de dashboard.auth, passe credenciais como cabeçalho — --header é repetível, e TELESCOPE_HEADER aceita uma para clientes que só podem definir variáveis de ambiente:

npx -y hono-telescope mcp-stdio --url https://example.com/telescope/mcp \
  --header "Authorization: Basic $(printf 'user:pass' | base64)"

A ponte encaminha; ela não implementa o protocolo uma segunda vez. Sua aplicação continua sendo o único lugar que responde ao MCP, então a ponte não adiciona ferramentas, estado de sessão ou nova dependência — e ela precisa que a aplicação já esteja em execução.

O endpoint MCP expõe exatamente o que o painel expõe — corpos de requisição e resposta, cabeçalhos e SQL — para qualquer agente que você conectar. Ele é coberto por dashboard.auth e pela mesma recusa em produção: com enabled: true sob NODE_ENV=production, montar sem credenciais lança um erro.

Configuração

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 as opções são opcionais — createTelescope() funciona com os padrões.

ChaveTipoPadrãoNotas
enabledbooleanNODE_ENV !== 'production'Desativado em produção por padrão
storageStorageAdaptermemoryStorage({ maxEntries: 1000 })Armazenamento em memória com limite de 1000 entradas
contextContextStrategyalsContext()Rastreamento de contexto de requisição baseado em AsyncLocalStorage
collectorsCollector[][consoleCollector(), exceptionCollector(), fetchCollector()]Coletores padrão para console, exceções e fetch; passe [] para desativar todos
dashboardPathstring'/telescope'Caminho de montagem do painel; deve corresponder ao caminho em app.route()
ignorePathsstring[]['.well-known']Caminhos a excluir do monitoramento
ignoreStaticAssetsbooleantrueIgnorar monitoramento de requisições para arquivos estáticos (.js, .css, .svg, etc.)
capture.requestBodybooleantrueCapturar corpos de requisições recebidas
capture.responseBodybooleantrueCapturar corpos de respostas de saída
capture.maxBodySizenumber65536Bytes máximos a capturar por corpo (64 KB)
redact.headersstring[]['authorization', 'cookie', 'set-cookie', 'x-api-key', 'proxy-authorization']Nomes de cabeçalhos a redigir
redact.bodyKeysstring[]['password', 'token', 'secret', 'apikey', 'authorization']Chaves de objeto a redigir em corpos de requisição/resposta
dashboard.authDashboardAuth | falseundefinedAutenticação básica opcional para o painel; obrigatória se enabled: true em produção

Montagem em um Caminho Personalizado

Se você montar o painel em um caminho diferente de /telescope, você deve definir dashboardPath com o mesmo valor:

const telescope = createTelescope({ dashboardPath: '/admin/debug' });
app.route('/admin/debug', telescope.dashboard());

O middleware usa dashboardPath para evitar registrar o tráfego do próprio painel, e o painel o usa para construir sua URL base.

Consultas de Banco de Dados

Passe seu cliente de banco de dados para o Telescope para instrumentação de consultas. O Prisma retorna um novo cliente — use o retornado:

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

Bancos de dados suportados:

const prisma = telescope.instrumentPrisma(new PrismaClient());
telescope.instrumentSequelize(sequelize);
const mongoClient = new MongoClient(url, { monitorCommands: true });
telescope.instrumentMongo(mongoClient);
telescope.instrumentBunSqlite(db);

Nota: A interceptação automática de banco de dados foi removida na versão 1.0 porque nunca funcionou sob Node ESM e capturava apenas SQL bruto onde funcionava. A instrumentação explícita por cliente agora é obrigatória.

Uma consulta que falha também é registrada, marcada como failed com a mensagem de erro do próprio cliente, para que um comando com falha seja distinguível de um lento no painel e via MCP. Isso cobre Prisma, MongoDB e Bun SQLite. Sequelize é a exceção: ele é instrumentado através do hook afterQuery, que parece não ser executado quando uma consulta falha, então consultas Sequelize com falha atualmente não são registradas. Corrigir isso exige verificação contra um Sequelize real.

Chame cada método instrument* uma vez por cliente. Diferente dos coletores, eles não são idempotentes (apenas instrumentBunSqlite protege contra dupla instrumentação), então instrumentar o mesmo cliente duas vezes registra cada consulta duas vezes.

instrumentBunSqlite envolve as fábricas de statements query e prepare, então chamadas de statement (all, get, run, values) são registradas. Consultas emitidas diretamente no banco — db.exec, db.run, db.all, db.get — não são capturadas.

Segurança

O painel expõe corpos de requisição e resposta, cabeçalhos e SQL. O Telescope é, portanto, desativado quando NODE_ENV === 'production'. Se você o ativar mesmo assim, deve fornecer dashboard.auth; montar sem ele lança um erro.

Você tem duas opções para produção:

  1. Forneça credenciais para proteger o painel com autenticação básica:
createTelescope({
  enabled: true,
  dashboard: { auth: { username: 'admin', password: 'secret' } },
});
  1. Opte explicitamente por não usar autenticação para reconhecer exposição total (sem autenticação, painel totalmente aberto):
createTelescope({
  enabled: true,
  dashboard: { auth: false },
});

Cabeçalhos sensíveis (authorization, cookie, set-cookie, x-api-key, proxy-authorization) e chaves de corpo (password, token, secret, apikey, authorization) são ocultados por padrão, em qualquer nível de aninhamento. A ocultação é recursiva através de objetos e arrays aninhados, insensível a maiúsculas/minúsculas, e substitui valores por [REDACTED] em vez de excluí-los.

Limitações

  • Corpos de requisições de saída são capturados apenas quando já estão em memória — uma string, um URLSearchParams ou um ArrayBuffer. Um corpo ReadableStream, FormData ou Blob, e o corpo de um objeto Request passado como primeiro argumento para fetch, são ignorados e o payload permanece vazio. Ler esses corpos consumiria o corpo que o chamador está prestes a enviar ou forçaria um clone() que pode travar no Node.
  • Respostas em streaming não são capturadas. Respostas produzidas por streamText e streamSSE do Hono são registradas sem corpo, para que o registro nunca bufferize ou atrase um stream. A detecção depende do cabeçalho Transfer-Encoding: chunked que esses helpers definem (o helper stream() puro não define content-type, então também é ignorado); um new Response(readableStream, { headers: { 'content-type': 'text/plain' } }) feito à mão não define nenhum dos dois cabeçalhos, então é lido e bufferizado antes de ser registrado. Defina Transfer-Encoding: chunked ou um content-type não textual em tal resposta para optar por não capturá-la.
  • Corpos de requisição e resposta maiores que capture.maxBodySize são registrados apenas como metadados ({ truncated: true, size }), e um corpo de requisição text/* não-JSON é registrado como { body: text }. Um corpo de array JSON é encapsulado para que um corpo registrado seja sempre um objeto: { body: [...] } para requisições, { response: [...] } para respostas. A ocultação ainda alcança o interior do array.

Adaptadores de Armazenamento Personalizados

Implemente StorageAdapter e verifique-o contra a suíte de contrato que acompanha o pacote:

import { runStorageContract } from 'hono-telescope/testing';
import { myStorage } from './my-storage';

runStorageContract('myStorage', () => myStorage());

A suíte (uma suíte Vitest; execute-a com seu próprio runner de testes instalado) fixa as duas garantias de ordenação nas quais o dashboard depende: list retorna os mais recentes primeiro, e findByParent retorna os mais antigos primeiro.

Atualizando a partir da versão 0.x

O lançamento 1.0 introduz uma nova API centrada em createTelescope():

0.x (API Antiga)

import { setupTelescope } from 'hono-telescope';

setupTelescope(app, {
  enabled: true,
  max_entries: 1000,
  sanitize_headers: ['authorization'],
});

1.0 (Nova API)

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());

Principais mudanças:

  • setupTelescope(app, config) é substituído por createTelescope(config) com montagem explícita de middleware e dashboard
  • As chaves de configuração agora estão em camelCase (ex.: max_entries → maxEntries, sanitize_headers → redact.headers)
  • A interceptação de banco de dados agora é explícita por cliente; a interceptação automática foi removida
  • A interceptação do Axios foi removida (axios no Node não usa fetch)
  • Uma requisição cujo handler lança uma exceção é registrada com o status que seu próprio onError retornou, e a exceção é registrada como uma entrada filha dessa requisição

Desenvolvimento

Começando

Primeiro, instale as dependências:

bun install

Em seguida, compile o projeto pela primeira vez:

bun run build

Executando em Modo de Desenvolvimento

Inicie o watcher do TypeScript e o aplicativo de exemplo:

Terminal 1 - Compilação TypeScript (Modo Watch)

bun run dev

Isso observa mudanças no TypeScript e as compila para JavaScript.

Terminal 2 - Aplicativo de Exemplo

bun run dev:example

Isso inicia o aplicativo Hono de exemplo com hot reload em http://localhost:3000

  • Endpoints de API de exemplo: http://localhost:3000/api/...
  • Dashboard: http://localhost:3000/telescope

Teste todos os endpoints de uma vez com o script de teste:

bash src/example/test-all-endpoints.sh

Isso testará automaticamente todos os endpoints e preencherá o dashboard com dados.

Licença

MIT