Telebrief
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
Automated Telegram Digest Generator powered by AI
Telebrief collects messages from your Telegram channels (in any language), generates AI-powered summaries, and delivers beautiful daily digests directly to your Telegram account. Group digests by channel or by AI-detected topics. Supports multiple AI providers: OpenAI, Ollama (local), and Anthropic. Output language is configurable (default: Russian).
โจ Features
๐ Multi-language Support - Reads channels in ANY language (English, Russian, Ukrainian, Chinese, etc.)
๐ Configurable Output Language - All UI labels, summaries, and bot messages in any language (default: Russian)
๐ค Multi-Provider AI - Supports OpenAI (including GPT-6 Luna, Sol, Astra), Ollama (local), and Anthropic for summarization
โฐ Scheduled & On-Demand - Daily automatic digests + instant generation via bot commands
๐ Private Channel Support - Access your private chats and channels
๐ Digest Modes - Group by channel (default) or by AI-detected topics like News, Events, Sport
๐จ Smart Formatting - Markdown with emojis, bullet points, and clickable channel links
๐จ Long Message Splitting - Digests that exceed Telegram's 4096-character limit are automatically split into sequential messages instead of being truncated
๐ Secure - Single-user only, credentials stored safely
๐งน Auto-cleanup - Automatically removes old digest messages
๐ MCP Server - Optional built-in MCP endpoint so AI agents can pull digests instead of reading Telegram
Related MCP server: telegram-briefing-mcp
๐ Prerequisites
Before you begin, you'll need:
Docker - Install Docker
Telegram App Credentials - Get from my.telegram.org
api_idandapi_hashIf the form at my.telegram.org/apps only shows
ERROR, the rejection comes from Telegram, not Telebrief. Workarounds that usually help:Use a unique, random alphanumeric App title and Short name (Short name: 5โ32 letters/digits, no spaces)
Turn off VPN, proxy, and ad-blocking extensions; try a private window or another browser
Switch networks, e.g. mobile data instead of Wi-Fi
Submit again a few times; the check is intermittent
If nothing works, contact Telegram support. Never enter your login code on third-party sites that offer to create an app for you.
Telegram Bot Token - Create via @BotFather
Send
/newbotto create a new botSave the bot token
AI Provider API Key (one of the following):
OpenAI: Get from platform.openai.com
Anthropic: Get from console.anthropic.com
Ollama: No API key needed - install locally
๐ Quick Start
No clone and no Python needed. 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. Your user ID is taken 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. Re-run the wizard any time: it reuses the existing session and asks before overwriting files.
Images are published to GitHub Container Registry on every release with tags latest, X.Y (minor), X.Y.Z (patch). To build from source, replace the image: line in docker-compose.yml with build: .. For all options beyond the wizard, see config.yaml.example.
๐ค Bot Commands
Open Telegram and message your bot:
Command | Description |
| Show welcome message and available commands |
| Display help message with all commands |
| Generate and send digest for last 24 hours (uses configured |
| Show configuration, next scheduled run, and system info |
| Manually delete old digest messages |
๐ Example Output
Telebrief supports two digest modes configured via digest_mode in config.yaml.
Channel mode (digest_mode: "channel" โ default)
Groups summaries by source channel with clickable channel links:
# ๐ Daily Digest โ May 2, 2026
## ๐ฏ Overview
Today's main themes: AI tooling dominated with Anthropic's Claude Opus 4.7
release, crypto markets rallied on spot ETF approvals, EU finalized
amendments to the AI Act.
---
## ๐ป TechCrunch
- ๐ **Claude Opus 4.7 released**: 1M context window, faster output
- ๐ค **OpenAI GPT-6 leak**: Multimodal benchmarks surface early
- ๐ฑ **Apple Vision Pro 2**: Rumored Q3 launch with lighter frame
## ๐ฐ Crypto News
- ๐ **Bitcoin hits $89K**: Spot ETF inflows reach record high
- โ ๏ธ **SEC settles with Ripple**: Final ruling closes 6-year case
- ๐ **Ethereum Pectra upgrade**: Mainnet activation confirmed
---
๐ **Stats**: 20 channels, 1,847 messages processedTopic mode (digest_mode: "digest")
Groups summaries by AI-detected topics. You define topic groups 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 don't match any defined group are placed into an automatic "Other" category.
All labels (header, statistics, bot commands) follow the configured
output_language. The example above uses English; setoutput_language: "Russian"(or any other language) to change the output.
dedup_topics โ cross-channel deduplication
When multiple channels cover the same event, the grouper normally produces one bullet point per channel. Enable dedup_topics to instruct the AI to keep only the most informative description and merge the source attributions:
settings:
digest_mode: "digest"
dedup_topics: true # default: false
digest_groups:
- name: "Tech"
description: "Technology news and releases"With deduplication enabled, if TechCrunch and HackerNews both report the same product launch, the digest will contain a single bullet point with source: "TechCrunch, HackerNews" instead of two separate entries.
Note:
dedup_topicshas no effect indigest_mode: "channel"โ deduplication only applies during topic-based grouping.
โ๏ธ Per-Channel Configuration
Each channel entry supports two optional overrides in addition to the required id and name fields.
lookback_hours โ per-channel lookback window
Override the global settings.lookback_hours for a specific channel. Useful when some channels post infrequently and need a wider collection window, or when you want a tighter window for high-volume channels.
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 omitted or set to null, the global value is used.
prompt_extra โ per-channel AI instructions
Append extra instructions to the AI system prompt when summarizing a specific channel. Use this to guide tone, focus, or format for channels that need special treatment.
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."prompt_extra is appended verbatim to the channel's summarization system prompt. Leave it empty (or omit the field) for standard behavior.
๐๏ธ Persistent Storage
By default, Telebrief generates digests on demand without storing raw messages. You can enable a persistent storage layer that saves every collected message to a database for historical access or external LLM workflows.
Storage is disabled by default and opt-in via config.yaml.
SQLite (default backend)
No extra setup required. Messages are saved to a local SQLite file.
storage:
enabled: true
backend: sqlite
path: data/messages.db # relative to project rootWhen running in Docker, the data/ directory is already mounted as a volume in docker-compose.yml, so the database persists across container restarts.
PostgreSQL (optional backend)
Use PostgreSQL for multi-host deployments or when you need concurrent read access to the message store.
storage:
enabled: true
backend: postgres
url: "postgresql://user:pass@host:5432/dbname"asyncpg is included in the standard dependencies and is installed automatically by uv sync. No extra install step is needed.
Schema
Both backends create the same logical schema on first run (table and index are created automatically โ no manual migration needed):
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 is append-only. Overlapping lookback_hours windows across runs will produce duplicate rows for messages collected in both windows.
๐ Extensibility
Telebrief exposes four hook surfaces that let you customise behaviour via config.yaml without modifying core logic. All new fields are optional โ existing configs run unchanged.
Filters
A filter chain runs after message collection and before storage and summarization. Dropped messages never reach the AI or the database.
Built-in filters live in src/extensions/filters.py:
Filter | Purpose |
| Keep/drop messages by keyword substring (case-insensitive) |
| Keep or drop messages matching a regex pattern |
| Drop messages shorter than a character threshold |
Configure a global filter chain under settings.filters. Each entry needs a class_path (dotted import path) and an optional config dict passed as keyword arguments to the constructor:
settings:
filters:
- class_path: src.extensions.filters.KeywordFilter
config:
include: ["job", "hiring", "remote"]
exclude: ["nsfw"]
- class_path: src.extensions.filters.MinLengthFilter
config:
min_chars: 30Override the global chain for a single channel by adding filters: under that channel entry. Set filters: [] to disable filtering for that channel entirely, or provide a different list to replace the global chain for that channel only:
channels:
- id: "@jobboard"
name: "Job Board"
filters:
- class_path: src.extensions.filters.RegexFilter
config:
pattern: "senior|staff|principal"
mode: "include"Write your own filter by implementing 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 lives in src/prompts/base_summary.txt. You can point to a custom template file or plug in a custom PromptComposer class.
prompts:
base_template: src/prompts/base_summary.txt # path to template file
composer: "" # empty = built-in DefaultComposerThe built-in DefaultComposer assembles the final system prompt in this order (empty parts are skipped):
base template (with {language} substituted)
+ group.prompt_extra (if channel belongs to a group with prompt_extra set)
+ channel.prompt_extra (if non-empty)To use 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 accept
(base_template: str, language: str)as its first two positional arguments. A mismatched signature raises aTypeErrorat startup with a descriptive message.
prompts:
composer: mypackage.mymodule.MyComposerGroup binding
Channels can be bound to a digest_groups entry. The group's prompt_extra is then injected into every channel in that 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."Channels without a group field (or group: null) use the base template and their own prompt_extra only.
Storage queries
When storage is enabled (storage.enabled: true), the StorageBackend exposes a query_messages read API for external tooling:
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 matches the human-readable channels[*].name value from config.yaml (this is the value persisted to the channel_name column at collection time); omit it to query across all channels. Renaming a channel in config will change the value stored for new rows โ historical rows keep the old name. Results are ordered by timestamp descending and capped at limit (default 1000, must be โฅ 1).
๐ MCP Server
Telebrief can expose its digests over the Model Context Protocol, so an MCP client (Claude Code, for example) can request a digest directly instead of reading it in Telegram.
The server runs inside the Telebrief process, sharing its Telegram session, configuration and generation lock with the scheduler and the bot. Digests it returns are byte-for-byte what Telegram receives, 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 launch 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 alongside the main service on the same session file: prefer the HTTP endpoint above when Telebrief is already running.
Tools
Tool | Arguments | Behaviour |
|
| Generates a fresh digest. Takes 20โ90 seconds and spends AI provider tokens. |
| โ | Returns the most recent digest from cache, with its generation time. Instant and free. |
|
| Returns the individual messages of one channel, unsummarized. No AI tokens spent. |
Every successful digest โ scheduled, bot-triggered or MCP-triggered โ is cached to data/last_digest.json, so get_last_digest serves the same digest that was delivered to Telegram.
Digest generation is serialized: if the scheduler is already building a digest, an MCP call waits for it to finish rather than opening a second Telegram session.
Reading a single channel
get_channel_messages answers "what was actually posted in this channel", as opposed to the AI summary a digest gives you.
channel accepts either form from config.yaml โ the human-readable channels[*].name or the channels[*].id (@username or numeric) โ matched case-insensitively. An unknown value fails with the list of configured channel names, so no separate discovery call is needed.
The tool reads from persistent storage when it is enabled and holds messages for the requested window, and falls back to a live Telegram read otherwise. The response header states which path was 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 back in chronological order; limit keeps the newest ones and drops the oldest. The live fallback runs under the same generation lock as digests and applies the channel's configured filters, so both paths return the same set of messages.
Two deliberate differences from digest generation:
channels[*].lookback_hoursis not applied โ the tool honours thehoursthe caller asked for.Media-only messages arrive as their placeholder text (
[photo],[video]), exactly as they are stored.
Security
The MCP server has no authentication. It relies on binding to loopback, where the SDK also enables DNS-rebinding protection. Anyone who can reach the port can trigger digest generation and read your channel summaries.
Keep host on 127.0.0.1. Telebrief logs a warning at startup if you bind anywhere else. In Docker, publish the port as 127.0.0.1:8765:8765 rather than exposing it on all interfaces, and put it behind a firewall or reverse proxy with auth if you genuinely need remote access.
๐ ๏ธ Development & Testing
This project uses uv for package management.
Running Tests
# Install development dependencies
uv sync --extra dev
# Run all tests
uv run pytest tests/ -v
# Type checking
uv run mypy src/
# Linting
uv tool run ruff check src/ tests/
# Auto-format code
make formatโ FAQ
Q: Can I change the output language?
A: Yes! Set output_language in config.yaml to any language (e.g., "English", "Spanish", "Chinese").
Q: How many channels can I monitor? A: Tested up to 50 channels. Performance depends on message volume.
Q: Can multiple users receive digests? A: Currently single-user only. Multi-user support would require database and additional auth logic.
Q: Does it work with group chats?
A: Yes! Add group chat IDs to config.yaml the same way as channels.
Q: How do I switch to topic-based digests?
A: Set digest_mode: "digest" in config.yaml and define your digest_groups. Each group has a name and description that guides the AI classification. An implicit "Other" group catches anything that doesn't match.
Q: Can I customize the digest format?
A: Yes! Edit src/formatter.py to change Markdown structure, emojis, and sections.
Q: How much does it cost to run? A: With OpenAI GPT-5-nano: ~$0.30/month. With Ollama: free (runs locally). Anthropic pricing varies by model.
Q: Can I use a local AI model?
A: Yes! Set ai_provider: "ollama" in config.yaml and install Ollama on your machine.
๐ค Contributing
Contributions are welcome! Bug reports, feature requests, documentation fixes, new filters, AI providers, storage backends, and translations are all appreciated.
Read the Contributing Guide for development setup, code style, and the PR process
This project follows the Contributor Covenant Code of Conduct
Found a security issue? Please report it privately โ see the Security Policy
๐ License
This project is licensed under the MIT License.
๐ Credits
Built with:
Telethon - Telegram User API
python-telegram-bot - Bot API
OpenAI API - AI Summarization (OpenAI provider)
Ollama - Local AI Summarization
Anthropic API - AI Summarization (Anthropic provider)
APScheduler - Task Scheduling
Available Tools
3 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.
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 full burden and does disclose meaningful behavior: non-trivial latency (20-90s), token cost, and that output matches the Telegram-delivered digest. It does not mention auth/permission needs, rate limits, or whether a run mutates any stored state (e.g., caching a digest), which are the remaining behavioral gaps.
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?
The purpose is front-loaded in the first sentence, followed by the two facts that affect tool choice (runtime/cost) and then the argument documentation. Every sentence earns its place; no restating of the name or 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 documentation is unnecessary, and the description still signals the output shape ('formats the result exactly as the digest delivered to Telegram'). Combined with the documented parameter and routing guidance, an agent has everything needed to call this correctly.
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% โ the schema exposes only a defaulted integer with no bounds or explanation. The description fully compensates by defining what 'hours' means (how far back to look), its valid range (1 to 168), and its default (24), adding constraints the schema does not encode.
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 gives a specific verb+resource ('Generate a fresh digest of the configured Telegram channels') and then spells out the internal pipeline (collect messages, summarize with AI, format as the Telegram digest). It also distinguishes itself from the sibling get_last_digest, so an agent can route between them 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?
It explicitly names the alternative and the condition that selects it: 'prefer get_last_digest when recent data is enough.' It also gives the cost/latency tradeoff (20-90 seconds, AI tokens) that justifies that preference, which is exactly the when-not guidance an agent needs.
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.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v3.6.0- First observed
get_channel_messages - First observed
get_digest - First observed
get_last_digest
TDQS
Scored across 3 tools
get_digest and get_last_digest are the closest pair, but the descriptions clearly frame one as cached/free/possibly-stale and the other as fresh/slow/costly, which makes the tradeoff easy to reason about. get_channel_messages is clearly distinct as raw per-channel data versus the summarized whole-digest view.
All three tools use a consistent snake_case verb_noun pattern (get_last_digest, get_digest, get_channel_messages), with the get_ prefix and the digest/channel noun matching the returned resource. No mixed conventions or vague verbs.
Three tools is lean but defensible for a focused read-only digest service: cached read, fresh generation, and drill-down into source messages. It errs slightly thin, as there is no discovery or configuration surface, but nothing feels redundant.
The core lifecycle (view last digest, generate new digest, inspect raw messages) is covered, but get_channel_messages requires a channel name 'as configured' with no tool to list configured channels, creating a discoverability dead end. No way to check freshness windows or configuration also limits self-service.
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.39 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.15 npm1MIT