Skip to content
MailGuardHQ
MCP server

Operate MailGuardHQ from an AI client

The Model Context Protocol is how an AI assistant calls a service on your behalf. Our MCP server exposes 20 tools over the same public REST API the web app uses - so an assistant can check a domain's email authentication, manage the domains you monitor and handle API tokens, with the same permissions you would have yourself.

Not released yet - there is nothing to connect to

This documents a server that exists and works, and that we do not host for you. There is no public endpoint, no published repository and no registry listing yet. Running it today means running it yourself, from source, against your own account.

Public release is tracked as its own piece of work and is not finished. This page is here so you can decide whether it is worth waiting for, and tell us what it is missing. Everything below is what the server does now, not what it is planned to do.

The MCP server is a translator, not a second product. Every permission decision, every limit and every audit row belongs to the API - so an MCP client can never do more than the same person could do with the same token.

What it is

A remote MCP server, spoken over HTTP. There is nothing to install on your machine and nothing to keep running beside your editor - a client opens a connection, asks what tools exist, and calls them.

It is a thin wrapper, and that word is doing real work. It holds no business logic, talks to no database, and makes no authorization decision of its own: no scope check, no role check, no plan check, no tenancy check. All of that stays in the API, computed on every request from what your token carries, what your role permits, what the token's audience allows and what your plan sells. A second place to decide permissions is a second place to be wrong, and it is always the second one that drifts.

The tools are generated from the API's OpenAPI document, not written by hand, and a build in the server's own repository fails if the generated list has drifted from the contract. 20 tools from 71 operations, with 51 operations deliberately left out - listed below with the reason for each.

Connecting

Three things: a credential, a running server, and a client pointed at it.

1. Get a token

You need a MailGuardHQ API token minted for the mcp audience. A free account is enough - sign up, verify your address, and mint one from your organisation. Tokens look like mgh_live_... in the live environment and mgh_test_... in test, and every one of them expires.

What free actually carries: one token at a time, a maximum lifetime of 30 days, and never dns:write - which an MCP token could not use anyway. That is enough to connect a client and drive the product; it is not enough to run a fleet on.

Scopes are chosen explicitly when you mint the token. There is no preset picker yet - you name the scopes you want, and a request for anything above what your role, your plan or the MCP audience permits is refused rather than quietly trimmed, so you find out at minting time instead of at the first call.

2. Run the server

One setting decides everything: which API it wraps. Nothing secret goes in a file - the credential travels on the request, from the client.

Run it against the live API

# the one setting that matters: which API this wraps
Api__BaseUrl=https://api.mailguardhq.com

dotnet run --project src/MailGuardHq.Mcp

Check it came up, and what it thinks it is. The readiness probe reports its own posture, including the honest state of audience verification, so an operator sees the gap in a probe rather than only in a document:

Readiness

curl -s localhost:5000/health/ready

{"status":"ok","tools":20,"excluded":51,
 "specVersion":"v1","audienceVerification":"FormatOnly",
 "audienceVerifierOperational":false,"credentialRedaction":true,
 ...}

3. Point a client at it

The transport is streamable HTTP and the endpoint is /mcp. Configuration differs slightly between clients; the shape is always a URL and an Authorization header.

A typical MCP client configuration

{
  "mcpServers": {
    "mailguardhq": {
      "type": "http",
      "url": "http://localhost:5000/mcp",
      "headers": {
        "Authorization": "Bearer mgh_live_..."
      }
    }
  }
}

The server is stateless - every request carries its own credential and nothing is remembered between them - so it scales sideways and a restart costs nobody a session.

Authentication

One credential kind works today: an API token you minted, sent as a bearer token. What the server does before it forwards anything upstream, in order:

What the credential guard refuses, and why
What arrivesWhat happens
No credential at allAnswered 401 with an RFC 9728 WWW-Authenticate challenge naming where to look, so a client that speaks OAuth discovery can find its own way rather than simply failing.
A credential minted by somebody elseRefused, never forwarded. Passing a token minted for another service straight through is a named MCP anti-pattern: it makes this server a confused deputy, detaches the audit trail from the real actor, and turns a token stolen anywhere into a token usable here.
A malformed credentialRefused arithmetically, from the checksum inside the token, with no network call. Without that, every junk string would cost the API a database round trip and flooding this endpoint would be a cheap way to take the authentication path down.
An interactive session tokenRefused by default. A session token is a browser credential; finding one in a client config file means somebody pasted their signed-in session somewhere it will outlive their attention.

