Telebrief
You can use Telebrief's MCP server to fetch AI digests of your Telegram channels, read raw channel messages, and search across channels without opening Telegram.
Generate a fresh AI digest for a configurable window (1–168 hours, default 24) with
get_digest, costing AI tokens and taking ~20–90 seconds.Instantly retrieve the latest cached digest, including its generation time, with
get_last_digestat no cost.Get unsummarized messages from one configured channel with
get_channel_messages, specifying hours and limit; it reads from storage when available or falls back to a live Telegram read.Search words across all configured channels or a single channel with
search_messages, using Telegram's full-history search, with configurable days and limit, spending no AI tokens.Reuse digests from any source—scheduled, bot, or MCP—because every successful digest is saved for
get_last_digest.Rely on the shared generation lock: MCP digest calls wait if the scheduler is already building one.
Access is unauthenticated and should stay bound to loopback for security.
Uses locally hosted Ollama models as an AI provider to summarize Telegram channel messages without relying on external AI services.
Uses OpenAI models as an AI provider to summarize Telegram channel messages and generate digest content.
Optional storage backend that persists collected Telegram messages to a PostgreSQL database for multi-host deployments and concurrent read access.
Optional storage backend that persists collected Telegram messages to a local SQLite database for historical access and external LLM workflows.
Collects messages from Telegram channels and dialogs, including private channels, and delivers AI-generated daily or on-demand digests to the user's Telegram account via a bot. Supports bot commands such as /digest, /status, and /cleanup.
Telebrief
AI digests of your Telegram channels, delivered by your own bot
Telebrief reads your Telegram channels, in any language, summarizes them with OpenAI, Anthropic or a local Ollama model, and sends you a daily digest through your own Telegram bot. The digest can be grouped by channel or by topics the AI picks out. It is written in English, Russian, Spanish, German or French (Russian by default).
Contents
Extensibility: filters, prompts, group binding, storage queries
MCP server: enabling, stdio mode, tools, single channel, searching, security
Related MCP server: telegram-briefing-mcp
Features
Reads channels in any language: English, Russian, Ukrainian, Chinese and so on.
Writes summaries, labels and bot messages in English, Russian, Spanish, German or French (Russian by default).
Summarizes with OpenAI (including GPT-6 Luna, Sol and Astra), Anthropic, or a local Ollama model.
Sends a digest on a daily schedule, or right away when you ask the bot.
Reads your private chats and channels, not only public ones.
Groups the digest by channel (default) or by topics such as News, Events and Sport.
Formats the digest as Markdown with emojis, bullet points and clickable channel links.
Splits a digest longer than Telegram's 4096-character limit into several messages instead of cutting it off.
Runs on your own server for a single user. Your session, API keys and messages stay there.
Deletes old digest messages automatically.
Can serve digests over an optional MCP endpoint, so AI agents get them without reading Telegram.
Prerequisites
You need four things:
Docker: install Docker.
Telegram app credentials (
api_idandapi_hash) from my.telegram.org.If the form at my.telegram.org/apps only shows
ERROR, Telegram rejected the request; Telebrief is not involved yet. These usually help:Use a unique, random alphanumeric App title and Short name (Short name: 5 to 32 letters or digits, no spaces).
Turn off VPN, proxy and ad-blocking extensions; try a private window or another browser.
Switch networks, for example to mobile data instead of Wi-Fi.
Submit the form a few more times; the check fails intermittently.
If nothing works, contact Telegram support. Never enter your login code on third-party sites that offer to create an app for you.
A Telegram bot token. Send
/newbotto @BotFather and save the token it gives you.An AI provider, one of:
OpenAI: API key from platform.openai.com
Anthropic: API key from console.anthropic.com
Ollama: no key, install it locally
Quick start
You don't need to clone the repository or install Python. In an empty directory, run the setup wizard:
mkdir telebrief && cd telebrief
docker run --rm -it --user "$(id -u):$(id -g)" -v "$PWD":/setup \
ghcr.io/belaytzev/telebrief python main.py init /setupThe wizard logs into your Telegram account (phone, code, 2FA), checks the bot token, lets you pick channels from your dialogs by number, and writes .env, config.yaml, docker-compose.yml and sessions/user.session. It takes your user ID from the login.
Then press Start in your bot's chat and launch the service:
docker compose up -d
docker compose logs -f telebriefSend /digest to the bot to get the first digest right away. You can re-run the wizard at any time: it reuses the existing session and asks before overwriting files.
To update to the latest release:
docker compose pull && docker compose up -dEach release is published to GitHub Container Registry with the tags latest, X.Y (minor) and X.Y.Z (patch). To build from source, replace the image: line in docker-compose.yml with build: .. Settings the wizard doesn't ask about are listed in config.yaml.example.
Bot commands
Message your bot in Telegram:
Command | Description |
| Same as |
| Lists all commands |
| Builds and sends a digest for the last 24 hours, using the configured |
| Shows the AI provider and model, the number of channels, auto-cleanup and the next scheduled run |
| Deletes old digest messages now |
Example output
digest_mode in config.yaml picks one of two layouts.
Channel mode (digest_mode: "channel", default)
Summaries are grouped by source channel, each with a link to the channel:
# 📊 Daily Digest - 02 May 2026
## 🎯 Brief Overview
A busy day in tech: a major framework release and a security patch worth
applying. Markets closed higher, and there is a self-hosting meetup this Friday.
---
## 💻 Tech News · [Open channel →](https://t.me/technews)
- 🚀 **Framework 2.0 released**: faster builds, new plugin API
- 🔐 **Security advisory**: patch for a popular web server
## 💰 Markets · [Open channel →](https://t.me/markets)
- 📈 **Stocks close higher**: tech shares lead the rally
- 🏦 **Rate decision**: central bank holds steady
---
📈 **Statistics**: 3 channels, 214 messages processedThe AI writes each channel's bullet points from the prompt, so their layout differs a little between providers and models.
Topic mode (digest_mode: "digest")
Summaries are grouped by topic. You define the topics in config.yaml:
digest_mode: "digest"
digest_groups:
- name: "Events"
description: "Conferences, meetups, releases, launches, announcements"
- name: "News"
description: "Politics, economy, world affairs, breaking news"
- name: "Sport"
description: "Sports results, transfers, tournaments, matches"Messages that fit none of them go into an automatic "Other" group.
All labels (header, statistics, bot commands) follow
output_language. The example above is inEnglish; the other values areRussian(default),Spanish,GermanandFrench.
Cross-channel deduplication (dedup_topics)
When several channels cover the same event, the digest normally gets one bullet point per channel. With dedup_topics on, the AI keeps the most informative description and lists all the sources on it:
settings:
digest_mode: "digest"
dedup_topics: true # default: false
digest_groups:
- name: "Tech"
description: "Technology news and releases"If TechCrunch and HackerNews both report the same product launch, the digest shows one bullet point with source: "TechCrunch, HackerNews" instead of two.
Note:
dedup_topicsdoes nothing indigest_mode: "channel", because deduplication happens while messages are grouped by topic.
Per-channel configuration
Besides the required id and name, each channel entry takes two optional overrides.
Per-channel lookback window (lookback_hours)
Overrides the global settings.lookback_hours for one channel. Give a quiet channel a wider window, or a busy one a narrower one.
channels:
- id: "@breaking_news"
name: "Breaking News"
# no lookback_hours — uses the global settings.lookback_hours
- id: "@weekly_digest"
name: "Weekly Newsletter"
lookback_hours: 168 # look back 7 days for this channel only
- id: -1001234567890
name: "High Volume Channel"
lookback_hours: 6 # only last 6 hours for this channellookback_hours must be a positive integer. If it is missing or null, the global value applies.
Per-channel AI instructions (prompt_extra)
Adds your own instructions to the system prompt for one channel, to steer tone, focus or format.
channels:
- id: "@cryptonews"
name: "Crypto News"
prompt_extra: "Focus only on price movements and regulatory news. Ignore opinion pieces."
- id: "@jobboard"
name: "Job Board"
prompt_extra: "Extract only senior engineering roles. Format as a list: Role — Company — Link."The text is appended to the channel's system prompt as is. Leave the field empty or omit it to keep the standard prompt.
Persistent storage
Telebrief doesn't keep raw messages by default. If you want the history, for your own queries or other LLM tools, turn on storage in config.yaml and every collected message is written to a database.
SQLite (default backend)
Needs no setup; messages go to a local SQLite file.
storage:
enabled: true
backend: sqlite
path: data/messages.db # relative to project rootIn Docker, docker-compose.yml already mounts data/ as a volume, so the database survives container restarts.
PostgreSQL (optional backend)
Use PostgreSQL when Telebrief runs on several hosts or when other programs read the messages at the same time.
storage:
enabled: true
backend: postgres
url: "postgresql://user:pass@host:5432/dbname"asyncpg is already in the Docker image and in the standard dependencies (uv sync), so there is nothing extra to install.
Schema
Both backends create the same table and index on first run; there are no migrations to apply by hand.
Column | Type | Description |
| text | Channel name from your config |
| text | Message author |
| text | Message body |
| text / timestamptz | Message timestamp |
| text | Telegram message link |
| bool / integer | Whether the message has media |
| text | Media type string |
| text / timestamptz | When the row was inserted |
Note: storage only appends. If lookback_hours windows of two runs overlap, messages from the overlap are stored twice.
Extensibility
Four parts of the pipeline can be swapped or extended from config.yaml without touching the core code. All the fields below are optional, and existing configs keep working.
Filters
Filters run after collection and before storage and summarization. A message a filter drops never reaches the AI or the database.
The built-in filters are in src/extensions/filters.py:
Filter | Purpose |
| Keep or drop messages containing a keyword (case-insensitive) |
| Keep or drop messages matching a regex pattern |
| Drop messages shorter than a set number of characters |
Set the global chain under settings.filters. Each entry needs a class_path (dotted import path) and can take a config dict, passed to the constructor as keyword arguments:
settings:
filters:
- class_path: src.extensions.filters.KeywordFilter
config:
include: ["job", "hiring", "remote"]
exclude: ["nsfw"]
- class_path: src.extensions.filters.MinLengthFilter
config:
min_chars: 30To change the chain for one channel, add filters: to that channel's entry. filters: [] turns filtering off for the channel; any other list replaces the global chain for it:
channels:
- id: "@jobboard"
name: "Job Board"
filters:
- class_path: src.extensions.filters.RegexFilter
config:
pattern: "senior|staff|principal"
mode: "include"A custom filter implements the MessageFilter Protocol:
from __future__ import annotations
from src.extensions.filters import MessageFilter
from src.config_loader import ChannelConfig
from src.collector import Message
class MyFilter:
name = "my_filter"
def __init__(self, custom_param: str = "") -> None:
self.custom_param = custom_param
async def filter(self, channel: ChannelConfig, messages: list[Message]) -> list[Message]:
return [m for m in messages if self.custom_param in (m.text or "")]Then reference it in config.yaml:
settings:
filters:
- class_path: mypackage.mymodule.MyFilter
config:
custom_param: "important"Prompts
The base prompt template is src/prompts/base_summary.txt. You can point to another template file or plug in your own PromptComposer class.
prompts:
base_template: src/prompts/base_summary.txt # path to template file
composer: "" # empty = built-in DefaultComposerThe built-in DefaultComposer builds the system prompt in this order, skipping empty parts:
base template (with {language} substituted)
+ group.prompt_extra (if channel belongs to a group with prompt_extra set)
+ channel.prompt_extra (if non-empty)For a custom composer, implement the PromptComposer Protocol and set composer to its dotted path:
from src.config_loader import ChannelConfig, DigestGroupConfig
from src.extensions.prompts import PromptComposer
class MyComposer:
def __init__(self, base_template: str, language: str) -> None:
self._base = base_template
self._language = language
def compose(self, channel: ChannelConfig, group: DigestGroupConfig | None) -> str:
return f"{self._base}\nRespond in {self._language}."Note: the constructor must take
(base_template: str, language: str)as its first two positional arguments. Otherwise Telebrief stops at startup with aTypeErrorthat says what is wrong.
prompts:
composer: mypackage.mymodule.MyComposerGroup binding
A channel can be bound to a digest_groups entry. The group's prompt_extra then goes into the prompt of every channel in the group, before the channel's own prompt_extra.
settings:
digest_groups:
- name: "Jobs"
description: "Job listings and hiring announcements"
prompt_extra: "Extract only role title, company, and link. Format as a list."
channels:
- id: "@techleads_jobs"
name: "Tech Jobs"
group: Jobs # must match a digest_groups name or "Other"
prompt_extra: "Focus on senior and staff-level positions only."A channel without group (or with group: null) gets the base template and its own prompt_extra only.
Storage queries
With storage on (storage.enabled: true), external tools can read messages through StorageBackend.query_messages:
from src.storage import SQLiteBackend
from datetime import datetime, timezone
backend = SQLiteBackend("data/messages.db")
await backend.initialize()
messages = await backend.query_messages(
channel_name="TechCrunch", # the configured channels[*].name (NOT the @id)
since=datetime(2026, 4, 1, tzinfo=timezone.utc),
until=datetime(2026, 4, 30, tzinfo=timezone.utc),
limit=500,
)All parameters are optional. channel_name is the readable channels[*].name from config.yaml, the same value written to the channel_name column at collection time; leave it out to query all channels. If you rename a channel in the config, new rows get the new name and old rows keep the old one. Results come newest first, at most limit of them (1000 by default, minimum 1).
MCP server
Telebrief can serve its digests over the Model Context Protocol, so an MCP client such as Claude Code can ask for a digest directly instead of reading it in Telegram.
The server runs inside the Telebrief process and shares the Telegram session, the configuration and the generation lock with the scheduler and the bot. It returns exactly the digest Telegram gets, byte for byte, including topic grouping and deduplication.
Enabling it
mcp:
enabled: true
host: "127.0.0.1"
port: 8765
path: "/mcp"Then register it with your client:
claude mcp add --transport http telebrief http://127.0.0.1:8765/mcpStdio mode
python main.py mcp serves the same tools over stdio, without the bot and the scheduler, for clients that start the server themselves. It reads the same config.yaml, .env and session and connects to Telegram only when a tool is called. Don't run it next to the main service on the same session file; if Telebrief is already running, use the HTTP endpoint above.
Tools
Tool | Arguments | Behaviour |
|
| Builds a fresh digest. Takes 20 to 90 seconds and spends AI provider tokens. |
| — | Returns the latest digest from the cache with the time it was built. Instant, costs nothing. |
|
| Returns the raw messages of one channel, without summarizing. Spends no AI tokens. |
|
| Searches all configured channels, or one, newest first. Spends no AI tokens. |
Every successful digest, whether started by the schedule, the bot or MCP, is saved to data/last_digest.json, so get_last_digest returns the same digest Telegram received.
Only one digest is built at a time. If the scheduler is already building one, an MCP call waits for it instead of opening a second Telegram session.
Reading a single channel
get_channel_messages shows what was actually posted in a channel, where a digest gives you the AI summary.
channel takes either the readable channels[*].name or the channels[*].id (@username or numeric) from config.yaml, case-insensitively. An unknown value returns an error with the list of configured channel names, so you don't need a separate call to find them.
The tool reads from persistent storage when storage is on and has messages for the requested window; otherwise it reads Telegram live. The response header says which one it used:
channel: AI News (from storage, 42 msgs, last 24h)
[2026-08-07T09:12:04+00:00] Alice
OpenAI released a new model...
https://t.me/ainews/1234
[2026-08-07T10:30:11+00:00] Bob
[photo] Benchmark chart
https://t.me/ainews/1235Messages come in chronological order; limit keeps the newest and drops the oldest. The live read takes the same lock as digest generation and applies the channel's filters, so both paths return the same messages.
It differs from digest generation in two ways, on purpose:
channels[*].lookback_hoursis ignored; the tool uses thehoursthe caller asked for.Messages that are only media come back as their placeholder text (
[photo],[video]), the same as in storage.
Searching
search_messages finds where and when something was mentioned. It uses Telegram's own search, so it covers each channel's full history, not only what Telebrief collected, and it works without persistent storage. It matches words, as the Telegram app does, not substrings or regexes.
Each call sends one Telegram request per channel searched, under the same lock as digest generation, so pass channel when you know where to look. Channel filters don't apply to search results. If a channel can't be searched (you left it, it's private, or Telegram rate-limited the request), the other channels still return results and the response header names the ones that failed:
search: 'kubernetes' (2 matches, last 30 days)
search failed in: Private Group
[2026-09-26T14:02:11+00:00] Tech News · Alice
Kubernetes 1.34 is out...
https://t.me/technews/812Security
The MCP server has no authentication. It relies on listening only on loopback, where the SDK also turns on DNS-rebinding protection. Anyone who can reach the port can start digest generation and read your channel summaries.
Keep host at 127.0.0.1; Telebrief logs a warning at startup if you bind to anything else. In Docker, publish the port as 127.0.0.1:8765:8765, not on all interfaces. If you do need remote access, put it behind a firewall or a reverse proxy with authentication.
Development and testing
The project uses uv and Python 3.14+. Setup, the full set of checks, code style and the PR process are described in the contributing guide.
Running tests
uv sync --extra dev
uv run pytest tests/ -v
uv run mypy src/FAQ
Q: Which output languages are supported?
A: English, Russian (default), Spanish, German and French, set with output_language. The channels themselves can be in any language.
Q: How many channels can I follow?
A: There is no hard limit. Each digest reads up to max_messages_per_channel messages per channel (500 by default), so run time and AI cost grow with the number of active channels.
Q: Can several people receive digests? A: No. Telebrief is built for one user: one Telegram account, one recipient.
Q: Does it work with group chats?
A: Yes. The setup wizard lists your groups along with channels, or you can add a group's ID to config.yaml like any channel.
Q: Is my Telegram account at risk?
A: Telebrief logs in as you through the Telegram user API (Telethon) and only reads messages. Still, it is a user session, not a bot, so Telegram's usual rules for third-party clients apply. The session file in sessions/ gives full access to your account, so keep it private.
Q: How much does it cost to run? A: Only the AI provider's tokens, which depend on the model and on how much your channels post. A nano or mini model keeps the cost low; with Ollama it's free.
Q: Can I use a local AI model?
A: Yes. Set ai_provider: "ollama" in config.yaml and run Ollama. From Docker, set ollama_base_url to http://host.docker.internal:11434; on Linux, also add extra_hosts: ["host.docker.internal:host-gateway"] to docker-compose.yml.
Q: Can I change the digest format?
A: The layout is defined in src/formatter.py, so changing it means building the image from source. Per-channel prompt_extra and custom prompts change what the AI writes without touching the code.
Contributing
Bug reports, feature requests, documentation fixes, new filters, AI providers, storage backends and translations are all welcome.
The contributing guide covers development setup, code style and the PR process.
The project follows the Contributor Covenant Code of Conduct.
Please report security issues privately, as described in the security policy.
License
MIT.
Credits
Built with:
Telethon: Telegram user API
python-telegram-bot: Bot API
OpenAI API: summarization with the OpenAI provider
Ollama: local summarization
Anthropic API: summarization with the Anthropic provider
APScheduler: task scheduling
MCP Python SDK: MCP server
Available Tools
4 toolsget_channel_messagesA
Return the individual messages of one configured channel, unsummarized.
Reads from Telebrief's message store when it holds the requested window, and falls back to a live Telegram read otherwise. Free and instant on the stored path; the fallback takes a few seconds. The response header says which was used.
Args: channel: Channel name or id as configured under channels[*] in config.yaml hours: How many hours back to look, 1 to 168 (default 24) limit: Maximum messages to return, 1 to 500, newest kept (default 200)
| Name | Required | Description | Default |
|---|---|---|---|
| hours | No | ||
| limit | No | ||
| channel | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses the dual data source (stored message store vs live Telegram read), the differing latency of each path, and that the response header reveals which path was used. It stops short of covering auth/prerequisite requirements or failure behavior on the fallback path.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
It is front-loaded with the core purpose, then the storage/fallback behavior, then a compact Args block. Every sentence carries information the agent cannot get from the structured fields, and the ranges and semantics justify restating the parameters despite the schema's default values.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be described. Given a 3-parameter, non-nested tool with no annotations, the description supplies the behavior (dual source, latency, header signal) and full parameter semantics needed to call it correctly. Nothing material is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate, and it does: it defines all three parameters, gives valid ranges (hours 1–168, limit 1–500), states defaults (24 and 200), explains that limit keeps the 'newest' messages, and clarifies that channel is a name or id 'as configured under channels[*] in config.yaml'. This is richer than the bare schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description pairs a concrete verb ('Return') with a precise resource ('individual messages of one configured channel') and adds the scoping qualifier 'unsummarized', which cleanly separates it from the digest-oriented siblings get_digest and get_last_digest. An agent can tell what it returns and how it differs from alternatives without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives clear operational context: the tool is 'free and instant on the stored path' when the store holds the window, and falls back to a live read taking 'a few seconds'. That tells the agent when it will be fast versus slow, but it never explicitly states when to prefer this over get_digest/get_last_digest or what happens if the channel is unconfigured.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_digestA
Generate a fresh digest of the configured Telegram channels.
Collects messages, summarizes them with AI and formats the result exactly as the digest delivered to Telegram. Takes roughly 20-90 seconds and costs AI provider tokens, so prefer get_last_digest when recent data is enough. Use hours=168 for a weekly digest.
Args: hours: How many hours back to look, 1 to 168 (default 24)
| Name | Required | Description | Default |
|---|---|---|---|
| hours | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the burden and does well: it discloses latency (20-90 seconds) and a real cost (AI provider tokens), which are the traits that actually affect an agent's decision to call it. It stops short of stating auth requirements or whether the generated digest is persisted, so it is strong but not exhaustive.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the action, then cost/latency context, then the routing alternative, then the argument. Every sentence carries decision-relevant information and the Args block is compact.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Output schema exists, so return values need no explanation. For a single-optional-param tool the description covers action, cost, latency, alternative selection and parameter bounds — nothing an agent needs to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0% (the schema only carries type and default), so the description must compensate, and it does: 'hours: How many hours back to look, 1 to 168 (default 24)' supplies the unit, meaning and a range constraint absent from the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Generate a fresh digest of the configured Telegram channels') and elaborates the pipeline (collect, AI-summarize, format). The word 'fresh' implicitly separates it from the sibling get_last_digest, so an agent can route without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes: 'prefer get_last_digest when recent data is enough', and gives a concrete configuration hint ('Use hours=168 for a weekly digest'). Both the alternative and the selecting condition are stated, not left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_last_digestA
Return the most recently generated digest without regenerating it.
Instant and free. The digest may be stale — its generation time is included in the response, so check whether it is recent enough before relying on it.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses that no regeneration occurs, that the call is instant and free, that results may be stale, and that generation time is in the response. It stops short of stating caching/auth behavior, but covers the key traits.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the core distinction ('without regenerating it') and adds only two short, high-value sentences with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return-value details needn't be repeated, and the staleness caveat plus the generation-time pointer make the description sufficient for correct invocation. Minor gaps only.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Zero parameters, so baseline is 4; there is nothing further the description needs to convey about inputs.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Return') and resource ('most recently generated digest') and immediately differentiates from the sibling get_digest by specifying 'without regenerating it'. An agent can distinguish it from get_digest without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides clear context for use ('Instant and free') and a condition for relying on the output ('check whether it is recent enough'). It doesn't explicitly name get_digest as the alternative for fresh digests, leaving that inference to the agent.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_messagesA
Find messages mentioning something across the configured Telegram channels.
Uses Telegram's own search, so it covers each channel's full history, not only what digests collected, and it matches words, not substrings or regexes. No AI tokens spent. Makes one Telegram request per channel: pass channel when you know where to look. Channel filters from config.yaml are not applied.
Args: query: Words to search for channel: Channel name or id as configured under channels[*]; all channels if omitted days: How many days back to search, 1 to 365 (default 30) limit: Maximum matches to return, 1 to 100, newest kept (default 30)
| Name | Required | Description | Default |
|---|---|---|---|
| days | No | ||
| limit | No | ||
| query | Yes | ||
| channel | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses Telegram-side search semantics (words, not substrings or regexes), that no AI tokens are spent, one Telegram request per channel, and that config.yaml channel filters are ignored. It omits auth/permission requirements and error or rate-limit handling.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded in the first sentence, followed by useful operational caveats and a compact Args block. The prose paragraph is slightly verbose relative to the four parameters but every sentence carries information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only search tool with an output schema (so return values need no explanation), the description covers matching semantics, request cost, scope defaults, and the config-filter caveat. It is nearly complete, lacking only guidance on empty results or failure modes.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate and it does: it documents all four parameters, including valid ranges (days 1-365, limit 1-100), defaults, and the non-obvious 'newest kept' truncation rule for limit. This is meaningfully more than the schema provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource with scope: 'Find messages mentioning something across the configured Telegram channels.' It also distinguishes itself from the digest tools by noting it covers each channel's full history rather than only digest-collected content. It does not explicitly contrast with get_channel_messages, the closest-looking sibling.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear context for use ('Uses Telegram's own search, so it covers each channel's full history') and tells the agent when to narrow scope ('pass channel when you know where to look'). It stops short of naming an explicit alternative tool or a when-not-to-use condition.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v3.7.4- Added
search_messages
3 tool updates
v3.6.0- First observed
get_channel_messages - First observed
get_digest - First observed
get_last_digest
TDQS
Scored across 4 tools
Each tool has a clearly distinct purpose, and the descriptions explicitly differentiate them: get_digest generates fresh summaries, get_last_digest returns cached output, search_messages searches across channels, and get_channel_messages returns raw per-channel messages. No overlap or ambiguity.
All tool names use a consistent snake_case verb_noun pattern (get_digest, search_messages, get_channel_messages, get_last_digest). The verbs are predictable and the names directly reflect their actions.
Four tools is a well-scoped set for a Telegram digest server. Each tool covers a distinct operation (fresh digest, cached digest, search, raw messages) and none feels redundant or missing for the core purpose.
The surface covers the main lifecycle: generating a digest, retrieving the latest digest, searching messages, and reading channel messages. A minor gap is the lack of a tool to list configured channels, which would help agents discover valid channel names without reading config.yaml.
Maintenance
Related MCP Connectors
Telegram bridge for your MCP-compatible agent. Bidirectional, no LLM in our stack.
- MysocialOAuthio.mysocial
Social media MCP server: your Instagram, TikTok, YouTube, LinkedIn and Threads history for your AI.
Share one project context across ChatGPT, Claude, Telegram and any MCP client.
Real-time chat for AI agents. Claude Code, Cursor, Cline and Codex join channels over MCP.
Related MCP Servers
- FlicenseAqualityAmaintenanceBridges AI assistants to a Telegram bot to enable two-way messaging, interactive confirmations, and live status updates. It supports automatic voice transcription via local Whisper models and provides secure, single-user communication for MCP-compatible hosts.46-
- AlicenseNot gradedqualityDmaintenanceA read-only Telegram MCP server that retrieves messages from your DMs, groups, and channels, enabling Claude to generate executive briefings from Telegram conversations.MIT
- FlicenseNot gradedqualityDmaintenanceModel Context Protocol server for Telegram. Let AI read, search, send, and forward your Telegram messages.15 npm-
- AlicenseNot gradedqualityDmaintenanceA local MCP server for Cursor and Claude Desktop that reads Telegram subscriptions over MTProto and exposes channel posts for digests and natural-language queries.13 npm1MIT