The pipe-delimited text format (ID: N | ... | Message: <text>) breaks when
messages contain newlines, since \n serves as both record separator and can
appear inside message text. Downstream parsers that split on \n only capture
the first line
Switch to a JSON array output with fields: id, sender, date, text,
reply_to (optional), and engagement (optional, with views/forwards/reactions)
This is a breaking change for MCP clients that parse the old text format.
Since MCP tool results are typically consumed by LLMs which handle JSON
natively, this should be transparent for most consumers
Without calling `client.disconnect()`, the TCP connection to Telegram's
servers is abandoned when the process exits. If the server restarts
quickly (common with MCP stdio servers), Telegram sees two connections
using the same auth key simultaneously and permanently revokes it with
`AuthKeyDuplicatedError`, requiring the user to regenerate their session.
Add a `finally` block to `_main()` so `client.disconnect()` is always
called on exit, allowing Telegram's servers to clean up the connection
state promptly.
When a caller passes a file_path with an explicit extension (e.g. ticket.jpg),
Telethon honours that extension verbatim even if the actual media is a PDF.
Stripping the suffix before calling client.download_media() lets Telethon
append the correct extension based on the real file content.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Display unread_mentions_count for each chat when it is greater than zero.
The value is already available on the dialog object from Telegram API,
so this adds no extra API calls.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add filtering capabilities to the list_chats tool:
- unread_only: filter to show only chats with unread messages
- unmuted_only: filter to show only unmuted chats
- Display mute status in chat info output
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address review feedback from @chigwell and @l1v0n1:
- Restore parse_mode=parse_mode in send_message and reply_to_message
- Restore _resolve_readable_file_path() in send_file, send_voice,
send_sticker, and edit_chat_photo
- Use str(safe_path) instead of raw file_path in upload_file calls
The entity cache fix (resolve_entity/resolve_input_entity) is preserved
in all affected functions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
StringSession has no persistent entity cache. After each MCP server
restart, client.get_entity(positive_int) fails for basic group chats
because Telethon interprets positive integers as PeerUser IDs when
they're not in the cache.
Fix:
- Add resolve_entity() / resolve_input_entity() helpers that retry
after warming cache via get_dialogs() on ValueError
- Warm entity cache on startup in _main()
- Replace all ~72 client.get_entity() / client.get_input_entity()
call sites with the new helpers
Tested: 60/60 pass across 6 basic group chats (send_message,
get_history, reply, edit, draft, reactions, search, mark_as_read,
delete) with cold cache -> warm cache transition verified.
Fixeschigwell/telegram-mcp#76
contacts.SearchRequest returns both result.users and result.chats,
but only users were returned. Chats (channels, groups) are now
included, making search work by channel/group title as intended.
Shared folders in Telegram use DialogFilterChatlist type instead of
DialogFilter. Previously, all folder operations (list, get, create,
add/remove chat) silently ignored shared folders. This fix ensures
they are recognized and handled correctly alongside regular folders.
MCP clients (e.g. ZeroClaw) communicate via JSON-RPC over stdin/stdout.
Startup messages and warnings written to stdout corrupt the protocol
stream, causing parse failures. Redirect all print() calls to stderr.
Telethon's DeletePhotosRequest requires InputPhoto objects, which it derives
from Photo objects via get_input_photo(). Passing photo.id (an int) causes:
AttributeError: 'int' object has no attribute 'SUBCLASS_OF_ID'
TypeError: Cannot cast int to any kind of InputPhoto.
Fix: pass the Photo object directly (photos.photos[0]) so Telethon can
convert it to InputPhoto internally.
- implement server-side allowlist via CLI positional roots (fallback)\n- implement client MCP Roots override semantics (replace server roots when available)\n- add realpath + in-root validation, traversal/glob rejection, extension and size checks\n- make write path default to <first_root>/downloads when file_path is omitted\n- reintroduce upload_file tool with the same path security model\n- update README with security model and usage\n- add tests for root resolution, replacement semantics, traversal checks, and default write path\n- add pytest and pytest-asyncio to dev dependencies