Forwarding your token upstream is not passthrough. The anti-pattern is accepting a token minted for a different service. Yours was minted by MailGuardHQ for MailGuardHQ, and forwarding it unchanged is what keeps the API the single place permissions are decided, with one audit trail and the real actor on every row.

Scopes, and the two things MCP can never do

Scopes are resource:action and never imply one another - domains:write does not grant domains:read. What a call is actually allowed to do is the intersection of four ceilings, recomputed on every request: the token's scopes, your current role, the token's audience, and what your plan sells. A demotion or a downgrade therefore applies immediately rather than at the next token rotation.

dns:write and members:write are excluded from MCP, whatever your token says

An MCP token is driven by a model reading text it did not write, so prompt injection is routine input rather than an exotic attack. The two actions a model must never be talked into are breaking a customer's mail flow and granting somebody administrative access - the first stops legitimate email and is slow to notice, the second is how a single bad instruction becomes a permanent one.

The API applies that ceiling itself, so it holds even if the MCP server is wrong, misconfigured or replaced.

The 20 tools

Generated from the API contract, so this table cannot describe a call the API does not serve. Each tool wraps exactly one endpoint.

Checks

Run the same thirteen checks the free domain check runs, and read what a past run found.

Checks tools
ToolWhat it doesCalls
mgh_list_check_historyList past check results
For an authenticated caller, the organisation's history - which needs checks:read. For an anonymous visitor, their own, identified by the X-Provisional-Session header they were issued. Paginated, newest first.
GET /v1/checks/history
mgh_list_checksList the checks that a run performs
Paginated. Default page size 100, maximum 200, in declaration order.
GET /v1/checks/catalog
mgh_run_domain_checkRun a domain check
Runs every configured check against a domain and returns discrete, individually-addressable results. Send Accept: text/event-stream to receive each result the moment it completes. The result is recorded against the caller - an organisation when authenticated, an anonymous visitor when an X-Provisional-Session header is present - so it can be listed later and claimed at signup.
POST /v1/checks

Domains

The domains an organisation monitors. Adding one is a data change - no deploy, no code.

Domains tools
ToolWhat it doesCalls
mgh_add_domainStart monitoring a domain
Adding a domain is a data change only - no deploy, no code change. Honours Idempotency-Key. The domain starts at p=none with enforcement off; advancing it requires an explicit, recorded human approval.
POST /v1/domains
mgh_get_domainFetch one monitored domain
Returns 404 - not 403 - for a domain belonging to another account.
GET /v1/domains/{id}
mgh_list_domainsList monitored domains
Paginated. Default page size 100, maximum 200, sorted by domain name.
GET /v1/domains

Account

Who the credential belongs to.

Account tools
ToolWhat it doesCalls
mgh_get_accountThe signed-in personGET /v1/accounts/me

API tokens

Mint, list, rotate and revoke credentials - including the breach-response path that revokes many at once.

API tokens tools
ToolWhat it doesCalls
mgh_get_tokenFetch one token's metadataGET /v1/orgs/{organizationId}/tokens/{tokenId}
mgh_list_tokensList an organisation's tokens
Paginated, newest first. Secrets are never included.
GET /v1/orgs/{organizationId}/tokens
mgh_revoke_tokenRevoke one tokenDELETE /v1/orgs/{organizationId}/tokens/{tokenId}

Organisations and members

The organisation record, who is on it, and the invitations outstanding.

