Skip to main content

MCP Tools Reference

The MCP endpoint exposes two tool sets behind two scopes: the mail tools, which the mcp scope opens, and the management tools, which the mcp-manage scope opens. This page documents each tool, what it returns, and the REST endpoint it dispatches to.

Tools are not a second implementation of the API. Each one wraps a route: the tool's input schema is generated from that route's request validation, and calling the tool runs that route in-process with the caller's own credential. Anything the API Reference says about an endpoint - identifier formats, provider differences, error conditions - is true of the tool that wraps it.

Beta

The mail tools shipped with MCP support in EmailEngine v2.79.2. The management tools and the mcp-manage scope were added in v2.80.1 (2026-09-10). The set may still change between releases; the live catalog described below is the authority for the instance in front of you.

The catalog​

The same catalog is rendered live in the admin interface, under Configuration > MCP > Exposed tools, grouped by scope. That page reads the running registry, so it is the authority on what the instance in front of you exposes:

The MCP tool catalog in the admin interface The Exposed tools card shows exactly what a client receives from tools/list

The Behavior column in the tables below is what a tool advertises through MCP annotations (readOnlyHint, destructiveHint, openWorldHint). Clients use them to decide what to run without asking and what to confirm first. A tool is marked as reaching outside the instance when it sends mail (send_message) or connects to a host the arguments or a stored record name (autodiscover_settings, verify_account_settings, verify_oauth2_app).

Mail tools (mcp scope)​

ToolWhat it doesBehaviorWraps
list_accountsLists accounts on the instance, pagedread-only, both scopesGET /v1/accounts
get_accountOne account: name, address, connection state, sync statusread-only, both scopesGET /v1/account/{account}
list_mailboxesFolder tree with counters and special-use rolesread-onlyGET /v1/account/{account}/mailboxes
list_messagesMessages in one folder, newest first, pagedread-onlyGET /v1/account/{account}/messages
search_messagesStructured search inside one folderread-onlyPOST /v1/account/{account}/search
get_messageOne message: envelope, flags, attachment list, and the body inlineread-onlyGET /v1/account/{account}/message/{message}
get_message_textA message body on its own, with a larger budget than get_messageread-onlyGET /v1/account/{account}/text/{text}
get_attachmentOne attachment, inline as base64read-onlyGET /v1/account/{account}/attachment/{attachment}
update_messageAdds or removes flags and Gmail labelswritePUT /v1/account/{account}/message/{message}
move_messageMoves a message to another folderwritePUT /v1/account/{account}/message/{message}/move
delete_messageMoves to Trash, or deletes permanently from TrashdestructiveDELETE /v1/account/{account}/message/{message}
create_draftStores a message in a folder without sending itwritePOST /v1/account/{account}/message
send_messageQueues an email for delivery to real recipientssends email, reaches outsidePOST /v1/account/{account}/submit
get_outboxThe sending queue, including scheduled messagesread-only, both scopesGET /v1/outbox
list_templatesStored email templatesread-only, both scopesGET /v1/templates

Four of these are reached by both scopes: list_accounts, get_account, get_outbox and list_templates are reads an agent managing the instance needs as much as one reading mail. The admin catalog files them under instance management with a "both scopes" badge.

Management tools (mcp-manage scope)​

Everything below is instance administration. A credential that holds only the mcp scope is not offered any of it, however wide its permissions record - see Access Control.

Accounts

ToolWhat it doesBehaviorWraps
create_accountRegisters an account: IMAP and SMTP settings the user supplied, or an OAuth2 application id with oauth2.authorize set, which returns a sign-in URLwritePOST /v1/account
update_accountChanges an account's configuration, connection settings included. Only the fields sent changewritePUT /v1/account/{account}
delete_accountRemoves an account, its index and the tokens bound to it; revoke also revokes the OAuth2 grant at the providerdestructiveDELETE /v1/account/{account}
reconnect_accountCloses and re-opens the account's connectionwritePUT /v1/account/{account}/reconnect
sync_accountRuns a sync now instead of waiting for the next scheduled onewritePUT /v1/account/{account}/sync
flush_accountDiscards the local index and syncs from scratch; every message counts as new againdestructivePUT /v1/account/{account}/flush
get_account_logsThe stored connection log: the raw IMAP or provider API trace, which names folders, message ids and subjectsread-onlyGET /v1/logs/{account}
create_account_setup_linkMints a one-time link to the hosted setup form, so the user signs in without a password passing through the conversationwritePOST /v1/authentication/form
verify_account_settingsTests IMAP and SMTP settings without saving themread-only, reaches outsidePOST /v1/verifyAccount
autodiscover_settingsDiscovers the IMAP and SMTP servers for an address from DNS and the provider's autoconfigurationread-only, reaches outsideGET /v1/autoconfig

