@teamnetwork-nz/mailstore-spe-mcp
v0.1.0
Published
MCP server providing a curated, safety-gated administrator interface over the MailStore Service Provider Edition (SPE) Management API. Multi-instance aware, read-only by default, with access modes, confirmation gating, capability discovery, and bounded st
Maintainers
Readme
MailStore SPE MCP Server
A Model Context Protocol (MCP) server that provides a curated, safety-gated administrator interface over the MailStore Service Provider Edition (SPE) 26.3 Management API.
It is deliberately opinionated: rather than exposing every raw API function, it offers a concise set of high-level tools suitable for reliable LLM use — multi-instance aware, read-only by default, with graduated access modes, exact confirmation strings for state changes, capability discovery against live metadata, and bounded, structured, secret-free output.
Verified against a live MailStore SPE 26.3.1.24182 installation.
Contents
- Architecture
- Prerequisites
- MailStore account setup
- Configuration
- Access modes
- Running locally (stdio)
- Docker deployment
- Supergateway + Caddy
- RooCode / Cline configuration
- Tool catalogue
- Confirmation model
- Resources
- Error model
- Development & testing
- Troubleshooting
Architecture
RooCode / Cline
│ SSE
▼
Caddy (TLS, access control)
│
▼
Supergateway (SSE ⇄ stdio)
│ stdio
▼
mailstore-spe-mcp ← this project
│ HTTPS + Basic auth
▼
MailStore SPE Management Server (:8474)Internally the server is layered so the MailStore client is independent of the MCP SDK:
src/
config/ env parsing & validation (Zod)
mailstore/ client, encoder, response-parser, operation-poller,
metadata, capability-registry, instance-resolver, errors
policy/ access-mode, tool-policy (risk classes), confirmations, redaction
output/ tool-result, truncation, normalizers
tools/ platform, instances, users, profiles, jobs, stores, smtp,
compliance, maintenance, raw-api, register-tools
resources/ health + read-only resources
server.ts wires everything together
index.ts stdio bootstrapPrerequisites
- Node.js 22 LTS (for local runs) or Docker
- Network reachability to the MailStore SPE Management API over HTTPS (default TCP 8474)
- A dedicated MailStore SPE system administrator account (see below)
MailStore account setup
Use a dedicated SPE system administrator account for the MCP — not a personal admin login.
- The Management API uses HTTP Basic authentication over HTTPS.
- Where MFA is enabled for the system administrator, you must use the administrator's API password rather than the interactive password. The MailStore API exposes
CreateSystemAdministratorAPIPasswordto generate one. - Grant the account only the privileges the deployment needs. Read-only monitoring needs far less than administrator mode.
Configuration
All configuration comes from environment variables (or Docker secrets). Copy .env.example to .env and fill it in. MAILSTORE_BASE_URL must not include /api.
Required
| Variable | Description |
|----------|-------------|
| MAILSTORE_BASE_URL | e.g. https://mailstore.example.com:8474 (HTTPS, no /api) |
| MAILSTORE_USERNAME | SPE administrator / API account username |
| MAILSTORE_PASSWORD | Password or API password (never logged) |
Recommended
| Variable | Default | Description |
|----------|---------|-------------|
| MAILSTORE_VERIFY_TLS | true | Verify the server certificate. Disable only in a lab. |
| MAILSTORE_CA_FILE | — | Path to a custom CA bundle (PEM). |
| MAILSTORE_REQUEST_TIMEOUT_MS | 30000 | Per-request timeout. |
| MAILSTORE_LONG_POLL_TIMEOUT_MS | 25000 | Long-poll window for async operations. |
| MAILSTORE_OPERATION_TIMEOUT_MS | 1800000 | Overall timeout for long-running operations. |
| MAILSTORE_METADATA_CACHE_TTL_SECONDS | 3600 | Metadata cache TTL. |
| MAILSTORE_INSTANCE_CACHE_TTL_SECONDS | 60 | Instance-list cache TTL. |
| MCP_ACCESS_MODE | read-only | read-only | operator | administrator. |
| MCP_ENABLE_RAW_API | false | Enable the generic escape-hatch tool. |
| MCP_ALLOW_DESTRUCTIVE | false | Permit destructive tools. |
| MCP_ALLOW_BULK_INSTANCE_ACTIONS | false | Reserved; first release keeps single-target only. |
| MCP_MAX_RESULT_ITEMS | 200 | Cap on list results before truncation. |
| LOG_LEVEL | info | debug | info | warn | error. |
| LOG_FORMAT | json | json | pretty. |
Access modes
Tools are registered — and therefore visible to the model — only when the current mode permits them. Read-only mode exposes no mutating tools at all.
| Mode | Adds |
|------|------|
| read-only | Retrieval, reporting, health checks. |
| operator | Run profile/job, cancel job, test SMTP, dry-run directory sync, verify a store, refresh statistics, start/stop/restart a single instance. |
| administrator | Create/update/delete users, set passwords, apply directory sync, change retention policies. |
Destructive tools (mailstore_delete_user, mailstore_verify_all_stores, mailstore_set_retention_policies) additionally require MCP_ALLOW_DESTRUCTIVE=true, and are not registered otherwise.
Running locally (stdio)
npm install
npm run build
MAILSTORE_BASE_URL=https://mailstore.example.com:8474 \
MAILSTORE_USERNAME=mcp-api \
MAILSTORE_PASSWORD=... \
MCP_ACCESS_MODE=read-only \
npm startOr in watch mode during development: npm run dev.
The process communicates over stdio: stdout carries only MCP protocol messages; all logs go to stderr.
Docker deployment
Multi-stage build, runs as the non-root node user, no credentials baked in:
docker build -t teamnetwork/mailstore-spe-mcp:latest .See docker-compose.example.yml for a full stack. Provide secrets via environment or Docker secrets.
Supergateway + Caddy
RooCode requires SSE. Supergateway converts the stdio server to SSE; Caddy terminates TLS and restricts access. Suggested hostname:
https://mcp-mailstore.ai.example.net.nz/sseMinimal Caddyfile:
mcp-mailstore.ai.example.net.nz {
# import your standard auth / IP allowlist snippet here
encode gzip
reverse_proxy mailstore-mcp:8115 {
# SSE needs unbuffered, HTTP/1.1 proxying
flush_interval -1
transport http {
versions 1.1
}
}
}Supergateway command (see compose file):
--stdio "node dist/index.js" --port 8115 --cors --healthEndpoint /healthzThe MCP server must not assume it is directly exposed to the Internet — always front it with Caddy.
RooCode / Cline configuration
RooCode (SSE):
{
"mcpServers": {
"mailstore-spe": {
"type": "sse",
"url": "https://mcp-mailstore.ai.example.net.nz/sse",
"timeout": 60
}
}
}Cline (stdio, running the built server directly):
{
"mcpServers": {
"mailstore-spe": {
"command": "node",
"args": ["/app/dist/index.js"],
"env": {
"MAILSTORE_BASE_URL": "https://mailstore.example.com:8474",
"MAILSTORE_USERNAME": "mcp-api",
"MAILSTORE_PASSWORD": "...",
"MCP_ACCESS_MODE": "read-only"
}
}
}
}Adjust key names to your client's current schema.
Tool catalogue
Every instance-scoped tool takes an instance selector (exact instanceID, alias, or display name — resolved to exactly one instance; ambiguous names are rejected). Tools requiring confirmation take a confirmation string (see below).
| Tool | Mode | Notes |
|------|------|-------|
| mailstore_ping | read-only | Connectivity + auth check. |
| mailstore_get_environment | read-only | Version / environment. |
| mailstore_get_service_status | read-only | SPE service status. |
| mailstore_get_platform_summary | read-only | Composed platform overview. |
| mailstore_list_instances | read-only | Instances with state filter/search. |
| mailstore_get_instance | read-only | Config + optional stats/stores (sizes off by default). |
| mailstore_get_instance_statistics | read-only | Archive statistics. |
| mailstore_list_users | read-only | |
| mailstore_get_user | read-only | |
| mailstore_list_profiles | read-only | |
| mailstore_get_profile_results | read-only | Worker results (handles timeZoneID spelling). |
| mailstore_list_jobs | read-only | |
| mailstore_get_job_results | read-only | |
| mailstore_list_stores | read-only | includeSize off by default (slow). |
| mailstore_get_smtp_settings | read-only | Secrets redacted. |
| mailstore_get_compliance | read-only | Compliance + retention + policy hash. |
| mailstore_start_instance | operator | Single instance; confirm START <id>. |
| mailstore_stop_instance | operator | Single instance; confirm STOP <id>. |
| mailstore_restart_instance | operator | Single instance; confirm RESTART <id>. |
| mailstore_run_profile | operator | Confirm RUN PROFILE <id> IN <instance>. |
| mailstore_run_job | operator | Confirm RUN JOB <id> IN <instance>. |
| mailstore_cancel_job | operator | Confirm CANCEL JOB <id> IN <instance>. |
| mailstore_verify_store | operator | Confirm VERIFY STORE <id> IN <instance>. |
| mailstore_refresh_store_statistics | operator | Confirm REFRESH STORE STATISTICS IN <instance>. |
| mailstore_test_smtp | operator | Confirm TEST SMTP IN <instance>. |
| mailstore_sync_directory_users | operator (dry-run) / administrator (apply) | Dry run by default. |
| mailstore_create_user | administrator | Confirm CREATE USER <userName> IN <instance>. |
| mailstore_update_user | administrator | Only changes the fields you pass. |
| mailstore_set_user_password | administrator | Password never returned. |
| mailstore_delete_user | administrator + destructive | Confirm DELETE USER <userName> FROM <instance>. |
| mailstore_verify_all_stores | administrator + destructive | Expensive; confirm VERIFY ALL STORES IN <instance>. |
| mailstore_set_retention_policies | administrator + destructive | Optimistic-concurrency hash guard. |
| mailstore_refresh_capabilities | administrator | Reload API metadata. |
| mailstore_invoke_api | administrator + MCP_ENABLE_RAW_API | Allow-listed generic invoke; denylist enforced. |
Phase-two tools (instance/store/credential/profile/job lifecycle, store transfer/merge/repair/recovery) are intentionally not in the first release. See the specification.
Confirmation model
State-changing tools require an exact confirmation string, generated from the resolved instance and target. Generic words (yes, confirm, proceed) are rejected. Examples:
STOP customer-aDELETE USER jsmith FROM customer-aCHANGE RETENTION POLICIES IN customer-a
If you omit or mismatch it, the tool returns CONFIRMATION_REQUIRED / CONFIRMATION_MISMATCH with the exact string required — nothing is executed. Retry with that string.
mailstore_set_retention_policies additionally requires expectedCurrentHash (from mailstore_get_compliance); if the live policies changed since you read them, the change is refused with CONCURRENT_MODIFICATION.
Resources
Read-only MCP resources (no credentials exposed):
mailstore://health— reachability, auth, TLS, version, access modemailstore://environmentmailstore://service-statusmailstore://instancesmailstore://capabilities
Error model
Errors are structured with a stable code, a concise message, and safe details — never secrets, auth headers, or stack traces:
{ "code": "AMBIGUOUS_INSTANCE", "message": "The instance name matched multiple MailStore instances.", "details": { "matches": [ ... ] } }Codes: CONFIGURATION_ERROR, AUTHENTICATION_FAILED, AUTHORIZATION_FAILED, TLS_ERROR, MAILSTORE_UNAVAILABLE, MAILSTORE_API_ERROR, MAILSTORE_OPERATION_FAILED, MAILSTORE_OPERATION_TIMEOUT, UNSUPPORTED_FUNCTION, INVALID_ARGUMENT, INSTANCE_NOT_FOUND, AMBIGUOUS_INSTANCE, OBJECT_NOT_FOUND, CONFIRMATION_REQUIRED, CONFIRMATION_MISMATCH, ACCESS_MODE_DENIED, DESTRUCTIVE_ACTION_DISABLED, CONCURRENT_MODIFICATION, RESULT_TOO_LARGE.
Development & testing
npm install
npm run build # tsc
npm run typecheck # tsc --noEmit
npm run lint # eslint
npm test # vitest (unit + mock server + in-process MCP)Unit tests cover the safety boundaries (form encoding, BOM handling, envelope/error parsing, long-operation polling and timeout, retry policy, metadata & argument validation, instance resolution & ambiguity, confirmations, access-mode gating, secret redaction, output truncation, time-zone parameter spelling). A mock MailStore server exercises the client end-to-end, and an in-process MCP client verifies that read-only mode registers no mutating tools and that confirmations are enforced.
Live integration tests (opt-in, read-only)
tests/integration/live-readonly.test.ts runs strictly read-only checks against a real server when MAILSTORE_BASE_URL is configured (via .env). It never mutates. Run with:
npm run test:integrationMutating integration tests are intentionally not included; if you add any, gate them behind MAILSTORE_MUTATION_TESTS=true and a dedicated MAILSTORE_TEST_INSTANCE — never a production customer instance.
Note on live metadata vs. published reference: the live SPE metadata is the source of truth. This server was aligned to it — e.g.
GetProfilesrequires arawargument,GetWorkerResultsusestimeZoneID/profileID(capitalised) whileGetJobResultsusestimeZoneId/jobId,SetRetentionPoliciestakes a singleconfigJSON argument, and user privilege/email/POP3 lists are comma-separated strings, not JSON arrays.
Troubleshooting
| Symptom | Likely cause |
|---------|--------------|
| CONFIGURATION_ERROR at startup | Missing/invalid env var, or MAILSTORE_BASE_URL includes /api. |
| AUTHENTICATION_FAILED | Wrong username/password, or MFA account needs an API password. |
| TLS_ERROR | Untrusted certificate — set MAILSTORE_CA_FILE, or MAILSTORE_VERIFY_TLS=false for a lab only. |
| Startup aborts: missing essential functions | The account/server lacks core API functions; check API access. |
| A tool is missing | It requires a higher access mode, the destructive flag, or an API function unavailable on this server. |
| AMBIGUOUS_INSTANCE | The name matches multiple instances — pass the exact instanceID. |
| SSE drops / hangs behind Caddy | Ensure unbuffered HTTP/1.1 proxying (flush_interval -1). |
License
MIT
