@emailens/mcp 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/LICENSE +21 -0
- package/README.md +156 -0
- package/dist/index.js +213 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Emailens
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# @emailens/mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for email compatibility analysis. Enables Claude and other MCP-compatible AI assistants to preview, analyze, and score HTML emails across 12 email clients.
|
|
4
|
+
|
|
5
|
+
Built on top of [`@emailens/engine`](https://github.com/emailens/engine).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @emailens/mcp
|
|
11
|
+
# or
|
|
12
|
+
bunx @emailens/mcp
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage with Claude Desktop
|
|
16
|
+
|
|
17
|
+
Add to your Claude Desktop config (`claude_desktop_config.json`):
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"emailens": {
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": ["-y", "@emailens/mcp"]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or if using Bun:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"emailens": {
|
|
36
|
+
"command": "bunx",
|
|
37
|
+
"args": ["@emailens/mcp"]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage with Claude Code
|
|
44
|
+
|
|
45
|
+
Add to your Claude Code settings:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
claude mcp add emailens -- npx -y @emailens/mcp
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Tools
|
|
52
|
+
|
|
53
|
+
The server exposes three tools:
|
|
54
|
+
|
|
55
|
+
### `preview_email`
|
|
56
|
+
|
|
57
|
+
Full email compatibility preview — transforms HTML per client, analyzes CSS, generates scores, and simulates dark mode.
|
|
58
|
+
|
|
59
|
+
**Parameters:**
|
|
60
|
+
|
|
61
|
+
| Name | Type | Required | Description |
|
|
62
|
+
|---|---|---|---|
|
|
63
|
+
| `html` | string | Yes | The email HTML source code |
|
|
64
|
+
| `clients` | string[] | No | Filter to specific client IDs (e.g. `["gmail_web", "outlook_windows"]`). Omit for all 12 clients. |
|
|
65
|
+
| `format` | enum | No | Input format: `"html"` (default), `"jsx"` (React Email), `"mjml"`, or `"maizzle"`. Controls which framework-specific fix snippets appear. |
|
|
66
|
+
|
|
67
|
+
**Returns:** JSON with `overallScore` (0–100), `compatibilityScores` (per-client), `cssWarnings` (with fix snippets), `clientCount`, and `darkModeWarnings`.
|
|
68
|
+
|
|
69
|
+
**Example prompt:**
|
|
70
|
+
> Analyze this email HTML and tell me what will break in Gmail and Outlook:
|
|
71
|
+
> ```html
|
|
72
|
+
> <div style="display: flex; gap: 16px; border-radius: 8px;">...</div>
|
|
73
|
+
> ```
|
|
74
|
+
|
|
75
|
+
### `analyze_email`
|
|
76
|
+
|
|
77
|
+
Quick CSS compatibility analysis — returns warnings and per-client scores without full transforms. Faster than `preview_email` when you only need the compatibility report.
|
|
78
|
+
|
|
79
|
+
**Parameters:**
|
|
80
|
+
|
|
81
|
+
| Name | Type | Required | Description |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| `html` | string | Yes | The email HTML source code |
|
|
84
|
+
| `format` | enum | No | Input format: `"html"`, `"jsx"`, `"mjml"`, or `"maizzle"` |
|
|
85
|
+
|
|
86
|
+
**Returns:** JSON with `overallScore`, `scores` (per-client), `warningCount`, and `warnings` (with severity, message, and fix).
|
|
87
|
+
|
|
88
|
+
**Example prompt:**
|
|
89
|
+
> What's the compatibility score for this React Email template?
|
|
90
|
+
|
|
91
|
+
### `list_clients`
|
|
92
|
+
|
|
93
|
+
Lists all 12 supported email client IDs and display names. Useful for discovering valid client IDs to pass to `preview_email`.
|
|
94
|
+
|
|
95
|
+
**Parameters:** None
|
|
96
|
+
|
|
97
|
+
**Returns:** JSON array of `{ id, name }` objects.
|
|
98
|
+
|
|
99
|
+
**Example prompt:**
|
|
100
|
+
> What email clients does Emailens support?
|
|
101
|
+
|
|
102
|
+
## Supported Email Clients
|
|
103
|
+
|
|
104
|
+
| Client | ID | Dark Mode |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| Gmail | `gmail-web` | Yes |
|
|
107
|
+
| Gmail Android | `gmail-android` | Yes |
|
|
108
|
+
| Gmail iOS | `gmail-ios` | Yes |
|
|
109
|
+
| Outlook 365 | `outlook-web` | Yes |
|
|
110
|
+
| Outlook Windows | `outlook-windows` | No |
|
|
111
|
+
| Apple Mail | `apple-mail-macos` | Yes |
|
|
112
|
+
| Apple Mail iOS | `apple-mail-ios` | Yes |
|
|
113
|
+
| Yahoo Mail | `yahoo-mail` | Yes |
|
|
114
|
+
| Samsung Mail | `samsung-mail` | Yes |
|
|
115
|
+
| Thunderbird | `thunderbird` | No |
|
|
116
|
+
| HEY Mail | `hey-mail` | Yes |
|
|
117
|
+
| Superhuman | `superhuman` | Yes |
|
|
118
|
+
|
|
119
|
+
## Framework-Aware Fixes
|
|
120
|
+
|
|
121
|
+
When you specify a `format`, the fix snippets in warnings are tailored to your framework:
|
|
122
|
+
|
|
123
|
+
- **`jsx`** — References React Email components (`Row`, `Column`, `Font`, `Container` from `@react-email/components`)
|
|
124
|
+
- **`mjml`** — References MJML elements (`mj-section`, `mj-column`, `mj-font`, `mj-style`)
|
|
125
|
+
- **`maizzle`** — References Tailwind CSS classes and Maizzle config (`googleFonts`, MSO conditionals)
|
|
126
|
+
- **`html`** (default) — Generic HTML with VML fallbacks for Outlook
|
|
127
|
+
|
|
128
|
+
## How It Works
|
|
129
|
+
|
|
130
|
+
The MCP server is a thin wrapper around `@emailens/engine`. For each tool call, it:
|
|
131
|
+
|
|
132
|
+
1. Validates inputs with Zod schemas
|
|
133
|
+
2. Calls engine functions (`transformForAllClients`, `analyzeEmail`, `generateCompatibilityScore`, `simulateDarkMode`)
|
|
134
|
+
3. Formats and returns JSON results via stdio transport
|
|
135
|
+
|
|
136
|
+
The engine uses [Cheerio](https://cheerio.js.org/) for HTML manipulation and [css-tree](https://github.com/csstree/csstree) for CSS parsing. No external API calls — all analysis runs locally.
|
|
137
|
+
|
|
138
|
+
## Development
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Install dependencies
|
|
142
|
+
bun install
|
|
143
|
+
|
|
144
|
+
# Build
|
|
145
|
+
bun run build
|
|
146
|
+
|
|
147
|
+
# Run locally
|
|
148
|
+
bun run src/index.ts
|
|
149
|
+
|
|
150
|
+
# Type check
|
|
151
|
+
bun run typecheck
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import {
|
|
9
|
+
transformForAllClients,
|
|
10
|
+
analyzeEmail,
|
|
11
|
+
generateCompatibilityScore,
|
|
12
|
+
simulateDarkMode,
|
|
13
|
+
EMAIL_CLIENTS
|
|
14
|
+
} from "@emailens/engine";
|
|
15
|
+
function toFramework(format) {
|
|
16
|
+
if (format === "jsx" || format === "mjml" || format === "maizzle") return format;
|
|
17
|
+
return void 0;
|
|
18
|
+
}
|
|
19
|
+
var server = new McpServer({
|
|
20
|
+
name: "emailens",
|
|
21
|
+
version: "0.1.0"
|
|
22
|
+
});
|
|
23
|
+
server.registerTool(
|
|
24
|
+
"preview_email",
|
|
25
|
+
{
|
|
26
|
+
title: "Preview Email",
|
|
27
|
+
description: "Analyze an HTML email and see how it renders across 12 email clients (Gmail, Outlook, Apple Mail, Yahoo, Samsung, Thunderbird, HEY, Superhuman). Returns CSS transforms, compatibility warnings with fix snippets, dark mode simulation, and per-client scores.",
|
|
28
|
+
inputSchema: {
|
|
29
|
+
html: z.string().describe("The email HTML source code"),
|
|
30
|
+
clients: z.array(z.string()).optional().describe(
|
|
31
|
+
"Optional array of client IDs to filter (e.g. ['gmail-web', 'outlook-windows']). Omit for all clients."
|
|
32
|
+
),
|
|
33
|
+
format: z.enum(["html", "jsx", "mjml", "maizzle"]).optional().describe(
|
|
34
|
+
"Input format of the email source: 'html' (default), 'jsx' (React Email), 'mjml', or 'maizzle'. Controls which framework-specific fix snippets appear in the warnings."
|
|
35
|
+
)
|
|
36
|
+
},
|
|
37
|
+
annotations: {
|
|
38
|
+
title: "Preview Email",
|
|
39
|
+
readOnlyHint: true,
|
|
40
|
+
destructiveHint: false,
|
|
41
|
+
idempotentHint: true,
|
|
42
|
+
openWorldHint: false
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
async ({ html, clients, format }) => {
|
|
46
|
+
const validClientIds = new Set(EMAIL_CLIENTS.map((c) => c.id));
|
|
47
|
+
let transforms = transformForAllClients(html);
|
|
48
|
+
if (clients) {
|
|
49
|
+
const filter = clients.filter((c) => validClientIds.has(c));
|
|
50
|
+
if (filter.length === 0) {
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: JSON.stringify(
|
|
56
|
+
{
|
|
57
|
+
error: "No valid client IDs provided",
|
|
58
|
+
validClientIds: Array.from(validClientIds)
|
|
59
|
+
},
|
|
60
|
+
null,
|
|
61
|
+
2
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
isError: true
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
transforms = transforms.filter(
|
|
69
|
+
(t) => filter.includes(t.clientId)
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const warnings = analyzeEmail(html, toFramework(format));
|
|
73
|
+
const scores = generateCompatibilityScore(warnings);
|
|
74
|
+
const darkMode = {};
|
|
75
|
+
for (const t of transforms) {
|
|
76
|
+
darkMode[t.clientId] = simulateDarkMode(t.html, t.clientId);
|
|
77
|
+
}
|
|
78
|
+
const scoreValues = Object.values(scores);
|
|
79
|
+
const overallScore = scoreValues.length > 0 ? Math.round(
|
|
80
|
+
scoreValues.reduce(
|
|
81
|
+
(a, b) => a + b.score,
|
|
82
|
+
0
|
|
83
|
+
) / scoreValues.length
|
|
84
|
+
) : 0;
|
|
85
|
+
return {
|
|
86
|
+
content: [
|
|
87
|
+
{
|
|
88
|
+
type: "text",
|
|
89
|
+
text: JSON.stringify(
|
|
90
|
+
{
|
|
91
|
+
overallScore,
|
|
92
|
+
compatibilityScores: scores,
|
|
93
|
+
cssWarnings: warnings.map((w) => ({
|
|
94
|
+
client: w.client,
|
|
95
|
+
property: w.property,
|
|
96
|
+
severity: w.severity,
|
|
97
|
+
message: w.message,
|
|
98
|
+
suggestion: w.suggestion,
|
|
99
|
+
fix: w.fix
|
|
100
|
+
})),
|
|
101
|
+
clientCount: transforms.length,
|
|
102
|
+
darkModeWarnings: Object.entries(darkMode).reduce(
|
|
103
|
+
(acc, [clientId, dm]) => {
|
|
104
|
+
if (dm.warnings.length > 0) {
|
|
105
|
+
acc[clientId] = dm.warnings.length;
|
|
106
|
+
}
|
|
107
|
+
return acc;
|
|
108
|
+
},
|
|
109
|
+
{}
|
|
110
|
+
)
|
|
111
|
+
},
|
|
112
|
+
null,
|
|
113
|
+
2
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
server.registerTool(
|
|
121
|
+
"analyze_email",
|
|
122
|
+
{
|
|
123
|
+
title: "Analyze Email",
|
|
124
|
+
description: "Quick CSS compatibility analysis \u2014 returns warnings and per-client scores without full transforms. Faster than preview_email when you only need the compatibility report.",
|
|
125
|
+
inputSchema: {
|
|
126
|
+
html: z.string().describe("The email HTML source code"),
|
|
127
|
+
format: z.enum(["html", "jsx", "mjml", "maizzle"]).optional().describe(
|
|
128
|
+
"Input format: 'html' (default), 'jsx' (React Email), 'mjml', or 'maizzle'. Determines which framework-specific fix snippets are returned."
|
|
129
|
+
)
|
|
130
|
+
},
|
|
131
|
+
annotations: {
|
|
132
|
+
title: "Analyze Email",
|
|
133
|
+
readOnlyHint: true,
|
|
134
|
+
destructiveHint: false,
|
|
135
|
+
idempotentHint: true,
|
|
136
|
+
openWorldHint: false
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
async ({ html, format }) => {
|
|
140
|
+
const warnings = analyzeEmail(html, toFramework(format));
|
|
141
|
+
const scores = generateCompatibilityScore(warnings);
|
|
142
|
+
const scoreValues = Object.values(scores);
|
|
143
|
+
const overallScore = scoreValues.length > 0 ? Math.round(
|
|
144
|
+
scoreValues.reduce(
|
|
145
|
+
(a, b) => a + b.score,
|
|
146
|
+
0
|
|
147
|
+
) / scoreValues.length
|
|
148
|
+
) : 0;
|
|
149
|
+
return {
|
|
150
|
+
content: [
|
|
151
|
+
{
|
|
152
|
+
type: "text",
|
|
153
|
+
text: JSON.stringify(
|
|
154
|
+
{
|
|
155
|
+
overallScore,
|
|
156
|
+
scores,
|
|
157
|
+
warningCount: warnings.length,
|
|
158
|
+
warnings: warnings.map((w) => ({
|
|
159
|
+
client: w.client,
|
|
160
|
+
property: w.property,
|
|
161
|
+
severity: w.severity,
|
|
162
|
+
message: w.message,
|
|
163
|
+
suggestion: w.suggestion,
|
|
164
|
+
fix: w.fix
|
|
165
|
+
}))
|
|
166
|
+
},
|
|
167
|
+
null,
|
|
168
|
+
2
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
]
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
server.registerTool(
|
|
176
|
+
"list_clients",
|
|
177
|
+
{
|
|
178
|
+
title: "List Email Clients",
|
|
179
|
+
description: "List all 12 supported email client IDs, display names, categories, rendering engines, and dark mode support.",
|
|
180
|
+
annotations: {
|
|
181
|
+
title: "List Email Clients",
|
|
182
|
+
readOnlyHint: true,
|
|
183
|
+
destructiveHint: false,
|
|
184
|
+
idempotentHint: true,
|
|
185
|
+
openWorldHint: false
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
async () => {
|
|
189
|
+
return {
|
|
190
|
+
content: [
|
|
191
|
+
{
|
|
192
|
+
type: "text",
|
|
193
|
+
text: JSON.stringify(
|
|
194
|
+
EMAIL_CLIENTS.map(
|
|
195
|
+
(c) => ({
|
|
196
|
+
id: c.id,
|
|
197
|
+
name: c.name,
|
|
198
|
+
category: c.category,
|
|
199
|
+
engine: c.engine,
|
|
200
|
+
darkModeSupport: c.darkModeSupport
|
|
201
|
+
})
|
|
202
|
+
),
|
|
203
|
+
null,
|
|
204
|
+
2
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
var transport = new StdioServerTransport();
|
|
212
|
+
await server.connect(transport);
|
|
213
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\r\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\r\nimport { z } from \"zod\";\r\nimport {\r\n transformForAllClients,\r\n analyzeEmail,\r\n generateCompatibilityScore,\r\n simulateDarkMode,\r\n EMAIL_CLIENTS,\r\n type Framework,\r\n type CSSWarning,\r\n} from \"@emailens/engine\";\r\n\r\nfunction toFramework(format?: string): Framework | undefined {\r\n if (format === \"jsx\" || format === \"mjml\" || format === \"maizzle\") return format;\r\n return undefined;\r\n}\r\n\r\nconst server = new McpServer({\r\n name: \"emailens\",\r\n version: \"0.1.0\",\r\n});\r\n\r\n// ── Tool: preview_email ─────────────────────────────────────────\r\nserver.registerTool(\r\n \"preview_email\",\r\n {\r\n title: \"Preview Email\",\r\n description:\r\n \"Analyze an HTML email and see how it renders across 12 email clients (Gmail, Outlook, Apple Mail, Yahoo, Samsung, Thunderbird, HEY, Superhuman). Returns CSS transforms, compatibility warnings with fix snippets, dark mode simulation, and per-client scores.\",\r\n inputSchema: {\r\n html: z.string().describe(\"The email HTML source code\"),\r\n clients: z\r\n .array(z.string())\r\n .optional()\r\n .describe(\r\n \"Optional array of client IDs to filter (e.g. ['gmail-web', 'outlook-windows']). Omit for all clients.\"\r\n ),\r\n format: z\r\n .enum([\"html\", \"jsx\", \"mjml\", \"maizzle\"])\r\n .optional()\r\n .describe(\r\n \"Input format of the email source: 'html' (default), 'jsx' (React Email), 'mjml', or 'maizzle'. Controls which framework-specific fix snippets appear in the warnings.\"\r\n ),\r\n },\r\n annotations: {\r\n title: \"Preview Email\",\r\n readOnlyHint: true,\r\n destructiveHint: false,\r\n idempotentHint: true,\r\n openWorldHint: false,\r\n },\r\n },\r\n async ({ html, clients, format }) => {\r\n const validClientIds = new Set(EMAIL_CLIENTS.map((c: { id: string }) => c.id));\r\n\r\n let transforms = transformForAllClients(html);\r\n if (clients) {\r\n const filter = clients.filter((c: string) => validClientIds.has(c));\r\n if (filter.length === 0) {\r\n return {\r\n content: [\r\n {\r\n type: \"text\" as const,\r\n text: JSON.stringify(\r\n {\r\n error: \"No valid client IDs provided\",\r\n validClientIds: Array.from(validClientIds),\r\n },\r\n null,\r\n 2\r\n ),\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n transforms = transforms.filter((t: { clientId: string }) =>\r\n filter.includes(t.clientId)\r\n );\r\n }\r\n\r\n const warnings = analyzeEmail(html, toFramework(format));\r\n const scores = generateCompatibilityScore(warnings);\r\n\r\n const darkMode: Record<string, { html: string; warnings: CSSWarning[] }> =\r\n {};\r\n for (const t of transforms) {\r\n darkMode[t.clientId] = simulateDarkMode(t.html, t.clientId);\r\n }\r\n\r\n const scoreValues = Object.values(scores);\r\n const overallScore =\r\n scoreValues.length > 0\r\n ? Math.round(\r\n scoreValues.reduce(\r\n (a: number, b: { score: number }) => a + b.score,\r\n 0\r\n ) / scoreValues.length\r\n )\r\n : 0;\r\n\r\n return {\r\n content: [\r\n {\r\n type: \"text\" as const,\r\n text: JSON.stringify(\r\n {\r\n overallScore,\r\n compatibilityScores: scores,\r\n cssWarnings: warnings.map((w: CSSWarning) => ({\r\n client: w.client,\r\n property: w.property,\r\n severity: w.severity,\r\n message: w.message,\r\n suggestion: w.suggestion,\r\n fix: w.fix,\r\n })),\r\n clientCount: transforms.length,\r\n darkModeWarnings: Object.entries(darkMode).reduce(\r\n (\r\n acc: Record<string, number>,\r\n [clientId, dm]: [string, { warnings: CSSWarning[] }]\r\n ) => {\r\n if (dm.warnings.length > 0) {\r\n acc[clientId] = dm.warnings.length;\r\n }\r\n return acc;\r\n },\r\n {}\r\n ),\r\n },\r\n null,\r\n 2\r\n ),\r\n },\r\n ],\r\n };\r\n }\r\n);\r\n\r\n// ── Tool: analyze_email ─────────────────────────────────────────\r\nserver.registerTool(\r\n \"analyze_email\",\r\n {\r\n title: \"Analyze Email\",\r\n description:\r\n \"Quick CSS compatibility analysis — returns warnings and per-client scores without full transforms. Faster than preview_email when you only need the compatibility report.\",\r\n inputSchema: {\r\n html: z.string().describe(\"The email HTML source code\"),\r\n format: z\r\n .enum([\"html\", \"jsx\", \"mjml\", \"maizzle\"])\r\n .optional()\r\n .describe(\r\n \"Input format: 'html' (default), 'jsx' (React Email), 'mjml', or 'maizzle'. Determines which framework-specific fix snippets are returned.\"\r\n ),\r\n },\r\n annotations: {\r\n title: \"Analyze Email\",\r\n readOnlyHint: true,\r\n destructiveHint: false,\r\n idempotentHint: true,\r\n openWorldHint: false,\r\n },\r\n },\r\n async ({ html, format }) => {\r\n const warnings = analyzeEmail(html, toFramework(format));\r\n const scores = generateCompatibilityScore(warnings);\r\n\r\n const scoreValues = Object.values(scores);\r\n const overallScore =\r\n scoreValues.length > 0\r\n ? Math.round(\r\n scoreValues.reduce(\r\n (a: number, b: { score: number }) => a + b.score,\r\n 0\r\n ) / scoreValues.length\r\n )\r\n : 0;\r\n\r\n return {\r\n content: [\r\n {\r\n type: \"text\" as const,\r\n text: JSON.stringify(\r\n {\r\n overallScore,\r\n scores,\r\n warningCount: warnings.length,\r\n warnings: warnings.map((w: CSSWarning) => ({\r\n client: w.client,\r\n property: w.property,\r\n severity: w.severity,\r\n message: w.message,\r\n suggestion: w.suggestion,\r\n fix: w.fix,\r\n })),\r\n },\r\n null,\r\n 2\r\n ),\r\n },\r\n ],\r\n };\r\n }\r\n);\r\n\r\n// ── Tool: list_clients ──────────────────────────────────────────\r\nserver.registerTool(\r\n \"list_clients\",\r\n {\r\n title: \"List Email Clients\",\r\n description:\r\n \"List all 12 supported email client IDs, display names, categories, rendering engines, and dark mode support.\",\r\n annotations: {\r\n title: \"List Email Clients\",\r\n readOnlyHint: true,\r\n destructiveHint: false,\r\n idempotentHint: true,\r\n openWorldHint: false,\r\n },\r\n },\r\n async () => {\r\n return {\r\n content: [\r\n {\r\n type: \"text\" as const,\r\n text: JSON.stringify(\r\n EMAIL_CLIENTS.map(\r\n (c: {\r\n id: string;\r\n name: string;\r\n category: string;\r\n engine: string;\r\n darkModeSupport: boolean;\r\n }) => ({\r\n id: c.id,\r\n name: c.name,\r\n category: c.category,\r\n engine: c.engine,\r\n darkModeSupport: c.darkModeSupport,\r\n })\r\n ),\r\n null,\r\n 2\r\n ),\r\n },\r\n ],\r\n };\r\n }\r\n);\r\n\r\n// ── Start ────────────────────────────────────────────────────────\r\nconst transport = new StdioServerTransport();\r\nawait server.connect(transport);\r\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@emailens/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for email compatibility analysis — preview, analyze, and list email clients via Claude or any MCP-compatible tool.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"emailens-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"dev": "tsup --watch",
|
|
24
|
+
"start": "bun run dist/index.js",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"prepublishOnly": "bun run build"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@emailens/engine": "^0.1.0",
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
31
|
+
"zod": "^3.24.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^25.3.0",
|
|
35
|
+
"bun-types": "^1.3.9",
|
|
36
|
+
"tsup": "^8.4.0",
|
|
37
|
+
"typescript": "^5.9.3"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"mcp",
|
|
41
|
+
"model-context-protocol",
|
|
42
|
+
"email",
|
|
43
|
+
"email-testing",
|
|
44
|
+
"compatibility",
|
|
45
|
+
"claude"
|
|
46
|
+
],
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/emailens/mcp.git"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://github.com/emailens/mcp#readme",
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://github.com/emailens/mcp/issues"
|
|
55
|
+
}
|
|
56
|
+
}
|