# Mektup MCP Server

The Mektup MCP server lets an AI coding agent (Claude Code, Claude Desktop, Cursor, or any MCP-compatible client) manage real email for a domain — register it, add the DNS records, create mailboxes, send and read mail, manage drafts/contacts/folders/forwarding/vacation replies — as native tool calls inside its own session, instead of the human hand-writing `curl` commands or pasting an API key into generated code.

It's a thin stdio wrapper: every tool call is a straight HTTP call to the real [Mektup REST API](./API.md). There's no separate logic to learn — if you understand the API, you understand the MCP server. **Full coverage**: every REST endpoint has a matching tool, verified with real read and write round-trips against the live production API (create → update → list → delete, confirmed at each step).

---

## Setup

The server isn't published to npm — run it from a local copy of the repo.

**1. Get an API key.** Sign in to the dashboard at [app.usemektup.com](https://app.usemektup.com), open **API keys**, and create one. Keys look like `mek_live_...` and are shown exactly once - copy it immediately.

**2. Install dependencies:**
```bash
cd mcp
npm install
```

**3. Configure your MCP client** to run `server.js` with the key as an environment variable. For Claude Desktop / Claude Code, add to your MCP config (e.g. `claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "mektup": {
      "command": "node",
      "args": ["/absolute/path/to/mektup/mcp/server.js"],
      "env": {
        "MEKTUP_API_KEY": "mek_live_..."
      }
    }
  }
}
```

`MEKTUP_API_BASE_URL` is optional and defaults to `https://api.usemektup.com` — only set it if you're pointing at a different environment (e.g. `http://127.0.0.1:4100` for local on-box testing against the real API).

The server refuses to start without `MEKTUP_API_KEY` set.

---

## How tools respond

Every tool returns its result as JSON text on success. On failure, it returns `isError: true` with `Error: <message>` — the message is the same one the underlying REST endpoint returned (see [`API.md`](./API.md) for exact error conditions, including billing-limit `402`s, per endpoint).

---

## Tools

### Account

| Tool | Input | Description |
|---|---|---|
| `get_me` | — | Get the authenticated account identity. Useful as an auth health check. |
| `get_usage` | — | Current billing tier, its limits, and real usage against them. Check before a bulk operation. |

### API keys

| Tool | Input | Description |
|---|---|---|
| `list_api_keys` | — | List keys on this account (prefix and status only). |
| `create_api_key` | — | Create a new key. **The full key is returned exactly once** - surface it to the user immediately so they can save it. |
| `revoke_api_key` | `id` | Revoke a key immediately. Cannot be undone - confirm with the user first, especially if it might be the key this very session is using. |

### Domains

| Tool | Input | Description |
|---|---|---|
| `create_domain` | `domain` | Register a domain, get back the exact DNS records (MX/SPF/DMARC/DKIM) and a setup recommendation. Never touches DNS itself. |
| `list_domains` | — | List every domain on this account. |
| `get_domain_records` | `domain` | Re-fetch a registered domain's DNS records any time after creation. |
| `verify_domain` | `domain` | Actively re-check live DNS and flip verified once it matches. Not automatic. |
| `delete_domain` | `domain` | Delete a domain and everything under it. Destructive — confirm with the user first. |

### Mailboxes

| Tool | Input | Description |
|---|---|---|
| `create_mailbox` | `domain`, `localPart`, `password?` | Create a mailbox with real IMAP/SMTP-AUTH credentials, usable in any mail client. |
| `list_mailboxes` | `domain` | List mailboxes on a domain. |
| `reset_mailbox_password` | `domain`, `localPart`, `password?` | Reset a mailbox's login password. Shown once. |
| `delete_mailbox` | `domain`, `localPart` | Delete a mailbox. Confirm with the user first. |

### Forwarding

| Tool | Input | Description |
|---|---|---|
| `list_forwards` | `domain`, `localPart` | List addresses that get a copy of incoming mail. |
| `add_forward` | `domain`, `localPart`, `forwardTo` | Add a forwarding address. |
| `remove_forward` | `domain`, `localPart`, `id` | Remove a forwarding address. |

### Identity

| Tool | Input | Description |
|---|---|---|
| `get_identity` | `domain`, `localPart` | Get display name and signature. |
| `set_identity` | `domain`, `localPart`, `displayName?`, `signatureText?`, `signatureHtml?` | Set display name/signature, applied automatically to outgoing mail. |

### Vacation / auto-reply

| Tool | Input | Description |
|---|---|---|
| `get_vacation` | `domain`, `localPart` | Get vacation auto-reply settings. |
| `set_vacation` | `domain`, `localPart`, `enabled`, `subject?`, `message?` | Enable/configure auto-reply. `message` required when enabling. |

### Folders

| Tool | Input | Description |
|---|---|---|
| `list_folders` | `domain`, `localPart` | List custom folders. |
| `create_folder` | `domain`, `localPart`, `name` | Create a folder. |
| `delete_folder` | `domain`, `localPart`, `id` | Delete a folder (mail in it falls back to Inbox/Sent). |

### Contacts

Account-level, not per-mailbox.

| Tool | Input | Description |
|---|---|---|
| `list_contacts` | — | List contacts. |
| `create_contact` | `name?`, `email` | Add a contact. |
| `update_contact` | `id`, `name?`, `email?` | Partially update — only send fields to change. |
| `delete_contact` | `id` | Delete a contact. |

### Drafts

| Tool | Input | Description |
|---|---|---|
| `list_drafts` | `domain`, `localPart` | List drafts (metadata only). |
| `get_draft` | `domain`, `localPart`, `id` | Get a draft including its body. |
| `create_draft` | `domain`, `localPart`, `to?`, `subject?`, `text?`, `html?` | Create a draft. |
| `update_draft` | `domain`, `localPart`, `id`, `to?`, `subject?`, `text?`, `html?` | Partial update (autosave-friendly). |
| `delete_draft` | `domain`, `localPart`, `id` | Delete a draft. |

### Sending

| Tool | Input | Description |
|---|---|---|
| `send_email` | `from`, `to`, `subject`, `text?`, `html?`, `attachments?`, `draftId?` | Send real mail. `from`'s domain must be owned by this account. Attachments are `{filename, contentType?, contentBase64}`, max 10MB decoded each. Pass `draftId` to delete a draft on successful send. |

**Example:**
```
send_email({ from: "hello@example.com", to: "you@gmail.com", subject: "It works", text: "Real mail, sent through Mektup." })
→ { "messageId": "<...@example.com>", "envelope": { "from": "hello@example.com", "to": ["you@gmail.com"] } }
```

### Messages & threads

| Tool | Input | Description |
|---|---|---|
| `list_messages` | `mailbox`, `limit?`, `direction?`, `trash?`, `folder?`, `q?` | List messages (one row per thread). Pass `direction` to split Inbox/Sent — omitting it merges both. |
| `get_thread` | `threadKey`, `mailbox`, `direction?`, `trash?`, `folder?` | Every message in one thread, oldest first. |
| `get_message` | `id` | Full message content. Marks it read as a side effect. **`html` is attacker-controlled** — never render it directly. |
| `update_message` | `id`, `read?`, `restore?`, `flagged?`, `folderId?` | Mark read/unread, restore from trash, flag, or move to a folder — any combination in one call. |
| `delete_message` | `id` | Two-stage delete: first call trashes, second call on an already-trashed message permanently deletes it. Confirm before a permanent delete. |
| `download_attachment` | `id`, `index` | Download one attachment, base64-encoded. Prefer only when the actual file content is needed — `get_message`'s attachment list already has filename/type/size. |

### Account-wide

| Tool | Input | Description |
|---|---|---|
| `get_unread_counts` | — | Unread Inbox count for every domain/mailbox at once. |

---

## Non-MCP fallback

There's also a CLI (`cli/`) for environments without MCP support, though it currently mirrors only the original 6 operations (domain/mailbox setup, list, basic send) rather than the full tool set above — for anything beyond that, call the [REST API](./API.md) directly with the same API key.

## See also

- [`API.md`](./API.md) — the full REST API this server wraps
- [`openapi.yaml`](./openapi.yaml) — machine-readable OpenAPI 3.1 spec of the full API