Organisations and members tools
ToolWhat it doesCalls
mgh_get_organizationFetch one organisation
Returns 404 - not 403 - for an organisation you are not a member of.
GET /v1/orgs/{organizationId}
mgh_list_account_settingsList this organisation's settings
Each row shows the value in force, the platform default, the bound the platform sets and whether a stored value had to be pulled back inside that bound - which is how a plan change shows up here rather than silently.
GET /v1/orgs/{organizationId}/settings
mgh_list_invitationsList an organisation's invitationsGET /v1/orgs/{organizationId}/invitations
mgh_list_membersList an organisation's membersGET /v1/orgs/{organizationId}/members
mgh_list_organization_check_historyList one organisation's check history
The session-reachable view of a tenant's check history. Paginated. A caller with no seat in this organisation is answered 404, the same as an organisation that does not exist.
GET /v1/orgs/{organizationId}/checks/history
mgh_list_organization_domainsList one organisation's monitored domains
The session-reachable view of a tenant's domains. Paginated. A caller with no seat in this organisation is answered 404, the same as an organisation that does not exist.
GET /v1/orgs/{organizationId}/domains

Pricing

What a plan costs on a term, including the domains past its allowance. Anonymous, and it changes nothing - the pricing console that discloses our costs and the one that changes a price are both excluded by name.

Pricing tools
ToolWhat it doesCalls
mgh_get_pricing_quotePrice a plan
Returns what a prospect would pay for a plan on a term, including any domains past the plan's allowance. Anonymous. The response carries what a customer is charged and what has to be said alongside it - never what the platform costs to run.
GET /v1/pricing/quote

Health

Whether the server is up and what it thinks it is. Neither needs a credential.

Health tools
ToolWhat it doesCalls
mgh_health_dependenciesRegistry dependency check - reads a real row and the schema. Not a probe.GET /health/dependencies
mgh_health_liveLiveness probeGET /health/live
mgh_health_readyReadiness probeGET /health/ready

A worked example

Connect, check a domain, read the findings. This is the whole loop.

  1. Point your client at the server as above. On connect it asks for the tool list and gets all 20, each with its JSON schema, so the model knows what arguments are valid without being told.
  2. Ask for a check in ordinary words - "check whether anyone can send email as yourcompany.com". The client picks the tool.
  3. Read the findings. Each is a discrete result you can act on, not a paragraph of prose to interpret.

The call the client makes, and what comes back

mgh_run_domain_check { "domain": "yourcompany.com" }

-> 13 findings, one per check, each with an id, a verdict of
   pass / warn / fail / not_applicable, and the evidence behind it.

mgh_run_domain_check accepts an Idempotency-Key, like every write on the API: repeat a call with the same key and the original response is replayed rather than the work being done twice.

The same check is available with no account and no client at all - the free domain check runs the identical thirteen checks in a browser.

51 operations are deliberately not exposed

Every one of them establishes a credential. A tool that takes a password puts that password in the model's context, in the client's transcript, and in every log along the way. None of the 51 is a product capability, so the whole product surface is still reachable.

