harden(mail): address PR #935 round-5 review
No blockers raised; residual cleanup: - processor.py: guard int(doc_task.doc_id) with is_valid_nextcloud_doc_id in the mail_message branch (consistent with search/context.py + the verifier). - mail metadata symmetry: store `bcc` in file_metadata and the Qdrant payload alongside cc (build_mail_content already emits a Bcc: line). - server/mail.py: extract _cap_attachment_content helper (byte-accurate cap) and unit-test it (small/None/oversized/multibyte). - client/mail.py: give the synthetic OCS-error Response an explicit empty body; add a test that a traversal-style attachment_id is percent-encoded. - models/mail.py: clarify ListMessagesResponse.total_count is the page count, not the mailbox total. Deferred (Deck #376): _potentially_deleted doc_type-in-key. It's pre-existing and spans ~30 sites across the notes/news/deck/file/mail scanners (whose deletion paths have no unit coverage), so it belongs in its own focused PR rather than expanding this mail PR's blast radius into other doc types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
0856d59956
commit
d006145444
@@ -32,6 +32,23 @@ logger = logging.getLogger(__name__)
|
||||
MAX_ATTACHMENT_CONTENT_BYTES = 5 * 1024 * 1024
|
||||
|
||||
|
||||
def _cap_attachment_content(content: str | None) -> str | None:
|
||||
"""Replace oversized attachment content with a size sentinel.
|
||||
|
||||
Measures UTF-8 byte length (what actually lands in the MCP response/LLM
|
||||
context), not character count. Non-string content is returned unchanged.
|
||||
"""
|
||||
if not isinstance(content, str):
|
||||
return content
|
||||
content_bytes = len(content.encode("utf-8"))
|
||||
if content_bytes > MAX_ATTACHMENT_CONTENT_BYTES:
|
||||
return (
|
||||
f"[attachment too large to inline: {content_bytes} bytes "
|
||||
f"(> {MAX_ATTACHMENT_CONTENT_BYTES})]"
|
||||
)
|
||||
return content
|
||||
|
||||
|
||||
def configure_mail_tools(mcp: FastMCP):
|
||||
"""Configure Mail app MCP tools (read-only)."""
|
||||
|
||||
@@ -221,19 +238,11 @@ def configure_mail_tools(mcp: FastMCP):
|
||||
client = await get_client(ctx)
|
||||
try:
|
||||
data = await client.mail.get_attachment(message_id, attachment_id)
|
||||
content = data.get("content")
|
||||
if isinstance(content, str):
|
||||
content_bytes = len(content.encode("utf-8"))
|
||||
if content_bytes > MAX_ATTACHMENT_CONTENT_BYTES:
|
||||
content = (
|
||||
f"[attachment too large to inline: {content_bytes} bytes "
|
||||
f"(> {MAX_ATTACHMENT_CONTENT_BYTES})]"
|
||||
)
|
||||
return GetAttachmentResponse(
|
||||
name=data.get("name"),
|
||||
mime=data.get("mime"),
|
||||
size=data.get("size"),
|
||||
content=content,
|
||||
content=_cap_attachment_content(data.get("content")),
|
||||
)
|
||||
except RequestError as e:
|
||||
raise McpError(
|
||||
|
||||
Reference in New Issue
Block a user