Skip to main content
Glama
Jem-HR
by Jem-HR

send_document

Send document messages via WhatsApp Business API. Upload documents with optional captions, filenames, footers, or reply functionality to specified recipients.

Instructions

Send a document message.

Args: to: Phone number or WhatsApp ID document: Document URL or media ID filename: Optional filename for the document caption: Optional document caption footer: Optional footer text reply_to_message_id: Message ID to reply to

Returns: Dictionary with success status and message ID

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
toYes
documentYes
filenameNo
captionNo
footerNo
reply_to_message_idNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • The send_document tool handler that sends document messages to WhatsApp users. It accepts parameters for recipient (to), document URL/media ID, optional filename, caption, footer, and reply_to_message_id. The function calls wa_client.send_document() and returns a success status with message ID.
    @mcp.tool()
    async def send_document(
        to: str,
        document: str,
        filename: Optional[str] = None,
        caption: Optional[str] = None,
        footer: Optional[str] = None,
        *,
        reply_to_message_id: Optional[str] = None,
    ) -> dict:
        """
        Send a document message.
        
        Args:
            to: Phone number or WhatsApp ID
            document: Document URL or media ID
            filename: Optional filename for the document
            caption: Optional document caption
            footer: Optional footer text
            reply_to_message_id: Message ID to reply to
        
        Returns:
            Dictionary with success status and message ID
        """
        try:
            result = wa_client.send_document(
                to=to,
                document=document,
                filename=filename,
                caption=caption,
                footer=footer,
                reply_to_message_id=reply_to_message_id,
            )
            
            logger.info(f"Document sent to {to}")
            message_id = getattr(result, 'id', str(result)) if result else None
            return {"success": True, "message_id": message_id}
        except Exception as e:
            logger.error(f"Failed to send document: {str(e)}")
            return {"success": False, "error": str(e)}
  • The register_messaging_tools function that registers all messaging-related tools including send_document. The send_document tool is registered using the @mcp.tool() decorator at line 140.
    def register_messaging_tools(mcp, wa_client: WhatsApp):
        """Register all messaging-related tools."""
        
        @mcp.tool()
        async def send_message(
            to: str,
            text: str,
            header: Optional[str] = None,
            footer: Optional[str] = None,
            *,
            preview_url: bool = False,
            reply_to_message_id: Optional[str] = None,
        ) -> dict:
            """
            Send a text message to a WhatsApp user.
            
            Args:
                to: Phone number (with country code) or WhatsApp ID
                text: The text message content
                header: Optional header text (for interactive messages)
                footer: Optional footer text (for interactive messages)
                preview_url: Whether to show URL previews (default False)
                reply_to_message_id: Message ID to reply to
            
            Returns:
                Dictionary with success status and message ID
            """
            try:
                result = wa_client.send_message(
                    to=to,
                    text=text,
                    header=header,
                    footer=footer,
                    preview_url=preview_url,
                    reply_to_message_id=reply_to_message_id,
                )
                
                logger.info(f"Message sent to {to}")
                # Extract just the message ID if result is a complex object
                message_id = getattr(result, 'id', str(result)) if result else None
                
                return {
                    "success": True,
                    "message_id": message_id,
                    "to": to,
                }
            except Exception as e:
                logger.error(f"Failed to send message: {str(e)}")
                return {"success": False, "error": str(e)}
        
        
        @mcp.tool()
        async def send_image(
            to: str,
            image: str,
            caption: Optional[str] = None,
            footer: Optional[str] = None,
            *,
            reply_to_message_id: Optional[str] = None,
        ) -> dict:
            """
            Send an image message.
            
            Args:
                to: Phone number or WhatsApp ID
                image: Image URL or media ID
                caption: Optional image caption
                footer: Optional footer text
                reply_to_message_id: Message ID to reply to
            
            Returns:
                Dictionary with success status and message ID
            """
            try:
                result = wa_client.send_image(
                    to=to,
                    image=image,
                    caption=caption,
                    footer=footer,
                    reply_to_message_id=reply_to_message_id,
                )
                
                logger.info(f"Image sent to {to}")
                message_id = getattr(result, 'id', str(result)) if result else None
                return {"success": True, "message_id": message_id}
            except Exception as e:
                logger.error(f"Failed to send image: {str(e)}")
                return {"success": False, "error": str(e)}
        
        
        @mcp.tool()
        async def send_video(
            to: str,
            video: str,
            caption: Optional[str] = None,
            footer: Optional[str] = None,
            *,
            reply_to_message_id: Optional[str] = None,
        ) -> dict:
            """
            Send a video message.
            
            Args:
                to: Phone number or WhatsApp ID
                video: Video URL or media ID
                caption: Optional video caption
                footer: Optional footer text
                reply_to_message_id: Message ID to reply to
            
            Returns:
                Dictionary with success status and message ID
            """
            try:
                result = wa_client.send_video(
                    to=to,
                    video=video,
                    caption=caption,
                    footer=footer,
                    reply_to_message_id=reply_to_message_id,
                )
                
                logger.info(f"Video sent to {to}")
                message_id = getattr(result, 'id', str(result)) if result else None
                return {"success": True, "message_id": message_id}
            except Exception as e:
                logger.error(f"Failed to send video: {str(e)}")
                return {"success": False, "error": str(e)}
        
        
        @mcp.tool()
        async def send_document(
            to: str,
            document: str,
            filename: Optional[str] = None,
            caption: Optional[str] = None,
            footer: Optional[str] = None,
            *,
            reply_to_message_id: Optional[str] = None,
        ) -> dict:
            """
            Send a document message.
            
            Args:
                to: Phone number or WhatsApp ID
                document: Document URL or media ID
                filename: Optional filename for the document
                caption: Optional document caption
                footer: Optional footer text
                reply_to_message_id: Message ID to reply to
            
            Returns:
                Dictionary with success status and message ID
            """
            try:
                result = wa_client.send_document(
                    to=to,
                    document=document,
                    filename=filename,
                    caption=caption,
                    footer=footer,
                    reply_to_message_id=reply_to_message_id,
                )
                
                logger.info(f"Document sent to {to}")
                message_id = getattr(result, 'id', str(result)) if result else None
                return {"success": True, "message_id": message_id}
            except Exception as e:
                logger.error(f"Failed to send document: {str(e)}")
                return {"success": False, "error": str(e)}
        
        
        @mcp.tool()
        async def send_audio(
            to: str,
            audio: str,
            *,
            reply_to_message_id: Optional[str] = None,
        ) -> dict:
            """
            Send an audio message.
            
            Args:
                to: Phone number or WhatsApp ID
                audio: Audio URL or media ID
                reply_to_message_id: Message ID to reply to
            
            Returns:
                Dictionary with success status and message ID
            """
            try:
                result = wa_client.send_audio(
                    to=to,
                    audio=audio,
                    reply_to_message_id=reply_to_message_id,
                )
                
                logger.info(f"Audio sent to {to}")
                message_id = getattr(result, 'id', str(result)) if result else None
                return {"success": True, "message_id": message_id}
            except Exception as e:
                logger.error(f"Failed to send audio: {str(e)}")
                return {"success": False, "error": str(e)}
        
        
        @mcp.tool()
        async def send_sticker(
            to: str,
            sticker: str,
            *,
            reply_to_message_id: Optional[str] = None,
        ) -> dict:
            """
            Send a sticker message.
            
            Args:
                to: Phone number or WhatsApp ID
                sticker: Sticker URL or media ID (must be webp format)
                reply_to_message_id: Message ID to reply to
            
            Returns:
                Dictionary with success status and message ID
            """
            try:
                result = wa_client.send_sticker(
                    to=to,
                    sticker=sticker,
                    reply_to_message_id=reply_to_message_id,
                )
                
                logger.info(f"Sticker sent to {to}")
                message_id = getattr(result, 'id', str(result)) if result else None
                return {"success": True, "message_id": message_id}
            except Exception as e:
                logger.error(f"Failed to send sticker: {str(e)}")
                return {"success": False, "error": str(e)}
        
        
        @mcp.tool()
        async def send_location(
            to: str,
            latitude: float,
            longitude: float,
            name: Optional[str] = None,
            address: Optional[str] = None,
            *,
            reply_to_message_id: Optional[str] = None,
        ) -> dict:
            """
            Send a location message.
            
            Args:
                to: Phone number or WhatsApp ID
                latitude: Latitude of the location
                longitude: Longitude of the location
                name: Optional location name
                address: Optional location address
                reply_to_message_id: Message ID to reply to
            
            Returns:
                Dictionary with success status and message ID
            """
            try:
                result = wa_client.send_location(
                    to=to,
                    latitude=latitude,
                    longitude=longitude,
                    name=name,
                    address=address,
                    reply_to_message_id=reply_to_message_id,
                )
                
                logger.info(f"Location sent to {to}")
                message_id = getattr(result, 'id', str(result)) if result else None
                return {"success": True, "message_id": message_id}
            except Exception as e:
                logger.error(f"Failed to send location: {str(e)}")
                return {"success": False, "error": str(e)}
        
        
        @mcp.tool()
        async def request_location(
            to: str,
            text: str,
            *,
            reply_to_message_id: Optional[str] = None,
        ) -> dict:
            """
            Request user's location.
            
            Args:
                to: Phone number or WhatsApp ID
                text: Message text asking for location
                reply_to_message_id: Message ID to reply to
            
            Returns:
                Dictionary with success status and message ID
            """
            try:
                result = wa_client.request_location(
                    to=to,
                    text=text,
                    reply_to_message_id=reply_to_message_id,
                )
                
                logger.info(f"Location request sent to {to}")
                message_id = getattr(result, 'id', str(result)) if result else None
                return {"success": True, "message_id": message_id}
            except Exception as e:
                logger.error(f"Failed to request location: {str(e)}")
                return {"success": False, "error": str(e)}
        
        
        @mcp.tool()
        async def send_contact(
            to: str,
            contact_name: str,
            contact_phone: str,
            *,
            reply_to_message_id: Optional[str] = None,
        ) -> dict:
            """
            Send a contact card.
            
            Args:
                to: Phone number or WhatsApp ID
                contact_name: Name of the contact
                contact_phone: Phone number of the contact
                reply_to_message_id: Message ID to reply to
            
            Returns:
                Dictionary with success status and message ID
            """
            try:
                # Create Contact object
                contact = Contact(
                    name=Contact.Name(formatted_name=contact_name),
                    phones=[Contact.Phone(phone=contact_phone)]
                )
                
                result = wa_client.send_contact(
                    to=to,
                    contact=contact,
                    reply_to_message_id=reply_to_message_id,
                )
                
                logger.info(f"Contact sent to {to}")
                message_id = getattr(result, 'id', str(result)) if result else None
                return {"success": True, "message_id": message_id}
            except Exception as e:
                logger.error(f"Failed to send contact: {str(e)}")
                return {"success": False, "error": str(e)}
        
        
        @mcp.tool()
        async def send_reaction(
            to: str,
            emoji: str,
            message_id: str,
            *,
            sender: Optional[str] = None,
        ) -> dict:
            """
            Send a reaction to a message.
            
            Args:
                to: Phone number or WhatsApp ID
                emoji: Reaction emoji
                message_id: ID of message to react to
                sender: Optional sender phone ID
            
            Returns:
                Dictionary with success status
            """
            try:
                result = wa_client.send_reaction(
                    to=to,
                    emoji=emoji,
                    message_id=message_id,
                    sender=sender,
                )
                
                logger.info(f"Reaction sent to message {message_id}")
                result_data = str(result) if result else None
                return {"success": True, "result": result_data}
            except Exception as e:
                logger.error(f"Failed to send reaction: {str(e)}")
                return {"success": False, "error": str(e)}
        
        
        @mcp.tool()
        async def remove_reaction(
            to: str,
            message_id: str,
            *,
            sender: Optional[str] = None,
        ) -> dict:
            """
            Remove a reaction from a message.
            
            Args:
                to: Phone number or WhatsApp ID
                message_id: ID of message to remove reaction from
                sender: Optional sender phone ID
            
            Returns:
                Dictionary with success status
            """
            try:
                result = wa_client.remove_reaction(
                    to=to,
                    message_id=message_id,
                    sender=sender,
                )
                
                logger.info(f"Reaction removed from message {message_id}")
                result_data = str(result) if result else None
                return {"success": True, "result": result_data}
            except Exception as e:
                logger.error(f"Failed to remove reaction: {str(e)}")
                return {"success": False, "error": str(e)}
        
        
        @mcp.tool()
        async def mark_message_as_read(
            message_id: str,
            *,
            sender: Optional[str] = None,
        ) -> dict:
            """
            Mark a message as read.
            
            Args:
                message_id: The WhatsApp message ID to mark as read
                sender: Optional phone ID
            
            Returns:
                Dictionary with success status
            """
            try:
                result = wa_client.mark_message_as_read(
                    message_id=message_id,
                    sender=sender,
                )
                
                logger.info(f"Message {message_id} marked as read")
                result_data = str(result) if result else None
                return {"success": True, "result": result_data}
            except Exception as e:
                logger.error(f"Failed to mark message as read: {str(e)}")
                return {"success": False, "error": str(e)}
        
        
        @mcp.tool()
        async def indicate_typing(
            message_id: str,
            *,
            sender: Optional[str] = None,
        ) -> dict:
            """
            Show typing indicator to WhatsApp user.
            
            This marks a message as read and displays a typing indicator to show
            the user that you are preparing a response. Best practice when it will
            take a few seconds to respond.
            
            IMPORTANT NOTES:
            - Typing indicator lasts max 25 seconds or until you send a message
            - Only use if you are actually going to respond  
            - Will be dismissed when you send the next message
            - Improves user experience for delayed responses
            
            EXAMPLE:
            {
              "message_id": "wamid.HBgNMjc2NTY4NjY5MDUVAgARGBI5QTNDMEM3RjVBMzY2Q0Y4AA=="
            }
            
            Args:
                message_id: The WhatsApp message ID to respond to (from incoming message)
                sender: Optional phone ID (defaults to client's phone ID)
            
            Returns:
                Dictionary with success status
            """
            try:
                result = wa_client.indicate_typing(
                    message_id=message_id,
                    sender=sender,
                )
                
                logger.info(f"Typing indicator shown for message {message_id}")
                # SuccessResult has a success boolean attribute
                success_status = getattr(result, 'success', bool(result))
                
                return {
                    "success": True, 
                    "typing_indicated": success_status,
                    "message_id": message_id
                }
            except Exception as e:
                logger.error(f"Failed to indicate typing: {str(e)}")
                return {"success": False, "error": str(e)}
        
        
        @mcp.tool()
        async def upload_media(
            media_path: str,
            *,
            mime_type: Optional[str] = None,
        ) -> dict:
            """
            Upload media file to WhatsApp servers.
            
            Args:
                media_path: Path to media file
                mime_type: Optional MIME type
            
            Returns:
                Dictionary with media ID
            """
            try:
                media_obj = wa_client.upload_media(
                    media=media_path,
                    mime_type=mime_type,
                )
                
                logger.info("Media uploaded successfully")
                return {
                    "success": True, 
                    "media_id": str(media_obj),
                    "media_type": type(media_obj).__name__
                }
            except Exception as e:
                logger.error(f"Failed to upload media: {str(e)}")
                return {"success": False, "error": str(e)}
  • The register_all_tools function that calls register_messaging_tools to register the send_document tool along with all other messaging tools.
    def register_all_tools(mcp, wa_client):
        """Register all available tools with the MCP server."""
        register_messaging_tools(mcp, wa_client)
        register_interactive_tools(mcp, wa_client)
        register_template_tools(mcp, wa_client)
