Automatización personal · Claude Code Channels (research preview), plugins MCP, Telegram Bot API

Claude Code Channels + Telegram → Blog privado automático

Un pipeline de input remoto que convierte un bot de Telegram en mi secretario personal: mando un link desde el móvil, Claude Code lo lee, escribe una nota privada deep-dive, commitea y pushea, y Cloudflare Pages despliega. Sin backend propio.

Claude Code Channels + Telegram → Blog privado automático

Un pipeline sin fricción para alimentar una knowledge base personal en /private. Mando un link desde el móvil y en menos de un minuto tengo una nota deep-dive en español. Sin backend propio.

Construido sobre Claude Code Channels, una feature en research preview (v2.1.80+) que permite a un plugin MCP empujar eventos directamente a una sesión de Claude Code en ejecución.

Contexto: el problema de la deuda de lectura

Cada día me cruzo con algo que vale la pena leer: un post de LinkedIn, un artículo de investigación de terceros, un blog de ingeniería, un paper. Guardo el link y me digo que volveré a él. Casi nunca lo hago.

Un link guardado no sirve de mucho si no lo lees, si no lo comprendes, si no puedes volver a él fácilmente. Llevaba meses/años acumulando links interesantes en Notion, marcadores del navegador y Saved Messages de Telegram -> un gran "agujero negro" donde los links se apilaban pero al que rara vez volvía a abrir. Cuando sí lo hacía, los mandaba a la trituradora (ChatGPT, que me lo explicaba bien pero a veces muy escueto), y pasados unos meses no era tan fácil de recuperar en un sinfín de chats que tenía.

Esta idea me la inspiró un post de Fran Pastor, "Tu WhatsApp de links no es conocimiento" (link al pie de la página), que diagnostica la misma deuda y construye un agente autónomo para triar un firehose multi-fuente (Gmail, Twitter, newsletters) de miles de señales a un puñado que valga la pena leer.

Mismo diagnóstico, prescripción y stack distintos: yo no tengo un firehose que filtrar, tengo un stream único y ya curado que lo que necesita es profundidad de procesamiento.

  • El cuello de botella no es descubrir. En este punto no necesito un filtro, ni un feed, ni una capa de scoring. Para mí eso es un poco el human in the loop... El compartir con los compañeros y debatir... Vamos, que seguimos siendo humanos y la comunicación es clave.
  • El cuello de botella es entenderlo. Me gusta que me expliquen las cosas al nivel exacto que me permite comprenderlas bien -> contra mi stack, mis puntos fuertes, mis puntos débiles.
  • Quiero poder volver. No solo leerlo una vez -> guardarlo, revisitarlo, construir sobre ello, citarlo en futuros proyectos.
The link-well: saving a link is not reading it Three capture buckets on the left — Notion dump, browser bookmarks, Telegram saved messages — all funnel into a single red "link well" labelled 'I will read it tomorrow'. From there an arrow leads to a black 'never' box marked as entropy. A caption at the bottom reads: save != process != understand != apply. Notion dump Bookmarks Saved Messages link well "I will read it tomorrow" never (entropy) save != process != understand != apply
Tres cubos de captura a la izquierda (Notion dump, marcadores del navegador, Saved Messages de Telegram) desembocan en una caja roja con borde dasheado 'pozo de links' etiquetada 'lo leo mañana', que apunta a una caja negra 'nunca (entropy)'. Al pie: guardar != procesar != entender != aplicar.

El valor real: un knowledge manager personal

