Skip to content

Commit ed81221

Browse files
author
alexesipenko
committed
feat: return media as MCP content
1 parent f3c7683 commit ed81221

2 files changed

Lines changed: 69 additions & 0 deletions

File tree

telegram_mcp/tools/media.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,47 @@
11
"""Media MCP tools."""
22

3+
from mcp.server.fastmcp import Audio, Image
4+
35
from telegram_mcp.runtime import *
46

57

8+
MAX_INLINE_MEDIA_BYTES = 20 * 1024 * 1024
9+
10+
11+
@mcp.tool(annotations=ToolAnnotations(title="Get Media", openWorldHint=True, readOnlyHint=True))
12+
@with_account(readonly=True)
13+
@validate_id("chat_id")
14+
async def get_media(
15+
chat_id: Union[int, str], message_id: int, account: str = None
16+
) -> Any:
17+
"""Return a Telegram image or audio attachment as native MCP media content."""
18+
try:
19+
cl = get_client(account)
20+
entity = await resolve_entity(chat_id, cl)
21+
msg = await cl.get_messages(entity, ids=message_id)
22+
if not msg or not msg.media:
23+
return "No media found in the specified message."
24+
25+
mime = getattr(getattr(msg, "file", None), "mime_type", None)
26+
if not mime and getattr(msg, "photo", None) is not None:
27+
mime = "image/jpeg"
28+
if not mime or not (mime.startswith("image/") or mime.startswith("audio/")):
29+
return "Native MCP media is available for images and audio only."
30+
31+
data = await cl.download_media(msg, bytes)
32+
if not data:
33+
return f"Download failed for message {message_id}."
34+
if len(data) > MAX_INLINE_MEDIA_BYTES:
35+
return f"Media is too large for inline MCP delivery ({len(data)} bytes)."
36+
37+
media_type, media_format = mime.split("/", 1)
38+
if media_type == "image":
39+
return Image(data=data, format=media_format)
40+
return Audio(data=data, format=media_format)
41+
except Exception as e:
42+
return log_and_format_error("get_media", e, chat_id=chat_id, message_id=message_id)
43+
44+
645
@mcp.tool(annotations=ToolAnnotations(title="Send File", openWorldHint=True, destructiveHint=True))
746
@with_account(readonly=False)
847
@validate_id("chat_id")

tests/test_media_album.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,36 @@ async def send_file(self, entity, file_paths, caption=None):
1616
}
1717

1818

19+
@pytest.mark.asyncio
20+
async def test_get_media_returns_native_image_content(monkeypatch):
21+
class Client:
22+
async def get_messages(self, entity, ids):
23+
assert (entity, ids) == ("chat", 42)
24+
return type(
25+
"Message",
26+
(),
27+
{"media": object(), "photo": object(), "file": type("File", (), {"mime_type": None})()},
28+
)()
29+
30+
async def download_media(self, message, output):
31+
assert output is bytes
32+
return b"image-bytes"
33+
34+
client = Client()
35+
monkeypatch.setattr(media, "get_client", lambda account=None: client)
36+
37+
async def resolve(chat_id, cl):
38+
assert (chat_id, cl) == (123, client)
39+
return "chat"
40+
41+
monkeypatch.setattr(media, "resolve_entity", resolve)
42+
43+
result = await media.get_media(123, 42)
44+
45+
assert result.data == b"image-bytes"
46+
assert result._format == "jpeg"
47+
48+
1949
@pytest.mark.asyncio
2050
@pytest.mark.parametrize("tool_name", ["send_album", "send_file"])
2151
async def test_album_mode_sends_multiple_files_as_one_media_group(

0 commit comments

Comments
 (0)