Skip to content

MCP Server (Model Context Protocol)

Mail Archiver ships an optional MCP (Model Context Protocol) server that exposes the archived mail to AI agents over the Streamable HTTP transport. It mirrors the read-only REST API: same data, same per-user permission model, same access logging — just packaged as MCP tools instead of REST endpoints, so an LLM agent can discover and call them directly.

The point is to let an AI agent read archived mail without ever exposing mailbox credentials or talking to the mail provider. A scoped per-user API key is a capability to read the archive, not a credential to be the mailbox — it inherits only its owner's mailbox permissions, grants no write path, and can be revoked instantly without touching the underlying mail account.

⚠️ The MCP server is disabled by default. It must be explicitly enabled in configuration (see Enabling the MCP server). When disabled, the /mcp endpoint returns 404 Not Found.

The MCP server is read-only by design: it exposes tools to list accounts, list folders, search messages, read a message and download an attachment. It never creates, modifies or deletes data.

Table of contents

Enabling the MCP server

The MCP server is configured under the Mcp section of appsettings.json (or the equivalent environment variables, e.g. Mcp__Enabled=true):

"Mcp": {
  "Enabled": false,
  "AllowAttachmentDownloads": true,
  "DefaultPageSize": 20,
  "MaxResults": 100,
  "MaxAttachmentBytes": 10000000,
  "RateLimitPerMinute": 120
}
Setting Default Description
Enabled false Master switch. When false, the /mcp endpoint returns 404.
AllowAttachmentDownloads true When false, get_attachment returns an error instead of attachment bytes. Metadata about attachments is still returned by get_email.
DefaultPageSize 20 Page size used by search_emails when the caller omits pageSize.
MaxResults 100 Upper bound for the search_emails page size; larger values are clamped.
MaxAttachmentBytes 10000000 Maximum attachment size (in bytes) get_attachment will return inline as base64; larger attachments are refused.
RateLimitPerMinute 120 Fixed-window request budget per API key per minute (see Rate limiting).

Authentication

The MCP server authenticates with the same per-user API keys as the REST API, sent as a bearer token:

Authorization: Bearer ma_<43 url-safe base64 characters>

API keys are created and managed in the web UI under API Keys. A key inherits the exact permissions of the user who owns it (admin sees all accounts; a restricted user sees only their assigned accounts). Keys can be revoked or expired independently of the underlying mail accounts. See the REST API authentication section for the full key lifecycle and permission model — MCP keys are not a separate credential store.

When the MCP server is disabled, or no/invalid bearer token is presented, the endpoint returns 404 / 401 respectively (it never redirects to the login page).

Transport and endpoint

The MCP server uses the Streamable HTTP transport and is mounted at:

POST /mcp

It runs in stateless mode (one JSON-RPC request per HTTP request), which is the recommended mode for servers that do not need server-to-client requests like sampling or elicitation. Tools are discovered via the standard MCP tools/list request and called via tools/call.

Tools

list_accounts

Lists the mail accounts the current API key is allowed to access. Returns id, name, emailAddress, provider, isEnabled and lastSync.

Parameters: none.

Result: array of account objects. Use the returned id with list_folders and search_emails.

list_folders

Lists the folder tree of a single mail account, with per-folder email counts.

Parameters:

Name Type Required Description
accountId integer yes The mail account id (from list_accounts).

Result: array of folder objects, each with name, fullPath, totalCount, level and recursive children. Returns an empty list if the account does not exist or the key is not authorized for it.

search_emails

Searches archived emails by full-text query, date range, account, folder and direction.

Parameters:

Name Type Required Description
q string no Full-text search query (subject, body, from/to).
from string (ISO-8601) no Sent-on-or-after UTC date/time filter.
to string (ISO-8601) no Sent-on-or-before UTC date/time filter.
accountId integer no Restrict to a single mail account id.
folder string no Restrict to a single folder full path (e.g. INBOX).
direction string no incoming or outgoing.
page integer no 1-based page number (default 1).
pageSize integer no Page size; clamped to [1, MaxResults]. 0 uses DefaultPageSize.
sortBy string no sentdate (default), receiveddate, subject, from, to.
sortOrder string no asc or desc (default desc).

Result: a paged object with items (array of email summaries), page, pageSize, totalItems, totalPages. Each summary carries id, accountId, subject, from, to, sentDate, isOutgoing, hasAttachments and folderName. Pass a returned id to get_email for the full body and attachment list.

get_email

Fetches the full content of a single archived email.

Parameters:

Name Type Required Description
id integer yes The archived email id (from search_emails).

Result: a single email object with all summary fields plus cc, bcc, receivedDate, messageId, textBody, htmlBody and attachments (array of { id, fileName, contentType, size }). Returns null if the email does not exist or the key is not authorized for its account.

get_attachment

Downloads a single email attachment as a base64 blob.

Parameters:

