This commit is contained in:
Taylor Wilsdon
2026-03-17 08:43:55 -04:00
parent 9ef6f72e26
commit 441f052dca

View File

@@ -7,6 +7,7 @@ This module provides MCP tools for interacting with the Gmail API.
import logging import logging
import asyncio import asyncio
import base64 import base64
import re
import ssl import ssl
import mimetypes import mimetypes
from html.parser import HTMLParser from html.parser import HTMLParser
@@ -441,33 +442,74 @@ def _extract_headers(payload: dict, header_names: List[str]) -> Dict[str, str]:
return headers return headers
def _parse_message_id_chain(header_value: Optional[str]) -> List[str]:
"""Extract Message-IDs from a reply header value."""
if not header_value:
return []
async def _fetch_thread_message_ids(service, thread_id: str) -> tuple[Optional[str], Optional[str]]: message_ids = re.findall(r"<[^>]+>", header_value)
if message_ids:
return message_ids
return header_value.split()
def _derive_reply_headers(
thread_message_ids: List[str],
in_reply_to: Optional[str],
references: Optional[str],
) -> tuple[Optional[str], Optional[str]]:
"""Fill missing reply headers while preserving caller intent."""
derived_in_reply_to = in_reply_to
derived_references = references
if not thread_message_ids:
return derived_in_reply_to, derived_references
if not derived_in_reply_to:
reference_chain = _parse_message_id_chain(derived_references)
derived_in_reply_to = (
reference_chain[-1] if reference_chain else thread_message_ids[-1]
)
if not derived_references:
if derived_in_reply_to and derived_in_reply_to in thread_message_ids:
reply_index = thread_message_ids.index(derived_in_reply_to)
derived_references = " ".join(thread_message_ids[: reply_index + 1])
elif derived_in_reply_to:
derived_references = derived_in_reply_to
else:
derived_references = " ".join(thread_message_ids)
return derived_in_reply_to, derived_references
async def _fetch_thread_message_ids(service, thread_id: str) -> List[str]:
""" """
Fetch Message-ID headers from a Gmail thread for reply threading. Fetch Message-ID headers from a Gmail thread for reply threading.
Returns the last message's Message-ID (for In-Reply-To) and the full chain
of Message-IDs (for References).
Args: Args:
service: Gmail API service instance service: Gmail API service instance
thread_id: Gmail thread ID thread_id: Gmail thread ID
Returns: Returns:
Tuple of (last_message_id, references_chain) where both are strings or None Message-IDs in thread order. Returns an empty list on failure.
""" """
try: try:
thread = await asyncio.to_thread( thread = await asyncio.to_thread(
service.users().threads().get( service.users()
.threads()
.get(
userId="me", userId="me",
id=thread_id, id=thread_id,
format="metadata", format="metadata",
metadataHeaders=["Message-ID"], metadataHeaders=["Message-ID"],
).execute )
.execute
) )
messages = thread.get("messages", []) messages = thread.get("messages", [])
if not messages: if not messages:
return None, None return []
# Collect all Message-IDs in thread order # Collect all Message-IDs in thread order
message_ids = [] message_ids = []
@@ -477,15 +519,12 @@ async def _fetch_thread_message_ids(service, thread_id: str) -> tuple[Optional[s
if mid: if mid:
message_ids.append(mid) message_ids.append(mid)
if not message_ids: return message_ids
return None, None
last_message_id = message_ids[-1]
references_chain = " ".join(message_ids)
return last_message_id, references_chain
except Exception as e: except Exception as e:
logger.warning(f"Failed to fetch thread Message-IDs for thread {thread_id}: {e}") logger.warning(
return None, None f"Failed to fetch thread Message-IDs for thread {thread_id}: {e}"
)
return []
def _prepare_gmail_message( def _prepare_gmail_message(
@@ -1695,13 +1734,10 @@ async def draft_gmail_message(
# Auto-populate In-Reply-To and References when thread_id is provided # Auto-populate In-Reply-To and References when thread_id is provided
# but headers are missing, to ensure the draft renders inline in Gmail # but headers are missing, to ensure the draft renders inline in Gmail
if thread_id and (not in_reply_to or not references): if thread_id and (not in_reply_to or not references):
fetched_reply_to, fetched_references = await _fetch_thread_message_ids( thread_message_ids = await _fetch_thread_message_ids(service, thread_id)
service, thread_id in_reply_to, references = _derive_reply_headers(
thread_message_ids, in_reply_to, references
) )
if not in_reply_to and fetched_reply_to:
in_reply_to = fetched_reply_to
if not references and fetched_references:
references = fetched_references
raw_message, thread_id_final, attached_count = _prepare_gmail_message( raw_message, thread_id_final, attached_count = _prepare_gmail_message(
subject=subject, subject=subject,