Skip to main content
Glama

send_interactive_message

Send a formatted message with interactive inline keyboard buttons to engage users. Supports multiple button rows with callback data or URLs.

Instructions

Send a message with inline keyboard buttons.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
chat_idYesTarget chat ID.
textYesMessage text.
buttonsYesRows of buttons. Each button: {"text": "Label", "callback_data": "value"} or {"text": "Label", "url": "https://..."}.
parse_modeNoHTML, Markdown, MarkdownV2, or None.HTML
disable_notificationNoSend silently.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
okYes
errorNo
message_idNo
chat_idNo
dateNo

Implementation Reference

  • The main tool handler that sends a message with an inline keyboard. Checks chat permissions, builds the keyboard, sends via bot, and returns SendInteractiveResult.
    @mcp.tool
    async def send_interactive_message(
        chat_id: int,
        text: str,
        buttons: list[list[dict[str, str]]],
        parse_mode: str | None = "HTML",
        disable_notification: bool = False,
    ) -> SendInteractiveResult:
        """Send a message with inline keyboard buttons.
    
        Args:
            chat_id: Target chat ID.
            text: Message text.
            buttons: Rows of buttons. Each button: {"text": "Label", "callback_data": "value"}
                     or {"text": "Label", "url": "https://..."}.
            parse_mode: HTML, Markdown, MarkdownV2, or None.
            disable_notification: Send silently.
        """
        if not ctx.is_chat_allowed(chat_id):
            result = SendInteractiveResult(
                ok=False,
                error=f"Chat {chat_id} is not in allowed_chat_ids.",
            )
            if ctx.audit_logger:
                ctx.audit_logger.log(
                    "send_interactive_message",
                    {"chat_id": chat_id, "text": text},
                    result.ok,
                    result.error,
                )
            return result
    
        keyboard = _build_keyboard(buttons)
        if isinstance(keyboard, str):
            result = SendInteractiveResult(ok=False, error=keyboard)
            if ctx.audit_logger:
                ctx.audit_logger.log(
                    "send_interactive_message",
                    {"chat_id": chat_id, "text": text},
                    result.ok,
                    result.error,
                )
            return result
    
        try:
            if ctx.rate_limiter:
                await ctx.rate_limiter.acquire()
            msg = await ctx.bot.send_message(
                chat_id=chat_id,
                text=text,
                parse_mode=normalize_parse_mode(parse_mode),
                reply_markup=keyboard,
                disable_notification=disable_notification,
            )
            result = SendInteractiveResult(
                ok=True,
                message_id=msg.message_id,
                chat_id=msg.chat.id,
                date=msg.date.isoformat(),
            )
        except ValueError as exc:
            result = SendInteractiveResult(ok=False, error=str(exc))
        except (TelegramBadRequest, TelegramForbiddenError) as exc:
            result = SendInteractiveResult(ok=False, error=str(exc))
    
        if ctx.audit_logger:
            ctx.audit_logger.log(
                "send_interactive_message",
                {"chat_id": chat_id, "text": text},
                result.ok,
                result.error,
            )
        return result
  • Pydantic response model for the send_interactive_message tool, extending ToolResponse with message_id, chat_id, and date fields.
    class SendInteractiveResult(ToolResponse):
        message_id: int | None = None
        chat_id: int | None = None
        date: str | None = None
  • Helper function that validates and constructs an InlineKeyboardMarkup from a nested list of button dicts.
    def _build_keyboard(
        buttons: list[list[dict[str, str]]],
    ) -> InlineKeyboardMarkup | str:
        """Build InlineKeyboardMarkup from a list of button rows.
    
        Returns the markup on success, or an error string on validation failure.
        Each button dict must have 'text' and either 'callback_data' or 'url'.
        """
        rows: list[list[InlineKeyboardButton]] = []
        for row_idx, row in enumerate(buttons):
            built_row: list[InlineKeyboardButton] = []
            for btn_idx, btn in enumerate(row):
                if "text" not in btn:
                    return f"Button [{row_idx}][{btn_idx}] is missing required 'text' field."
                if "callback_data" not in btn and "url" not in btn:
                    return (
                        f"Button [{row_idx}][{btn_idx}] must have 'callback_data' or 'url'."
                    )
                built_row.append(
                    InlineKeyboardButton(
                        text=btn["text"],
                        callback_data=btn.get("callback_data"),
                        url=btn.get("url"),
                    )
                )
            rows.append(built_row)
        return InlineKeyboardMarkup(inline_keyboard=rows)
  • Registration call that wires the interactive tools (including send_interactive_message) into the FastMCP server.
    register_interactive_tools(self._mcp, self._ctx, allowed_tools=at)
  • Permission mapping assigning send_interactive_message to the MESSAGING permission level.
    "send_interactive_message": PermissionLevel.MESSAGING,
    "edit_message": PermissionLevel.MESSAGING,
Behavior2/5

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

With no annotations provided, the description carries full burden for behavioral traits, but only mentions the core action. It doesn't disclose limits on buttons, behavior on click, or interaction with callback data, leaving key behavioral aspects unexplained.

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?

Single sentence is very concise and front-loaded with the key differentiator (inline keyboard buttons). No wasted words, but could be expanded slightly without breaking 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?

Despite having an output schema (implying structured return), the description is too minimal. It doesn't explain button behavior, limits, or how the message appears. For a tool with 5 parameters and complex button structure, more context is needed.

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?

Schema covers 100% of parameters, so baseline is appropriate. The description adds no extra parameter meaning beyond what the schema already provides, but doesn't detract either.

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?

Description clearly states the tool sends a message with inline keyboard buttons, distinguishing it from plain send_message and other send tools. It's specific and directly matches the tool name.

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 like send_message, edit_message, or answer_callback_query. The description lacks context about when inline keyboard messages are appropriate or how they differ from regular messages.

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/Py2755/aiogram-mcp'

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