@mailloom/mcp 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -3
- package/bin/mailloom-mcp.js +2 -0
- package/dist/client.d.ts +29 -0
- package/dist/client.js +78 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +111 -0
- package/package.json +50 -6
package/README.md
CHANGED
|
@@ -1,3 +1,81 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# @mailloom/mcp
|
|
2
|
+
|
|
3
|
+
Official [Model Context Protocol](https://modelcontextprotocol.io) server for [Mailloom](https://mailloom.app) — manage email templates, brand kits, and assets from Cursor, Claude, and other MCP hosts.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
1. Sign in to Mailloom → **Account settings**.
|
|
8
|
+
2. Ensure **Allow MCP / API write access** is on (default) if agents should mutate data.
|
|
9
|
+
3. **Create key** and copy the `ml_live_…` secret once.
|
|
10
|
+
|
|
11
|
+
API auth runs in the Supabase Edge Function `mailloom-api` (service role never leaves Supabase). Prefer the Next `/api/v1` base so `export_template` works; that route proxies CRUD to the edge function.
|
|
12
|
+
|
|
13
|
+
### Cursor (`mcp.json`)
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"mcpServers": {
|
|
18
|
+
"mailloom": {
|
|
19
|
+
"command": "npx",
|
|
20
|
+
"args": ["-y", "@mailloom/mcp"],
|
|
21
|
+
"env": {
|
|
22
|
+
"MAILLOOM_API_KEY": "ml_live_…",
|
|
23
|
+
"MAILLOOM_API_BASE": "http://localhost:3847/api/v1"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Production Next: `https://your-host/api/v1`.
|
|
31
|
+
Edge Function only (CRUD, no HTML export): `https://<project>.supabase.co/functions/v1/mailloom-api`.
|
|
32
|
+
|
|
33
|
+
### Local package (unpublished)
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cd packages/mailloom-mcp
|
|
37
|
+
npm install
|
|
38
|
+
npm run build
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"mcpServers": {
|
|
44
|
+
"mailloom": {
|
|
45
|
+
"command": "node",
|
|
46
|
+
"args": ["/absolute/path/to/packages/mailloom-mcp/dist/index.js"],
|
|
47
|
+
"env": {
|
|
48
|
+
"MAILLOOM_API_KEY": "ml_live_…",
|
|
49
|
+
"MAILLOOM_API_BASE": "http://localhost:3847/api/v1"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Environment
|
|
57
|
+
|
|
58
|
+
| Variable | Required | Default | Purpose |
|
|
59
|
+
| --- | --- | --- | --- |
|
|
60
|
+
| `MAILLOOM_API_KEY` | yes | — | `ml_live_…` from Account settings |
|
|
61
|
+
| `MAILLOOM_API_BASE` | no | `http://localhost:3847/api/v1` | API origin including `/api/v1` |
|
|
62
|
+
|
|
63
|
+
## Tools
|
|
64
|
+
|
|
65
|
+
| Tool | Write? | Description |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| `health_check` | | API liveness + write flag |
|
|
68
|
+
| `get_account` | | Email, write flag, key metadata |
|
|
69
|
+
| `list_templates` / `get_template` | | Templates (user + platform) |
|
|
70
|
+
| `create_template` / `update_template` / `duplicate_template` / `delete_template` | ✓ | Mutate user templates |
|
|
71
|
+
| `export_template` | | Render HTML (optional inline CSS) |
|
|
72
|
+
| `list_brand_kits` / `get_brand_kit` | | Brand kits |
|
|
73
|
+
| `create_brand_kit` / `update_brand_kit` / `delete_brand_kit` | ✓ | Mutate kits |
|
|
74
|
+
| `list_assets` | | Assets |
|
|
75
|
+
| `upload_asset` / `delete_asset` | ✓ | Upload (base64/url) / delete |
|
|
76
|
+
|
|
77
|
+
When write access is disabled in Account settings, write tools return `403 write_disabled`. Platform templates stay read-only; duplicate them to edit.
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin Mailloom API client for MCP.
|
|
3
|
+
* Auth: Authorization Bearer ml_live_…
|
|
4
|
+
*
|
|
5
|
+
* Prefer Next /api/v1 (proxies to the mailloom-api Edge Function + HTML export).
|
|
6
|
+
* Or point at https://<project>.supabase.co/functions/v1/mailloom-api for CRUD only.
|
|
7
|
+
*/
|
|
8
|
+
export type ApiConfig = {
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
apiKey: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function loadConfig(): ApiConfig;
|
|
13
|
+
export declare function apiRequest(config: ApiConfig, method: string, path: string, opts?: {
|
|
14
|
+
body?: unknown;
|
|
15
|
+
query?: Record<string, string | number | undefined | null>;
|
|
16
|
+
}): Promise<unknown>;
|
|
17
|
+
export declare function jsonResult(data: unknown): {
|
|
18
|
+
content: {
|
|
19
|
+
type: "text";
|
|
20
|
+
text: string;
|
|
21
|
+
}[];
|
|
22
|
+
};
|
|
23
|
+
export declare function errorResult(err: unknown): {
|
|
24
|
+
content: {
|
|
25
|
+
type: "text";
|
|
26
|
+
text: string;
|
|
27
|
+
}[];
|
|
28
|
+
isError: true;
|
|
29
|
+
};
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin Mailloom API client for MCP.
|
|
3
|
+
* Auth: Authorization Bearer ml_live_…
|
|
4
|
+
*
|
|
5
|
+
* Prefer Next /api/v1 (proxies to the mailloom-api Edge Function + HTML export).
|
|
6
|
+
* Or point at https://<project>.supabase.co/functions/v1/mailloom-api for CRUD only.
|
|
7
|
+
*/
|
|
8
|
+
const DEFAULT_BASE = 'http://localhost:3847/api/v1';
|
|
9
|
+
export function loadConfig() {
|
|
10
|
+
const apiKey = (process.env.MAILLOOM_API_KEY || '').trim();
|
|
11
|
+
if (!apiKey) {
|
|
12
|
+
throw new Error('MAILLOOM_API_KEY is required (ml_live_…). Create one in Mailloom → Account settings.');
|
|
13
|
+
}
|
|
14
|
+
if (!apiKey.startsWith('ml_live_')) {
|
|
15
|
+
throw new Error('MAILLOOM_API_KEY must start with ml_live_');
|
|
16
|
+
}
|
|
17
|
+
const baseUrl = (process.env.MAILLOOM_API_BASE || DEFAULT_BASE).replace(/\/$/, '');
|
|
18
|
+
return { baseUrl, apiKey };
|
|
19
|
+
}
|
|
20
|
+
export async function apiRequest(config, method, path, opts) {
|
|
21
|
+
const url = new URL(path.startsWith('http') ? path : `${config.baseUrl}${path.startsWith('/') ? path : `/${path}`}`);
|
|
22
|
+
if (opts?.query) {
|
|
23
|
+
for (const [k, v] of Object.entries(opts.query)) {
|
|
24
|
+
if (v !== undefined && v !== null && String(v) !== '') {
|
|
25
|
+
url.searchParams.set(k, String(v));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const headers = {
|
|
30
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
31
|
+
Accept: 'application/json',
|
|
32
|
+
};
|
|
33
|
+
if (opts?.body !== undefined) {
|
|
34
|
+
headers['Content-Type'] = 'application/json';
|
|
35
|
+
}
|
|
36
|
+
const res = await fetch(url, {
|
|
37
|
+
method,
|
|
38
|
+
headers,
|
|
39
|
+
body: opts?.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
40
|
+
});
|
|
41
|
+
const text = await res.text();
|
|
42
|
+
let data = null;
|
|
43
|
+
if (text) {
|
|
44
|
+
try {
|
|
45
|
+
data = JSON.parse(text);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
data = { raw: text };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!res.ok) {
|
|
52
|
+
const msg = data && typeof data === 'object' && 'error' in data
|
|
53
|
+
? String(data.error)
|
|
54
|
+
: `HTTP ${res.status}`;
|
|
55
|
+
const code = data && typeof data === 'object' && 'code' in data
|
|
56
|
+
? String(data.code)
|
|
57
|
+
: undefined;
|
|
58
|
+
throw new Error(code ? `${msg} (${code})` : msg);
|
|
59
|
+
}
|
|
60
|
+
return data;
|
|
61
|
+
}
|
|
62
|
+
export function jsonResult(data) {
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: 'text',
|
|
67
|
+
text: JSON.stringify(data, null, 2),
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export function errorResult(err) {
|
|
73
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
74
|
+
return {
|
|
75
|
+
content: [{ type: 'text', text: message }],
|
|
76
|
+
isError: true,
|
|
77
|
+
};
|
|
78
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Official Mailloom MCP server — templates, brand kits, assets for AI hosts.
|
|
4
|
+
*
|
|
5
|
+
* Env:
|
|
6
|
+
* MAILLOOM_API_KEY required — ml_live_…
|
|
7
|
+
* MAILLOOM_API_BASE optional — default http://localhost:3847/api/v1
|
|
8
|
+
*
|
|
9
|
+
* Write tools require Account settings → Allow MCP / API write access (default on).
|
|
10
|
+
* When disabled, mutating tools return 403 write_disabled; reads still work.
|
|
11
|
+
*/
|
|
12
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
13
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
import { apiRequest, errorResult, jsonResult, loadConfig } from './client.js';
|
|
16
|
+
const PACKAGE_VERSION = '0.1.0';
|
|
17
|
+
const config = loadConfig();
|
|
18
|
+
const server = new McpServer({
|
|
19
|
+
name: 'mailloom',
|
|
20
|
+
version: PACKAGE_VERSION,
|
|
21
|
+
});
|
|
22
|
+
function tool(name, description, schema, handler) {
|
|
23
|
+
server.tool(name, description, schema, async (args) => {
|
|
24
|
+
try {
|
|
25
|
+
const data = await handler(args);
|
|
26
|
+
return jsonResult(data);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
return errorResult(err);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// —— Account ——
|
|
34
|
+
tool('health_check', 'Check Mailloom API liveness and whether write access is enabled.', {}, async () => apiRequest(config, 'GET', '/health'));
|
|
35
|
+
tool('get_account', 'Get account email, MCP write-enabled flag, and current API key metadata (never the secret).', {}, async () => apiRequest(config, 'GET', '/me'));
|
|
36
|
+
// —— Templates ——
|
|
37
|
+
tool('list_templates', 'List user and platform email templates.', {}, async () => apiRequest(config, 'GET', '/templates'));
|
|
38
|
+
tool('get_template', 'Get one template by id (includes full email document).', { id: z.string().uuid().describe('Template UUID') }, async (args) => apiRequest(config, 'GET', `/templates/${args.id}`));
|
|
39
|
+
tool('create_template', 'Create a user template. Requires write access. document matches the editor shape (settings + blocks).', {
|
|
40
|
+
name: z.string().describe('Template name'),
|
|
41
|
+
document: z.record(z.unknown()).describe('Email document (settings + blocks)'),
|
|
42
|
+
brand_kit_id: z.string().uuid().nullable().optional().describe('Optional brand kit UUID'),
|
|
43
|
+
}, async (args) => apiRequest(config, 'POST', '/templates', {
|
|
44
|
+
body: {
|
|
45
|
+
name: args.name,
|
|
46
|
+
document: args.document,
|
|
47
|
+
brand_kit_id: args.brand_kit_id,
|
|
48
|
+
},
|
|
49
|
+
}));
|
|
50
|
+
tool('update_template', 'Update a user template name and/or document. Platform templates are read-only — duplicate first. Requires write access.', {
|
|
51
|
+
id: z.string().uuid().describe('Template UUID'),
|
|
52
|
+
name: z.string().optional().describe('New name'),
|
|
53
|
+
document: z.record(z.unknown()).optional().describe('Full email document'),
|
|
54
|
+
brand_kit_id: z.string().uuid().nullable().optional().describe('Brand kit UUID or null'),
|
|
55
|
+
}, async (args) => {
|
|
56
|
+
const { id, ...body } = args;
|
|
57
|
+
return apiRequest(config, 'PATCH', `/templates/${id}`, { body });
|
|
58
|
+
});
|
|
59
|
+
tool('duplicate_template', 'Copy a platform or user template into a new editable user template. Requires write access.', {
|
|
60
|
+
id: z.string().uuid().describe('Source template UUID'),
|
|
61
|
+
name: z.string().optional().describe('Name for the copy'),
|
|
62
|
+
}, async (args) => apiRequest(config, 'POST', `/templates/${args.id}/duplicate`, {
|
|
63
|
+
body: { name: args.name },
|
|
64
|
+
}));
|
|
65
|
+
tool('delete_template', 'Delete a user template. Platform templates cannot be deleted. Requires write access.', { id: z.string().uuid().describe('Template UUID') }, async (args) => apiRequest(config, 'DELETE', `/templates/${args.id}`));
|
|
66
|
+
tool('export_template', 'Render a template as email HTML (inline CSS by default). Read-only.', {
|
|
67
|
+
id: z.string().uuid().describe('Template UUID'),
|
|
68
|
+
inline: z.boolean().optional().describe('Inline CSS (default true)'),
|
|
69
|
+
}, async (args) => apiRequest(config, 'POST', `/templates/${args.id}/export`, {
|
|
70
|
+
body: { inline: args.inline },
|
|
71
|
+
}));
|
|
72
|
+
// —— Brand kits ——
|
|
73
|
+
tool('list_brand_kits', 'List brand kits for the account.', {}, async () => apiRequest(config, 'GET', '/brand-kits'));
|
|
74
|
+
tool('get_brand_kit', 'Get one brand kit by id.', { id: z.string().uuid().describe('Brand kit UUID') }, async (args) => apiRequest(config, 'GET', `/brand-kits/${args.id}`));
|
|
75
|
+
tool('create_brand_kit', 'Create a brand kit (colors, fonts, tone, logo_path). Requires write access.', {
|
|
76
|
+
name: z.string().describe('Kit name'),
|
|
77
|
+
data: z
|
|
78
|
+
.record(z.unknown())
|
|
79
|
+
.optional()
|
|
80
|
+
.describe('BrandKitData: colors, fonts, tone, company, socialDefaults, …'),
|
|
81
|
+
logo_path: z.string().nullable().optional().describe('Storage path in assets bucket'),
|
|
82
|
+
}, async (args) => apiRequest(config, 'POST', '/brand-kits', {
|
|
83
|
+
body: { name: args.name, data: args.data, logo_path: args.logo_path },
|
|
84
|
+
}));
|
|
85
|
+
tool('update_brand_kit', 'Update a brand kit. Requires write access.', {
|
|
86
|
+
id: z.string().uuid().describe('Brand kit UUID'),
|
|
87
|
+
name: z.string().optional(),
|
|
88
|
+
data: z.record(z.unknown()).optional(),
|
|
89
|
+
logo_path: z.string().nullable().optional(),
|
|
90
|
+
}, async (args) => {
|
|
91
|
+
const { id, ...body } = args;
|
|
92
|
+
return apiRequest(config, 'PATCH', `/brand-kits/${id}`, { body });
|
|
93
|
+
});
|
|
94
|
+
tool('delete_brand_kit', 'Delete a brand kit. Requires write access.', { id: z.string().uuid().describe('Brand kit UUID') }, async (args) => apiRequest(config, 'DELETE', `/brand-kits/${args.id}`));
|
|
95
|
+
// —— Assets ——
|
|
96
|
+
tool('list_assets', 'List uploaded assets for the account.', {}, async () => apiRequest(config, 'GET', '/assets'));
|
|
97
|
+
tool('upload_asset', 'Upload an asset from base64 or a public URL. Requires write access.', {
|
|
98
|
+
filename: z.string().optional().describe('Original filename (for extension)'),
|
|
99
|
+
mime: z.string().optional().describe('MIME type'),
|
|
100
|
+
base64: z.string().optional().describe('Base64 or data-URL payload'),
|
|
101
|
+
url: z.string().url().optional().describe('Public URL to fetch and store'),
|
|
102
|
+
}, async (args) => apiRequest(config, 'POST', '/assets', { body: args }));
|
|
103
|
+
tool('delete_asset', 'Delete an asset by id. Requires write access.', { id: z.string().uuid().describe('Asset UUID') }, async (args) => apiRequest(config, 'DELETE', `/assets/${args.id}`));
|
|
104
|
+
async function main() {
|
|
105
|
+
const transport = new StdioServerTransport();
|
|
106
|
+
await server.connect(transport);
|
|
107
|
+
}
|
|
108
|
+
main().catch((err) => {
|
|
109
|
+
console.error(err);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@mailloom/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@mailloom/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official Mailloom MCP server — manage email templates, brand kits, and assets via the Mailloom API.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mailloom-mcp": "bin/mailloom-mcp.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc -p tsconfig.json",
|
|
19
|
+
"start": "node dist/index.js",
|
|
20
|
+
"dev": "tsx src/index.ts",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
28
|
+
"zod": "^3.24.2"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^22.13.10",
|
|
32
|
+
"tsx": "^4.19.3",
|
|
33
|
+
"typescript": "^5.8.2"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"bin",
|
|
37
|
+
"dist",
|
|
38
|
+
"README.md"
|
|
39
|
+
],
|
|
40
|
+
"keywords": [
|
|
41
|
+
"mailloom",
|
|
42
|
+
"mcp",
|
|
43
|
+
"model-context-protocol",
|
|
44
|
+
"cursor",
|
|
45
|
+
"claude",
|
|
46
|
+
"email",
|
|
47
|
+
"templates"
|
|
48
|
+
],
|
|
49
|
+
"license": "MIT"
|
|
50
|
+
}
|