Skip to main content
Glama

list_recent_chats

List recent iMessage conversations with previews and unread counts, organized from most recent to oldest.

Instructions

List recent chats, newest-first, with previews and unread counts.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
limitNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • MCP tool handler that exposes list_recent_chats as a @mcp.tool(), delegating to db.list_recent_chats()
    @mcp.tool()
    def list_recent_chats(limit: int = 20) -> list[dict[str, Any]]:
        """List recent chats, newest-first, with previews and unread counts."""
        return db.list_recent_chats(limit=limit)
  • The @mcp.tool() decorator registers list_recent_chats with the FastMCP server
    @mcp.tool()
    def list_recent_chats(limit: int = 20) -> list[dict[str, Any]]:
        """List recent chats, newest-first, with previews and unread counts."""
        return db.list_recent_chats(limit=limit)
  • Core business logic: queries chat.db SQLite for recent chats (LIMIT clamped to 1-200), returns chat_id/display_name/participants/last_message_date/truncated preview/unread_count
    def list_recent_chats(limit: int = 20) -> list[dict[str, Any]]:
        limit = max(1, min(int(limit), 200))
        with _open() as conn:
            rows = conn.execute(
                """
                SELECT c.ROWID AS chat_id,
                       c.display_name,
                       c.chat_identifier,
                       (
                         SELECT GROUP_CONCAT(h.id, ', ')
                         FROM chat_handle_join chj
                         JOIN handle h ON h.ROWID = chj.handle_id
                         WHERE chj.chat_id = c.ROWID
                       ) AS participants,
                       (
                         SELECT MAX(m.date)
                         FROM chat_message_join cmj
                         JOIN message m ON m.ROWID = cmj.message_id
                         WHERE cmj.chat_id = c.ROWID
                       ) AS last_date,
                       (
                         SELECT COUNT(*)
                         FROM chat_message_join cmj
                         JOIN message m ON m.ROWID = cmj.message_id
                         WHERE cmj.chat_id = c.ROWID
                           AND m.is_read = 0
                           AND m.is_from_me = 0
                       ) AS unread_count
                FROM chat c
                WHERE EXISTS (
                    SELECT 1 FROM chat_message_join cmj WHERE cmj.chat_id = c.ROWID
                )
                ORDER BY last_date DESC
                LIMIT ?
                """,
                (limit,),
            ).fetchall()
    
            results: list[dict[str, Any]] = []
            for r in rows:
                msg_row = conn.execute(
                    """
                    SELECT m.text, m.attributedBody
                    FROM chat_message_join cmj
                    JOIN message m ON m.ROWID = cmj.message_id
                    WHERE cmj.chat_id = ?
                    ORDER BY m.date DESC
                    LIMIT 1
                    """,
                    (r["chat_id"],),
                ).fetchone()
                preview = _extract_text(msg_row) if msg_row else None
                if preview:
                    preview = preview[:120]
                display = r["display_name"] or r["chat_identifier"] or ""
                results.append(
                    {
                        "chat_id": r["chat_id"],
                        "display_name": display,
                        "participants": (r["participants"] or "").split(", ") if r["participants"] else [],
                        "last_message_date": apple_ts_to_iso(r["last_date"]),
                        "last_message_preview": preview,
                        "unread_count": r["unread_count"] or 0,
                    }
                )
            return results
  • Helper used by list_recent_chats to extract message preview text from either the 'text' column or the attributedBody NSKeyedArchive blob
    def _extract_text(row: sqlite3.Row) -> str | None:
        """Return message.text, falling back to a best-effort read of attributedBody.
    
        attributedBody is an NSKeyedArchive blob. We do not parse it fully; we scan
        for the literal NSString payload that most text messages embed so that reply
        messages / richer content on newer macOS still show something useful.
        """
        text = row["text"]
        if text:
            return text
        blob: bytes | None = row["attributedBody"] if "attributedBody" in row.keys() else None
        if not blob:
            return None
        # typedstream layout after the NSString class tag:
        #   ... NSString <class-ref bytes> '+' <length-prefix> <utf-8 bytes>
        # The '+' (0x2b) byte is typedstream's variable-length-field marker.
        idx = blob.find(b"NSString")
        if idx == -1:
            return None
        plus = blob.find(b"+", idx)
        if plus == -1 or plus + 1 >= len(blob):
            return None
        cursor = plus + 1
        length_byte = blob[cursor]
        cursor += 1
        if length_byte == 0x81 and cursor + 2 <= len(blob):
            length = int.from_bytes(blob[cursor : cursor + 2], "little")
            cursor += 2
        elif length_byte == 0x82 and cursor + 4 <= len(blob):
            length = int.from_bytes(blob[cursor : cursor + 4], "little")
            cursor += 4
        elif length_byte < 0x80:
            length = length_byte
        else:
            return None
        try:
            return blob[cursor : cursor + length].decode("utf-8", errors="replace")
        except Exception:
            return None
  • Helper that converts Apple Core Data timestamps to ISO8601 strings, used by list_recent_chats for last_message_date
    def apple_ts_to_iso(apple_ts: int | None) -> str | None:
        """Convert Apple Core Data timestamp to ISO8601 UTC string.
    
        Newer macOS stores date as nanoseconds since 2001-01-01 UTC.
        Older rows stored plain seconds. Heuristic: values > 1e11 are nanoseconds.
        """
        if apple_ts is None or apple_ts == 0:
            return None
        if apple_ts > 10**11:
            unix_ts = apple_ts / 1_000_000_000 + APPLE_EPOCH_OFFSET
        else:
            unix_ts = apple_ts + APPLE_EPOCH_OFFSET
        return datetime.fromtimestamp(unix_ts, tz=timezone.utc).isoformat()
Behavior3/5

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

With no annotations, the description partially discloses behavior by noting ordering and output content. However, it omits safety info (read-only nature), pagination behavior, and constraints on the 'recent' definition. The presence of an output schema partially offsets the need for return details, but behavioral traits like side effects are not addressed.

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

Conciseness5/5

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

The description is a single concise sentence (8 words) that front-loads the core purpose and key details. No unnecessary words or repetition.

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 the available output schema and sibling tools, the description minimally covers the tool's purpose. However, it lacks clarity on scope ('recent' definition, whether all chats or user-specific), filtering capabilities, and potential limitations. The presence of an output schema mitigates the need for return value details, but the context is not fully comprehensive.

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

Parameters2/5

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

Schema description coverage is 0%, so the description should explain parameters. It mentions 'newest-first' and output content but does not explain the only parameter 'limit', its effect, or default behavior. The description assumes the tool returns all recent chats, while the schema includes a default limit of 20, creating ambiguity.

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 clearly states the tool's action ('List recent chats'), specifies ordering ('newest-first'), and mentions included content ('previews and unread counts'). This distinguishes it from siblings like get_chat_messages (messages within a chat) and search_imessages (content search).

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 its siblings. It does not state explicit use cases, prerequisites, or alternatives. For example, it does not clarify when to prefer this over get_chat_messages or search_imessages.

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/camfortin/imessage-mcp'

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