Zoho DMARC MCP
Integrates with Zoho Mail to download DMARC report attachments from an explicitly configured folder using read-only ZohoMail account and message APIs, then exposes bounded read-only DMARC summary, failures, report details, and health reporting tools.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Zoho DMARC MCPshow DMARC failures for example.test from 2025-01-01Z to 2025-01-31Z"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Zoho DMARC MCP
Downloads DMARC attachments from an explicitly configured Zoho folder into SQLite
and exposes bounded read-only reporting through OpenAI Secure MCP Tunnel. Raw
attachments and message bodies are discarded. The application and chart are
available as release 0.1.3; real-account and host acceptance are operator checks.
Development
Use the checked-in devcontainer with Python 3.13 and the locked MCP SDK. On Podman,
select the intended connection in the current shell and pass --docker-path podman
to the devcontainer CLI:
devcontainer up --docker-path podman --workspace-folder .
devcontainer exec --docker-path podman --workspace-folder . uv run --frozen python -m pytest -qThe runtime image uses a separate non-root user. Development dependencies live in a named volume rather than a Windows virtual environment.
Related MCP server: edu-db-readonly-mcp
Connectivity proof
poc/hello.py exposes only dmarc_hello and /healthz. Its Streamable HTTP endpoint
is /mcp, bound to loopback for an official tunnel-client sidecar.
poc/deployment.yaml requires replacing PROOF_IMAGE with the loaded proof image
and creating the openai-tunnel Secret first. It creates no public Service,
Ingress, or database listener. Both image dependencies are pinned by digest.
The host-only helper python ops/configure_tunnel.py opens a one-time loopback
form for the tunnel ID and runtime key. It creates a Secret directly through
kubectl stdin, without saving credential files or using shell arguments. It does
not overwrite an existing Secret. Close the form after creation. Never commit
private values, actual reports, or credential material.
The gate passes only when the intended ChatGPT/dot account discovers and calls
dmarc_hello; local protocol tests alone do not establish account connectivity.
Install
Use a rootful Podman machine on Windows and select its connection explicitly with
CONTAINER_CONNECTION. Availability requires an awake host and owner sign-in.
minikube start -p zoho-dmarc --driver=podman --container-runtime=cri-o --cpus=4 --memory=4096 --keep-context
helm repo add zoho-dmarc https://nephilus.github.io/zoho-dmarc-mcp/
helm repo update
helm upgrade --install zoho-dmarc zoho-dmarc/zoho-dmarc-mcp --version 0.1.3 --kube-context zoho-dmarc --namespace zoho-dmarc --create-namespace -f private-values.yaml --waitOCI alternative: substitute oci://ghcr.io/nephilus/charts/zoho-dmarc-mcp for the
chart argument. Release packages contain the application digest; source charts
require an explicit image.digest. Both application and tunnel images are pinned.
The chart deploys one Pod with collector, MCP server and official tunnel containers,
Recreate updates and a retained 10 GiB RWO PVC. No Service or Ingress is needed.
Private setup
Create existing Kubernetes Secrets in the release namespace:
Secret | Keys | Access |
zoho-oauth | client_id, client_secret, refresh_token | Collector only |
zoho-dmarc-config | config.json | Collector only |
openai-tunnel | api_key, tunnel_id | Tunnel only |
Example config.json (replace privately):
{"owner":"owner@example.test","account":"123","folder":"456","region":"com","domains":["example.test"],"scan_seconds":1800}private-values.yaml contains Secret references, never credential values:
zoho:
secretName: zoho-oauth
config:
secretName: zoho-dmarc-config
remediationUtc: ""
tunnel:
secretName: openai-tunnelCreate a Self Client in the Zoho API Console as the
mailbox owner. Request only ZohoMail.accounts.READ,ZohoMail.messages.READ. Run
python ops/configure_zoho.py --region com, enter client ID, secret and a freshly
generated authorization code in its expiring loopback form. It exchanges the code
in memory and creates the Secret through stdin without displaying or writing tokens.
Zoho Self Client instructions
Create a workspace-associated OpenAI tunnel and restricted runtime key with
Tunnels Read + Use. Run python ops/configure_tunnel.py for local entry. Enable
developer mode in the intended ChatGPT workspace and create a private Tunnel plugin.
OpenAI tunnel guide
Tools and counting
dmarc_summary(start,end,page_size,cursor) and dmarc_failures(...) require explicit
UTC bounds ending in Z or +00:00, a positive window no longer than 365 days,
and pages of 1–100 items. dmarc_report_details(report_id,...) exposes bounded rows
and provenance. dmarc_health() reports freshness, incomplete scans, retries,
rejections, conflicts and backup export status. Responses are capped at 256 KiB.
Counts cover whole reports overlapping the requested window, disclose their actual periods, and are never prorated. Receipt times remain separate. Failures mean both aligned DKIM and SPF failed. Logical identity is reporter, report ID, domain and period. Identical XML replay is idempotent. Different XML fingerprints create a visible conflict and exclude all variants from totals; formatting changes also create conflicts deliberately. An optional private remediation boundary labels pre-change, post-change and spanning reports without implying causality.
Coverage describes observed reporters and uncovered periods only. Unknown reporters cannot be inferred, and gaps do not prove delivery failure. Coverage truncation is explicit; reduce the window or page size when the response limit is reached. Stale history remains queryable. No DNS changes or enforcement recommendations are made.
Collection and parser limits
Initial collection backfills the folder. Hourly scans apply a seven-day receipt overlap locally; weekly reconciliation checks all available history. Pagination continues to an empty page and a second full inventory must agree before completion. Changes, deadlines and pending retries leave scans incomplete. Discovered work is durable, and ingestion commits atomically with completion. Retries are bounded with capped exponential backoff. Transient incomplete scans retry after one minute, doubling up to 15 minutes; successful scans resume hourly polling. Credential and ownership errors retain hourly polling. Rejections retain reasons and provenance, not contents.
Mailbox calls are GET-only; OAuth exchange uses POST. Account ownership is validated before collection. XML, gzip and ZIP containing XML are supported, including legacy and RFC 9990 DMARC namespaces. Foreign namespace extensions cannot replace core fields. Unsafe paths, symlinks, nested archives, DTDs/entities, invalid fields and unapproved domains are rejected. Defaults: 10 MiB input, 50 MiB expansion, 16 entries, 100:1 ratio, depth 32, 100,000 records and a 30-second worker deadline, with Linux CPU/memory limits. The allowlist applies to published policy domains; header-from subdomains of an allowed policy domain are accepted.
Collector holds an exclusive writer lock and persistent WAL connection. MCP mounts SQLite read-only and receives no Zoho credentials. Containers use UID 10001, read-only root filesystems, dropped capabilities and no service-account token.
Backups and operations
Collector creates consistent daily backups with SQLite's backup API and integrity
checks. On Windows, run pwsh -File ops/windows_host.ps1 -RegisterTask once. The
single owner logon/hourly task starts only the designated machine/profile, exports
completed backups with kubectl cp, verifies SHA256 and retains host copies for
30 days under .private/host-backups. Export failures are recorded separately,
locally and in MCP health when cluster access permits.
Operator commands are separate from MCP:
python -m zoho_dmarc backup-list
python -m zoho_dmarc restore --source /private/dmarc-TIMESTAMP.sqlite --target /private/restored.sqlite
python -m zoho_dmarc backup
python -m zoho_dmarc reconcileReconciliation requires exclusive writer ownership: stop the collector first.
Operator backup opens the live source read-only and uses SQLite's backup API.
Restore requires a matching .sha256 file and a new target and checks integrity.
Verify restoration in a disposable instance before relying on backups. Helm
uninstall retains the PVC; cluster deletion does not. Verify a host backup before
deleting the cluster.
CI and releases
CI uses the development container, tests parser/collector/storage, smoke-tests the
runtime image, checks Helm boundaries, and deploys synthetic Zoho/tunnel fixtures
to disposable Kind, including Pod replacement and persistence. Fixtures are mounted
only during CI and are excluded from the production image.
Manual CI input published_install=true with release_version performs fresh Helm installs
from both the Pages index and GHCR OCI chart using synthetic fixtures. These tests
use Helm 3 executable post-renderers; the production chart also works with Helm 4.
Trusted v* tags publish ghcr.io/nephilus/zoho-dmarc-mcp and the packaged chart to
oci://ghcr.io/nephilus/charts/zoho-dmarc-mcp. The identical package is added to
the retained GitHub Pages Helm index. Evidence records commit, chart version, both
image digests and checks. Actions are SHA-pinned; permissions default to read-only,
with publishing rights only in the trusted release job. PRs receive no publishing
credentials. Real account, reboot and backup acceptance are operator checks.
The retained gh-pages branch can be republished independently with the
Republish Helm Pages workflow, without rebuilding or replacing releases.
This server cannot be deployed
Maintenance
Related MCP Connectors
Read your own DMARC aggregate and forensic reports: sources, alignment, alerts, CSV export.
DMARC analytics and domain onboarding for AI assistants — health, SPF/DKIM, compliance, anomalies.
AI-operated. Free SPF (RFC 7208 count), DMARC, MTA-STS, DMARC rua/ruf readers. Paid audit $29.
Query your org's data in natural language — read-only MCP access to SQL, NoSQL, files & warehouses.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceProvides fail-closed, read-only PostgreSQL and MongoDB access for AI agents via MCP, enabling structured data inspection and bounded queries without mutation capabilities.MIT
- FlicenseNot gradedqualityCmaintenanceEnables LLM agents to safely query educational administration databases through read-only MCP tools, enforcing SQL whitelisting, automatic LIMIT caps, parameterized queries, timeouts, token authentication, and audit logging.-
- AlicenseNot gradedqualityCmaintenanceEnables MCP clients such as Claude, ChatGPT and Cursor to answer business questions about sales, customers, orders and spreadsheets in natural language by reading live Postgres/SQLite databases and Google Sheets without exporting files or gaining write access. Guardrails include AST-based read-only SQL validation, read-only database roles, row/byte caps with pagination, PII masking, bearer-token auth for remote HTTP use, and a full JSONL audit log of every tool call.MIT
- AlicenseNot gradedqualityBmaintenanceEnables Claude and other MCP clients to map a SQLite database's schema and run read-only SQL queries against it, with writes blocked at the SQLite level through multiple enforcement layers. Results come back as context-friendly Markdown tables with row caps and a query timeout.2MIT