diff --git a/backend/src/api/mcp/server.py b/backend/src/api/mcp/server.py index 7915d54..326f13c 100644 --- a/backend/src/api/mcp/server.py +++ b/backend/src/api/mcp/server.py @@ -1,3 +1,4 @@ +import re from collections.abc import Sequence from datetime import datetime from typing import Any @@ -23,6 +24,7 @@ from utils.files import resolve_mime from utils.read import ( annotations, chats, + feed, media, peers, people, @@ -32,6 +34,7 @@ from utils.read import ( watches, ) from utils.read.accounts import self_user_id, unknown_account_hint +from utils.read.feed import CURSOR_PATTERN, FeedChatKind, FeedFilter, FeedKind from utils.read.models import DEFAULT_LIMIT, Page from utils.read.period import DEFAULT_TZ, MAX_DAYS, known_timezone from utils.read.read_receipts import read_info @@ -73,8 +76,10 @@ Typical flows: transcripts; supports chat/sender/date filters and regex). - People: `get_person` gives one person in full: profile changes, shared groups, joins and leaves, deleted message count, the private chat. -- Forensics: `get_deleted_messages` and `get_message_versions` recover content - removed or edited in Telegram but kept in the archive. +- Forensics: `get_feed` lists what was deleted or edited in Telegram across + all chats, newest first, with the text before and after each edit; a bulk + deletion is one item with `message_count`. `get_deleted_messages` and + `get_message_versions` recover the content of one chat or one message. - Files: `download_media` gets any message attachment (photo, video, voice, document, ...). It pulls the file from Telegram if the archive lacks it and returns a public link valid 24h that needs no auth (`curl -O `), plus @@ -255,6 +260,38 @@ async def get_deleted_messages( ) +@mcp.tool +async def get_feed( + account_id: int, + kind: FeedKind = FeedKind.ALL, + since: datetime | None = None, + chat_id: int | None = None, + chat_kind: FeedChatKind | None = None, + limit: int = DEFAULT_LIMIT, + cursor: str | None = None, +) -> dict[str, Any]: + """Deleted and edited messages across chats, newest first. + + `kind` picks deleted, edited or all; `since` keeps items at or after that + time; `chat_id` or `chat_kind` (dm, group, channel) narrow it down. Edited + items carry `before` and `after` texts. A deleted item with + `message_count` above 1 is a bulk deletion in one chat; read its messages + with `get_deleted_messages` for that chat. Pass `next_cursor` back as + `cursor` for the next page. + """ + if cursor is not None and re.fullmatch(CURSOR_PATTERN, cursor) is None: + msg = "cursor must be a next_cursor returned by get_feed" + raise ToolError(msg) + page = await feed.list_feed( + await _pool(), + account_id, + FeedFilter(kind=kind, chat_id=chat_id, chat_kind=chat_kind, since=since), + limit=limit, + cursor=cursor, + ) + return page.model_dump(mode="json") + + @mcp.tool async def get_message_versions( account_id: int, chat_id: int, message_id: int