Este proyecto no va realmente de un bot de Telegram. Va de convertir un link capturado en una página dentro de un sitio privado que es mío, explicada de la forma en que mejor aprendo, guardada donde ya busco lo que sé.

  • Input -> un solo link mandado a un bot privado desde el móvil.
  • Output -> una nota deep-dive en español dentro de mi sitio privado, tras Cloudflare Access, junto al resto de mi knowledge base.
  • Por qué funciona -> el sistema ya me conoce. CLAUDE.md codifica mi stack, mi nivel, cómo me gusta que me expliquen las cosas, qué analogías me encajan y qué doy ya por sabido.
  • Cada post está hecho a medida. El mismo link mandado por otra persona produciría una nota distinta. El sistema no resume -> traduce la fuente a *mi* modelo mental.
  • El conocimiento se compone. Las notas viven en git, versionadas, searchables y accesibles con el mismo esquema de URLs que el resto del sitio. El yo del futuro puede encontrarlas.
  • La fricción desaparece. Del momento en que veo un link al momento en que existe una página privada que me lo explica a mi nivel: alrededor de un minuto, cero context switch, cero editor abierto.
The CLAUDE.md profile acts as a lens that tailors each link to the reader Left-to-right flow. A single blue link card on the left represents the raw URL — the same one anyone else would see. A big amber CLAUDE.md card in the middle is labelled "the lens" and contains four white chips describing what the file encodes about the reader: stack (AWS, TS, NestJS, Terraform), level (senior backend, hands-on architect), style (deep dive, ES, inline SVG, analogies) and priors (already knows Docker, MCP, hex arch...). An arrow leads from CLAUDE.md into a green "your note" card on the right showing a tailored deep-dive in Spanish with an inline SVG diagram and a /private/... slug tag. Two semi-transparent cards behind the main note hint at the rest of the knowledge base that accumulates over time. A bottom caption reads: same link, different reader -> different note. Yours, persistent, and it compounds with every new one. El perfil es la lente el sistema ya te conoce INPUT one link https://example/post same URL anyone else would see THE LENS CLAUDE.md encodes who I am, in the repo itself stack AWS · TS · NestJS · Terraform level senior backend, hands-on architect style deep dive · ES · inline SVG · analogies priors already knows Docker, MCP, hex arch… same file drives the full style guide of the site YOUR NOTE deep dive · ES + inline SVG diagrams /private/... same link, different reader -> different note yours, persistent, and it compounds with every new one
Flujo de izquierda a derecha. Una caja azul con un link a la izquierda representa la URL cruda, la misma que vería cualquier otra persona. En el centro una caja amber grande etiquetada 'CLAUDE.md (the lens)' con cuatro chips: stack (AWS, TS, NestJS, Terraform), level (senior backend), style (deep dive, ES, SVG inline, analogías) y priors (ya conoce Docker, MCP, arquitectura hexagonal). Una flecha lleva a una caja verde 'your note' a la derecha mostrando un deep-dive hecho a medida con un placeholder de diagrama SVG y un tag de slug /private/.... Detrás hay dos tarjetas semi-transparentes que insinúan el resto de la knowledge base. Al pie: mismo link, distinto lector -> distinta nota. Tuya, persistente, y se acumula con cada nueva.

La idea: push, no pull

Un channel invierte el control flow de MCP. En vez de que Claude salga a buscar una tool, el plugin empuja un evento directamente a la sesión viva.

  • MCP tradicional -> Claude es el iniciador. Pregunta, el servidor responde.
  • Channel -> el plugin es el iniciador. Empuja un prompt directamente a Claude Code.
  • Un DM a un bot privado mío se convierte en un input remoto que aterriza en mi terminal como si lo hubiese tecleado.
  • Todo local. El plugin corre junto a Claude Code, nada público queda expuesto.
  • El flujo en sí vive en CLAUDE.md, colocado junto al código del sitio que alimenta.