Behavior2/5

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

No annotations are provided, so the description carries full burden. It states the tool sends a document message but doesn't disclose behavioral traits like authentication requirements, rate limits, error conditions, or what happens if the document URL is invalid. The return value is mentioned but without details on failure cases or side effects.

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 appropriately sized and front-loaded: the first sentence states the purpose clearly. The parameter and return sections are organized but could be more concise; some explanations are brief but effective. Overall, it avoids unnecessary verbosity while maintaining clarity.

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

Completeness3/5

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

Given 6 parameters with 0% schema coverage and no annotations, the description provides basic parameter semantics and mentions returns, but lacks behavioral context. An output schema exists, so return values don't need explanation, but for a messaging tool with siblings, more guidance on usage and errors would improve completeness.

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 description coverage is 0%, so the description must compensate. It lists all 6 parameters with brief explanations (e.g., 'Phone number or WhatsApp ID' for 'to'), which adds meaning beyond the bare schema. However, it doesn't provide format details, constraints, or examples, leaving gaps in understanding parameter usage.

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

Purpose4/5

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

The description clearly states the tool's purpose: 'Send a document message.' It specifies the action (send) and resource (document message), which is specific and unambiguous. However, it doesn't explicitly differentiate from sibling tools like send_image or send_video, which would require a 5.

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?

The description provides no guidance on when to use this tool versus alternatives. With sibling tools like send_message, send_image, send_video, and send_audio, there's no indication of when a document message is appropriate versus other message types. The description only lists parameters without contextual usage advice.

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/Jem-HR/pywa-mcp-server'

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