Settings and queues

ToolWhat it doesBehaviorWraps
get_settingsReads instance settings. Secrets come back as a boolean, credentials inside URLs are masked, and eventTypes lists the webhook event namesread-onlyGET /v1/settings
update_settingsChanges instance settings; they take effect at once on every workerwritePOST /v1/settings
get_queueWhether the notify or submit queue is paused and how many jobs are active, waiting and delayedread-onlyGET /v1/settings/queue/{queue}
set_queue_statePauses or resumes a queuewritePUT /v1/settings/queue/{queue}
cancel_queued_messageCancels a message waiting in the sending queue, by its queue iddestructiveDELETE /v1/outbox/{queueId}

The two settings tools offer only the keys a settings editor may hold. The keys that would widen the credential - operator scripts, the link signing secret, the authentication server, proxy trust and local addresses, proxies, the built-in listeners, TLS certificates and provisioning, the service URL, mail certificate checking, the AI key and endpoint, custom webhook headers, hosted page markup, the MCP and audit switches, and error reporting - are absent from both schemas, and the REST route refuses them to a narrowed token as well. See Access Control.

OAuth2 applications

ToolWhat it doesBehaviorWraps
list_oauth2_appsThe applications accounts can sign in through, paged. Client secrets are maskedread-onlyGET /v1/oauth2
get_oauth2_appOne application: provider, client id, scopes, redirect URL and Pub/Sub settingsread-onlyGET /v1/oauth2/{app}
create_oauth2_appRegisters an application with the credentials created at the providerwritePOST /v1/oauth2
update_oauth2_appChanges an application; a client secret can be replaced but not read backwritePUT /v1/oauth2/{app}
delete_oauth2_appRemoves an application; accounts that signed in through it stop syncingdestructiveDELETE /v1/oauth2/{app}
verify_oauth2_appChecks an application against its provider, and optionally one account's connectionread-only, reaches outsidePOST /v1/oauth2/{app}/verify
get_pubsub_statusThe Gmail Pub/Sub subscriptions of the applications and their state, pagedread-onlyGET /v1/pubsub/status

SMTP gateways

ToolWhat it doesBehaviorWraps
list_gatewaysRelay servers messages can be sent through, paged. Passwords are maskedread-onlyGET /v1/gateways
get_gatewayOne gateway: host, port, TLS mode, username and the last delivery errorread-onlyGET /v1/gateway/{gateway}
create_gatewayRegisters a gatewaywritePOST /v1/gateway
update_gatewayChanges a gateway. Only the fields sent changewritePUT /v1/gateway/edit/{gateway}
delete_gatewayRemoves a gateway; messages queued through it fail to deliverdestructiveDELETE /v1/gateway/{gateway}

Templates, suppression lists and webhook routes

ToolWhat it doesBehaviorWraps
create_templateStores a template send_message can reference by idwritePOST /v1/templates/template
update_templateChanges a template's name, description, format or contentwritePUT /v1/templates/template/{template}
delete_templateRemoves a templatedestructiveDELETE /v1/templates/template/{template}
delete_account_templatesRemoves every template of one account; requires forcedestructiveDELETE /v1/templates/account/{account}
list_blocklistsThe suppression lists and how many addresses each holds, pagedread-onlyGET /v1/blocklists
get_blocklistThe addresses on one list, paged, with the reason and source of eachread-onlyGET /v1/blocklist/{listId}
add_to_blocklistAdds a recipient address to a listwritePOST /v1/blocklist/{listId}
remove_from_blocklistRemoves an address from a listdestructiveDELETE /v1/blocklist/{listId}
list_webhook_routesThe custom webhook routes, paged. Credentials in target URLs are maskedread-onlyGET /v1/webhookRoutes
get_webhook_routeOne route: target, events, whether it is enabled, and its filter and mapping functionsread-onlyGET /v1/webhookRoutes/webhookRoute/{webhookRoute}

