Server data from the Official MCP Registry
Gmail MCP server for email triage: search, labels, archive, trash, unsubscribe, snooze.
About
Gmail MCP server for email triage: search, labels, archive, trash, unsubscribe, snooze.
Security Report
Valid MCP server (2 strong, 3 medium validity signals). 5 known CVEs in dependencies (0 critical, 3 high severity) Package registry verified. Imported from the Official MCP Registry. Trust signals: 3 highly-trusted packages.
4 files analyzed · 6 issues found
Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.
Permissions Required
This plugin requests these system permissions. Most are normal for its category.
What You'll Need
Set these up before or after installing:
Environment variable: MAILWARDEN_DIR
Environment variable: MAILWARDEN_CREDENTIALS
Environment variable: MAILWARDEN_AUTO_SWEEP
Environment variable: MAILWARDEN_DOWNLOAD_DIR
How to Install
Add this to your MCP configuration file:
{
"mcpServers": {
"io-github-csitte-mailwarden": {
"env": {
"MAILWARDEN_DIR": "your-mailwarden-dir-here",
"MAILWARDEN_AUTO_SWEEP": "your-mailwarden-auto-sweep-here",
"MAILWARDEN_CREDENTIALS": "your-mailwarden-credentials-here",
"MAILWARDEN_DOWNLOAD_DIR": "your-mailwarden-download-dir-here"
},
"args": [
"-y",
"mailwarden"
],
"command": "npx"
}
}
}Documentation
View on GitHubFrom the project's GitHub README.
mailwarden
A reliable, native Gmail MCP server — full mailbox triage for AI assistants, with the feature no other Gmail MCP server ships: mailbox-side snooze.
Highlights
- Snooze — the only mailbox-side snooze in a Gmail MCP server. Archive a thread now, have it resurface in the inbox on a date. Built on dated labels + a sweep, so it works from any client, is visible in Gmail itself, and survives restarts. (Where another server offers a "snooze", it is a local reminder list — the mail never leaves or re-enters the inbox.)
- Search you can trust. Gmail's
threads.list— the call any thread search goes through — can answeris:unreadfrom a stale thread-level read state: measured in one real mailbox, 86% of the threads it returned held no unread message at all; in a second mailbox, no drift whatsoever. You cannot tell which mailbox you are in without looking, sosearchre-verifies every hit against its live labels. Paginated viapageToken/nextPageToken. (the measurements) - Sender authentication, not sender spelling.
get_threadreports the SPF/DKIM/DMARC results the receiving server recorded, so "is this really from my bank?" is answered from the message's own headers instead of from how the domain looks. It reads Gmail's own report only — the one written under the idmx.google.com; a message can carry forged ones of its own — says whether a passing DKIM signature belongs to theFromdomain, and saysuncheckedwhen nobody checked, because a missing check is not a passing one. - Bulk operations that scale.
bulk_modifyarchives/labels everything matching a query at 1000 messages per API request — with per-chunk partial-success reporting instead of all-or-nothing. The snooze sweep uses the same batch path. - Structured outputs. Every tool declares an
outputSchemaand returns validatedstructuredContentalongside fenced JSON text — no parsing guesswork for clients. Failures are structured as well: acodeand aretryableflag, so a client can tell "try again later" from "re-authorize" without reading prose. - Small attack surface. No send tools (no exfiltration path for prompt-injected mail),
optional read-only mode, no telemetry, no open ports by default, symlink-safe download fencing,
injection-fenced output. And no code path that could send: every Gmail request passes an egress
checkpoint that refuses
messages.send,drafts.send, permanent deletion and forwarding settings, whatever a compromised or careless caller asks for — and every other draft endpoint too, unless you switch on the opt-indraftstier, which saves drafts for you to send yourself. One deliberate exception:unsubscribe/bulk_unsubscribe(manage tier) contact the opt-out endpoint named in a message's own header — the only non-Google host mailwarden ever reaches, and aread-tier deployment makes no outbound request at all. Details under Security & privacy and Unsubscribing. - Correct with real-world mail. RFC 2047 headers decoded (
=?UTF-8?B?…?=→ readable text), bodies decoded by their bytes and their declared charset (text Gmail already converted to UTF-8 is read as UTF-8 whatever the header says, anything else in the charset it names; no mojibake for ISO-8859-1/Shift_JIS mail), 429/5xx retried with exponential backoff.
Why
Connectors that sync or cache your mailbox can lag behind it — and even Gmail's own search index is sometimes loose (see below). mailwarden talks straight to the live Gmail API (no cached snapshot) and re-verifies what the index returns, so what you see is what's actually there. It's a generic Gmail capability layer — keep your own rules/logic in your AI client, not in the server.
search goes one step further than the raw API: Gmail's threads.list index can answer read-state operators from a stale copy of that state, so is:unread returns threads you finished reading weeks ago — in one measured mailbox, the large majority of what came back. Since every hit is fetched live anyway, search re-checks the unambiguous predicates (is:unread/is:read/is:starred/in:inbox/category:…, with negation) against each thread's true labels and drops the index's false positives.
Compared to other Gmail MCP servers
Most Gmail MCP servers cover the same read/label/send surface. Two capabilities are still unique to mailwarden as far as we have looked (mailbox-side snooze, search re-verification), and one deliberate omission is a security feature, not a gap. A third, sender authentication, was unique when it shipped and no longer is: @agentcomms/gmail-mcp has read Authentication-Results since 18 September 2026, trusting only the report whose authserv-id is mx.google.com. It has its own column below since 13 October 2026. Google's own server is also narrower than it looks: draft-only, and no trash, filters or unsubscribe.
| Capability | mailwarden | Google official | taylorwilsdon | a-bonus | aaronsb | klodr | agentcomms | gmail-tidy |
|---|---|---|---|---|---|---|---|---|
| Mailbox-side snooze — archive now, resurface in the inbox on a date/time or preset | ✅ | — | — | — | — | — | — | — |
| Search-result re-verification — drops the thread index's false positives against live labels | ✅ | — | — | — | — | — | — | — |
| Sender authentication — SPF/DKIM/DMARC as the receiving server recorded them, on every message | ✅ Gmail's own report only, values token-validated, DKIM alignment | — | — | — | — | — | ✅ Gmail's own report only, DKIM alignment | ⚠️ fail values from the first header, issuer unchecked |
| Sweep / bulk over a query — one action across every thread a search returns | ✅ 1000/req, partial-success | — | ⚠️ batch by explicit ids; filter apply relabels over a query, no re-check | — | ✅ previewed query, up to 10,000, no re-check | ⚠️ batch by explicit ids | ⚠️ batch by explicit ids, exact per-message undo | ⚠️ over a query, capped at 5,000, no preview or re-check |
| Unsubscribe — per-sender overview + RFC 8058 one-click opt-out, no send scope needed | ✅ | — | ⚠️ header shown, no action | — | — | — | — | ⚠️ link shown, no action |
| Inbox triage overview — one call that buckets what is waiting | ✅ sender/label/age + header signals | — | — | ✅ heuristic flags + stats | ⚠️ unread list, no buckets | — | — | ⚠️ sender stats + newsletter list, no buckets |
| Server-side filters — rules that keep triaging with no assistant in the loop | ✅ never forwarding | — | ✅ | — | ✅ previewed; ⚠️ forwarding allowed unless draft-only policy | ✅ never forwarding | — | ✅ never forwarding or trash |
| No send tools — by design — a prompt-injected mail has no exfiltration path | ✅ no compose by default; ⚠️ opt-in draft-only tier | ⚠️ draft-only | ❌ sends | ❌ sends | ❌ sends; ⚠️ opt-in draft-only policy | ❌ sends | ❌ sends; ⚠️ per-message approval, in chat by default | ⚠️ draft-only |
| Least-privilege tool tiers — OAuth scopes derived from the tools you enable | ✅ | ⚠️ scope split | ⚠️ --permissions narrows scopes per service (cumulative or +-union); tiers narrow tools only | — | ⚠️ per-account read/readwrite, at call time | ⚠️ inverse: tools gated by granted scopes | ⚠️ tier per mailbox picks the scopes; writes checked against the grant at call time | — |
| Token encryption at rest | ✅ AES-256-GCM by default, key in the OS keystore; a passphrase where there is none | n/a (hosted) | ⚠️ file mode 0600; bucket CMEK on GCS | — | — | — | ✅ OS keychain by default; owner-only file by choice | ✅ AES-256-GCM by default, key in the OS keystore |
| No vendor cloud — you operate the server | ✅ | ❌ Google-hosted | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Structured outputs — every tool declares an outputSchema | ✅ | ✅ documented per tool in its reference | — | — | — | ⚠️ two tools (download_email, download_all_attachments), more planned | ✅ every tool | — |
Snapshot as of 13 October 2026 — the oldest of the seven column checks (Google's reference and a-bonus, both last read that day), and so the only date the table as a whole can claim. Each repository column is brought forward by reading the diff against the revision recorded in docs/comparison-sources.json whenever npm run table-sources reports a move. On 25 October 2026 four of them moved at once. aaronsb went to 9c9f8033 (v4.8.0), and two of its cells with it: since 16 October 2026 it applies a label change to everything a query matches, behind a preview that counts the set, keeps the ids on the server and hands back a token valid once for thirty minutes (up to 10,000 messages; the matches are not re-checked), and it creates, updates and deletes filters behind the same kind of preview — forwarding included unless its draft-only policy is set. taylorwilsdon went to fcbf5a2c (v2.2.0); --permissions now also takes a union such as gmail:readonly+send that grants only the named levels, which the least-privilege cell now names. klodr went to e5c080c3 (3.0.0); create_filter now refuses a forwarding action whatever the scopes, and its filter cell says so. agentcomms went to 261cae4f (0.28.0, with per-recipient rules on what it sends); no cell moves. a-bonus has not moved since 13 October 2026. Google's tool reference, last read on 13 October 2026, documents an output schema on every one of its eleven tools; it is still Developer Preview, with no send, trash, filter, snooze, unsubscribe or query-driven tool. — = not offered / not documented. Columns are the servers a reader is most likely to reach for — Google's first-party one, plus the three largest community servers still under maintenance — and klodr, which comes closest to mailwarden's own least-privilege design. agentcomms (@agentcomms/gmail-mcp, read at 01cee751 on 13 October 2026) was added the same day for two reasons: it is the only other server we know of that reads Authentication-Results, and it pins that read to Gmail's own report, which is stricter than what mailwarden did until then and the reason mailwarden now does the same; and its npm reach overtook klodr's within a month of its first release. Its bulk path is per id, not per query, but every change returns an exact per-message undo — the design mailwarden's bulk_modify adopted as undo: true. It sends, behind a per-message approval whose default is a yes in the conversation; its own README says the software cannot tell that yes from the agent's, and offers a terminal confirmation (confirm) or no sending at all (never) instead. gmail-tidy (gmail-tidy-mcp, read at 6df48c69 on 25 October 2026) was added that day because its stated position is the closest to this one: no send tool and no trash, a build test that fails on either, and filters that can neither forward nor trash. It does write drafts, which puts it on the send row next to Google's server rather than next to this one — its own README says plainly that its token could send. It was ahead of mailwarden on one row, and mailwarden has since taken the same design over: it encrypts the token by default, with the key in the OS keystore. Its bulk runs over a query, capped at 5,000 messages, with neither a preview nor a re-check, and it reads SPF/DKIM/DMARC failures from the first Authentication-Results header without asking which server wrote it. Its npm reach matched mailwarden's within ten days of its first release. aaronsb was added on 8 September 2026, and the reason it was missing is worth stating plainly: the selection had been made by capability overlap, so a server an order of magnitude larger than the smallest column sat outside the table while that column stayed in it. It earns its place on the send row rather than on size. It was the first server here besides mailwarden that can be made to refuse sending at all — GWS_SAFETY_POLICY=draft-only-email blocks send, reply, replyAll and forward; agentcomms has since joined it with a never send policy — and the difference that keeps the row honest is where the promise lives: there, sending is the default and the refusal is an operator's environment variable; here, no tool to send was ever written. Its no-delete policy stands in the same relation to this project's trash-only rule. The filter row asks the same question from the other end, and a third project shows why it is one: c0webster/hardened-google-workspace-mcp, a fork of the taylorwilsdon column made for no reason but this one, removes create_gmail_filter and delete_gmail_filter outright because a filter can forward — which is exactly the risk, answered by dropping the capability. mailwarden keeps the capability and removes the risk instead: create_filter has no way to express a forward, and list_filters surfaces the ones that already exist so they can be audited. That fork also shows where a promise of this kind usually leaks, which is why the row above is about the tool surface and the egress guard rather than about scopes: it drops gmail.send but keeps gmail.compose — commented "for draft creation/editing only (NOT sending)" — and gmail.modify. Google documents the first as "Manage drafts and send emails" and the second as "Read, compose, and send emails", so the promise does not rest on one scope too many but on two, and gmail.modify alone would be enough — the same scope this project holds in its own manage tier, which is exactly why the row points at the tool surface. Its token can send; only its tool list cannot. The most installed Gmail server is absent for that reason and not by oversight: GongRzhe/Gmail-MCP-Server is archived, its last commit dating to August 2025, and it still drew 112,163 npm downloads in the month to 29 August 2026. Reach and currency are different questions, and a comparison of what a server does today can only answer the second. Send capability is listed as a security property: mailwarden's lack of it is intentional (see Security & privacy). The encryption row asks who holds the key: mailwarden keeps a random key in the OS keystore by default, as gmail-tidy does, and falls back to a passphrase you set where no keystore is reachable — and to a plaintext file where you set none, which is why the cell names both; taylorwilsdon relies on file permissions locally and on the storage bucket's own CMEK when hosted on GCS — protection against a stolen file in the first case, against a stolen disk in the second. The last row asks who operates the server, not where it happens to run: self-hosting is common ground here, and every community server on this table offers some remote deployment except klodr and gmail-tidy (stdio only) — mailwarden via --http, taylorwilsdon over streamable HTTP with OAuth 2.1, a-bonus on Cloud Run. Running one of them on your own host is not a cloud copy; running it on the vendor's is.
The moat isn't any single row — it's snooze + live re-verification together: an actual inbox-workflow layer that acts on the mailbox's current state, not a cached snapshot. Where others have caught up it's noted honestly above: at-rest encryption (taylorwilsdon; by default and with the key in the OS keystore, agentcomms and gmail-tidy, whose design mailwarden has since taken over), scope-driven tool gating (klodr inversely; taylorwilsdon in our direction, and further than this row said until it was corrected — besides --read-only, which switches the OAuth flow to the read-only scope map, he has had per-service permission levels since 25 February 2026: --permissions gmail:organize builds the requested set from cumulative levels — readonly, organize, drafts, send, full — and his tool registry then disables every tool whose declared scopes fall outside them, deliberately skipping scope-hierarchy expansion so that organize really does exclude gmail.send. What separates that from this row's claim is the direction, not the strength: the level is picked per service rather than derived from the tools you enable, so --tool-tier core --tools gmail still asks for the full Gmail scopes unless a level is named, and neither mode has a guard at the request itself. Our August round read only the tier path and understated him here from 26 August on; re-read in his auth/permissions.py, auth/scopes.py and core/tool_registry.py at 54b1c56 on 7 September 2026 — the day we read, at the revision his repository stood at. The tier half was checked on 26 August 2026 and corrected the same day by csitte.at, who verified it against their own clone rather than taking our word for it), a richer per-message triage heuristic (a-bonus), and bulk organize over a mailbox (the hosted mcpemails.com, which has no snooze either). What none of them do is act on a query and check the mailbox's answer before acting on it — taylorwilsdon's filter apply, aaronsb's previewed modifyMatching and gmail-tidy's organize_emails now do the first half, all over messages.list, and none of them the second.
Running it next to a Workspace server
mailwarden is a Gmail server, not a Workspace suite — if you want Calendar, Drive, Docs and Sheets
from one place, a broad server like taylorwilsdon/google_workspace_mcp covers ground this one never
will, and the two are not mutually exclusive. Adding both is a reasonable setup, and the reason to is
the token, not the tool count: a suite server that can send mail holds a credential that can send
mail, for every mailbox it is pointed at. Giving Gmail to mailwarden instead means the mail half of
your setup has no compose, reply, forward or send tool at all — or, with the opt-in drafts tier,
tools that save drafts and none that send them. Where that promise rests differs by
tier, and the distinction matters: on read Google enforces it at the token (gmail.readonly,
which the send endpoints reject), while on manage it rests on the tool surface — Gmail does
accept gmail.modify for sending, so the scope alone is no guarantee. In both cases the egress
guard refuses messages.send and drafts.send in the server itself, and every
other draft endpoint unless the drafts tier is on, so an injected message in your inbox has no
tool to reach for and no endpoint to reach.
Practical shape: point the suite server at the services you want and disable its Gmail tools
(--disabled-tools, or a tier that omits them), and run mailwarden alongside for mail. Keep the
tier rule in mind — at most one mailbox per client config should carry
writing tiers.
Why re-verification matters — a concrete case
Ask an assistant to "archive the unread promotional mail that's already skipped my inbox" and it will reach for the obvious query, category:updates is:unread -in:inbox. A server that trusts Gmail's index now archives threads you had already read — mail you never meant to touch, gone in a bulk action you can't easily reverse.
Measured, not asserted. In one real mailbox (~70,000 messages), category:updates is:unread returned 131 threads through threads.list, and only 17 of them held an unread message — 87% stale. The same query, same mailbox, same minute, asked through messages.list instead: 19 messages, none stale. So this is not "Gmail search is unreliable" — the thread view of read state lags while the per-message view does not, and search goes through threads.list. A second mailbox, measured identically on the same day, drifted not at all.
Method, all three queries, the controls, and what the finding is not (it is not the index dropping the predicate, and not a quirk of exotic operator combinations): Gmail's thread index can answer is:unread from a stale read state — a standalone report, every figure traced to a recorded measurement.
Which is the whole point: a server cannot know which kind of mailbox it is in. Re-verification costs nothing where nothing drifts, and saves you where it does — in the measurement above, every thread search dropped was genuinely read, and it discarded no genuinely unread mail.
Where it is not free: the bulk tools. search re-verifies because it fetches every hit anyway; bulk_modify (and create_filter's applyToExisting sweep) is sized in thousands of messages, where one fetch per hit is a different order of cost. Both act on what the index returns — and bulk_modify reports unverifiedPredicates, the conditions from your query that were taken on the index's word (+UNREAD, -INBOX, …). Empty means there was nothing to distrust. Non-empty and the result has to be read-state-precise? Resolve the set with search first and act on those thread ids. A dryRun does not close this gap: it re-reads the same index, so it confirms how big the set is, never whether it is right. What its previewToken does close is the other gap, between showing a set and changing one: without it, the write runs the query again and changes whatever matches by then.
The filter sweep does not report that field, deliberately. Its query is not yours — create_filter builds one from the criteria, parenthesising a query criterion and double-quoting every from/to/subject value, and both of those switch the predicate derivation off. The list would come back empty for every filter you can define, and an empty list here would read as "nothing to distrust" when it means "could not tell". So the caveat stays in prose, where it holds without exception: treat a sweep as acting on the index's answer, whatever it matched. Use verify: true to learn what the sweep actually changed, and search when the set has to be right before anything is written.
A cheaper half-measure, honestly labelled. bulk_modify also takes crossCheck: true, which asks Gmail the same question a second way before writing: every derived predicate is re-run as a label filter (labelIds) rather than as a query operator, and any message the two routes disagree about is left untouched and reported. The cost is one extra list call per predicate — flat, independent of how many messages match — where re-verification costs one fetch per hit. What it buys is bounded and worth stating plainly: a disagreement is real evidence, agreement is none at all, because both routes read the same index and an index can be consistently wrong. So unverifiedPredicates still reports what it always did, cross-check or not, and only search re-checks against the mailbox itself. Whether the two routes ever diverge in practice is unmeasured — node scripts/probe-crosscheck.mjs measures exactly that in your own mailbox, read-only and ids only.
mailwarden fetches every hit live anyway, so search re-checks the unambiguous predicates (is:unread, is:read, in:inbox, category:…, with negation) against each thread's true labels and drops the index's false positives before any tool sees them. The bulk action then runs on exactly the set you asked for. This is the difference between acting on what Gmail indexed and acting on what's actually in the mailbox right now — and it's why snooze/sweep are safe to hand to an assistant: the sweep resurfaces only threads whose snooze is genuinely due, verified against live labels at run time.
See it yourself — no Gmail account needed. From a clone of the repo (the demo is a repo-only verification script, not part of the npm package):
git clone https://github.com/csitte/mailwarden && cd mailwarden
npm install && npm run build
node scripts/demo-reverify.mjs
There is a second script next to it, node scripts/probe-reverify.mjs, which measures the same thing in your mailbox instead of a fake one — read-only, metadata only (no subject, sender or body is fetched), printing counts and label names. It is how the numbers above were produced, and how you can check whether your mailbox drifts at all.
The demo drives the real search() against a fake Gmail API whose index is deliberately stale (returns a read thread for an is:unread query, exactly as Gmail does) and shows mailwarden dropping the false positive. It asserts the outcome, so it exits non-zero if the behavior ever regresses. The same case is locked by unit tests in test/gmail.test.ts ("drops index false positives via live-label re-verify").
Asking again without asking for everything
A recurring check — what came in since I last looked — is the expensive shape for a live server: the
obvious way to answer it is to search the whole slice again and compare. what_changed (read tier)
answers it from Gmail's own event log instead. Hand it the historyId a previous call or get_profile
returned, and it comes back with what arrived, what left, and which labels went on or came off, plus the
next id to keep.
This is not a cache, and the distinction is the whole design. The only thing that persists between calls is one number, and it persists in the caller. mailwarden still stores nothing about the mailbox, keeps no mirror and no index, and every call remains live against the Gmail API — the same rule as everywhere else here.
Two properties worth knowing before relying on it. It reports events, not state: a message marked
unread and then read appears under both, and both are true — for how the mailbox looks now, ask
search. And Gmail keeps roughly a week of history, after which an id is refused; mailwarden turns that
refusal into an error rather than an empty result, because nothing changed and I can no longer tell
you what changed call for opposite reactions and only one of them is safe to act on.
Judging a sender — what authentication answers, and what it doesn't
Every message from get_thread carries an authentication object: the SPF, DKIM and DMARC
results the receiving server recorded, plus the domains each check actually validated.
{
"spf": "pass", "mailedBy": "forwarder.example", // envelope sender SPF checked
"dkim": "pass", "signedBy": "authority.example", // the signature DMARC can pass on
"alsoSignedBy": ["forwarder.example", "esp.example"], // further signatures that passed
"dmarc": "pass", "headerFrom": "authority.example", // the From domain DMARC evaluated
"dkimAligned": true, // a passing signer is the From domain
"authservId": "mx.google.com", // who asserts all of the above
"returnPath": "bounces+…@forwarder.example"
}
Read dmarc first. It is the only one of the three that ties a passing check to the From
address a human sees. spf: "pass" on its own says an envelope sender was authorised to send —
something a lookalike domain gets in minutes.
The domains do not have to match, and a mismatch is not a finding. The object above has the
shape of a real receipt, forwarded through a custom domain on a mail-routing service before it
reached the mailbox. Forwarding rewrites the envelope sender (mailedBy and returnPath become the
forwarder) and adds the forwarder's own DKIM signature, while the sender's signature travels on
intact behind it — so DMARC still passes, and headerFrom still names the original sender. That is
why DMARC, not SPF, is the check that carries meaning here. Treat the domains as the explanation
of a result, not as a test of their own.
Several signatures: the one that counts is reported. A message often carries more than one
DKIM signature — the sender's, its mail provider's, a forwarder's. signedBy is the passing one
aligned with the From domain wherever there is one, because that is the one DMARC passes on;
alsoSignedBy names the other domains whose signature also passed, which is where a forwarder
shows. Through 0.25.0 the first signature was reported, and a re-signing forwarder puts its own in
front: in a mailbox that receives through Cloudflare Email Routing, 156 of 167 received messages
read as dkimAligned: false although each carried an aligned, passing signature further down
(measured 30 October 2026, see docs/measurements.json).
What a pass does not mean. That the mail really came from that domain — not that the domain deserves anything. A phisher holds perfect SPF, DKIM and DMARC on the lookalike domain he registered this morning; authentication tells you who sent it, and the answer can be "exactly who it claims to be, and that is the problem".
dkimAligned answers the question a signature raises. Anyone can sign mail with a key for a
domain they own, so dkim: "pass" alone says only that someone signed. dkimAligned says whether
that someone is the From domain, or a parent or child of it. For two sibling subdomains it says
nothing: news.bank.example and mail.bank.example are aligned, a.co.uk and b.co.uk are not,
and without a public-suffix list the two cases look the same — so the field is absent rather than
wrong. false means that no passing signature belongs to the From domain — a forwarder that
altered the message (a mailing list adding a footer breaks the sender's signature) leaves only its
own. It is absent when DKIM did not pass. As with the domains, false is an explanation, not a
verdict — dmarc remains the check to read first.
What unchecked: true means. The message carried no Authentication-Results header from
Gmail — nobody looked. It is not a failure, and it is not a pass. The header is written by a server that
receives a message, so anything that never arrived from outside — your own sent mail, for
instance — should be expected to have none.
Forged reports. A message can carry Authentication-Results headers of its own — an attacker
writes whatever he likes into the mail he sends. Only Gmail's own report is read: the topmost
one whose authserv-id is mx.google.com, the trust boundary RFC 8601 tells a reader to rely on.
A report under any other id is never read, however convincing it looks, and a message that has
none from Gmail is unchecked even if it carries a forged one under another id. The limit: mail that
reached the mailbox without passing Gmail's inbound mail server — fetched from another account over
POP, or inserted by an app — may carry no Gmail report at all, and a forged one that claims
mx.google.com is then indistinguishable from the real thing. Mail Gmail received itself always
carries its own report on top, so there a forgery is never read; otherReports counts every report
that was not read. (Through 0.22.1 the first report was read whatever its id — right wherever Gmail
had written one, and wrong exactly where it had not.) Values are validated as tokens rather than passed through, so a field that reads
like a verdict cannot carry a sentence. If two results for the same method disagree — a second DKIM
signature that failed — the disagreement shows up in alsoReported instead of being swallowed.
Tools
Documentation truncated — see the full README on GitHub.
Reviews
No reviews yet
Be the first to review this server!
More Developer Tools MCP Servers
Rea
Freeby Morluto · Developer Tools
Reverse engineer anything from your terminal or agent with one CLI and MCP server.
Paperclip
Freeby Paperclipai · Developer Tools
Trending hip-hop artist momentum scores across four cultural dimensions.
Fetch
Freeby Modelcontextprotocol · Developer Tools
Web content fetching and conversion for efficient LLM usage
Git
Freeby Modelcontextprotocol · Developer Tools
Read, search, and manipulate Git repositories programmatically
Toleno
Freeby Toleno · Developer Tools
Toleno Network MCP Server — Manage your Toleno mining account with Claude AI using natural language.
MCP Marketplace
Freeby mcp-marketplace · Developer Tools
Search and install MCP servers from inside your AI client.