Traditional MCP (pull) versus Claude Code Channels (push) Side-by-side comparison. Left: the traditional MCP pattern with Claude Code as initiator, asking an MCP Server and getting an answer. Right: a Claude Code Channel where the plugin is the initiator and pushes events into the running Claude Code session. Traditional MCP (pull) Claude decides when to reach out Claude Code initiator host + MCP client 1. asks 2. answers MCP Server passive · waits to be called exposes tools + resources the host controls the exchange Channel (push) server pushes events into the session Channel plugin (MCP) initiator Bun process · polls external source pushes event Claude Code receives <channel> as a prompt --channels plugin:telegram@… the external world lands in the live session inversion of control — the initiator moves from the host to the server
MCP tradicional a la izquierda (Claude Code pregunta a un MCP Server bajo demanda) frente a un Claude Code Channel a la derecha (el plugin empuja eventos a la sesión de Claude Code en ejecución). El iniciador se mueve del host al servidor.

Arquitectura end-to-end

Tres piezas locales cooperan. Solo dos interacciones cruzan internet pública.

  • Telegram Bot API -> plugin channel en Bun (hace getUpdates).
  • Plugin channel -> sesión de Claude Code. Empuja el mensaje como prompt <channel>.
  • Claude Code -> WebFetch + Write + npm run build + git commit/push + reply a Telegram.
  • GitHub -> build de Cloudflare Pages -> redeploy del sitio tras Cloudflare Access.
  • Solo dos cosas salen del portátil: el poll a Telegram y el git push final.
End-to-end architecture: phone to Telegram to Claude Code Channels to Cloudflare Pages Full publishing loop. A mobile user sends a URL to a Telegram bot. The Telegram Bot API is polled by a Bun-based MCP channel plugin running on the local machine. The plugin pushes the message as a channel event into a long-running Claude Code session on the same machine. Claude Code reads CLAUDE.md, uses WebFetch to read the URL, writes a markdown file under src/content/private, runs npm build, git commit and git push. GitHub triggers a Cloudflare Pages build that publishes the site. The new private post is gated by Cloudflare Access, and the same mobile user reads it back when access is approved. You mobile sends link Telegram Bot API @my_bot public HTTPS LOCAL MACHINE · NO PUBLIC ENDPOINT Channel plugin plugin:telegram@… Bun · MCP server poll Claude Code claude --channels reads CLAUDE.md push Actions driven by CLAUDE.md 1. WebFetch(url) read the linked article 2. analyse + write deep-dive note in Spanish 3. Write src/content/private/ save as <slug>.md 4. npm run build verify the site compiles 5. git commit + push straight to master 6. reply to Telegram slug + live URL no Lambda · no Worker · no webhook · no PAT on the edge GitHub master Cloudflare Pages build + deploy Cloudflare Access /private gate reads back when access is approved the only things crossing the public internet: the Telegram poll and the final git push
Loop completo de publicación: móvil → Telegram Bot API → plugin channel en Bun corriendo en local → sesión de Claude Code de larga duración → WebFetch / Write / npm build / git push → GitHub → build de Cloudflare Pages → Cloudflare Access → vuelta al móvil. El plugin y Claude Code viven dentro de una caja 'local machine, sin endpoint público' marcada con borde dasheado.

Infraestructura que colapsa

El diseño ingenuo pediría cinco piezas públicas en movimiento. Los channels lo reducen a tres piezas locales.

  • Fuera -> Cloudflare Worker hosteando un webhook de Telegram.
  • Fuera -> token del bot viviendo en el edge.
  • Fuera -> GitHub App o PAT con permiso de escritura al repo.
  • Fuera -> llamada a la Claude API desde el Worker y su API key.
  • Fuera -> endpoint HTTPS público que asegurar y monitorizar.
  • Dentro -> sesión claude --channels de larga duración, plugin de Telegram y CLAUDE.md. Todo local.
  • Único trade-off -> la sesión tiene que estar viva en alguna parte.