Access tokens, license and diagnostics

ToolWhat it doesBehaviorWraps
list_tokensThe access tokens on the instance, paged: description, scopes, restrictions, binding and last use. Never the token valuesread-onlyGET /v1/tokens
get_tokenOne token by id: scopes, permissions, restrictions and last useread-onlyGET /v1/tokens/{token}
get_token_logThe audit log of one token, paged, while the tokenAuditLog setting is onread-onlyGET /v1/tokens/{token}/log
revoke_tokenRevokes a token; every client using it is cut off at oncedestructiveDELETE /v1/tokens/{token}
get_licenseThe active license, or an empty object when the instance runs unlicensedread-onlyGET /v1/license
set_licenseApplies a license key, as the full text including its BEGIN and END lineswritePOST /v1/license
get_instance_statsVersion, license, account counts by state, and message and webhook counters for the last secondsread-onlyGET /v1/stats
check_delivery_testThe result of a delivery test started over the REST APIread-onlyGET /v1/delivery-test/check/{deliveryTest}

There is no tool that mints a token. A management credential can list, inspect and revoke tokens, but creating one stays outside every MCP scope, which is what keeps an agent credential from widening itself.

Not everything the API can do

Operations with no tool: bulk export, creating, renaming and deleting folders, the bulk message actions, a message's raw source, re-submitting a stored draft, starting a delivery test, server signatures, the credentialed autodiscovery lookup (POST /v1/autoconfig), the change stream, a single outbox entry or template by id, removing the license, minting tokens, and an account's live OAuth2 access token. An MCP-scoped token is refused those operations even if it asks for them by another route. Use the REST API for them.

A tool schema is narrower than its endpoint​

The arguments a tool offers are a curated subset of what the REST route accepts. Three rules shape them, and they are worth knowing before comparing a tool against its endpoint documentation:

  • Hidden arguments. Fields that are an operator's decision rather than an agent's do not appear. send_message offers the message an agent composes and nothing else: no gateway or envelope routing, no trackOpens/trackClicks, no dsn, no headers/messageId, no raw, and no mailMerge - an agent that should write to several people calls the tool several times, so that each send is visible as a send. create_account, update_account and verify_account_settings hide proxy, so an agent cannot route a stored credential through a host of its choosing, and the two account tools also hide smtpEhloName, webhooksCustomHeaders, copy, locale and tz. create_account_setup_link offers only account, name, email, expectedEmail, redirectUrl and type. The settings tools leave out every privileged key.
  • Forced values. Some arguments are pinned by the server and removed from the schema, so the model is not offered a choice it does not have. Both body tools force sanitized web-safe HTML with a size budget, which is why neither takes textType or maxBytes. reconnect_account, sync_account and flush_account force the flag their routes take, so each is a plain call with an account id.
  • Tightened bounds. Every paged listing caps pageSize at 100 rather than the endpoint's 1000. The schema says so, and the dispatch clamps a larger value anyway.

None of this is enforcement. It shapes what the agent is offered; what it is allowed to do is decided by the credential - see Access Control.

Account-bound credentials see simpler tools

When the token is bound to one account, the account argument disappears from every tool that takes one, and EmailEngine fills the binding in on dispatch. Tools that take no account argument at all - the instance-wide listings, and most of the management tools - are not offered to such a credential. The agent is told which account it is working with in the connect instructions, so it never has to look one up. Everything below shows the unbound shape.

Typical agent workflows​

The tool descriptions steer a model through these sequences, and the server sends the same guidance as MCP instructions on connect.

