MCP server for Mailprotector — customers, domains, users, quarantine, allow/block rules for MSPs.
Runs on your machine from its package; the steps are below.
At most: moves money, runs code or commands, deletes data, or sends messages for you.
Odel charges nothing per call. The publisher may charge for its own service.
Runs locally over stdio
This server isn't hosted — your MCP client launches it from a package registry. Use one of the commands below, or drop the config into your client (e.g. Claude Desktop).
Run
docker run -i --rm ghcr.io/wyre-ai/mailprotector-mcp:v1.0.4
MCP client config
{
"mcpServers": {
"mailprotector": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"ghcr.io/wyre-ai/mailprotector-mcp:v1.0.4"
]
}
}
}MCP server for Mailprotector — email security management for MSPs (CloudFilter, Bracket, SafeSend, XtraMail): customers, domains, users, quarantine, allow/block rules, logs, configuration, and the full long-tail API via a router.
Built on the MCP 2026-07-28 spec via the split v2 SDK
(@modelcontextprotocol/server / /node / /client ^2.0.0-beta.5) with dual-era
serving: one shared McpServerFactory behind createMcpHandler({ legacy: 'stateless' })
answers both 2025-era initialize-handshake clients (the WYRE gateway today) and modern
2026-07-28 envelope clients — with an identical, deterministic 22-tool surface for every
caller. Ships as a GHCR container only (no MCPB bundle).
Router: mailprotector_status, mailprotector_list_categories,
mailprotector_list_category_tools, mailprotector_execute_tool.
Promoted reads: mailprotector_customers_list/get, mailprotector_domains_list/get,
mailprotector_users_list/get/find_by_address, mailprotector_user_groups_list,
mailprotector_messages_list, mailprotector_allow_block_rules_list,
mailprotector_logs_list, mailprotector_configuration_get.
Promoted writes: mailprotector_customers_create, mailprotector_domains_create,
mailprotector_messages_release / release_many (⚠ HIGH-IMPACT),
mailprotector_allow_block_rules_create (⚠ HIGH-IMPACT), and the
⚠ DESTRUCTIVE — IRREVERSIBLE mailprotector_allow_block_rules_delete.
Router categories (52 routed operations via mailprotector_execute_tool):
resellers (CRUD), customers (update/delete), domains
(update/delete/move/aliases), user_groups (CRUD + services), users
(create/create_many/update/delete/reset_password/aliases), managers (CRUD +
notification destinations), configuration (update), statements,
email_routing (destinations/sources for domain|user_group), user_syncs
(CRUD + schedule + filters), notifications (user notification destinations),
results (find_by_code). Every routed delete is ⚠ DESTRUCTIVE and
confirmation-gated; discover schemas with mailprotector_list_category_tools.
Mailprotector's hierarchy is Provider → Reseller → Customer → Domain → User Group → User,
and many operations exist at several scopes. Scoped tools take scope
(reseller | customer | domain | user_group | user, per-tool subsets) plus scope_id;
scope_id defaults to the bound reseller when scope is "reseller".
Every delete flows through the SDK v2 MRTR seam: the handler returns
input_required, interactive clients see a confirmation prompt, and declining
cancels the delete. Callers that declared no elicitation capability — including
stateless 2025-era callers such as the WYRE Conduit gateway — fall back to
proceeding per the integration contract: elicitation here is purely additive,
and the gateway enforces destructive-tool access with its own server-side
per-tool classification gate.
| Env var (env mode) | Gateway header (AUTH_MODE=gateway) | Notes |
|---|---|---|
MAILPROTECTOR_API_KEY | X-Mailprotector-Api-Key | Per manager-role, from the web console profile page. Sent as Authorization: Bearer. |
MAILPROTECTOR_RESELLER_ID | X-Mailprotector-Reseller-Id | The MSP's reseller id — the default scope for scoped tools. |
MAILPROTECTOR_BASE_URL (optional) | — | Default https://emailservice.io. |
Copy .env.example. The gateway Compose service does not load
MAILPROTECTOR_API_KEY or MAILPROTECTOR_RESELLER_ID.
| Variable | Required | Description |
|---|---|---|
MCP_TRANSPORT | No | stdio (default) or http. The container image sets http. |
AUTH_MODE | No | gateway (Docker image and Compose) or env. Gateway mode requires X-Mailprotector-Api-Key and X-Mailprotector-Reseller-Id on each /mcp request and never falls back to MAILPROTECTOR_*. env uses those variables only after S2S auth succeeds. Unset means env (stdio and single-tenant HTTP). |
CONDUIT_S2S_SECRET | Yes for HTTP | Service-to-service secret. The HTTP server logs an error and exits non-zero when this is empty or a known placeholder (changeme, secret, replace-me, replace-with-a-real-secret, your-s2s-secret, and the other example values). Generate one with openssl rand -hex 32. Compose will not start without it. The value is never logged. |
MCP_ALLOW_INSECURE_DEV | No | Set to exactly 1 only for local development to start HTTP without CONDUIT_S2S_SECRET. The process logs a warning, does not enforce X-Gateway-S2S, and binds 127.0.0.1 only. A non-loopback MCP_HTTP_HOST refuses to start. A known placeholder secret still refuses to start. |
MCP_HTTP_HOST | No | Bind address when a real CONDUIT_S2S_SECRET is set. Unset keeps 0.0.0.0, the historical container bind. The image and Compose set 0.0.0.0 inside the container. |
MCP_HTTP_PORT | No | HTTP port. Defaults to 8080. |
/health and /healthz stay unauthenticated and do not read credentials. The stdio transport does not use CONDUIT_S2S_SECRET.
In gateway mode a request missing either credential header (or with a non-numeric reseller id) is
answered 401 (JSON-RPC error -32001) before the MCP handler runs — it never falls
through to env credentials.
With MCP_TRANSPORT=http and CONDUIT_S2S_SECRET set, every /mcp request must include X-Gateway-S2S. The check is verifyS2sHeader in src/s2s-verify.ts: the header value is t=<unix seconds>,v1=<hex>, where the hex is HMAC-SHA256 of the literal string t=<unix seconds> keyed with CONDUIT_S2S_SECRET (64 lowercase hex characters). The timestamp must be within 300 seconds of the server clock. A missing or invalid header is HTTP 401, before vendor credentials are read.
TS=$(date +%s)
SIG=$(printf 't=%s' "$TS" | openssl dgst -sha256 -hmac "$CONDUIT_S2S_SECRET" -hex | awk '{print $NF}')
curl -sS http://127.0.0.1:8080/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H "X-Gateway-S2S: t=${TS},v1=${SIG}" \
-H "X-Mailprotector-Api-Key: $MAILPROTECTOR_API_KEY" \
-H "X-Mailprotector-Reseller-Id: $MAILPROTECTOR_RESELLER_ID" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
In AUTH_MODE=gateway, /mcp also requires X-Mailprotector-Api-Key and X-Mailprotector-Reseller-Id. Those headers are the only credential source for that request. AUTH_MODE=env still requires the signed X-Gateway-S2S header, then uses MAILPROTECTOR_API_KEY and MAILPROTECTOR_RESELLER_ID from the environment.
export NODE_AUTH_TOKEN=$(gh auth token) # GitHub Packages auth for @wyre-ai/* + @wyre-technology/*
npm install
npm run build
node dist/index.js # stdio (default; no S2S secret)
# HTTP refuses to start until CONDUIT_S2S_SECRET is a real secret
CONDUIT_S2S_SECRET="$CONDUIT_S2S_SECRET" MCP_TRANSPORT=http node dist/index.js
# Local HTTP only, without a service-to-service secret (always binds 127.0.0.1)
MCP_ALLOW_INSECURE_DEV=1 MCP_TRANSPORT=http node dist/index.js
node scripts/smoke-dual-era.mjs # proves both protocol eras serve the same tools
Docker (linux/amd64 per fleet law). The image listens on 0.0.0.0 inside the container (MCP_HTTP_HOST) and defaults to AUTH_MODE=gateway. Publish the port on loopback and pass a real S2S secret. In gateway mode, Mailprotector credentials arrive on each request as headers; they are not read from the environment.
docker build --platform linux/amd64 --build-arg NODE_AUTH_TOKEN=$(gh auth token) -t mailprotector-mcp .
docker run \
-e CONDUIT_S2S_SECRET="$CONDUIT_S2S_SECRET" \
-p 127.0.0.1:8080:8080 \
mailprotector-mcp
Compose refuses to start when CONDUIT_S2S_SECRET is unset. The gateway service sets AUTH_MODE=gateway, does not inject MAILPROTECTOR_API_KEY or MAILPROTECTOR_RESELLER_ID, and publishes 127.0.0.1:8080:8080:
CONDUIT_S2S_SECRET="$CONDUIT_S2S_SECRET" docker compose up
Single-tenant HTTP (AUTH_MODE=env) still requires the S2S secret, then uses MAILPROTECTOR_API_KEY and MAILPROTECTOR_RESELLER_ID. It is separate from the gateway Compose service above.
docker run \
-e CONDUIT_S2S_SECRET="$CONDUIT_S2S_SECRET" \
-e AUTH_MODE=env \
-e MAILPROTECTOR_API_KEY=... \
-e MAILPROTECTOR_RESELLER_ID=... \
-p 127.0.0.1:8080:8080 \
mailprotector-mcp
text/event-stream) — parse the last
data: line.Apache-2.0