Naive design versus channels: infrastructure collapse Before-and-after comparison. The naive design on top requires five public moving pieces: a Cloudflare Worker hosting a Telegram webhook, a bot token stored at the edge, a GitHub App or personal access token, a call to the Claude API, and a public HTTPS endpoint to secure. The channels-based design on the bottom collapses all of that into three local pieces: a claude --channels session, the Telegram plugin, and CLAUDE.md as the system prompt — with nothing public and no secrets on the edge. Naive design five public moving pieces — each one a place for things to go wrong Cloudflare Worker deploy + maintain public HTTPS Telegram webhook TLS cert handler endpoint Bot token at the edge secret to rotate blast radius GitHub App or PAT scoped write creds commit on behalf Claude API + API key another secret billing scope collapses into Claude Code Channels three local pieces — nothing public, no secret at the edge claude --channels long-running session host + client + tools Telegram plugin Bun · MCP channel polls Bot API locally CLAUDE.md in the repo the flow = system prompt 5 pieces public → 3 pieces local
Antes/después. Arriba: cinco cajas encadenadas (Cloudflare Worker, webhook de Telegram, bot token en el edge, GitHub App/PAT, llamada a la Claude API con API key) que representan el diseño ingenuo con infraestructura pública. Abajo: tres cajas locales (sesión claude --channels, plugin de Telegram, CLAUDE.md) que representan el diseño basado en channels. Una flecha 'colapsa en' las conecta.

Por qué no Lambda, un Worker o una automatización hosted

Consideré todas las formas obvias antes de elegir channels. Ninguna encajaba en un caso de uso curado, reactivo y con deep-dive por mensaje de manera barata o simple.

  • AWS Lambda + API Gateway + Claude API -> montas un servicio entero para un flujo de una sola persona. Pagas invocaciones, pagas el gateway, pagas por token al LLM, y mantienes Terraform para algo al que mandas unos pocos links a la semana.
  • Cloudflare Worker + webhook de Telegram -> más barato por invocación, pero sigues hosteando un endpoint HTTPS público, sigues guardando el bot token en el edge, y sigues necesitando un GitHub App o PAT con permiso de escritura para pushear commits.
  • Zapier / Make / n8n -> superficial. Te dan pasos de "fetch URL" y "call LLM", pero no un flujo deep-dive-con-diagramas-en-español dirigido por el propio CLAUDE.md del repo. No puedes apuntarlo a convenciones del proyecto, así que acabas reescribiendo la mitad de la lógica igualmente.
  • Un daemon self-hosted -> lo más cercano en espíritu, pero pasas a ser dueño del polling de la Bot API, de la lógica de reconexión, del manejo de secrets, del pipeline de deploy y del alerting. Acabas de reconstruir el plugin de channels a mano.
  • Claude Code Channels -> la única forma donde todo colapsa. MCP ya existe, Claude Code ya está corriendo, CLAUDE.md ya es el system prompt. Añado un plugin y un flag de CLI. El flujo está vivo.

Costes

Este pipeline añade cero euros marginales por post. Ninguna línea nueva en la factura. Todo va sobre herramientas que ya pago o ya uso.

  • Bot de Telegram -> gratis. La Bot API no tiene pricing.
  • Suscripción a Claude Code -> con el plan básico de ~30€/mes es suficiente. Los channel events consumen del mismo allowance mensual que cualquier otra sesión de Claude Code. A mi volumen (un puñado de posts por semana, unas 2k palabras cada uno) entra cómodamente dentro del plan básico.
  • Cloudflare Pages -> free tier (500 builds al mes, sitios ilimitados).
  • Cloudflare Access -> gratis en el plan Zero Trust hasta 50 usuarios.
  • GitHub -> gratis para repos públicos.
  • Bun -> runtime gratuito para el plugin de channel.
  • Compara con la forma Lambda: invocaciones de API Gateway + compute de Lambda + Claude API por token + logs de CloudWatch + bucket de estado de Terraform + dominio + TLS + tiempo de mantenimiento. Euros bajos, pero no-cero, y la factura es tuya.
  • Mi flujo añade exactamente un artefacto por ejecución: un commit nuevo en git. Nada que monitorizar, renovar o rotar.

Setup y pairing