Reading and answering mail

  1. list_accounts gives the account id every other mail tool needs. A bound token skips this step entirely.
  2. list_mailboxes gives folder paths. Paths are what list_messages, search_messages and move_message take, and they are provider-specific strings, not names to guess at.
  3. list_messages or search_messages gives message ids.
  4. get_message gives the envelope, the attachment list and the body in one call. Only when text.hasMore is true is a second call needed, and get_message_text is the one that makes it.
  5. Acting on the message: flags with update_message, filing with move_message, answering with send_message and a reference block.

Operating the instance

A management credential is told to start with get_instance_stats and list_accounts, and to confirm with the user before changing where webhooks are delivered, before changing where an account or gateway connects, and before anything destructive. To add a mailbox it is told to prefer create_account_setup_link, so the user signs in on the hosted form and no password passes through the conversation; autodiscover_settings and verify_account_settings exist for the case where the user has supplied IMAP settings and a password and wants them checked before create_account.

Arguments​

Every mail tool takes an account argument except list_accounts and get_outbox, which are instance-wide; list_templates takes one optionally, to pick account-specific templates over shared ones. Among the management tools, the ones that act on one account (get_account, update_account, delete_account, reconnect_account, sync_account, flush_account, get_account_logs, delete_account_templates) take it as a required argument, and create_account, create_account_setup_link, create_template, add_to_blocklist, list_tokens and verify_oauth2_app take it as a field of the operation. Required arguments are marked.

Accounts and folders​

list_accounts

ArgumentTypeNotes
pageintegerZero-indexed
pageSizeintegerEntries per page, at most 100
statestringFilter by connection state, for example connected
querystringSubstring match on the account id, name or address

get_account

ArgumentTypeNotes
account (required)stringAccount id
quotabooleanInclude mailbox quota

Credentials are masked in the response.

list_mailboxes

ArgumentTypeNotes
account (required)stringAccount id
countersbooleanInclude message and unseen counts

Reading​

list_messages

ArgumentTypeNotes
account (required)stringAccount id
path (required)stringFolder path, or a special-use label like \Sent. \All works on Gmail IMAP
cursorstringnextPageCursor or prevPageCursor from a previous response
pageintegerZero-indexed. IMAP accounts only
pageSizeintegerEntries per page, at most 100

search_messages

ArgumentTypeNotes
account (required)stringAccount id
path (required)stringFolder to search. One folder at a time, not the whole account
search (required)objectSearch criteria: from, to, subject, body, since, before, seen, flagged, emailId, header and more. See Searching Messages
cursor, page, pageSizePaging, as above
useOutlookSearchbooleanMS Graph only: use $search instead of $filter

get_message

ArgumentTypeNotes
account (required)stringAccount id
message (required)stringMessage id from a listing or search
markAsSeenbooleanSet \Seen while reading

The body comes back inline - see Message bodies for its shape and the rendering the tool pins.

get_message_text

ArgumentTypeNotes
account (required)stringAccount id
text (required)stringThe text.id value from get_message or a listing

get_attachment

ArgumentTypeNotes
account (required)stringAccount id
attachment (required)stringAttachment id from the attachments array of get_message

Returns the file inline as a base64 MCP resource. See Binary results for the size limit.

Organizing​

update_message

ArgumentTypeNotes
account (required)stringAccount id
message (required)stringMessage id
flagsobject{ "add": ["\\Seen"], "delete": ["\\Flagged"], "set": [...] }
labelsobjectSame shape, Gmail only

move_message

ArgumentTypeNotes
account (required)stringAccount id
message (required)stringMessage id
path (required)stringDestination folder path
sourcestringSource folder path. Gmail API accounts only, where it is what removes the old label

The message id changes when a message moves. The response carries the new one.

delete_message

ArgumentTypeNotes
account (required)stringAccount id
message (required)stringMessage id
forcebooleanDelete outright instead of moving to Trash. Not supported on Gmail API accounts

Deleting moves the message to Trash when it is not already there, and deletes it permanently when it is.

Writing and sending​

create_draft stores a message in a folder. Nothing is sent.

ArgumentTypeNotes
account (required)stringAccount id
path (required)stringTarget folder, usually the Drafts folder from list_mailboxes
from, to, cc, bccobject / arrayAddresses
subject, text, htmlstringContent
attachmentsarrayAttachment objects
referenceobjectDraft a reply or a forward, see below
flagsarrayFlags for the stored copy, for example ["\\Draft"]

