Skip to main content
Glama
P4rthPat3l

ZeptoMail Templates MCP

by P4rthPat3l
README.md
# ZeptoMail Templates MCP

An MCP server that lets an AI agent (opencode, Claude Desktop, Cursor, ...) inspect and manage email templates across **all ZeptoMail Agents in your Zoho account**.

It intentionally does **not** send email, create/delete Agents, expose Send Mail Tokens, or manage domains. It only reads the Agent list and manages templates.

## Quickstart (local, ~5 minutes)

### 1. Install the server

```bash
npm i -g zeptomail-mcp
```

This gives you a `zeptomail-mcp` command on your PATH. Skip this if you'd rather run from a local clone of [the repo](https://github.com/P4rthPat3l/zeptomail-mcp) (`node /path/to/zeptomail-mcp/dist/src/server.js`).

### 2. Create a Zoho OAuth app

1. Open the [Zoho API Console](https://api-console.zoho.com/).
2. Create a **Server-based Application**:
   - Client name: `zeptomailmcp` (no hyphens — Zoho rejects them)
   - Homepage URL: anything, e.g. `https://github.com/P4rthPat3l/zeptomail-mcp`
   - **Authorized redirect URI: `http://localhost:4567/callback`**
3. Note the **Client ID** and **Client Secret**.

> A Self Client also works. It has no redirect URI field, so instead of step 2 use its **Generate Code** tab (see _Self Client setup_ below).

### 3. Get a refresh token

```bash
ZOHO_CLIENT_ID=<your-client-id> ZOHO_CLIENT_SECRET=<your-secret> zeptomail-mcp-login
```

Your browser opens Zoho's consent screen for scopes `Zeptomail.MailAgents.READ` + `Zeptomail.MailTemplates.All`. After you approve, the script prints a refresh token.

> If you're running from a local clone instead of a global install, this same command is `npm run login`.

### 4. Configure your MCP host

You can pass the Zoho credentials **inline** in the host config, or load them from a **`.env` file** so the config file itself stays secret-free. Both work; pick one.

#### Option A — load from a `.env` file (recommended)

Put the secrets in a `.env` file (gitignore it — it's a credential):

```bash
# .env
ZOHO_CLIENT_ID=<your-client-id>
ZOHO_CLIENT_SECRET=<your-secret>
ZOHO_REFRESH_TOKEN=<your-refresh-token>
ZOHO_ACCOUNTS_URL=https://accounts.zoho.com
```

Then point the server at it with `--env-file`. The path is resolved relative to the host's working directory; use an absolute path if unsure.

**opencode** — `opencode.json` (or `.opencode/opencode.json`):

```json
{
  "mcp": {
    "zeptomail": {
      "type": "local",
      "command": ["zeptomail-mcp", "--env-file=/absolute/path/to/.env"],
      "enabled": true
    }
  }
}
```

**Claude Desktop** — `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "zeptomail": {
      "command": "zeptomail-mcp",
      "args": ["--env-file=/absolute/path/to/.env"]
    }
  }
}
```

#### Option B — inline in the host config

**opencode** — `opencode.json` (or `.opencode/opencode.json`):

```json
{
  "mcp": {
    "zeptomail": {
      "type": "local",
      "command": ["zeptomail-mcp"],
      "enabled": true,
      "environment": {
        "ZOHO_CLIENT_ID": "...",
        "ZOHO_CLIENT_SECRET": "...",
        "ZOHO_REFRESH_TOKEN": "...",
        "ZOHO_ACCOUNTS_URL": "https://accounts.zoho.com"
      }
    }
  }
}
```

**Claude Desktop** — `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "zeptomail": {
      "command": "zeptomail-mcp",
      "env": {
        "ZOHO_CLIENT_ID": "...",
        "ZOHO_CLIENT_SECRET": "...",
        "ZOHO_REFRESH_TOKEN": "...",
        "ZOHO_ACCOUNTS_URL": "https://accounts.zoho.com"
      }
    }
  }
}
```

#### Running from a local clone instead of a global install

Run the built launcher directly with `node`. It parses `--env-file` the same way the global `zeptomail-mcp` command does, so the flag goes **after** the script:

- opencode (Option A env-file): `["node", "/absolute/path/to/zeptomail-mcp/dist/src/cli.js", "--env-file=/absolute/path/to/.env"]`
- opencode (Option B inline): `["node", "/absolute/path/to/zeptomail-mcp/dist/src/cli.js"]` + the `environment` block
- Claude Desktop: `"command": "node"`, `"args": [".../dist/src/cli.js", "--env-file=..."]` (or just `[".../dist/src/cli.js"]` with the `env` block)

### 5. Use it

Ask your agent to call the tools:

- "list my ZeptoMail agents"
- "find the template with alias `team_invite`"
- "show me the OTP template in the sandbox agent"

## Self Client setup

A Self Client has no redirect URI, so `zeptomail-mcp-login` cannot catch its callback. Instead:

1. API Console → Self Client → **Generate Code**.
2. Scope: `Zeptomail.MailAgents.READ,Zeptomail.MailTemplates.All` → CREATE.
3. Copy the generated code and exchange it:

```bash
curl -X POST https://accounts.zoho.com/oauth/v2/token \
  -d "code=<generated code>" \
  -d "client_id=<client id>" \
  -d "client_secret=<client secret>" \
  -d "grant_type=authorization_code" \
  -d "redirect_uri=https://api-console.zoho.com/"
```

The JSON response contains `refresh_token`.

## Tools

| MCP tool                     | Purpose                                                                              | Mutates data |
| ---------------------------- | ------------------------------------------------------------------------------------ | ------------ |
| `zeptomail_list_agents`      | List accessible Agents and exact Agent keys/aliases                                  | No           |
| `zeptomail_list_templates`   | List templates in one explicit Agent                                                 | No           |
| `zeptomail_find_templates`   | Search one Agent or all Agents by name/alias/subject                                 | No           |
| `zeptomail_get_template`     | Read one complete template from one Agent                                            | No           |
| `zeptomail_export_templates` | Dump every full template (HTML/text/subject) from one Agent as JSON for local backup | No           |
| `zeptomail_create_template`  | Create a template in one explicit Agent                                              | Yes          |
| `zeptomail_update_template`  | Partial update with Agent + stale-write protection                                   | Yes          |
| `zeptomail_delete_template`  | Permanent delete with Agent/name/timestamp checks                                    | Yes          |

Write tools require **both**:

1. `ZEPTOMAIL_MCP_ALLOW_WRITES=true` in the server environment.
2. `confirm=true` in the individual tool call.

Every write also requires the exact `agentKey` and `expectedAgentName` from a fresh `zeptomail_list_agents` call. Update/delete additionally require current template safety values (`expectedModifiedTime`, `expectedTemplateName`), so a stale read can never overwrite a newer edit.

## Configuration reference

| Variable                           | Required    | Default                          | Purpose                                                         |
| ---------------------------------- | ----------- | -------------------------------- | --------------------------------------------------------------- |
| `ZOHO_CLIENT_ID`                   | yes         | —                                | Zoho OAuth app client ID                                        |
| `ZOHO_CLIENT_SECRET`               | yes         | —                                | Zoho OAuth app client secret                                    |
| `ZOHO_REFRESH_TOKEN`               | yes (stdio) | —                                | Long-lived token; the server mints 1-hour access tokens from it |
| `ZOHO_ACCOUNTS_URL`                | no          | `https://accounts.zoho.com`      | Zoho data center (`.eu`, `.in`, `.au`, ...)                     |
| `ZEPTOMAIL_API_BASE_URL`           | no          | `https://api.zeptomail.com/v1.1` | ZeptoMail API base URL                                          |
| `ZEPTOMAIL_MCP_ALLOW_WRITES`       | no          | `false`                          | Set `true` to enable create/update/delete                       |
| `ZEPTOMAIL_MCP_ALLOWED_AGENT_KEYS` | no          | all agents                       | Comma-separated `mailagent_key` allowlist                       |
| `ZEPTOMAIL_MCP_TRANSPORT`          | no          | `stdio`                          | `http` enables the hosted OAuth mode (below)                    |

All variables can be provided either as process environment variables (set by the host config) or via a `--env-file=<path>` argument to the server (see _Configure your MCP host_ above). Process vars take precedence; the file only fills vars that are unset.

## Hosted (remote) mode with OAuth 2.0 + PKCE

For a shared/public MCP endpoint, run with `ZEPTOMAIL_MCP_TRANSPORT=http`. The server becomes an OAuth authorization server that proxies to Zoho as the upstream authorization server:

```
MCP client ⇄ this MCP server (OAuth AS + resource server) ⇄ Zoho (upstream AS) ⇄ ZeptoMail API
```

```bash
ZEPTOMAIL_MCP_TRANSPORT=http \
ZEPTOMAIL_MCP_SERVER_URL=https://mcp.example.com \
ZEPTOMAIL_MCP_PORT=3006 \
ZEPTOMAIL_MCP_TOKEN_STORE=/var/lib/zeptomail-mcp/tokens.json \
node dist/src/server.js
```

- Clients discover metadata at `/.well-known/oauth-protected-resource` and `/.well-known/oauth-authorization-server`, dynamically register, and consent in the browser.
- The server redirects to Zoho with the client's PKCE S256 challenge and `access_type=offline`, exchanges the code with the server's Zoho client secret, and stores a **per-client Zoho refresh token** that never leaves the server.
- Each user's tools operate on their own ZeptoMail account.

Host config (opencode):

```json
{
  "mcp": {
    "zeptomail": {
      "type": "remote",
      "url": "https://mcp.example.com/mcp",
      "oauth": {}
    }
  }
}
```

Requirements for hosted mode: HTTPS (the SDK rejects non-HTTPS issuer URLs except localhost), the Zoho callback URI registered in the API console (`https://mcp.example.com/callback`), and a persistent token store. Restarting the server invalidates issued tokens — clients re-consent once.

## Development

```bash
npm install
npm run typecheck
npm test
npm run build
```

## Security notes

- Agent discovery is read-only; there are no tools for creating Agents, generating API keys, or accessing Send Mail Tokens.
- The MCP never sends email.
- `ZEPTOMAIL_MCP_ALLOWED_AGENT_KEYS` constrains which Agents the MCP may touch even when the OAuth account can see more.
- The refresh token is a long-lived credential: keep it server-side, treat it like a password, and revoke it in the Zoho console if it ever leaks.

TDQS

A4.3/5.0

Scored across 8 tools

Disambiguation5/5

Each tool targets a distinct resource and action: agents vs templates, and list/search/get/export/create/update/delete are clearly separated. Even the similar list_templates and find_templates are disambiguated by one being an enumeration and the other being a search across name, alias, and subject.

Naming Consistency5/5

All tool names use the consistent zeptomail_ prefix followed by verb_noun snake_case, such as zeptomail_list_agents and zeptomail_update_template. The naming pattern is uniform and predictable across the entire set.

Tool Count5/5

Eight tools is well-scoped for a ZeptoMail template management server. Each tool covers a necessary operation without redundancy or bloat, and the count is within the ideal range for a focused domain server.

Completeness5/5

The tool surface provides full lifecycle coverage for templates: list, search, get, export, create, update, and delete. Agent enumeration supports the required context for template operations, so agents can accomplish end-to-end template management without dead ends.

Maintenance

ActivityMaintained
ResponsivenessNo issues