Skip to main content
Glama

send_platform_message_direct

Send messages directly to platform groups or users, bypassing LLM processing, with support for text, images, files, videos, and audio attachments.

Instructions

Directly send a message chain to a platform group/user (bypass LLM).

This calls AstrBot dashboard endpoint: POST /api/platform/send_message

Notes:

  • This is for sending to a real platform target (group/user), not WebChat.

  • Media parts:

    • If file_path is a local path, this tool will upload it to AstrBot first, then send it as an AstrBot-hosted URL.

    • If file_path/url is an http(s) URL, it will be forwarded as-is.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
platform_idYes
target_idYes
message_chainNo
messageNo
imagesNo
filesNo
videosNo
recordsNo
message_typeNoGroupMessage

Implementation Reference

  • The primary handler function for the 'send_platform_message_direct' tool. Processes input parameters, handles local media files by uploading them or using URLs/local paths based on config, normalizes the message chain, and invokes the AstrBotClient to send the message directly to the platform via /api/platform/send_message.
    async def send_platform_message_direct( platform_id: str, target_id: str, message_chain: Optional[List[MessagePart]] = None, message: Optional[str] = None, images: Optional[List[str]] = None, files: Optional[List[str]] = None, videos: Optional[List[str]] = None, records: Optional[List[str]] = None, message_type: Literal["GroupMessage", "FriendMessage"] = "GroupMessage", ) -> Dict[str, Any]: """ Directly send a message chain to a platform group/user (bypass LLM). This calls AstrBot dashboard endpoint: POST /api/platform/send_message Notes: - This is for sending to a real platform target (group/user), not WebChat. - Media parts: - If `file_path` is a local path, this tool will upload it to AstrBot first, then send it as an AstrBot-hosted URL. - If `file_path`/`url` is an http(s) URL, it will be forwarded as-is. """ client = AstrBotClient.from_env() onebot_like = platform_id.strip().lower() in { "napcat", "onebot", "cqhttp", "gocqhttp", "llonebot", } if message_chain is None: message_chain = [] if message: message_chain.append({"type": "plain", "text": message}) for src in images or []: message_chain.append({"type": "image", "file_path": src}) for src in files or []: message_chain.append({"type": "file", "file_path": src}) for src in records or []: message_chain.append({"type": "record", "file_path": src}) for src in videos or []: message_chain.append({"type": "video", "file_path": src}) async def build_chain(mode: str) -> tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: normalized_chain: List[Dict[str, Any]] = [] uploaded_attachments: List[Dict[str, Any]] = [] for part in message_chain or []: p_type = part.get("type") if p_type in ("image", "file", "record", "video"): file_path = part.get("file_path") url = part.get("url") file_name = part.get("file_name") mime_type = part.get("mime_type") src = url or file_path if not src: continue normalized = dict(part) if not isinstance(src, str): raise ValueError(f"Invalid media source (expected str): {src!r}") if src.startswith(("http://", "https://")): normalized["file_path"] = src if onebot_like: normalized.setdefault("file", src) normalized.pop("url", None) normalized_chain.append(normalized) continue try: local_path = _resolve_local_file_path(client, src) except ValueError as e: raise ValueError(str(e)) from e except FileNotFoundError as e: raise FileNotFoundError(f"Local file_path does not exist: {src!r}") from e if mode == "local": normalized["file_path"] = local_path normalized.pop("url", None) if onebot_like: uri = _as_file_uri(local_path) normalized.setdefault("file", uri or local_path) normalized_chain.append(normalized) continue if mode != "upload": raise ValueError(f"Unknown direct media mode: {mode!r}") if not file_name: file_name = os.path.basename(local_path) or None attach_resp = await client.post_attachment_file( local_path, file_name=file_name, mime_type=mime_type, ) if attach_resp.get("status") != "ok": raise RuntimeError(attach_resp.get("message") or "Attachment upload failed") attach_data = attach_resp.get("data") or {} attachment_id = attach_data.get("attachment_id") if not attachment_id: raise RuntimeError( "Attachment upload succeeded but attachment_id is missing" ) download_url = _attachment_download_url(client, str(attachment_id)) normalized["file_path"] = download_url if onebot_like: normalized.setdefault("file", download_url) normalized.pop("url", None) normalized.pop("file_name", None) normalized.pop("mime_type", None) uploaded_attachments.append(attach_data) normalized_chain.append(normalized) else: normalized_chain.append(dict(part)) return normalized_chain, uploaded_attachments # Prefer local paths (more compatible with Napcat / Windows), but keep an upload fallback. try: mode = _direct_media_mode(client) except ValueError as e: return { "status": "error", "message": str(e), "platform_id": platform_id, "session_id": str(target_id), "message_type": message_type, } modes_to_try = ["local", "upload"] if mode == "auto" else [mode] last_error: Dict[str, Any] | None = None for attempt_mode in modes_to_try: try: normalized_chain, uploaded_attachments = await build_chain(attempt_mode) except FileNotFoundError as e: return { "status": "error", "message": str(e), "platform_id": platform_id, "session_id": str(target_id), "message_type": message_type, "hint": "If you passed a relative path, set ASTRBOTMCP_FILE_ROOT (or run the server in the correct working directory).", } except ValueError as e: return { "status": "error", "message": str(e), "platform_id": platform_id, "session_id": str(target_id), "message_type": message_type, "hint": "Set ASTRBOTMCP_FILE_ROOT to control how relative paths are resolved.", } except Exception as e: return { "status": "error", "message": str(e), "platform_id": platform_id, "session_id": str(target_id), "message_type": message_type, "attempt_mode": attempt_mode, } if not normalized_chain: return { "status": "error", "message": "message_chain did not produce any valid message parts", "platform_id": platform_id, "session_id": str(target_id), "message_type": message_type, } try: direct_resp = await client.send_platform_message_direct( platform_id=platform_id, message_type=message_type, session_id=str(target_id), message_chain=normalized_chain, ) except Exception as e: status_code = getattr(getattr(e, "response", None), "status_code", None) hint = "Ensure AstrBot includes /api/platform/send_message and you are authenticated." if status_code in (404, 405): hint = ( "Your AstrBot may not expose /api/platform/send_message (some versions only provide " "/api/platform/stats and /api/platform/webhook). Upgrade AstrBot or add an HTTP route for sending." ) return { "status": "error", "message": ( f"AstrBot API error: HTTP {status_code}" if status_code is not None else f"AstrBot API error: {e}" ), "platform_id": platform_id, "session_id": str(target_id), "message_type": message_type, "attempt_mode": attempt_mode, "detail": _httpx_error_detail(e), "hint": hint, } status = direct_resp.get("status") if status == "ok": data = direct_resp.get("data") or {} return { "status": "ok", "platform_id": data.get("platform_id", platform_id), "session_id": data.get("session_id", str(target_id)), "message_type": data.get("message_type", message_type), "attempt_mode": attempt_mode, "uploaded_attachments": uploaded_attachments, } last_error = { "status": status, "platform_id": platform_id, "session_id": str(target_id), "message_type": message_type, "attempt_mode": attempt_mode, "message": direct_resp.get("message"), "raw": direct_resp, } return last_error or { "status": "error", "message": "Failed to send message", "platform_id": platform_id, "session_id": str(target_id), "message_type": message_type, }
  • Registers the send_platform_message_direct tool handler with the FastMCP server instance.
    server.tool(astrbot_tools.send_platform_message_direct, name="send_platform_message_direct")
  • Supporting method in AstrBotClient that executes the HTTP POST request to AstrBot's /api/platform/send_message endpoint with the normalized message chain.
    async def send_platform_message_direct( self, *, platform_id: str, message_type: str, session_id: str, message_chain: List[Dict[str, Any]], ) -> Dict[str, Any]: """ Send a message via AstrBot platform adapter (bypass LLM). Calls /api/platform/send_message (requires AstrBot >= version that includes this route). """ payload: Dict[str, Any] = { "platform_id": platform_id, "message_type": message_type, "session_id": session_id, "message_chain": message_chain, } response = await self._request("POST", "/api/platform/send_message", json_body=payload) return response.json()
  • Function signature defines the input schema (parameters and types), including MessagePart type import. Output is Dict[str, Any] with status, platform_id, etc.
    from ..types import MessagePart async def send_platform_message_direct( platform_id: str, target_id: str, message_chain: Optional[List[MessagePart]] = None, message: Optional[str] = None, images: Optional[List[str]] = None, files: Optional[List[str]] = None, videos: Optional[List[str]] = None, records: Optional[List[str]] = None, message_type: Literal["GroupMessage", "FriendMessage"] = "GroupMessage", ) -> Dict[str, Any]:

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/xunxiing/astrbotmcp'

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