Skip to main content
Glama

Telegram send photo

tg_send_photo

Send a photo to a Telegram chat via bot. Provide a photo URL or base64 data URI, with optional caption and parse mode.

Instructions

Send photo via telegram bot

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
photoYesPhoto URL or base64 data URI (e.g., data:image/png;base64,...
chat_idNoTelegram chat id, Default to get from environment variables
captionNoPhoto caption, 0-1024 characters after entities parsing
parse_modeNoMode for parsing entities in the caption. [text/MarkdownV2]
reply_to_message_idNoIdentifier of the message that will be replied to

Implementation Reference

  • The tg_send_photo async function that sends a photo via Telegram bot. It accepts a photo URL or base64 data URI, optional chat_id, caption, parse_mode, and reply_to_message_id. If photo starts with 'data:', it decodes the base64 into an InputFile; otherwise treats it as a URL. Then calls bot.send_photo() and returns the result as JSON.
    @mcp.tool(
        title="Telegram send photo",
        description="Send photo via telegram bot",
    )
    async def tg_send_photo(
        photo: str = Field(description="Photo URL or base64 data URI (e.g., data:image/png;base64,..."),
        chat_id: str = Field("", description="Telegram chat id, Default to get from environment variables"),
        caption: str = Field("", description="Photo caption, 0-1024 characters after entities parsing"),
        parse_mode: str = Field("", description=f"Mode for parsing entities in the caption. [text/MarkdownV2]"),
        reply_to_message_id: int = Field(0, description="Identifier of the message that will be replied to"),
    ):
        if parse_mode == TELEGRAM_MARKDOWN_V2:
            caption = telegramify_markdown.markdownify(caption)
    
        if photo.startswith("data:"):
            match = re.match(r"data:image/([^;]+);base64,(.*)", photo)
            if not match:
                return {"error": "Invalid base64 data URL format"}
            try:
                datas = base64.b64decode(match.group(2))
                photo = InputFile(io.BytesIO(datas), f"image.{match.group(1)}")
            except Exception as e:
                return {"error": f"Failed to decode base64: {str(e)}"}
    
        res = await bot.send_photo(
            chat_id=chat_id or TELEGRAM_DEFAULT_CHAT,
            photo=photo,
            caption=caption or None,
            parse_mode=parse_mode if parse_mode in [TELEGRAM_MARKDOWN_V2] else None,
            reply_to_message_id=reply_to_message_id or None,
        )
        return res.to_json()
  • Pydantic Field definitions for tg_send_photo parameters: photo (str, URL or base64 data URI), chat_id (str, default from env), caption (str, optional), parse_mode (str, text/MarkdownV2), reply_to_message_id (int, optional).
    @mcp.tool(
        title="Telegram send photo",
        description="Send photo via telegram bot",
    )
    async def tg_send_photo(
        photo: str = Field(description="Photo URL or base64 data URI (e.g., data:image/png;base64,..."),
        chat_id: str = Field("", description="Telegram chat id, Default to get from environment variables"),
        caption: str = Field("", description="Photo caption, 0-1024 characters after entities parsing"),
        parse_mode: str = Field("", description=f"Mode for parsing entities in the caption. [text/MarkdownV2]"),
        reply_to_message_id: int = Field(0, description="Identifier of the message that will be replied to"),
  • The add_tools function registers all Telegram tools (including tg_send_photo) on the FastMCP instance via @mcp.tool decorator. Called from mcp_notify/__init__.py line 20.
    def add_tools(mcp: FastMCP, logger=None):
        bot = Bot(
            TELEGRAM_BOT_TOKEN,
            base_url=f"{TELEGRAM_BASE_URL}/bot",
            base_file_url=f"{TELEGRAM_BASE_URL}/file/bot",
        ) if TELEGRAM_BOT_TOKEN else None
    
    
        @mcp.tool(
            title="Telegram send text",
            description="Send text or markdown message via telegram bot",
        )
        async def tg_send_message(
            text: str = Field(description="Text of the message to be sent, 1-4096 characters after entities parsing"),
            chat_id: str = Field("", description="Telegram chat id, Default to get from environment variables"),
            parse_mode: str = Field("", description=f"Mode for parsing entities in the message text. [text/MarkdownV2]"),
            reply_to_message_id: int = Field(0, description="Identifier of the message that will be replied to"),
        ):
            if not bot:
                return "Please set the `TELEGRAM_BOT_TOKEN` environment variable"
            if parse_mode == TELEGRAM_MARKDOWN_V2:
                text = telegramify_markdown.markdownify(text)
            res = await bot.send_message(
                chat_id=chat_id or TELEGRAM_DEFAULT_CHAT,
                text=text,
                parse_mode=parse_mode if parse_mode in [TELEGRAM_MARKDOWN_V2] else None,
                reply_to_message_id=reply_to_message_id or None,
            )
            return res.to_json()
    
    
        @mcp.tool(
            title="Telegram send photo",
            description="Send photo via telegram bot",
        )
        async def tg_send_photo(
            photo: str = Field(description="Photo URL or base64 data URI (e.g., data:image/png;base64,..."),
            chat_id: str = Field("", description="Telegram chat id, Default to get from environment variables"),
            caption: str = Field("", description="Photo caption, 0-1024 characters after entities parsing"),
            parse_mode: str = Field("", description=f"Mode for parsing entities in the caption. [text/MarkdownV2]"),
            reply_to_message_id: int = Field(0, description="Identifier of the message that will be replied to"),
        ):
            if parse_mode == TELEGRAM_MARKDOWN_V2:
                caption = telegramify_markdown.markdownify(caption)
    
            if photo.startswith("data:"):
                match = re.match(r"data:image/([^;]+);base64,(.*)", photo)
                if not match:
                    return {"error": "Invalid base64 data URL format"}
                try:
                    datas = base64.b64decode(match.group(2))
                    photo = InputFile(io.BytesIO(datas), f"image.{match.group(1)}")
                except Exception as e:
                    return {"error": f"Failed to decode base64: {str(e)}"}
    
            res = await bot.send_photo(
                chat_id=chat_id or TELEGRAM_DEFAULT_CHAT,
                photo=photo,
                caption=caption or None,
                parse_mode=parse_mode if parse_mode in [TELEGRAM_MARKDOWN_V2] else None,
                reply_to_message_id=reply_to_message_id or None,
            )
            return res.to_json()
    
    
        @mcp.tool(
            title="Telegram send video",
            description="Send video via telegram bot",
        )
        async def tg_send_video(
            video: str = Field(description="Video URL or base64 data URI (e.g., data:video/mp4;base64,..."),
            cover: str = Field("", description="Cover for the video in the message. Optional"),
            chat_id: str = Field("", description="Telegram chat id, Default to get from environment variables"),
            caption: str = Field("", description="Video caption, 0-1024 characters after entities parsing"),
            parse_mode: str = Field("", description=f"Mode for parsing entities in the caption. [text/MarkdownV2]"),
            reply_to_message_id: int = Field(0, description="Identifier of the message that will be replied to"),
        ):
            if parse_mode == TELEGRAM_MARKDOWN_V2:
                caption = telegramify_markdown.markdownify(caption)
    
            if video.startswith("data:"):
                match = re.match(r"data:video/([^;]+);base64,(.*)", video)
                if not match:
                    return {"error": "Invalid base64 data URL format"}
                try:
                    datas = base64.b64decode(match.group(2))
                    video = InputFile(io.BytesIO(datas), f"video.{match.group(1)}")
                except Exception as e:
                    return {"error": f"Failed to decode base64: {str(e)}"}
    
            res = await bot.send_video(
                chat_id=chat_id or TELEGRAM_DEFAULT_CHAT,
                video=video,
                cover=cover or None,
                caption=caption or None,
                parse_mode=parse_mode if parse_mode in [TELEGRAM_MARKDOWN_V2] else None,
                reply_to_message_id=reply_to_message_id or None,
            )
            return res.to_json()
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must disclose behavioral traits, but it only states the basic function. It fails to mention authentication, photo accessibility constraints, rate limits, or behavior when chat_id defaults to environment variable.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence, efficient and front-loaded with the core action. However, it could be slightly expanded with critical context without losing conciseness.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema and no description of return values or error handling. For a tool with 5 parameters and no annotations, the description lacks completeness on behavioral outcomes.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Input schema has 100% description coverage, so the schema already explains parameters. The description adds no additional semantic value beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description 'Send photo via telegram bot' clearly states the action (send) and resource (photo) with context (telegram bot), distinguishing it from sibling tools like tg_send_message or tg_send_audio.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to use this tool versus alternatives such as tg_send_file or other notification siblings. The context of multiple similar tools (e.g., tg_send_video, lark_send_text) makes this a significant omission.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aahl/mcp-notify'

If you have feedback or need assistance with the MCP directory API, please join our Discord server