El happy path es corto y el orden importa. Primero pairing, después lockdown.

  • 1. Prerequisitos -> Claude Code v2.1.80+, login de claude.ai (Pro / Max / Team) y Bun.
  • 2. Instalar -> /plugin install telegram@claude-plugins-official.
  • 3. Configurar -> /telegram:configure <token>. Token con permisos 600 en ~/.claude/channels/telegram/.env.
  • 4. Arrancar -> claude --channels plugin:telegram@claude-plugins-official.
  • 5. Emparejar -> DM al bot, pegar el código devuelto en /telegram:access pair <code>.
  • 6. Bloquear -> inmediatamente después, /telegram:access policy allowlist.
  • Nunca dejar el bot en dmPolicy: "pairing". Cualquiera que lo encuentre puede pedir un código nuevo.
# Dentro de una sesión de Claude Code
/plugin install telegram@claude-plugins-official
/reload-plugins
/telegram:configure <token>

# Reiniciar Claude Code con el channel habilitado
claude --channels plugin:telegram@claude-plugins-official

# Emparejar la cuenta de Telegram y bloquear el bot inmediatamente
/telegram:access pair <code>
/telegram:access policy allowlist
El setup completo, de arriba a abajo. Sustituye <token> y <code> por los valores reales de BotFather y de la respuesta al DM.
Pairing sequence and lockdown Sequence diagram showing how to pair a Telegram account with a running Claude Code session and immediately lock the bot down. Three lifelines: you on Telegram, the channel plugin, and the access.json file. Steps: 1) DM any message to the bot; 2) plugin replies with a pairing code; 3) you run /telegram:access pair with that code inside Claude Code; 4) the plugin appends your sender id to access.json; 5) you run /telegram:access policy allowlist to lock the bot; 6) the plugin sets dmPolicy to allowlist in access.json. You (Telegram) mobile DM Channel plugin local Bun process access.json ~/.claude/channels/telegram 1 DM any message 2 reply with pairing code code: d87f4c 3 /telegram:access pair d87f4c typed inside Claude Code 4 append sender id allowFrom += [senderId] DO IMMEDIATELY — LOCKDOWN 5 /telegram:access policy allowlist 6 close the door dmPolicy = "allowlist" never leave the bot in dmPolicy "pairing" — any DM would request a fresh code
Diagrama de secuencia con tres lifelines (Tú en Telegram, el plugin channel, y el fichero access.json). Seis mensajes numerados recorren el pairing y el lockdown: DM cualquier mensaje → el plugin responde con un código → /telegram:access pair dentro de Claude Code → el senderId se añade a access.json → /telegram:access policy allowlist → dmPolicy pasa a 'allowlist' en access.json.

Modelo de seguridad

El handle del bot es público, como una dirección de email. Todo lo que de verdad protege el flujo vive debajo.

  • Los DMs de paso se descartan en el plugin. dmPolicy: "allowlist" más una única entrada en allowFrom implica que cualquier senderId desconocido se tira antes de llegar a Claude.
  • Suplantar el senderId es inviable. Telegram lo firma del lado del servidor. El único camino realista es el robo de cuenta, que cierra la Two-Step Verification de Telegram.
  • Amenaza real: prompt injection vía contenido fetcheado. Un "ignore previous instructions" oculto en una página linkeada puede empujar a Claude hacia llamadas no deseadas.
  • Mitigación: aprobaciones de tool use por invocación. Cada Write, Bash o git push saca un botón Allow / Deny en el móvil.
  • El channel no puede ampliar la superficie de tools de Claude ni escapar del sandbox del directorio de trabajo. Solo entrega un prompt.
  • El único interruptor que rompería el modelo, --dangerously-skip-permissions, se queda fuera de este flujo.

CLAUDE.md como system prompt