Name Type Required Description
id integer yes The archived email id the attachment belongs to.
attachmentId integer yes The attachment id (from get_email's attachments).

Result: an object with id, emailId, fileName, contentType, size and base64Data (the raw attachment bytes, base64-encoded). The tool returns an error when Mcp:AllowAttachmentDownloads=false, when the attachment exceeds Mcp:MaxAttachmentBytes, or when the key is not authorized for the email's account.

Rate limiting

The MCP server has its own fixed-window rate-limit policy ("Mcp"), partitioned by the non-secret API-key prefix (first 11 characters of the ma_... token), falling back to the client IP when no bearer token is present. The budget is configured via Mcp:RateLimitPerMinute (default 120 per minute). When exceeded, the endpoint returns 429 Too Many Requests with a Retry-After header. This is independent of the REST API's rate-limit policy.

Access logging

Every tool call is logged through the same IAccessLogService as the REST API, with the owning username and the matching access type (Search, Open, Download). MCP activity is therefore visible on the Logs page alongside REST API and web-UI activity, for a single audit trail.

Connecting an agent

The MCP server speaks the standard MCP Streamable HTTP transport, so any MCP client that supports HTTP can connect. The URL is the application base URL plus /mcp, e.g. http://localhost:5179/mcp, and the API key is supplied via the Authorization: Bearer ma_... header.

For agents configured via the standard MCP client JSON, an entry looks like:

{
  "mcpServers": {
    "mail-archiver": {
      "url": "https://archive.example.com/mcp",
      "headers": {
        "Authorization": "Bearer ma_<your-api-key>"
      }
    }
  }
}

Security guidance for internet exposure

The MCP server is designed so that, with the defaults and the hardening below in place, archived mail cannot leave the system without a valid per-user API key that has been explicitly issued to an account. The points below are mandatory reading before exposing /mcp to the internet.

Mandatory (block exploitation / data exfiltration)

  1. HTTPS only. Terminate TLS at the edge (reverse proxy). A bearer API key over plain HTTP can be sniffed and reused.
  2. Keep Mcp:Enabled=false unless you actually use it. When disabled, /mcp returns 404 and no code path is reachable; this is the safe default and the safe state for deployments that do not need AI-agent access.
  3. Rate limiting is enforced on the endpoint. The "Mcp" policy (Mcp:RateLimitPerMinute, default 120) is applied on the mapped /mcp endpoint. It is partitioned by the non-secret API-key prefix (first 11 chars of the ma_... token) or the client IP when no bearer token is present. Tune RateLimitPerMinute down for internet exposure (e.g. 30–60) — MCP tool calls are heavier than REST calls and the endpoint should not be left at a high budget when untrusted clients can reach it.
  4. Reverse-proxy X-Forwarded-For must be trusted carefully. The rate limiter falls back to RemoteIpAddress, which under a proxy is the proxy IP unless UseForwardedHeaders is fed the real client IP. Ensure your proxy sets X-Forwarded-For correctly and that only your proxy can set it; otherwise an attacker can spoof the rate-limit partition.
  5. Do not place /mcp behind a CDN cache. MCP responses are per-request JSON-RPC and must never be cached — caching would both break the protocol and leak one user's result to another. Mark /mcp as uncacheable at the edge (Cache-Control: no-store).

API-key hygiene (block unauthorized access)

  1. Issue dedicated, short-lived keys for MCP agents. A single ma_... key is valid for both /api and /mcp; there is no per-protocol scope today. Therefore:
  2. Create a separate key per agent (do not reuse a reporting-script key for an external LLM agent).
  3. Always set a short ExpiresAt and rotate it; revoke immediately when the agent is decommissioned.
  4. Prefer keys owned by a restricted (non-admin) user whose only assigned accounts are the ones the agent needs. An admin key grants visibility into all mail accounts; a restricted key limits blast radius.
  5. Treat the API key like a password. It is a bearer credential. Never embed it in client-side code, never log it, never commit it to a repo. Put it in the MCP client's secret store and rotate on suspected compromise.
  6. Audit on the Logs page. Every tool call is recorded in AccessLogs (Search, Open, Download) under the owning user. Periodically review MCP activity for unexpected patterns (volume spikes, off-hours reads, mass downloads).

Trust model for returned data (block downstream harm)

  1. Email bodies and attachments are untrusted content, exactly as they were when they arrived in the mailbox. They may contain malware, phishing links, or active HTML/JavaScript.
  2. get_email returns htmlBody raw and unsanitized. Do not render it to a human without sanitizing (DOMPurify / HtmlSanitizer / a sandbox). Rendering raw archived HTML is a stored-XSS vector.
  3. get_attachment returns the raw bytes with the original contentType. Do not auto-open or auto-execute attachments; treat the contentType field as untrusted metadata, not as a safe instruction.
  4. Do not let the agent take write actions based on mail content without confirmation. A malicious email can contain prompt-injection text aimed at the consuming LLM. Enforce permissions and confirmation in the agent's own logic — the system prompt is not a security boundary.

Defense-in-depth recommendations

  1. Restrict /mcp at the network layer when feasible (e.g. allow only the agent host's IP / VPN range at the reverse proxy), in addition to API-key auth. This is the single most effective control for internet exposure.
  2. Lower Mcp:AllowAttachmentDownloads to false if your agents do not need attachment bytes — attachment exfiltration is the highest-volume data path. Metadata (file name, content type, size) is still returned via get_email.
  3. Lower Mcp:MaxAttachmentBytes to the smallest value your agents actually need (default 10000000 ≈ 10 MB). This bounds the largest single response an API key can pull per request.
  4. Lower Mcp:MaxResults and Mcp:DefaultPageSize to bound the largest search result an API key can pull per request (default 100 / 20).
  5. Monitor AccessLogs and rate-limit rejections (429) operationally. A sudden spike from a single key prefix is a signal to revoke that key.