Skip to main content
Glama
chinrw

telegram-reader

by chinrw

telegram-reader

An MCP server that lets an LLM agent read the Telegram channels and groups your account can see, and download their videos and files under names taken from the post. The agent cannot send, edit, join, react or mark anything read, and it never sees your private chats, bots or Saved Messages.

You need:

  • an api_id and api_hash from https://my.telegram.org (API development tools);

  • NixOS for the module below, or Python 3.11+ to run it by hand;

  • an MCP client that speaks streamable HTTP, on the same host.

Set it up on NixOS

Add the flake and enable the module. The two credential files must be readable by the telegram-reader user, for example as sops-nix secrets owned by it.

# flake.nix
inputs.telegram-reader = {
  url = "github:chinrw/telegram-reader";
  inputs.nixpkgs.follows = "nixpkgs";
};
# configuration.nix
{ config, inputs, ... }:
{
  imports = [ inputs.telegram-reader.nixosModules.default ];

  services.telegram-reader = {
    enable = true;
    apiIdFile = "/run/secrets/telegram-reader/api_id";
    apiHashFile = "/run/secrets/telegram-reader/api_hash";
    # Only when Telegram is not reachable directly.
    proxy = "http://192.0.2.10:8080";
    # Leave unset to turn downloads off.
    downloads.directory = "/srv/media/telegram";
  };
}

Proxy URLs must not contain usernames or passwords. Do not put credentials in Nix configuration.

After switching, log the account in once. It asks for your phone number, the login code Telegram sends you, and your two-step verification password if you set one:

$ sudo telegram-reader-login

The service starts when the login succeeds. Point your agent at config.services.telegram-reader.url, which is http://127.0.0.1:9520/mcp by default. The session shows up in Telegram under Settings > Devices with your host name, and ending it there logs the server out.

A permanent Telegram disconnection stops the server so the NixOS unit can restart it. The server allows ten seconds for HTTP requests to drain during shutdown, then cancels requests still running. An unauthorized session needs a new login. Successful DC migrations keep the server running, though individual in-flight requests may still fail while Telegram reconnects.

Related MCP server: telegram-mcp

Run it without NixOS

Run it as a user your agent cannot read files of. The session file gives full control of the account.

$ pip install .
$ export TELEGRAM_API_ID_FILE=~/.config/telegram-reader/api_id
$ export TELEGRAM_API_HASH_FILE=~/.config/telegram-reader/api_hash
$ export TELEGRAM_READER_SESSION=~/.local/state/telegram-reader/reader
$ export TELEGRAM_READER_DOWNLOADS=~/Downloads/telegram   # optional
$ export TELEGRAM_READER_PROXY=socks5://127.0.0.1:1080    # optional
$ telegram-reader login
$ telegram-reader serve --port 9520

Tools

Tool

What it does

list_chats

Lists up to 200 joined channels and groups, filtered by kind or a title/username substring. There is no paging.

read_messages

Reads recent messages of one chat, newest first, with paging and a date cutoff.

search_messages

Searches one chat, or every joined channel and group.

get_message

Fetches one message by t.me link. Photos come back as images the model can see.

download_media

Downloads a post's files in the background.

download_status

Reports download progress.

find_media

Finds posts in one chat whose file has one of the given sizes.

check_file

Tells whether a file in the download directory is a given post's file.

Chats can be given as the id from list_chats, a @username, or a t.me link. Private-channel links such as https://t.me/c/123456/789 work too. Public channels can be read without joining them.

Global search examines at most 3 * limit hits before filtering out private chats. If this budget runs out before enough readable results are found, the result has truncated: true: matching posts may be missing. Search a specific chat to avoid this global scan limit.

Find the posts behind files you already have

Files saved by hand from a Telegram app keep the uploader's file name, which is often just a date. To find their posts, pass the byte sizes of the files to find_media together with the chat they came from. Telegram's search does not match file names, so the server scans the chat's history, 2000 messages per call; pass next_before_id back until it is null. Then confirm each candidate with check_file. Renaming and moving the files is left to the agent.