Operations excluded from the MCP tool list, with the reason for each
OperationWhy not
AcceptInvitation
POST /v1/invitations/accept
Refuses API keys by design (session-only route) and consumes a single-use token from an invitation email. Accepting an invitation is a human act of joining an organisation.
AdminAnalysePricing
GET /v1/admin/pricing/analysis
The platform admin surface, and the one route on it that discloses what the platform COSTS US: fully-loaded COGS, the margin model in force, and the multiple the published price achieves. MGH-264 kept every one of those off the public quote by construction - there is no property on the customer-facing type able to carry one - and publishing them as an MCP tool would undo that through the other door. MGH-119 additionally makes the route INVISIBLE, answering a non-administrator exactly as an unroutable path does, which advertising it as a tool defeats.
AdminChangeLimits
PUT /v1/admin/organizations/{organizationId}/limits
The platform admin surface. MGH-119 makes it INVISIBLE - an ordinary caller gets an identical not_found for an admin route and a route that does not exist - and advertising it as an MCP tool undoes exactly that. An MCP token is driven by a model reading untrusted content, so prompt injection is routine input rather than an exotic attack; administrative reach is the last thing that should be available that way. Support work belongs in the console, behind a human.
AdminChangeOrganizationStatus
PUT /v1/admin/organizations/{organizationId}/status
The platform admin surface. MGH-119 makes it INVISIBLE - an ordinary caller gets an identical not_found for an admin route and a route that does not exist - and advertising it as an MCP tool undoes exactly that. An MCP token is driven by a model reading untrusted content, so prompt injection is routine input rather than an exotic attack; administrative reach is the last thing that should be available that way. Support work belongs in the console, behind a human.
AdminChangePlan
PUT /v1/admin/organizations/{organizationId}/plan
The platform admin surface. MGH-119 makes it INVISIBLE - an ordinary caller gets an identical not_found for an admin route and a route that does not exist - and advertising it as an MCP tool undoes exactly that. An MCP token is driven by a model reading untrusted content, so prompt injection is routine input rather than an exotic attack; administrative reach is the last thing that should be available that way. Support work belongs in the console, behind a human.
AdminGetAccount
GET /v1/admin/accounts/{userId}
The platform admin surface. MGH-119 makes it INVISIBLE - an ordinary caller gets an identical not_found for an admin route and a route that does not exist - and advertising it as an MCP tool undoes exactly that. An MCP token is driven by a model reading untrusted content, so prompt injection is routine input rather than an exotic attack; administrative reach is the last thing that should be available that way. Support work belongs in the console, behind a human.
AdminGetOrganization
GET /v1/admin/organizations/{organizationId}
The platform admin surface. MGH-119 makes it INVISIBLE - an ordinary caller gets an identical not_found for an admin route and a route that does not exist - and advertising it as an MCP tool undoes exactly that. An MCP token is driven by a model reading untrusted content, so prompt injection is routine input rather than an exotic attack; administrative reach is the last thing that should be available that way. Support work belongs in the console, behind a human.
AdminListAudit
GET /v1/admin/audit
The platform admin surface. MGH-119 makes it INVISIBLE - an ordinary caller gets an identical not_found for an admin route and a route that does not exist - and advertising it as an MCP tool undoes exactly that. An MCP token is driven by a model reading untrusted content, so prompt injection is routine input rather than an exotic attack; administrative reach is the last thing that should be available that way. Support work belongs in the console, behind a human.
AdminListPlans
GET /v1/admin/plans
The platform admin surface. MGH-119 makes it INVISIBLE - an ordinary caller gets an identical not_found for an admin route and a route that does not exist - and advertising it as an MCP tool undoes exactly that. An MCP token is driven by a model reading untrusted content, so prompt injection is routine input rather than an exotic attack; administrative reach is the last thing that should be available that way. Support work belongs in the console, behind a human.
AdminRepricePlan
POST /v1/admin/pricing/plans/{planCode}/price
It changes what every NEW customer is charged, and MGH-17 AC3 says outright that an AI operator must never reprice autonomously. An MCP token is a model reading untrusted content, so prompt injection is routine input here rather than an exotic attack - this is the clearest case in this file. Repricing belongs in the console, behind a human who has to type a reason that is recorded against the change.
AdminRevokeDomainOwnership
POST /v1/admin/organizations/{organizationId}/domains/{domainId}/ownership/revoke
The platform admin surface. MGH-119 makes it INVISIBLE - an ordinary caller gets an identical not_found for an admin route and a route that does not exist - and advertising it as an MCP tool undoes exactly that. An MCP token is driven by a model reading untrusted content, so prompt injection is routine input rather than an exotic attack; administrative reach is the last thing that should be available that way. Support work belongs in the console, behind a human.
AdminSearchAccounts
GET /v1/admin/accounts
The platform admin surface. MGH-119 makes it INVISIBLE - an ordinary caller gets an identical not_found for an admin route and a route that does not exist - and advertising it as an MCP tool undoes exactly that. An MCP token is driven by a model reading untrusted content, so prompt injection is routine input rather than an exotic attack; administrative reach is the last thing that should be available that way. Support work belongs in the console, behind a human.
AdminSearchOrganizations
GET /v1/admin/organizations
The platform admin surface. MGH-119 makes it INVISIBLE - an ordinary caller gets an identical not_found for an admin route and a route that does not exist - and advertising it as an MCP tool undoes exactly that. An MCP token is driven by a model reading untrusted content, so prompt injection is routine input rather than an exotic attack; administrative reach is the last thing that should be available that way. Support work belongs in the console, behind a human.
ChangeMemberRole
PATCH /v1/orgs/{organizationId}/members/{seatId}
The API does not serve this route to the "mcp" audience, so every call would be refused 403 wrong_audience by the only kind of token this server accepts. An advertised tool that cannot be called is worse than an absent one: a model retries it and reports a product failure to the user. Publishing it becomes automatic the day the route declares mcp in x-mgh-audiences.
ConfirmCheckReportEmail
POST /v1/checks/report-email/confirm
The report-by-email flow is a MARKETING-SITE mechanism for an anonymous visitor, not an API capability: it is the one place the product will send mail to an address that was typed into a form. Handing that to an MCP token means handing an outbound-mail primitive, aimed at an address chosen in the call, to a model reading untrusted content - which is how our sending reputation gets spent on somebody else's phishing. It also completes in an inbox: confirm and unsubscribe consume one-shot tokens that arrive by email, so a model could only ever start a flow it cannot finish. An authenticated MCP caller needs none of it - RunDomainCheck returns the findings in the response.
CreateOrganization
POST /v1/orgs
The API serves this route to NO token audience - it accepts an interactive session only, which is how it refuses a machine credential acting unattended on a person's behalf. This is not an mcp narrowing and widening the audience would not help: there is no token to mint that would work.
CreateProvisionalSession
POST /v1/provisional-sessions
Mints an anonymous pre-signup identity for the marketing site's free check. It is a website mechanism, not an API capability, and an authenticated MCP caller already has a real identity.
EndImpersonation
DELETE /v1/impersonation
Impersonation is a property of a browser SESSION, and an MCP client authenticates with a token - so this can only ever answer nothing at all, or refuse. Publishing a tool whose entire purpose is to report on a capability the caller cannot have advertises the platform admin surface for no capability at all, which is precisely what StartImpersonation is excluded to avoid.
GetDomainOwnership
GET /v1/domains/{id}/ownership
The API does not serve this route to the "mcp" audience, so every call would be refused 403 wrong_audience by the only kind of token this server accepts. An advertised tool that cannot be called is worse than an absent one: a model retries it and reports a product failure to the user. Publishing it becomes automatic the day the route declares mcp in x-mgh-audiences.
GetImpersonation
GET /v1/impersonation
Impersonation is a property of a browser SESSION, and an MCP client authenticates with a token - so this can only ever answer nothing at all, or refuse. Publishing a tool whose entire purpose is to report on a capability the caller cannot have advertises the platform admin surface for no capability at all, which is precisely what StartImpersonation is excluded to avoid.
GetReportEmailNotice
GET /v1/checks/report-email/notice
The report-by-email flow is a MARKETING-SITE mechanism for an anonymous visitor, not an API capability: it is the one place the product will send mail to an address that was typed into a form. Handing that to an MCP token means handing an outbound-mail primitive, aimed at an address chosen in the call, to a model reading untrusted content - which is how our sending reputation gets spent on somebody else's phishing. It also completes in an inbox: confirm and unsubscribe consume one-shot tokens that arrive by email, so a model could only ever start a flow it cannot finish. An authenticated MCP caller needs none of it - RunDomainCheck returns the findings in the response.
GetSystemSetting
GET /v1/admin/settings/{key}
A SYSTEM-scope setting is vendor-controlled and platform-wide: one value changes behaviour for every tenant at once, which is why MGH-70 restricts the scope to admins. A model talked into moving one has a blast radius no single customer could have. Account-scope settings stay available - those are the customer's own, bounded by the system value above them, and audited.
GetSystemSettingAudit
GET /v1/admin/settings/{key}/audit
A SYSTEM-scope setting is vendor-controlled and platform-wide: one value changes behaviour for every tenant at once, which is why MGH-70 restricts the scope to admins. A model talked into moving one has a blast radius no single customer could have. Account-scope settings stay available - those are the customer's own, bounded by the system value above them, and audited.
InviteMember
POST /v1/orgs/{organizationId}/invitations
The API does not serve this route to the "mcp" audience, so every call would be refused 403 wrong_audience by the only kind of token this server accepts. An advertised tool that cannot be called is worse than an absent one: a model retries it and reports a product failure to the user. Publishing it becomes automatic the day the route declares mcp in x-mgh-audiences.
ListActivity
GET /v1/orgs/{organizationId}/activity
The API does not serve this route to the "mcp" audience, so every call would be refused 403 wrong_audience by the only kind of token this server accepts. An advertised tool that cannot be called is worse than an absent one: a model retries it and reports a product failure to the user. Publishing it becomes automatic the day the route declares mcp in x-mgh-audiences.
ListDomainOwnershipEvidence
GET /v1/domains/{id}/ownership/evidence
The API does not serve this route to the "mcp" audience, so every call would be refused 403 wrong_audience by the only kind of token this server accepts. An advertised tool that cannot be called is worse than an absent one: a model retries it and reports a product failure to the user. Publishing it becomes automatic the day the route declares mcp in x-mgh-audiences.
ListOrganizations
GET /v1/orgs
The API serves this route to NO token audience - it accepts an interactive session only, which is how it refuses a machine credential acting unattended on a person's behalf. This is not an mcp narrowing and widening the audience would not help: there is no token to mint that would work.
ListSignalAttestations
GET /v1/orgs/{organizationId}/domains/{monitoredDomainId}/signal-attestations
REST-only, customer self-attestation is not an agent tool. The checklist exists precisely because MailGuardHQ cannot measure these signals itself; the answers are the organisation's own statements about things a model cannot verify on their behalf. MGH-249 declares this route to the "api" audience only, not "mcp", so the audience gate already excludes it - this entry is recorded anyway so the exclusion survives an audience change instead of silently republishing.
ListSystemSettings
GET /v1/admin/settings
A SYSTEM-scope setting is vendor-controlled and platform-wide: one value changes behaviour for every tenant at once, which is why MGH-70 restricts the scope to admins. A model talked into moving one has a blast radius no single customer could have. Account-scope settings stay available - those are the customer's own, bounded by the system value above them, and audited.
Login
POST /v1/accounts/login
Accepts a password and returns a session token - same reason as Signup. An MCP client authenticates with an already-minted API token.
Logout
POST /v1/accounts/logout
Refuses API keys by design (session-only route). Ending a browser session is not something a machine credential does on a human's behalf.
MintToken
POST /v1/orgs/{organizationId}/tokens
The API does not serve this route to the "mcp" audience, so every call would be refused 403 wrong_audience by the only kind of token this server accepts. An advertised tool that cannot be called is worse than an absent one: a model retries it and reports a product failure to the user. Publishing it becomes automatic the day the route declares mcp in x-mgh-audiences.
RecheckDomainOwnership
POST /v1/admin/ownership/recheck
The platform admin surface. MGH-119 makes it INVISIBLE - an ordinary caller gets an identical not_found for an admin route and a route that does not exist - and advertising it as an MCP tool undoes exactly that. An MCP token is driven by a model reading untrusted content, so prompt injection is routine input rather than an exotic attack; administrative reach is the last thing that should be available that way. Support work belongs in the console, behind a human.
RecordSignalAttestation
PUT /v1/orgs/{organizationId}/domains/{monitoredDomainId}/signal-attestations/{signalCode}
REST-only, customer self-attestation is not an agent tool. Recording an answer here is a human asserting they personally checked something MailGuardHQ cannot measure; a model reading untrusted content must never be the one making that assertion on a customer's behalf. MGH-249 declares this route to the "api" audience only, not "mcp", so the audience gate already excludes it - this entry is recorded anyway so the exclusion survives an audience change instead of silently republishing.
RemoveMember
DELETE /v1/orgs/{organizationId}/members/{seatId}
The API does not serve this route to the "mcp" audience, so every call would be refused 403 wrong_audience by the only kind of token this server accepts. An advertised tool that cannot be called is worse than an absent one: a model retries it and reports a product failure to the user. Publishing it becomes automatic the day the route declares mcp in x-mgh-audiences.
RequestCheckReportByEmail
POST /v1/checks/report-email
The report-by-email flow is a MARKETING-SITE mechanism for an anonymous visitor, not an API capability: it is the one place the product will send mail to an address that was typed into a form. Handing that to an MCP token means handing an outbound-mail primitive, aimed at an address chosen in the call, to a model reading untrusted content - which is how our sending reputation gets spent on somebody else's phishing. It also completes in an inbox: confirm and unsubscribe consume one-shot tokens that arrive by email, so a model could only ever start a flow it cannot finish. An authenticated MCP caller needs none of it - RunDomainCheck returns the findings in the response.
ResendVerification
POST /v1/accounts/resend-verification
Part of the email-verification loop, which completes in a browser. Exposing only half of a flow invites a model to start something it cannot finish.
ResetAccountSetting
POST /v1/orgs/{organizationId}/settings/{key}/reset
The API does not serve this route to the "mcp" audience, so every call would be refused 403 wrong_audience by the only kind of token this server accepts. An advertised tool that cannot be called is worse than an absent one: a model retries it and reports a product failure to the user. Publishing it becomes automatic the day the route declares mcp in x-mgh-audiences.
ResetSystemSetting
POST /v1/admin/settings/{key}/reset
A SYSTEM-scope setting is vendor-controlled and platform-wide: one value changes behaviour for every tenant at once, which is why MGH-70 restricts the scope to admins. A model talked into moving one has a blast radius no single customer could have. Account-scope settings stay available - those are the customer's own, bounded by the system value above them, and audited.
RevokeTokens
POST /v1/orgs/{organizationId}/tokens/revoke
The API does not serve this route to the "mcp" audience, so every call would be refused 403 wrong_audience by the only kind of token this server accepts. An advertised tool that cannot be called is worse than an absent one: a model retries it and reports a product failure to the user. Publishing it becomes automatic the day the route declares mcp in x-mgh-audiences.
RotateToken
POST /v1/orgs/{organizationId}/tokens/{tokenId}/rotate
The API does not serve this route to the "mcp" audience, so every call would be refused 403 wrong_audience by the only kind of token this server accepts. An advertised tool that cannot be called is worse than an absent one: a model retries it and reports a product failure to the user. Publishing it becomes automatic the day the route declares mcp in x-mgh-audiences.
Signup
POST /v1/accounts/signup
Accepts a password and returns a session token. A tool that takes a password puts it in the model's context and in the client transcript; sign up on the web console instead, then mint an mcp-audience token.
StartDomainOwnershipVerification
POST /v1/domains/{id}/ownership
The API does not serve this route to the "mcp" audience, so every call would be refused 403 wrong_audience by the only kind of token this server accepts. An advertised tool that cannot be called is worse than an absent one: a model retries it and reports a product failure to the user. Publishing it becomes automatic the day the route declares mcp in x-mgh-audiences.
StartImpersonation
POST /v1/admin/impersonation
The platform admin surface, and the sharpest instrument on it: it mints a credential that sees the product as a named customer. MGH-120 gates it on consent and audit and a human being present. A model reading untrusted content is exactly who must never be able to start one, and an exclusion recorded here survives an audience change - the audience gate alone would silently republish it.
StepUp
POST /v1/accounts/step-up
Accepts a password to re-confirm identity, and an API token can never satisfy step-up anyway - so exposing it would only ever leak a password for a guaranteed refusal.
StopCheckReportEmail
POST /v1/checks/report-email/unsubscribe
The report-by-email flow is a MARKETING-SITE mechanism for an anonymous visitor, not an API capability: it is the one place the product will send mail to an address that was typed into a form. Handing that to an MCP token means handing an outbound-mail primitive, aimed at an address chosen in the call, to a model reading untrusted content - which is how our sending reputation gets spent on somebody else's phishing. It also completes in an inbox: confirm and unsubscribe consume one-shot tokens that arrive by email, so a model could only ever start a flow it cannot finish. An authenticated MCP caller needs none of it - RunDomainCheck returns the findings in the response.
UpdateAccountSetting
PUT /v1/orgs/{organizationId}/settings/{key}
The API does not serve this route to the "mcp" audience, so every call would be refused 403 wrong_audience by the only kind of token this server accepts. An advertised tool that cannot be called is worse than an absent one: a model retries it and reports a product failure to the user. Publishing it becomes automatic the day the route declares mcp in x-mgh-audiences.
UpdateSystemSetting
PUT /v1/admin/settings/{key}
A SYSTEM-scope setting is vendor-controlled and platform-wide: one value changes behaviour for every tenant at once, which is why MGH-70 restricts the scope to admins. A model talked into moving one has a blast radius no single customer could have. Account-scope settings stay available - those are the customer's own, bounded by the system value above them, and audited.
VerifyDomainOwnership
POST /v1/domains/{id}/ownership/verify
The API does not serve this route to the "mcp" audience, so every call would be refused 403 wrong_audience by the only kind of token this server accepts. An advertised tool that cannot be called is worse than an absent one: a model retries it and reports a product failure to the user. Publishing it becomes automatic the day the route declares mcp in x-mgh-audiences.
VerifyEmail
POST /v1/accounts/verify-email
Consumes a single-use token from a verification email. The link belongs in a browser, not in a model's context.
WithdrawDomainOwnership
DELETE /v1/domains/{id}/ownership
The API does not serve this route to the "mcp" audience, so every call would be refused 403 wrong_audience by the only kind of token this server accepts. An advertised tool that cannot be called is worse than an absent one: a model retries it and reports a product failure to the user. Publishing it becomes automatic the day the route declares mcp in x-mgh-audiences.

