feat(tools): add read_email MCP tool for fetching full inbound mail bodies#8
Merged
Conversation
Graph's receivedDateTime gt filter can re-deliver messages at the cursor's exact second after a server restart when per-session dedup is lost. Bump the watermark by 1 ms after each poll batch and isolate email poll tests from blob env leakage.
…odies
The 60-second email-poll channel push truncates the body preview of
inbound mail. Long forwards (recipient lists, threaded replies,
attached metadata) get cut off mid-content, so the agent can't read
past the cut even when the message_id is right there in the push.
Adds `read_email(message_id, mailbox="")` which calls Graph
`GET /me/messages/{message_id}` (or `/users/{mailbox}/messages/{id}`
for shared mailboxes) with `$select` covering body (text + HTML), all
recipient lists, sender, subject, internetMessageHeaders, and
hasAttachments. Reuses the same Agent User token chain + `Mail.Read`
scope as `email_poll`. Errors mirror `send_email`: 401 →
TokenExpiredError (auto-refresh + retry); 404/403/5xx → clean
{"error", "status", "message_id"} dict; bearer token never echoed.
+7 tests (happy path + verbatim long body + shared mailbox + 401/404/500
+ no-token-leak).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0d2d74b to
28fdfc0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
read_email(message_id, mailbox="")to the MCP tool surface so an agent can fetch the full body and metadata of an inbound mail by its Graphmessage_id.Why
The 60-second email-poll background task pushes a channel notification when mail arrives, but the body preview in that notification is capped — long forwarded mails (recipient lists, threaded replies, attached metadata) get truncated mid-content. Until now, the agent had no exposed way to fetch past the cutoff:
send_emailwas the only mailbox-touching tool, andread_file/read_a365_text_fileare scoped to OneDrive/SharePoint items rather than mailbox items.What it does
GET /me/messages/{message_id}(or/users/{mailbox}/messages/{id}when a shared mailbox is named).$selectcoversbody(text + HTML),toRecipients,ccRecipients,bccRecipients,from,sender,subject,internetMessageHeaders, andhasAttachments.Mail.Readscope thatemail_pollalready uses — no new auth flow, no new scope grant required.send_email's conventions: 401 →TokenExpiredError(auto-refresh + retry via the existing_with_token_retrywrapper); 404 / 403 / 5xx → clean{"error", "status", "message_id"}dict; bearer token never echoed in error paths.Tests
7 new tests in
tests/tools/test_read_email.py:hasAttachmentsreturned verbatimmailboxparameter routes to/users/{mailbox}/messages/{id}correctlyTokenExpiredErrorFull suite:
1,248 passed, 1 skippedagainst this branch.ruff check .clean on changed files.Notes
ruff format --check .reports 45 pre-existing files onmainthat would be reformatted; the files this PR touches are all format-clean. Separate cleanup PR worth filing.Mail.Readis already in the Agent User consent grant (scripts/create_entra_agent_ids.py).Test plan
read_emailappears in the tool list.read_email(message_id=<id-of-recent-inbox-item>)and confirm the full body, all recipient lists, and headers come back populated.mailboxparameter against a shared mailbox the Agent User has access to.🤖 Generated with Claude Code