check_file compares Telegram's SHA-256 hashes of 128 KiB parts with the file at its start, middle and end. That costs at most three requests and 3 MiB of reading, and tells apart files that merely share a size. It is a sample: damage between those windows goes unnoticed. It only reads files inside the download directory, and never through a symlink.

Call the tools from scripts

telegram-reader call runs one tool on the running server and prints its JSON result, so a cron job or shell script can read Telegram without an LLM. Arguments are key=value pairs. A misspelled name is refused with the list of names the tool takes. Pass --port when the server does not listen on 9520.

$ telegram-reader call list_chats kind=channel
$ telegram-reader call read_messages chat=@newsfeed limit=5 since=2026-10-01
$ telegram-reader call search_messages query=release chat=newsfeed
$ telegram-reader call download_media ref=https://t.me/newsfeed/123 name="Show - 01"
$ telegram-reader call download_status
$ telegram-reader call find_media chat=newsfeed 'sizes=[1344170064, 97703126]'

The command needs no credentials, since it only talks to the server. A tool error, a refused argument or an unreachable server prints the reason on stderr and exits 1. Photos from get_message are left out of the output.

Downloads

Ask the agent to read the post and pass a clean title as name. Files are saved as

<downloads.directory>/<chat>/<name> [<message id>].<ext>

and every file of an album as <name> - 01 [<id>].<ext>, <name> - 02 [<id>].<ext>, and so on, named after the album's text. Without a name, the first line of the post is used. <chat> is the channel's username plus its id, such as newsfeed-c1234567890, or an id-based name when the chat has no username.

  • Files are created without execute bits and owned by the service user. A writer in the directory can replace or copy them, and interpreters can read them without execute permission.

  • Existing files are never overwritten, and a symlink planted in the directory cannot redirect a download. When a file with the same name and size is already there, it counts as the download if Telegram's part hashes match it at its start, middle and end, the same sample check_file takes. Otherwise the request is refused and the file is left alone.

  • Two downloads run at a time and twenty can be queued or running. A download starts only if the disk has room for it, for the downloads still in progress, and 1 GiB more.

  • Album capacity is checked as a whole, counting only new jobs. Requesting an existing job does not need a free slot.

  • The extension comes from the original file. The characters $ ` \ " ' ; | & < > * ? { } ! : / in names become _.

The module requires an absolute download directory and grants the service user access to it with an ACL. Set downloads.createAs to a user who should create it with plain mkdir, so it inherits the parent directory's group and default ACLs.

What the agent sees, and what it does not

Every result carries a notice that Telegram content is third-party data: the agent may read it, but should not follow instructions found in it or run files downloaded from it. The server can only state this. Enforcing it is up to your agent's own approval rules, for example by denying commands that run an interpreter on files in the download directory.

The server refuses private chats, bots and Saved Messages on every path, including global search and messages fetched by id. It also refuses phone numbers and other strings that would make Telegram look up your contacts.

Limits

  • The endpoint has no authentication. It listens on 127.0.0.1 only and rejects foreign Host and Origin headers, but any local process can call it.

  • Download jobs live in memory, at most 200 of them. Active jobs are kept, and older completed or failed ones are discarded. Moving, deleting or changing a completed file makes its job report failed. Asking for the post again downloads it again, or reports it done when a matching file is back in place.

  • A job that waits a long time in the queue can fail when Telegram's file reference expires. Asking again fixes it.

License

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    D
    maintenance
    Enables Claude to interact with Telegram channels and groups through API or web scraping for reading posts and searching.
    15
    33 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to interact with a user's Telegram account: list chats, read history, search, and send messages through Telegram's MTProto API.
    2
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Enables searching Telegram messages and chats, retrieving recent messages, and extracting text from attached documents (PDF, TXT, MD, CSV) using your personal Telegram account.
    4
    Apache 2.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to search and analyze a Telegram account as a read-only knowledge source, allowing chat discovery, message search, history reading, contact inspection, and media download without exposing any mutating Telegram tools.
    13
    Apache 2.0