send_message queues a message for delivery.

ArgumentTypeNotes
account (required)stringAccount id
to, cc, bccarrayRecipients
subject, text, htmlstringContent
from, replyToobjectSender addresses. from defaults to the account's own address
referenceobjectReply to or forward a stored message, see below
templatestringSend a stored template
renderobjectValues for the template's placeholders
attachmentsarrayAttachment objects
sendAtstringISO date to schedule the send

Delivery is queued, not immediate. The response carries a queueId, and the message shows up in the get_outbox listing until it is delivered.

Sending is the one irreversible mail tool

send_message reaches real recipients, and a queued message can only be cancelled while it is still in the outbox. Clients that honor MCP annotations treat it as an open-world call and ask for confirmation. If you would rather they could not call it at all, issue the token at the read-only mail level - see Access Control.

Replying and forwarding​

Both send_message and create_draft take a reference block, which is how an agent answers the message a user is looking at. Point it at a stored message and write only the new text: EmailEngine derives the subject, the recipients of a reply and the In-Reply-To/References headers from the referenced message, and flags that message as answered or forwarded once the new one is sent.

FieldTypeNotes
messagestringId of the message being answered or passed on
actionstringreply (default), reply-all or forward
inlinebooleanQuote the original under the new text, the way an email client does. Off by default
forwardAttachmentsbooleanCarry the original's attachments into the forwarded copy. Only meaningful with forward
ignoreMissingbooleanSend anyway if the referenced message cannot be found
messageIdstringVerify the original's Message-ID before sending
threadIdstringGmail thread to attach the outgoing message to
{
"name": "send_message",
"arguments": {
"account": "user123",
"reference": { "message": "AAAAAQAACnA", "action": "reply", "inline": true },
"text": "Thanks - Tuesday at 10:00 works for me."
}
}

Queue and templates​

get_outbox takes page and pageSize (at most 100). It lists queued and scheduled messages with their delivery progress.

list_templates takes account (for account-specific templates; omit for shared ones), page and pageSize (at most 100).

Management tools​

The management tools take the fields of the endpoint they wrap, minus the hidden ones. The endpoint pages linked from the catalog are the field reference; the shapes that differ from a plain wrap are:

ToolArguments
reconnect_account, sync_accountaccount only. The route's flag is forced on
flush_accountaccount, plus the optional notifyFrom and imapIndexer the route accepts for the re-sync
delete_accountaccount, revoke
create_account_setup_linkredirectUrl (required), account, name, email, expectedEmail, type
verify_account_settingsimap, smtp, mailboxes. No proxy
autodiscover_settingsemail
get_settingsOne boolean per setting to return, plus eventTypes
update_settingsThe settings to change, by key
get_queue, set_queue_statequeue (notify or submit); paused on the setter
get_instance_statsseconds, the window the counters cover
delete_account_templatesaccount, force
get_token_log, get_blocklist and every listingpage, pageSize (at most 100), plus the listing's own filters (query and account on list_tokens)

Message bodies​

Both body tools return exactly one rendering: sanitized web-safe HTML, generated from the plaintext part when a message carries no HTML one. There is no plaintext twin beside it, and the rendering is not the agent's to choose - textType, webSafeHtml, preProcessHtml, embedAttachedImages and maxBytes are all pinned by the server and absent from the tool schemas.

get_message carries the body inline, so reading one message is one call:

{
"subject": "Your ticket #8812 has been resolved",
"from": { "name": "Support", "address": "support@example.org" },
"flags": [],
"text": {
"id": "AAAAAQAAAAaTkaExkaEykA",
"encodedSize": { "plain": 175, "html": 219 },
"html": "<div style=\"overflow: auto;\"><p>We closed ticket #8812.</p>...</div>",
"hasMore": false,
"webSafe": true
}
}

hasMore: true means the body was longer than the budget below. That is when get_message_text earns its call, using text.id:

{
"html": "<div style=\"overflow: auto;\">...</div>",
"webSafe": true,
"hasMore": false
}