What is not built yet

Stated here rather than discovered later. Each one is real, each is open, and none of them is worked around.

There is no hosted endpoint, and no public release

Nothing is running at a URL you can point a client at. The server is not on GitHub, not in any MCP registry, and not installable from a package manager. Today it runs from source, against your own MailGuardHQ credential.

Public release - the repository, the licence and the registry listings - is tracked separately and is not done. Until it is, everything below describes a server you would run yourself.

Interactive sign-in is not available - clients paste a token

MailGuardHQ does not operate an OAuth authorization server. There is no /authorize, no /token and no client registration, so the OAuth 2.1 and PKCE flow an interactive client would normally use has nothing to talk to.

The server implements the half it can: it publishes RFC 9728 protected-resource metadata and challenges correctly. The authorization_servers list in that document is deliberately empty, because naming a server that does not exist sends every compliant client into a discovery loop that cannot end. So an interactive client asks you to paste a token, which works and is honest about what is available.

The server cannot verify a token was minted for it

The API mints tokens with an audience and applies the MCP ceiling when it does, so the restriction exists on the issuing side. What is missing is any way for this server to read the audience of a token it has been handed - there is no introspection endpoint - so it checks the token's format and no more, and logs a warning saying so on every start.

The consequence, stated plainly: a token minted for the ordinary API audience is accepted here. The verifier that closes this is written and tested against a stub, and starts working the day the API can answer the question.

Domain and organisation calls refuse an MCP token today

The API defaults every route to the API audience and opts individual routes in. The three check routes and five of the six token routes are opted in. The domain routes, the account route, the organisation and member routes and the bulk token-revoke route are not - so a correctly-minted MCP token is refused on them with wrong_audience.

That means "operate the product entirely through MCP" is not true today: checks and credential management work, domains do not. It is not worked around, because both available workarounds are worse - shipping tools that fail, or telling people to mint a token for the wrong audience, which is exactly what the audience mechanism exists to prevent.

A token minted over MCP does not hand back its secret

Minting and rotating work, and the response carries the token's id, prefix, scopes and expiry - but the secret is stripped before it reaches the model. You collect it from the web console.

A real narrowing, deliberately chosen: the alternative was a brand-new credential, at its maximum useful lifetime, deposited into a model's context and from there into any transcript, log or screenshot that context reaches.

The tool list on this page is generated from the server's checked-in tool manifest (7bea9c625eae), which the server's own build regenerates from the API contract. A copy is a copy: it tells you which revision you are reading, not that it is today's.

Nothing to connect to yet - so tell us what you would connect

The MCP server ships when the release work behind it is done. What it should do first is driven by what people actually ask for.

Check a domain now Read the REST API reference