get_conversation
Retrieve recent messages from a contact or group, supporting pagination and date filtering. Locally marks messages as read without sending read receipts.
Instructions
Get recent message history with a contact or group from local store. Automatically marks returned messages as read in the local store (does NOT send a Signal read receipt — call send_read_receipt for that).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| recipient | Yes | Phone number or group ID | |
| limit | No | Max messages to return (default: 50) | |
| offset | No | Number of messages to skip for pagination (default: 0) | |
| since | No | Only messages after this ISO datetime (e.g. 2024-01-01T00:00:00) |
Implementation Reference
- src/signal_mcp/store.py:152-172 (handler)Core handler: retrieves message history from SQLite store for a contact (by phone number) or group (by group_id). Filters by recipient/sender/group_id, supports pagination (limit/offset) and optional 'since' timestamp. Returns messages in chronological order.
def get_conversation( recipient: str, limit: int = 50, offset: int = 0, since: datetime | None = None ) -> list[Message]: """Get message history with a contact (by number) or group (by group_id).""" init_db() with _db() as conn: params: list = [recipient, recipient, recipient] since_clause = "" if since: since_clause = "AND timestamp >= ?" params.append(int(since.timestamp() * 1000)) params.extend([limit, offset]) rows = conn.execute( f"""SELECT * FROM messages WHERE (group_id = ? OR (group_id IS NULL AND (sender = ? OR recipient = ?))) {since_clause} ORDER BY timestamp DESC LIMIT ? OFFSET ?""", params, ).fetchall() return _rows_to_messages(conn, list(reversed(rows))) - src/signal_mcp/client.py:860-873 (handler)Client wrapper: delegates to store.get_conversation via asyncio.to_thread. Auto-marks received messages as read after fetching them.
async def get_conversation( self, recipient: str, limit: int = 50, offset: int = 0, since: datetime | None = None ) -> list[Message]: messages = await asyncio.to_thread( _store.get_conversation, recipient, limit=limit, offset=offset, since=since ) # Auto-mark received messages as read (like every Signal client does) unread_ids = [m.id for m in messages if not m.is_read and m.sender != self.account] if unread_ids: await asyncio.to_thread(_store.mark_as_read, unread_ids) for m in messages: if m.id in unread_ids: m.is_read = True return messages - src/signal_mcp/store.py:152-154 (schema)Input schema: accepts recipient (str, phone number or group_id), limit (int, default 50), offset (int, default 0), and optional since (datetime). Returns list[Message].
def get_conversation( recipient: str, limit: int = 50, offset: int = 0, since: datetime | None = None ) -> list[Message]: - src/signal_mcp/cli.py:177-192 (registration)CLI command 'history' calls client.get_conversation to display message history for a recipient.
async def _run(): async with SignalClient() as client: messages = await client.get_conversation(recipient, limit=limit, offset=offset, since=since_dt) if not messages: click.echo("No messages found.") return if as_json: click.echo(json.dumps([m.to_dict() for m in messages], indent=2)) else: for msg in messages: _print_message(msg) try: run(_run()) except SignalError as e: click.echo(f"Error: {e}", err=True) sys.exit(1) - src/signal_mcp/store.py:348-367 (helper)Helper that counts total messages matching get_conversation's filter logic, used for 'has_more' pagination support.
def count_conversation( recipient: str, since: datetime | None = None ) -> int: """Return total message count matching get_conversation's filter — used for has_more.""" init_db() with _db() as conn: params: list = [recipient, recipient, recipient] since_clause = "" if since: since_clause = "AND timestamp >= ?" params.append(int(since.timestamp() * 1000)) row = conn.execute( f"""SELECT COUNT(*) FROM messages WHERE (group_id = ? OR (group_id IS NULL AND (sender = ? OR recipient = ?))) {since_clause}""", params, ).fetchone() return row[0] if row else 0