Quoted history is marked, not stripped. Reply and forward history is wrapped in a <details class="ee-collapsed-thread"> element - the boundary an email client hides behind a "show more" control. Everything outside it is what the sender wrote this time, which on a long thread is a small fraction of the bytes. The server instructions name the class, so a model can use it without being told. See Web Safe HTML.

Attached images are not inlined. An agent reads a body rather than displaying it, so cid: references are left as they are instead of being expanded into data URIs that would multiply the size of the result. The references name attachments get_attachment can fetch.

Results​

A successful tool call returns the endpoint's JSON response twice: once as text, for models that read the content block, and once as structuredContent, for clients that parse it.

{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "{\"total\":1,\"pages\":1,\"page\":0,\"accounts\":[{\"account\":\"user123\",\"name\":\"John Doe\",\"email\":\"john.doe@example.com\",\"type\":\"imap\",\"state\":\"connected\"}]}"
}
],
"structuredContent": {
"total": 1,
"pages": 1,
"page": 0,
"accounts": [
{
"account": "user123",
"name": "John Doe",
"email": "john.doe@example.com",
"type": "imap",
"state": "connected"
}
]
}
}
}

The JSON is compact on purpose: indentation is padding to a model, and it counts against the size cap below.

Errors​

A failed tool call is a result, not a protocol error. The result carries isError: true and the API's own error body, so an agent can read what went wrong and correct itself:

{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "{\"statusCode\":403,\"error\":\"Forbidden\",\"message\":\"Unauthorized permission\",\"requiredPermission\":{\"action\":\"send\",\"group\":\"submit\"}}"
}
],
"isError": true
}
}

Argument mistakes are caught before dispatch and named:

{ "content": [{ "type": "text", "text": "Missing required tool argument: message" }], "isError": true }
{ "content": [{ "type": "text", "text": "Unknown tool argument: bogus" }], "isError": true }

Calling a tool that does not exist is a JSON-RPC error rather than a result, because no tool ran - see Error codes.

Size limits​

LimitValueWhat happens
Tool result128 KBThe text is cut at the limit and a notice is appended naming the full size; structuredContent is omitted so the cap is not defeated
get_message body32768 charactersThe body is cut before rendering and text.hasMore is set
get_message_text body65536 charactersSame, reported as hasMore
Page size on any listing100 entriesThe schema says so, and a larger request is clamped
Inline attachment1 MBget_attachment refuses and points at the REST download endpoint
Accounts in resources/list500Larger instances are browsed with list_accounts and its paging arguments

The two body budgets are input bounds: they cut each text part before the web-safe rendering runs, and that rendering can come out somewhat larger than what went in. The 128 KB result cap is the promise. They are set well under it so an ordinary message never reaches truncation, because a truncated result leaves the caller with a JSON fragment it cannot parse.

A single message can carry megabytes of text, and an oversized result degrades or breaks the calling model, so the caps err low. When one bites, narrow the request rather than working around it: page smaller, search instead of listing, and follow hasMore only when the rest of the body actually matters.

Binary results​

get_attachment returns an embedded resource rather than text:

{
"content": [
{
"type": "resource",
"resource": {
"uri": "emailengine://account/user123/attachment/AAAAAQAACnAcdefgh",
"mimeType": "application/pdf",
"blob": "JVBERi0xLjQKJcfs..."
}
}
]
}

The URI is a stable identifier for the client to attach the blob to. It is not listed by resources/list and not readable with resources/read - the content is in the result.

Resources​

Each account the credential can see is published as an MCP resource:

{
"uri": "emailengine://account/user123",
"name": "user123",
"title": "John Doe",
"description": "john.doe@example.com, state: connected",
"mimeType": "application/json"
}

resources/read on that URI returns the same payload as get_account. Clients that browse resources can therefore show what a credential reaches without calling a tool, and an account-bound credential sees exactly its own account.

Accounts whose id contains /, ? or #, or is . or .., are skipped in the listing: they cannot round-trip through the URI. Their mail is still reachable through the tools, which take the id as an argument.

Modern-revision clients can also subscribe to an account resource and be notified when its state changes - see Subscriptions.

See Also​