El flujo no es código. Es una sección corta del CLAUDE.md del propio repo, leída por Claude Code una vez por sesión.

  • Idioma -> español.
  • Profundidad -> deep dive, lector backend senior.
  • Estructura -> TL;DR -> Contexto -> Cómo funciona -> Puntos clave -> Cómo aplica a mi stack -> Opinión crítica.
  • Diagramas -> SVG inline, fondo white-poster con paleta fija. Renderiza idéntico en light y dark mode.
  • Destino -> src/content/private/<slug>.md, commiteado y pusheado a master.
  • El repo que hostea el sitio también define cómo el sitio se alimenta. Sin script wrapper, sin servicio separado.
## Telegram Channel -> Private Blog Posts

When a message arrives via the Telegram channel containing a URL:

1. Fetch the link content (WebFetch)
2. Analyze the content thoroughly
3. Generate a private blog post following the style guide
4. Save as src/content/private/<slug>.md
5. Run `npm run build` to verify
6. Commit & push to master
7. Reply via Telegram with the slug and live URL

Style guide: Spanish, deep dive, inline SVG diagrams on the
white-poster palette, structure TL;DR -> Contexto -> Cómo funciona
-> Puntos clave -> Cómo aplica a mi stack -> Opinión crítica.
Fragmento del CLAUDE.md del repo. Este es todo el "código" detrás del flujo — el resto es comportamiento estándar de Claude Code.
CLAUDE.md as the system prompt for the channel flow Three-stage flow. A channel event arrives with a URL from Telegram. Claude Code loads the CLAUDE.md file in the working directory and matches the "Telegram Channel → Private Blog Posts" section. Claude then executes the numbered recipe: WebFetch the URL, analyse the content, generate a Spanish deep-dive post with inline SVG diagrams, save it under src/content/private, run npm run build, git commit and git push to master, and reply to Telegram with the slug and live URL. INCOMING Channel event <channel source="telegram"> https://example.com/post treated like a normal prompt LOADED ONCE PER SESSION CLAUDE.md ## Telegram Channel → Private Blog Posts style guide · diagram palette · slug rules a section of the repo’s own CLAUDE.md EXECUTED IN ORDER Recipe 1. WebFetch(url) 2. analyse full article 3. generate deep-dive (ES) 4. inline SVG diagrams 5. Write src/content/private/<slug>.md 6. npm run build 7. git commit + push master 8. reply to Telegram no wrapper script, no separate service — the flow lives in the codebase it touches
Flujo en tres etapas: llega un channel event con una URL; Claude Code matchea la sección 'Telegram Channel → Private Blog Posts' de CLAUDE.md (cargado una vez por sesión); Claude ejecuta luego una receta ordenada (WebFetch, analizar, generar deep-dive en español, diagramas SVG inline, escribir src/content/private, npm run build, git commit+push, reply a Telegram).

Trade-offs

  • Dependiente de sesión por diseño -> el pipeline solo está vivo mientras corre la sesión de Claude Code. No hay encolado offline por mi parte.
  • Por qué aun así me funciona -> tengo el portátil encendido ~24/7 con una sesión claude --channels de larga duración, así que el pipeline está siempre alcanzable en la práctica.
  • Si apago la máquina -> la cola la hace Telegram por mí. Los mensajes enviados al bot se quedan dentro de los servidores de Telegram hasta que vuelvo a abrir el channel. Al reabrirlo el plugin los vuelve a procesar en orden vía getUpdates. Nada se pierde, y nada llega a Claude hasta que reabro la sesión.
  • Research preview -> el contrato de --channels todavía no está congelado. Espera cambios.
  • Las aprobaciones de tool use por invocación rompen la ergonomía "mando link y me voy".
  • El único escape, --dangerously-skip-permissions, es exactamente lo que suena y no tiene lugar en un setup compartido.
  • git push directo a master es cómodo pero arriesgado. v2 más segura: rama content/telegram-<date> con auto-merge solo al pasar el build.

Stack

Claude Code MCP Channels (research preview) Telegram Bot API Bun Astro SSG Cloudflare Pages Cloudflare Access

Enlaces