@emailens/mcp 0.4.2 → 0.6.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 +283 -201
- package/dist/index.js +155 -35
- package/dist/index.js.map +1 -1
- package/package.json +60 -60
package/README.md
CHANGED
|
@@ -1,201 +1,283 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
|
176
|
-
|
|
|
177
|
-
|
|
|
178
|
-
|
|
|
179
|
-
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
|
186
|
-
|
|
187
|
-
|
|
|
188
|
-
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="./docs/wordmark-dark.svg">
|
|
5
|
+
<img src="./docs/wordmark-light.svg" alt="emailens / mcp" width="444">
|
|
6
|
+
</picture>
|
|
7
|
+
|
|
8
|
+
**Email rendering analysis for AI assistants**
|
|
9
|
+
|
|
10
|
+
[](https://www.npmjs.com/package/@emailens/mcp)
|
|
11
|
+
[](./LICENSE)
|
|
12
|
+
[](https://registry.modelcontextprotocol.io)
|
|
13
|
+
[](https://smithery.ai/server/emailens/emailens-mcp)
|
|
14
|
+
[](https://github.com/emailens/mcp/stargazers)
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
MCP server for email compatibility analysis. Analyze, preview, diff, and fix HTML emails across 21 email clients — plus capture real screenshots and create shareable links with an optional API key.
|
|
19
|
+
|
|
20
|
+
Why your assistant needs this: across the 255 CSS and HTML features we track, only 6 are fully supported in every major email client ([see the data](https://emailens.dev/email-css/report)). Ask Claude to check your email before you send it.
|
|
21
|
+
|
|
22
|
+
Built on [`@emailens/engine`](https://github.com/emailens/engine). Also available as a [GitHub Action](https://github.com/marketplace/actions/emailens-email-preview-check).
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx -y @emailens/mcp
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Setup
|
|
31
|
+
|
|
32
|
+
### Claude Desktop
|
|
33
|
+
|
|
34
|
+
Add to `claude_desktop_config.json`:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"mcpServers": {
|
|
39
|
+
"emailens": {
|
|
40
|
+
"command": "npx",
|
|
41
|
+
"args": ["-y", "@emailens/mcp"]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Claude Code
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
claude mcp add emailens -- npx -y @emailens/mcp
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### With API Key (optional — unlocks screenshots + sharing)
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"mcpServers": {
|
|
58
|
+
"emailens": {
|
|
59
|
+
"command": "npx",
|
|
60
|
+
"args": ["-y", "@emailens/mcp"],
|
|
61
|
+
"env": {
|
|
62
|
+
"EMAILENS_API_KEY": "ek_live_..."
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Get your free API key at [emailens.dev/settings/api-keys](https://emailens.dev/settings/api-keys).
|
|
70
|
+
|
|
71
|
+
### Remote (no install)
|
|
72
|
+
|
|
73
|
+
Use the hosted endpoint — no npm or Node.js needed. API key required.
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"mcpServers": {
|
|
78
|
+
"emailens": {
|
|
79
|
+
"url": "https://emailens.dev/api/mcp",
|
|
80
|
+
"headers": {
|
|
81
|
+
"Authorization": "Bearer ek_live_..."
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Tools
|
|
89
|
+
|
|
90
|
+
### Local Tools (no account needed)
|
|
91
|
+
|
|
92
|
+
#### `preview_email`
|
|
93
|
+
|
|
94
|
+
Full email compatibility preview — transforms HTML for 21 clients, analyzes CSS, generates scores, simulates dark mode, checks inbox preview and email size.
|
|
95
|
+
|
|
96
|
+
| Parameter | Type | Required | Description |
|
|
97
|
+
|---|---|---|---|
|
|
98
|
+
| `html` | string | Yes | Email HTML source |
|
|
99
|
+
| `clients` | string[] | No | Filter to specific client IDs |
|
|
100
|
+
| `format` | enum | No | `"html"`, `"jsx"`, `"mjml"`, `"maizzle"` |
|
|
101
|
+
|
|
102
|
+
#### `analyze_email`
|
|
103
|
+
|
|
104
|
+
Quick CSS compatibility analysis — returns per-client scores and one finding per problem. Faster than `audit_email` when you only need CSS compatibility.
|
|
105
|
+
|
|
106
|
+
| Parameter | Type | Required | Description |
|
|
107
|
+
|---|---|---|---|
|
|
108
|
+
| `html` | string | Yes | Email HTML source |
|
|
109
|
+
| `format` | enum | No | Input format |
|
|
110
|
+
| `detail` | enum | No | `"summary"` (default) or `"full"` |
|
|
111
|
+
| `clients` | string[] | No | Only report these client IDs |
|
|
112
|
+
|
|
113
|
+
**One finding per problem, not one per client.** The engine reports per client
|
|
114
|
+
because a score is per client, and per selector because a fix is per selector.
|
|
115
|
+
On an ordinary newsletter `border-radius` arrives twelve times — two clients
|
|
116
|
+
that drop it, six selectors that use it — with the same sentence in every copy.
|
|
117
|
+
That was 286KB of JSON, about 73,000 tokens, for an 11KB email.
|
|
118
|
+
|
|
119
|
+
Findings group by property, severity and message, listing the clients affected,
|
|
120
|
+
with positions merged across all of them. The same email now returns 40KB.
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"property": "border-radius",
|
|
125
|
+
"severity": "warning",
|
|
126
|
+
"clients": ["outlook-windows", "outlook-windows-legacy"],
|
|
127
|
+
"message": "Does not support \"border-radius\". Round corners can be used in VML…",
|
|
128
|
+
"fixType": "structural",
|
|
129
|
+
"hasFix": true,
|
|
130
|
+
"loc": { "line": 61, "column": 28, "offset": 2753, "length": 171 },
|
|
131
|
+
"alsoAtLines": [62, 64, 75, 78, 87]
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Fix snippets are not included — they were 93KB of that 286KB, and `fix_email`
|
|
136
|
+
produces them for the issues you decide to act on. `hasFix` tells you one is
|
|
137
|
+
available; `fixType` tells you whether the repair is markup or CSS.
|
|
138
|
+
|
|
139
|
+
Pass `detail: "full"` for the engine's per-client shape with snippets, and
|
|
140
|
+
`clients: ["gmail-web", "outlook-windows"]` to report only what you care about
|
|
141
|
+
— the fastest further saving, roughly halving the response for two clients.
|
|
142
|
+
Scores stay whole-email either way: narrowing the report does not change what
|
|
143
|
+
the email is worth elsewhere. An unknown client ID is rejected by name — an
|
|
144
|
+
empty result would read as "this email is fine for that client".
|
|
145
|
+
|
|
146
|
+
**Source positions.** For HTML input, every warning carries `loc` — `line`,
|
|
147
|
+
`column`, `offset`, `length` — for the first occurrence, plus `alsoAtLines` for
|
|
148
|
+
any others, so an assistant can edit the exact source and knows where the rest
|
|
149
|
+
are. `audit_email` positions its other findings the same way.
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"property": "border-radius",
|
|
154
|
+
"loc": { "line": 7, "column": 8, "offset": 142, "length": 25 },
|
|
155
|
+
"alsoAtLines": [12, 19]
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Later occurrences are line numbers rather than full positions on purpose: this
|
|
160
|
+
response is read by a model paying for every token, and a real newsletter can
|
|
161
|
+
produce over a thousand occurrences — carrying them all in full nearly doubles
|
|
162
|
+
the payload.
|
|
163
|
+
|
|
164
|
+
Positions are reported for `html` input only. JSX, MJML and Maizzle are compiled
|
|
165
|
+
before analysis, so a line number would point into generated output rather than
|
|
166
|
+
the file you have open — the tools omit it instead of returning one that looks
|
|
167
|
+
authoritative.
|
|
168
|
+
|
|
169
|
+
#### `audit_email`
|
|
170
|
+
|
|
171
|
+
Comprehensive quality audit — CSS compatibility, spam scoring, link validation, accessibility, images, inbox preview, size (Gmail clipping), template variables, content overflow, and visual bugs.
|
|
172
|
+
|
|
173
|
+
| Parameter | Type | Required | Description |
|
|
174
|
+
|---|---|---|---|
|
|
175
|
+
| `html` | string | Yes | Email HTML source |
|
|
176
|
+
| `format` | enum | No | Input format |
|
|
177
|
+
| `detail` | enum | No | `"summary"` (default) or `"full"` |
|
|
178
|
+
| `clients` | string[] | No | Only report these client IDs |
|
|
179
|
+
| `skip` | string[] | No | Checks to skip (e.g. `["spam", "images"]`) |
|
|
180
|
+
|
|
181
|
+
#### `fix_email`
|
|
182
|
+
|
|
183
|
+
Generate a structured fix prompt for compatibility issues. Returns markdown with fix instructions that the AI can apply directly.
|
|
184
|
+
|
|
185
|
+
| Parameter | Type | Required | Description |
|
|
186
|
+
|---|---|---|---|
|
|
187
|
+
| `html` | string | Yes | Email HTML to fix |
|
|
188
|
+
| `format` | enum | No | Controls fix syntax |
|
|
189
|
+
| `scope` | enum | No | `"all"` or `"current"` |
|
|
190
|
+
| `selectedClientId` | string | No | Client ID for scoped fixes |
|
|
191
|
+
|
|
192
|
+
#### `list_clients`
|
|
193
|
+
|
|
194
|
+
List all 21 supported email clients with IDs, names, engines, and dark mode support.
|
|
195
|
+
|
|
196
|
+
#### `diff_emails`
|
|
197
|
+
|
|
198
|
+
Compare two email HTML versions — shows score changes, fixed issues, and introduced issues per client.
|
|
199
|
+
|
|
200
|
+
| Parameter | Type | Required | Description |
|
|
201
|
+
|---|---|---|---|
|
|
202
|
+
| `before` | string | Yes | Original email HTML |
|
|
203
|
+
| `after` | string | Yes | Modified email HTML |
|
|
204
|
+
| `format` | enum | No | Input format |
|
|
205
|
+
|
|
206
|
+
#### `check_deliverability`
|
|
207
|
+
|
|
208
|
+
Check email deliverability for a domain — SPF, DKIM, DMARC, MX, BIMI records with a score and actionable issues.
|
|
209
|
+
|
|
210
|
+
| Parameter | Type | Required | Description |
|
|
211
|
+
|---|---|---|---|
|
|
212
|
+
| `domain` | string | Yes | Domain to check (e.g. `"company.com"`) |
|
|
213
|
+
|
|
214
|
+
### Hosted Tools (require `EMAILENS_API_KEY`)
|
|
215
|
+
|
|
216
|
+
#### `capture_screenshots`
|
|
217
|
+
|
|
218
|
+
Capture real email screenshots across 21 clients in real browsers. Screenshots are hosted on CDN.
|
|
219
|
+
|
|
220
|
+
| Parameter | Type | Required | Description |
|
|
221
|
+
|---|---|---|---|
|
|
222
|
+
| `html` | string | Yes | Email HTML source |
|
|
223
|
+
| `format` | enum | No | Input format |
|
|
224
|
+
| `clients` | string[] | No | Filter clients |
|
|
225
|
+
| `modes` | string[] | No | `["light"]`, `["dark"]`, or `["light", "dark"]` |
|
|
226
|
+
| `title` | string | No | Name for the preview |
|
|
227
|
+
|
|
228
|
+
Free plan: 30 previews/day. [Sign up](https://emailens.dev?ref=mcp)
|
|
229
|
+
|
|
230
|
+
#### `share_preview`
|
|
231
|
+
|
|
232
|
+
Create a shareable link. Recipients see the full analysis without an account.
|
|
233
|
+
|
|
234
|
+
| Parameter | Type | Required | Description |
|
|
235
|
+
|---|---|---|---|
|
|
236
|
+
| `html` | string | Yes | Email HTML source |
|
|
237
|
+
| `title` | string | No | Display title |
|
|
238
|
+
| `format` | enum | No | Input format |
|
|
239
|
+
|
|
240
|
+
Requires Dev plan ($9/mo). Share links expire after 7 days (Dev) or never (Pro).
|
|
241
|
+
|
|
242
|
+
## Supported Email Clients (21)
|
|
243
|
+
|
|
244
|
+
| Client | ID | Dark Mode | Notes |
|
|
245
|
+
|---|---|---|---|
|
|
246
|
+
| Gmail | `gmail-web` | Yes | |
|
|
247
|
+
| Gmail Android | `gmail-android` | Yes | |
|
|
248
|
+
| Gmail iOS | `gmail-ios` | Yes | |
|
|
249
|
+
| Outlook 365 | `outlook-web` | Yes | |
|
|
250
|
+
| Outlook Windows | `outlook-windows` | No | |
|
|
251
|
+
| Outlook Windows Legacy | `outlook-windows-legacy` | No | Deprecated Oct 2026 |
|
|
252
|
+
| Outlook iOS | `outlook-ios` | Yes | New in v0.4.0 |
|
|
253
|
+
| Outlook Android | `outlook-android` | Yes | New in v0.4.0 |
|
|
254
|
+
| Outlook for Mac | `outlook-macos` | Yes | New in v0.10.0 |
|
|
255
|
+
| Apple Mail | `apple-mail-macos` | Yes | |
|
|
256
|
+
| Apple Mail iOS | `apple-mail-ios` | Yes | |
|
|
257
|
+
| Yahoo Mail | `yahoo-mail` | Yes | |
|
|
258
|
+
| Yahoo Mail Android | `yahoo-mail-android` | Yes | New in v0.10.0 |
|
|
259
|
+
| Yahoo Mail iOS | `yahoo-mail-ios` | Yes | New in v0.10.0 |
|
|
260
|
+
| Samsung Mail | `samsung-mail` | Yes | |
|
|
261
|
+
| Thunderbird | `thunderbird` | No | |
|
|
262
|
+
| HEY Mail | `hey-mail` | Yes | |
|
|
263
|
+
| Proton Mail | `protonmail` | Yes | New in v0.10.0 |
|
|
264
|
+
| AOL Mail | `aol` | Yes | New in v0.10.0 |
|
|
265
|
+
| Fastmail | `fastmail` | Yes | New in v0.10.0 |
|
|
266
|
+
| Superhuman | `superhuman` | Yes | |
|
|
267
|
+
|
|
268
|
+
## Development
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
bun install
|
|
272
|
+
bun run build
|
|
273
|
+
bun test
|
|
274
|
+
bun run typecheck
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## License
|
|
278
|
+
|
|
279
|
+
MIT
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
If this saved you from an Outlook surprise, [a star](https://github.com/emailens/mcp) helps other email developers find it.
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,77 @@
|
|
|
4
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
// src/findings.ts
|
|
9
|
+
import { EMAIL_CLIENTS } from "@emailens/engine";
|
|
10
|
+
var CLIENT_NAMES = new Map(EMAIL_CLIENTS.map((c) => [c.id, c.name]));
|
|
11
|
+
function withoutClientName(message, clientId) {
|
|
12
|
+
const name = CLIENT_NAMES.get(clientId);
|
|
13
|
+
if (!name || !message.startsWith(name)) return message;
|
|
14
|
+
const rest = message.slice(name.length).trimStart();
|
|
15
|
+
return rest ? rest.charAt(0).toUpperCase() + rest.slice(1) : message;
|
|
16
|
+
}
|
|
17
|
+
function mergeLocations(group) {
|
|
18
|
+
const byOffset = /* @__PURE__ */ new Map();
|
|
19
|
+
for (const w of group) {
|
|
20
|
+
for (const loc of w.locs ?? (w.loc ? [w.loc] : [])) {
|
|
21
|
+
if (!byOffset.has(loc.offset)) byOffset.set(loc.offset, loc);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return [...byOffset.values()].sort((a, b) => a.offset - b.offset);
|
|
25
|
+
}
|
|
26
|
+
function collapseWarnings(warnings) {
|
|
27
|
+
const groups = /* @__PURE__ */ new Map();
|
|
28
|
+
for (const w of warnings) {
|
|
29
|
+
const key = [w.property, w.severity, withoutClientName(w.message, w.client)].join("\0");
|
|
30
|
+
const seen = groups.get(key);
|
|
31
|
+
if (seen) seen.push(w);
|
|
32
|
+
else groups.set(key, [w]);
|
|
33
|
+
}
|
|
34
|
+
return [...groups.values()].map((group) => {
|
|
35
|
+
const first = group[0];
|
|
36
|
+
const clients = [...new Set(group.map((w) => w.client))];
|
|
37
|
+
const locs = mergeLocations(group);
|
|
38
|
+
const suggestions = new Set(group.map((w) => w.suggestion ?? ""));
|
|
39
|
+
const suggestion = suggestions.size === 1 ? first.suggestion : void 0;
|
|
40
|
+
return {
|
|
41
|
+
property: first.property,
|
|
42
|
+
severity: first.severity,
|
|
43
|
+
clients,
|
|
44
|
+
message: withoutClientName(first.message, first.client),
|
|
45
|
+
...suggestion ? { suggestion } : {},
|
|
46
|
+
...first.fixType ? { fixType: first.fixType } : {},
|
|
47
|
+
...group.some((w) => w.fix) ? { hasFix: true } : {},
|
|
48
|
+
...locs.length ? {
|
|
49
|
+
loc: {
|
|
50
|
+
line: locs[0].line,
|
|
51
|
+
column: locs[0].column,
|
|
52
|
+
offset: locs[0].offset,
|
|
53
|
+
length: locs[0].length
|
|
54
|
+
},
|
|
55
|
+
// Lines, not positions, and each line once: two occurrences on
|
|
56
|
+
// one line are two offsets but one place to look.
|
|
57
|
+
...(() => {
|
|
58
|
+
const lines = [...new Set(locs.slice(1).map((l) => l.line))].filter(
|
|
59
|
+
(line) => line !== locs[0].line
|
|
60
|
+
);
|
|
61
|
+
return lines.length ? { alsoAtLines: lines } : {};
|
|
62
|
+
})()
|
|
63
|
+
} : {},
|
|
64
|
+
...group.some((w) => w.locsTruncated) ? { locsTruncated: true } : {}
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function forClients(warnings, clients) {
|
|
69
|
+
if (!clients?.length) return warnings;
|
|
70
|
+
const wanted = new Set(clients);
|
|
71
|
+
return warnings.filter((w) => wanted.has(w.client));
|
|
72
|
+
}
|
|
73
|
+
function knownClientIds() {
|
|
74
|
+
return EMAIL_CLIENTS.map((c) => c.id);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/index.ts
|
|
7
78
|
import {
|
|
8
79
|
createSession,
|
|
9
80
|
analyzeEmail,
|
|
@@ -13,7 +84,7 @@ import {
|
|
|
13
84
|
structuralWarnings,
|
|
14
85
|
diffResults,
|
|
15
86
|
toPlainText,
|
|
16
|
-
EMAIL_CLIENTS,
|
|
87
|
+
EMAIL_CLIENTS as EMAIL_CLIENTS2,
|
|
17
88
|
MAX_HTML_SIZE
|
|
18
89
|
} from "@emailens/engine";
|
|
19
90
|
|
|
@@ -112,6 +183,58 @@ function toFramework(format) {
|
|
|
112
183
|
return void 0;
|
|
113
184
|
}
|
|
114
185
|
var formatEnum = z.enum(["html", "jsx", "mjml", "maizzle"]).optional();
|
|
186
|
+
var detailEnum = z.enum(["summary", "full"]).optional().describe(
|
|
187
|
+
"'summary' (default) returns one entry per problem, listing the clients it affects \u2014 roughly 10x smaller. 'full' returns one entry per client with fix snippets."
|
|
188
|
+
);
|
|
189
|
+
var clientsParam = z.array(z.string()).optional().describe(
|
|
190
|
+
"Only report these client IDs (e.g. ['gmail-web','outlook-windows']). The fastest way to cut the response when you care about specific clients. Use list_clients for the IDs."
|
|
191
|
+
);
|
|
192
|
+
function validateClients(clients) {
|
|
193
|
+
if (!clients?.length) return void 0;
|
|
194
|
+
const known = new Set(knownClientIds());
|
|
195
|
+
const unknown = clients.filter((c) => !known.has(c));
|
|
196
|
+
if (!unknown.length) return void 0;
|
|
197
|
+
return mcpError(
|
|
198
|
+
`Unknown client ${unknown.length > 1 ? "IDs" : "ID"}: ${unknown.join(", ")}. Call list_clients for the valid IDs.`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
function compatibilityPayload(warnings, detail, clients) {
|
|
202
|
+
const scoped = forClients(warnings, clients);
|
|
203
|
+
if (detail === "full") {
|
|
204
|
+
return {
|
|
205
|
+
warningCount: scoped.length,
|
|
206
|
+
warnings: scoped.map((w) => ({
|
|
207
|
+
client: w.client,
|
|
208
|
+
property: w.property,
|
|
209
|
+
severity: w.severity,
|
|
210
|
+
message: w.message,
|
|
211
|
+
suggestion: w.suggestion,
|
|
212
|
+
fix: w.fix,
|
|
213
|
+
fixType: w.fixType,
|
|
214
|
+
...withPositions(w)
|
|
215
|
+
}))
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const findings = collapseWarnings(scoped);
|
|
219
|
+
return {
|
|
220
|
+
warningCount: scoped.length,
|
|
221
|
+
findingCount: findings.length,
|
|
222
|
+
findings
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function positionsApply(format) {
|
|
226
|
+
return (format ?? "html") === "html";
|
|
227
|
+
}
|
|
228
|
+
function withPositions(w) {
|
|
229
|
+
if (!w.loc) return {};
|
|
230
|
+
const { line, column, offset, length } = w.loc;
|
|
231
|
+
const others = (w.locs ?? []).slice(1).map((l) => l.line);
|
|
232
|
+
return {
|
|
233
|
+
loc: { line, column, offset, length },
|
|
234
|
+
...others.length ? { alsoAtLines: others } : {},
|
|
235
|
+
...w.locsTruncated ? { locsTruncated: true } : {}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
115
238
|
function validateHtmlSize(html) {
|
|
116
239
|
if (html.length > MAX_HTML_SIZE) {
|
|
117
240
|
return mcpError(`HTML input exceeds ${Math.round(MAX_HTML_SIZE / 1024)}KB limit. Reduce the email size and try again.`);
|
|
@@ -126,7 +249,7 @@ server.registerTool(
|
|
|
126
249
|
"preview_email",
|
|
127
250
|
{
|
|
128
251
|
title: "Preview Email",
|
|
129
|
-
description:
|
|
252
|
+
description: `Full email compatibility preview \u2014 transforms HTML for ${EMAIL_CLIENTS2.length} email clients (Gmail, Outlook, Apple Mail, Yahoo, Samsung, Thunderbird, HEY, Proton Mail, AOL, Fastmail, Superhuman), analyzes CSS, generates scores, simulates dark mode, checks inbox preview and email size.`,
|
|
130
253
|
inputSchema: {
|
|
131
254
|
html: z.string().describe("The email HTML source code"),
|
|
132
255
|
clients: z.array(z.string()).optional().describe("Optional client ID filter (e.g. ['gmail-web', 'outlook-windows'])"),
|
|
@@ -143,7 +266,7 @@ server.registerTool(
|
|
|
143
266
|
async ({ html, clients, format }) => {
|
|
144
267
|
const sizeError = validateHtmlSize(html);
|
|
145
268
|
if (sizeError) return sizeError;
|
|
146
|
-
const validClientIds = new Set(
|
|
269
|
+
const validClientIds = new Set(EMAIL_CLIENTS2.map((c) => c.id));
|
|
147
270
|
const framework = toFramework(format);
|
|
148
271
|
const session = createSession(html, { framework });
|
|
149
272
|
let transforms;
|
|
@@ -203,10 +326,12 @@ server.registerTool(
|
|
|
203
326
|
"analyze_email",
|
|
204
327
|
{
|
|
205
328
|
title: "Analyze Email",
|
|
206
|
-
description: "Quick CSS compatibility analysis \u2014 returns
|
|
329
|
+
description: "Quick CSS compatibility analysis \u2014 returns per-client scores and one finding per problem, listing the clients it affects. For HTML input each finding carries loc (line, column, offset) and alsoAtLines, so you can edit the exact source. Narrow with clients, or pass detail:'full' for the per-client breakdown with fix snippets. Use audit_email for a full quality report (spam, links, a11y, images, etc.).",
|
|
207
330
|
inputSchema: {
|
|
208
331
|
html: z.string().describe("The email HTML source code"),
|
|
209
|
-
format: formatEnum.describe("Input format for framework-specific fix snippets")
|
|
332
|
+
format: formatEnum.describe("Input format for framework-specific fix snippets"),
|
|
333
|
+
detail: detailEnum,
|
|
334
|
+
clients: clientsParam
|
|
210
335
|
},
|
|
211
336
|
annotations: {
|
|
212
337
|
title: "Analyze Email",
|
|
@@ -216,10 +341,14 @@ server.registerTool(
|
|
|
216
341
|
openWorldHint: false
|
|
217
342
|
}
|
|
218
343
|
},
|
|
219
|
-
async ({ html, format }) => {
|
|
344
|
+
async ({ html, format, detail, clients }) => {
|
|
220
345
|
const sizeError = validateHtmlSize(html);
|
|
221
346
|
if (sizeError) return sizeError;
|
|
222
|
-
const
|
|
347
|
+
const clientError = validateClients(clients);
|
|
348
|
+
if (clientError) return clientError;
|
|
349
|
+
const warnings = analyzeEmail(html, toFramework(format), {
|
|
350
|
+
positions: positionsApply(format)
|
|
351
|
+
});
|
|
223
352
|
const scores = generateCompatibilityScore(warnings);
|
|
224
353
|
const scoreValues = Object.values(scores);
|
|
225
354
|
const overallScore = scoreValues.length > 0 ? Math.round(scoreValues.reduce((a, b) => a + b.score, 0) / scoreValues.length) : 0;
|
|
@@ -231,16 +360,7 @@ server.registerTool(
|
|
|
231
360
|
{
|
|
232
361
|
overallScore,
|
|
233
362
|
scores,
|
|
234
|
-
|
|
235
|
-
warnings: warnings.map((w) => ({
|
|
236
|
-
client: w.client,
|
|
237
|
-
property: w.property,
|
|
238
|
-
severity: w.severity,
|
|
239
|
-
message: w.message,
|
|
240
|
-
suggestion: w.suggestion,
|
|
241
|
-
fix: w.fix,
|
|
242
|
-
fixType: w.fixType
|
|
243
|
-
}))
|
|
363
|
+
...compatibilityPayload(warnings, detail, clients)
|
|
244
364
|
},
|
|
245
365
|
null,
|
|
246
366
|
2
|
|
@@ -254,11 +374,13 @@ server.registerTool(
|
|
|
254
374
|
"audit_email",
|
|
255
375
|
{
|
|
256
376
|
title: "Audit Email",
|
|
257
|
-
description: "Comprehensive email quality audit \u2014 CSS compatibility, spam scoring, link validation, accessibility, images, inbox preview, size (Gmail clipping), and template variables. Use skip to omit
|
|
377
|
+
description: "Comprehensive email quality audit \u2014 CSS compatibility, spam scoring, link validation, accessibility, images, inbox preview, size (Gmail clipping), and template variables. For HTML input, findings tied to a specific element carry loc (line, column, offset) so you can edit the exact source. Compatibility is collapsed to one finding per problem listing the clients it affects; pass detail:'full' for the per-client breakdown with fix snippets. Use skip to omit checks and clients to narrow which clients are reported.",
|
|
258
378
|
inputSchema: {
|
|
259
379
|
html: z.string().describe("The email HTML source code"),
|
|
260
380
|
format: formatEnum.describe("Input format for framework-specific fix snippets"),
|
|
261
|
-
skip: z.array(z.enum(["spam", "links", "accessibility", "images", "compatibility", "inboxPreview", "size", "templateVariables"])).optional().describe("Checks to skip (e.g. ['spam', 'images'])")
|
|
381
|
+
skip: z.array(z.enum(["spam", "links", "accessibility", "images", "compatibility", "inboxPreview", "size", "templateVariables"])).optional().describe("Checks to skip (e.g. ['spam', 'images'])"),
|
|
382
|
+
detail: detailEnum,
|
|
383
|
+
clients: clientsParam
|
|
262
384
|
},
|
|
263
385
|
annotations: {
|
|
264
386
|
title: "Audit Email",
|
|
@@ -268,10 +390,15 @@ server.registerTool(
|
|
|
268
390
|
openWorldHint: false
|
|
269
391
|
}
|
|
270
392
|
},
|
|
271
|
-
async ({ html, format, skip }) => {
|
|
393
|
+
async ({ html, format, skip, detail, clients }) => {
|
|
272
394
|
const sizeError = validateHtmlSize(html);
|
|
273
395
|
if (sizeError) return sizeError;
|
|
274
|
-
const
|
|
396
|
+
const clientError = validateClients(clients);
|
|
397
|
+
if (clientError) return clientError;
|
|
398
|
+
const session = createSession(html, {
|
|
399
|
+
framework: toFramework(format),
|
|
400
|
+
positions: positionsApply(format)
|
|
401
|
+
});
|
|
275
402
|
const report = session.audit({ skip });
|
|
276
403
|
const scoreValues = Object.values(report.compatibility.scores);
|
|
277
404
|
const overallCompatibility = scoreValues.length > 0 ? Math.round(scoreValues.reduce((a, b) => a + b.score, 0) / scoreValues.length) : 0;
|
|
@@ -284,16 +411,7 @@ server.registerTool(
|
|
|
284
411
|
overallCompatibility,
|
|
285
412
|
compatibility: {
|
|
286
413
|
scores: report.compatibility.scores,
|
|
287
|
-
|
|
288
|
-
warnings: report.compatibility.warnings.map((w) => ({
|
|
289
|
-
client: w.client,
|
|
290
|
-
property: w.property,
|
|
291
|
-
severity: w.severity,
|
|
292
|
-
message: w.message,
|
|
293
|
-
suggestion: w.suggestion,
|
|
294
|
-
fix: w.fix,
|
|
295
|
-
fixType: w.fixType
|
|
296
|
-
}))
|
|
414
|
+
...compatibilityPayload(report.compatibility.warnings, detail, clients)
|
|
297
415
|
},
|
|
298
416
|
spam: report.spam,
|
|
299
417
|
links: report.links,
|
|
@@ -301,7 +419,9 @@ server.registerTool(
|
|
|
301
419
|
images: report.images,
|
|
302
420
|
inboxPreview: report.inboxPreview,
|
|
303
421
|
size: report.size,
|
|
304
|
-
templateVariables: report.templateVariables
|
|
422
|
+
templateVariables: report.templateVariables,
|
|
423
|
+
overflow: report.overflow,
|
|
424
|
+
visual: report.visual
|
|
305
425
|
},
|
|
306
426
|
null,
|
|
307
427
|
2
|
|
@@ -394,7 +514,7 @@ server.registerTool(
|
|
|
394
514
|
"list_clients",
|
|
395
515
|
{
|
|
396
516
|
title: "List Email Clients",
|
|
397
|
-
description: "List
|
|
517
|
+
description: "List every supported email client with IDs, names, engines, and dark mode support.",
|
|
398
518
|
annotations: {
|
|
399
519
|
title: "List Email Clients",
|
|
400
520
|
readOnlyHint: true,
|
|
@@ -409,7 +529,7 @@ server.registerTool(
|
|
|
409
529
|
{
|
|
410
530
|
type: "text",
|
|
411
531
|
text: JSON.stringify(
|
|
412
|
-
|
|
532
|
+
EMAIL_CLIENTS2.map((c) => ({
|
|
413
533
|
id: c.id,
|
|
414
534
|
name: c.name,
|
|
415
535
|
category: c.category,
|
|
@@ -539,7 +659,7 @@ server.registerTool(
|
|
|
539
659
|
"capture_screenshots",
|
|
540
660
|
{
|
|
541
661
|
title: "Capture Screenshots",
|
|
542
|
-
description: "Capture real email screenshots across
|
|
662
|
+
description: "Capture real email screenshots across 21 clients (Gmail, Outlook, Apple Mail, etc.) with light and dark mode variants. Screenshots are rendered in real browsers and hosted on CDN. Requires EMAILENS_API_KEY env var \u2014 free plan at emailens.dev?ref=mcp.",
|
|
543
663
|
inputSchema: {
|
|
544
664
|
html: z.string().describe("The email HTML source code"),
|
|
545
665
|
format: formatEnum.describe("Input format"),
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/hosted.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { z } from \"zod\";\nimport {\n createSession,\n analyzeEmail,\n generateCompatibilityScore,\n generateFixPrompt,\n estimateAiFixTokens,\n structuralWarnings,\n diffResults,\n toPlainText,\n EMAIL_CLIENTS,\n MAX_HTML_SIZE,\n type Framework,\n type CSSWarning,\n} from \"@emailens/engine\";\n// Lazy-imported: @emailens/engine/server may not exist in all engine versions\nlet _checkDeliverability: ((domain: string) => Promise<unknown>) | null = null;\nimport { config } from \"./config.js\";\nimport { apiRequest, mcpError, noApiKeyError } from \"./hosted.js\";\n\nfunction toFramework(format?: string): Framework | undefined {\n if (format === \"jsx\" || format === \"mjml\" || format === \"maizzle\") return format;\n return undefined;\n}\n\nconst formatEnum = z.enum([\"html\", \"jsx\", \"mjml\", \"maizzle\"]).optional();\n\nfunction validateHtmlSize(html: string) {\n if (html.length > MAX_HTML_SIZE) {\n return mcpError(`HTML input exceeds ${Math.round(MAX_HTML_SIZE / 1024)}KB limit. Reduce the email size and try again.`);\n }\n return null;\n}\n\nconst server = new McpServer({\n name: \"emailens\",\n version: \"0.4.0\",\n});\n\n// ── Local Tool: preview_email ──────────────────────────────────────\n\nserver.registerTool(\n \"preview_email\",\n {\n title: \"Preview Email\",\n description:\n \"Full email compatibility preview — transforms HTML for 15 email clients (Gmail, Outlook, Apple Mail, Yahoo, Samsung, Thunderbird, HEY, Superhuman), analyzes CSS, generates scores, simulates dark mode, checks inbox preview and email size.\",\n inputSchema: {\n html: z.string().describe(\"The email HTML source code\"),\n clients: z\n .array(z.string())\n .optional()\n .describe(\"Optional client ID filter (e.g. ['gmail-web', 'outlook-windows'])\"),\n format: formatEnum.describe(\"Input format: 'html' (default), 'jsx', 'mjml', or 'maizzle'\"),\n },\n annotations: {\n title: \"Preview Email\",\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async ({ html, clients, format }) => {\n const sizeError = validateHtmlSize(html);\n if (sizeError) return sizeError;\n\n const validClientIds = new Set(EMAIL_CLIENTS.map((c) => c.id));\n const framework = toFramework(format);\n const session = createSession(html, { framework });\n\n let transforms;\n if (clients) {\n const filter = clients.filter((c) => validClientIds.has(c));\n if (filter.length === 0) {\n return mcpError(\n JSON.stringify({ error: \"No valid client IDs provided\", validClientIds: Array.from(validClientIds) }, null, 2),\n );\n }\n transforms = filter.map((c) => session.transformForClient(c));\n } else {\n transforms = session.transformForAllClients();\n }\n\n const warnings = session.analyze();\n const scores = session.score(warnings);\n const inboxPreview = session.extractInboxPreview();\n const sizeReport = session.checkSize();\n\n const darkMode: Record<string, { html: string; warnings: CSSWarning[] }> = {};\n for (const t of transforms) {\n darkMode[t.clientId] = session.simulateDarkMode(t.clientId);\n }\n\n const scoreValues = Object.values(scores);\n const overallScore =\n scoreValues.length > 0\n ? Math.round(scoreValues.reduce((a, b) => a + b.score, 0) / scoreValues.length)\n : 0;\n\n const plainText = toPlainText(html);\n\n const result: Record<string, unknown> = {\n overallScore,\n compatibilityScores: scores,\n cssWarnings: warnings.map((w) => ({\n client: w.client,\n property: w.property,\n severity: w.severity,\n message: w.message,\n suggestion: w.suggestion,\n fix: w.fix,\n fixType: w.fixType,\n })),\n inboxPreview,\n sizeReport,\n ...(plainText ? { plainText } : {}),\n clientCount: transforms.length,\n darkModeWarnings: Object.entries(darkMode).reduce(\n (acc, [clientId, dm]) => {\n if (dm.warnings.length > 0) acc[clientId] = dm.warnings.length;\n return acc;\n },\n {} as Record<string, number>,\n ),\n };\n\n // Contextual tip: only when no API key and score indicates issues\n if (!config.isHosted && overallScore < 90) {\n result.tip =\n \"Capture real screenshots to verify these issues across clients. Add EMAILENS_API_KEY to your MCP config \\u2192 emailens.dev/settings/api-keys?ref=mcp\";\n }\n\n return { content: [{ type: \"text\" as const, text: JSON.stringify(result, null, 2) }] };\n },\n);\n\n// ── Local Tool: analyze_email (CSS-only, reverted from audit) ──────\n\nserver.registerTool(\n \"analyze_email\",\n {\n title: \"Analyze Email\",\n description:\n \"Quick CSS compatibility analysis — returns warnings and per-client scores. Use audit_email for full quality report (spam, links, a11y, images, etc.).\",\n inputSchema: {\n html: z.string().describe(\"The email HTML source code\"),\n format: formatEnum.describe(\"Input format for framework-specific fix snippets\"),\n },\n annotations: {\n title: \"Analyze Email\",\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async ({ html, format }) => {\n const sizeError = validateHtmlSize(html);\n if (sizeError) return sizeError;\n\n const warnings = analyzeEmail(html, toFramework(format));\n const scores = generateCompatibilityScore(warnings);\n\n const scoreValues = Object.values(scores);\n const overallScore =\n scoreValues.length > 0\n ? Math.round(scoreValues.reduce((a, b) => a + b.score, 0) / scoreValues.length)\n : 0;\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(\n {\n overallScore,\n scores,\n warningCount: warnings.length,\n warnings: warnings.map((w) => ({\n client: w.client,\n property: w.property,\n severity: w.severity,\n message: w.message,\n suggestion: w.suggestion,\n fix: w.fix,\n fixType: w.fixType,\n })),\n },\n null,\n 2,\n ),\n },\n ],\n };\n },\n);\n\n// ── Local Tool: audit_email ────────────────────────────────────────\n\nserver.registerTool(\n \"audit_email\",\n {\n title: \"Audit Email\",\n description:\n \"Comprehensive email quality audit — CSS compatibility, spam scoring, link validation, accessibility, images, inbox preview, size (Gmail clipping), and template variables. Use skip to omit specific checks.\",\n inputSchema: {\n html: z.string().describe(\"The email HTML source code\"),\n format: formatEnum.describe(\"Input format for framework-specific fix snippets\"),\n skip: z\n .array(z.enum([\"spam\", \"links\", \"accessibility\", \"images\", \"compatibility\", \"inboxPreview\", \"size\", \"templateVariables\"]))\n .optional()\n .describe(\"Checks to skip (e.g. ['spam', 'images'])\"),\n },\n annotations: {\n title: \"Audit Email\",\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async ({ html, format, skip }) => {\n const sizeError = validateHtmlSize(html);\n if (sizeError) return sizeError;\n\n const session = createSession(html, { framework: toFramework(format) });\n const report = session.audit({ skip });\n\n const scoreValues = Object.values(report.compatibility.scores);\n const overallCompatibility =\n scoreValues.length > 0\n ? Math.round(scoreValues.reduce((a, b) => a + b.score, 0) / scoreValues.length)\n : 0;\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(\n {\n overallCompatibility,\n compatibility: {\n scores: report.compatibility.scores,\n warningCount: report.compatibility.warnings.length,\n warnings: report.compatibility.warnings.map((w) => ({\n client: w.client,\n property: w.property,\n severity: w.severity,\n message: w.message,\n suggestion: w.suggestion,\n fix: w.fix,\n fixType: w.fixType,\n })),\n },\n spam: report.spam,\n links: report.links,\n accessibility: report.accessibility,\n images: report.images,\n inboxPreview: report.inboxPreview,\n size: report.size,\n templateVariables: report.templateVariables,\n },\n null,\n 2,\n ),\n },\n ],\n };\n },\n);\n\n// ── Local Tool: fix_email ──────────────────────────────────────────\n\nserver.registerTool(\n \"fix_email\",\n {\n title: \"Fix Email\",\n description:\n \"Generate a structured fix prompt for email compatibility issues. Returns markdown with the original code, detected issues (CSS or structural), fix snippets, and format-specific instructions. Use after preview_email or analyze_email.\",\n inputSchema: {\n html: z.string().describe(\"The email HTML source code to fix\"),\n format: formatEnum.describe(\"Input format — controls fix syntax\"),\n scope: z.enum([\"all\", \"current\"]).optional().describe(\"'all' (default) or 'current' (requires selectedClientId)\"),\n selectedClientId: z.string().optional().describe(\"Client ID to scope fixes to (e.g. 'outlook-windows')\"),\n },\n annotations: {\n title: \"Fix Email\",\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async ({ html, format, scope, selectedClientId }) => {\n const sizeError = validateHtmlSize(html);\n if (sizeError) return sizeError;\n\n const framework = toFramework(format);\n const fixScope = scope === \"current\" ? \"current\" : \"all\";\n const inputFormat = format || \"html\";\n\n const warnings = analyzeEmail(html, framework);\n const scores = generateCompatibilityScore(warnings);\n\n if (warnings.length === 0) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({ message: \"No compatibility issues found \\u2014 nothing to fix.\", overallScore: 100 }, null, 2),\n },\n ],\n };\n }\n\n const estimate = await estimateAiFixTokens({\n originalHtml: html,\n warnings,\n scores,\n scope: fixScope,\n selectedClientId,\n format: inputFormat,\n });\n\n const prompt = generateFixPrompt({\n originalHtml: html,\n warnings,\n scores,\n scope: fixScope,\n selectedClientId,\n format: inputFormat,\n });\n\n const structural = structuralWarnings(warnings);\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(\n {\n totalWarnings: warnings.length,\n structuralWarnings: structural.length,\n cssWarnings: warnings.length - structural.length,\n tokenEstimate: {\n inputTokens: estimate.inputTokens,\n estimatedOutputTokens: estimate.estimatedOutputTokens,\n truncated: estimate.truncated,\n },\n note: \"Apply the fixes in the prompt below. Structural issues require HTML restructuring (tables, VML conditionals), not just CSS changes.\",\n },\n null,\n 2,\n ),\n },\n { type: \"text\" as const, text: prompt },\n ],\n };\n },\n);\n\n// ── Local Tool: list_clients ───────────────────────────────────────\n\nserver.registerTool(\n \"list_clients\",\n {\n title: \"List Email Clients\",\n description: \"List all 15 supported email clients with IDs, names, engines, and dark mode support.\",\n annotations: {\n title: \"List Email Clients\",\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async () => {\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(\n EMAIL_CLIENTS.map((c) => ({\n id: c.id,\n name: c.name,\n category: c.category,\n engine: c.engine,\n darkModeSupport: c.darkModeSupport,\n ...(c.deprecated ? { deprecated: c.deprecated } : {}),\n })),\n null,\n 2,\n ),\n },\n ],\n };\n },\n);\n\n// ── Local Tool: diff_emails ────────────────────────────────────────\n\nserver.registerTool(\n \"diff_emails\",\n {\n title: \"Diff Emails\",\n description:\n \"Compare two email HTML versions — shows score changes, fixed issues, and newly introduced issues per client. Use after making fixes to verify improvements.\",\n inputSchema: {\n before: z.string().describe(\"Original email HTML\"),\n after: z.string().describe(\"Modified email HTML\"),\n format: formatEnum.describe(\"Input format for framework-specific analysis\"),\n },\n annotations: {\n title: \"Diff Emails\",\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n },\n async ({ before, after, format }) => {\n const beforeSizeError = validateHtmlSize(before);\n if (beforeSizeError) return beforeSizeError;\n const afterSizeError = validateHtmlSize(after);\n if (afterSizeError) return afterSizeError;\n\n const framework = toFramework(format);\n\n const beforeWarnings = analyzeEmail(before, framework);\n const beforeScores = generateCompatibilityScore(beforeWarnings);\n\n const afterWarnings = analyzeEmail(after, framework);\n const afterScores = generateCompatibilityScore(afterWarnings);\n\n const results = diffResults(\n { scores: beforeScores, warnings: beforeWarnings },\n { scores: afterScores, warnings: afterWarnings },\n );\n\n let clientsImproved = 0;\n let clientsRegressed = 0;\n let clientsUnchanged = 0;\n let totalDelta = 0;\n\n const mappedResults = results.map((r) => {\n if (r.scoreDelta > 0) clientsImproved++;\n else if (r.scoreDelta < 0) clientsRegressed++;\n else clientsUnchanged++;\n totalDelta += r.scoreDelta;\n\n return {\n clientId: r.clientId,\n scoreBefore: r.scoreBefore,\n scoreAfter: r.scoreAfter,\n scoreDelta: r.scoreDelta,\n fixed: r.fixed.map((w) => ({ property: w.property, client: w.client, message: w.message })),\n introduced: r.introduced.map((w) => ({ property: w.property, client: w.client, message: w.message })),\n unchanged: r.unchanged.length,\n };\n });\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(\n {\n results: mappedResults,\n summary: {\n clientsImproved,\n clientsRegressed,\n clientsUnchanged,\n avgScoreDelta: results.length > 0 ? Math.round((totalDelta / results.length) * 10) / 10 : 0,\n },\n },\n null,\n 2,\n ),\n },\n ],\n };\n },\n);\n\n// ── Local Tool: check_deliverability ───────────────────────────────\n\nserver.registerTool(\n \"check_deliverability\",\n {\n title: \"Check Deliverability\",\n description:\n \"Check email deliverability for a domain — SPF, DKIM, DMARC, MX, and BIMI records. Returns a score (0-100) and actionable issues. Uses DNS lookups (no API key needed).\",\n inputSchema: {\n domain: z\n .string()\n .min(3)\n .regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i, \"Invalid domain format\")\n .describe(\"Domain to check (e.g. 'company.com')\"),\n },\n annotations: {\n title: \"Check Deliverability\",\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async ({ domain }) => {\n try {\n if (!_checkDeliverability) {\n const mod = await import(\"@emailens/engine/server\");\n _checkDeliverability = mod.checkDeliverability;\n }\n const report = await _checkDeliverability!(domain);\n return {\n content: [{ type: \"text\" as const, text: JSON.stringify(report, null, 2) }],\n };\n } catch (err) {\n const msg = (err as Error).message ?? String(err);\n if (msg.includes(\"Cannot find\") || msg.includes(\"cannot find\") || msg.includes(\"MODULE_NOT_FOUND\")) {\n return mcpError(\n \"check_deliverability requires @emailens/engine >=0.9.1 with the /server subpath. Update with: npm install @emailens/engine@latest\",\n );\n }\n return mcpError(`Deliverability check failed for \"${domain}\": ${msg}`);\n }\n },\n);\n\n// ── Hosted Tool: capture_screenshots ───────────────────────────────\n\nserver.registerTool(\n \"capture_screenshots\",\n {\n title: \"Capture Screenshots\",\n description:\n \"Capture real email screenshots across 15 clients (Gmail, Outlook, Apple Mail, etc.) with light and dark mode variants. Screenshots are rendered in real browsers and hosted on CDN. Requires EMAILENS_API_KEY env var \\u2014 free plan at emailens.dev?ref=mcp.\",\n inputSchema: {\n html: z.string().describe(\"The email HTML source code\"),\n format: formatEnum.describe(\"Input format\"),\n clients: z.array(z.string()).optional().describe(\"Filter to specific client IDs\"),\n modes: z\n .array(z.enum([\"light\", \"dark\"]))\n .optional()\n .describe(\"Screenshot modes (default: ['light'])\"),\n title: z.string().optional().describe(\"Name for the hosted preview\"),\n },\n annotations: {\n title: \"Capture Screenshots\",\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n },\n async ({ html, format, clients, modes, title }) => {\n if (!config.isHosted) {\n return noApiKeyError(\"capture_screenshots\");\n }\n\n const opts = { apiKey: config.apiKey!, apiUrl: config.apiUrl };\n\n // Step 1: Create hosted preview\n const previewResult = await apiRequest<{\n id: string;\n overallScore: number;\n }>(\"/api/preview\", {\n ...opts,\n body: { html, format, clients, title },\n timeoutMs: 30_000,\n });\n\n if (previewResult.isError) {\n return mcpError(previewResult.error!);\n }\n\n const previewId = previewResult.data!.id;\n\n // Step 2: Capture screenshots (with one retry on timeout)\n let screenshotResult = await apiRequest<{\n screenshots: Record<string, string>;\n screenshotStatus: string;\n captured: string[];\n failed: string[];\n }>(`/api/preview/${previewId}/screenshots`, {\n ...opts,\n body: { clients, modes },\n timeoutMs: 120_000,\n });\n\n // Retry once on timeout\n if (screenshotResult.isError && screenshotResult.error?.includes(\"timed out\")) {\n screenshotResult = await apiRequest(`/api/preview/${previewId}/screenshots`, {\n ...opts,\n body: { clients, modes },\n timeoutMs: 120_000,\n });\n }\n\n // If screenshots still failed, return preview analysis with error note\n if (screenshotResult.isError) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(\n {\n previewId,\n previewUrl: `${config.apiUrl}/preview/${previewId}`,\n overallScore: previewResult.data!.overallScore,\n screenshots: {},\n screenshotStatus: \"failed\",\n captured: [],\n failed: [],\n note: `Preview created but screenshot capture failed: ${screenshotResult.error}. View the full preview at the URL above.`,\n },\n null,\n 2,\n ),\n },\n ],\n };\n }\n\n const ss = screenshotResult.data!;\n const result: Record<string, unknown> = {\n previewId,\n previewUrl: `${config.apiUrl}/preview/${previewId}`,\n overallScore: previewResult.data!.overallScore,\n screenshots: ss.screenshots,\n screenshotStatus: ss.screenshotStatus,\n captured: ss.captured,\n failed: ss.failed,\n };\n\n if (ss.failed?.length > 0) {\n result.note = `${ss.failed.length} of ${(ss.captured?.length ?? 0) + ss.failed.length} screenshots failed: ${ss.failed.join(\", \")}`;\n }\n\n return { content: [{ type: \"text\" as const, text: JSON.stringify(result, null, 2) }] };\n },\n);\n\n// ── Hosted Tool: share_preview ─────────────────────────────────────\n\nserver.registerTool(\n \"share_preview\",\n {\n title: \"Share Preview\",\n description:\n \"Create a shareable link for an email preview. Recipients see the full analysis without needing an account. Requires EMAILENS_API_KEY env var and Dev plan ($9/mo).\",\n inputSchema: {\n html: z.string().describe(\"The email HTML source code\"),\n title: z.string().optional().describe(\"Display title for the share page\"),\n format: formatEnum.describe(\"Input format\"),\n },\n annotations: {\n title: \"Share Preview\",\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n },\n async ({ html, title, format }) => {\n if (!config.isHosted) {\n return noApiKeyError(\"share_preview\", \"Shareable links require the Dev plan ($9/mo).\");\n }\n\n const result = await apiRequest<{ id: string; expiresAt: string | null }>(\"/api/share\", {\n apiKey: config.apiKey!,\n apiUrl: config.apiUrl,\n body: { html, title, format },\n });\n\n if (result.isError) {\n return mcpError(result.error!);\n }\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(\n {\n shareUrl: `${config.apiUrl}/share/${result.data!.id}`,\n expiresAt: result.data!.expiresAt,\n },\n null,\n 2,\n ),\n },\n ],\n };\n },\n);\n\n// ── Start ────────────────────────────────────────────────────────────\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n","export const config = {\n get apiKey(): string | null {\n return process.env.EMAILENS_API_KEY ?? null;\n },\n get apiUrl(): string {\n return process.env.EMAILENS_API_URL ?? \"https://emailens.dev\";\n },\n get isHosted(): boolean {\n return this.apiKey !== null;\n },\n};\n","export interface ApiRequestOptions {\n apiKey: string;\n apiUrl: string;\n method?: \"GET\" | \"POST\" | \"PATCH\" | \"DELETE\";\n body?: unknown;\n timeoutMs?: number;\n}\n\nexport interface ApiResult<T = unknown> {\n data?: T;\n isError?: true;\n error?: string;\n}\n\nexport async function apiRequest<T = unknown>(\n path: string,\n options: ApiRequestOptions,\n): Promise<ApiResult<T>> {\n const { apiKey, apiUrl, method = \"GET\", body, timeoutMs = 30_000 } = options;\n const url = `${apiUrl}${path}`;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, {\n method: method !== \"GET\" ? method : body ? \"POST\" : \"GET\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n if (response.status === 401) {\n return {\n isError: true,\n error: \"Invalid or revoked API key. Generate a new one at emailens.dev/settings/api-keys?ref=mcp\",\n };\n }\n\n if (response.status === 403) {\n const data = (await response.json().catch(() => ({}))) as Record<string, unknown>;\n return {\n isError: true,\n error: data.upgrade\n ? \"This feature requires a plan upgrade. Visit emailens.dev/pricing?ref=mcp\"\n : \"Access denied. Check your plan at emailens.dev/pricing?ref=mcp\",\n };\n }\n\n if (response.status === 429) {\n const data = (await response.json().catch(() => ({}))) as Record<string, number>;\n return {\n isError: true,\n error: `Daily quota exceeded${data.limit ? ` (${data.limit - (data.remaining ?? 0)}/${data.limit})` : \"\"}. Resets tomorrow. Upgrade at emailens.dev/pricing?ref=mcp`,\n };\n }\n\n if (!response.ok) {\n const data = (await response.json().catch(() => ({}))) as Record<string, unknown>;\n return { isError: true, error: (data.error as string) ?? `API error: ${response.status}` };\n }\n\n const data = (await response.json()) as T;\n return { data };\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n return { isError: true, error: `Request timed out after ${timeoutMs / 1000}s. Try again or check emailens.dev status.` };\n }\n return { isError: true, error: `Network error: ${(err as Error).message}` };\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport function mcpError(message: string) {\n return {\n content: [{ type: \"text\" as const, text: message }],\n isError: true,\n };\n}\n\nexport function noApiKeyError(toolName: string, extraInfo?: string) {\n const lines = [\n `${toolName} requires an Emailens API key.`,\n \"\",\n \"1. Sign up free at emailens.dev?ref=mcp\",\n \"2. Create a key at emailens.dev/settings/api-keys?ref=mcp\",\n \"3. Add to your MCP config:\",\n \"\",\n ' \"env\": { \"EMAILENS_API_KEY\": \"ek_live_...\" }',\n ];\n if (extraInfo) {\n lines.push(\"\", extraInfo);\n } else {\n lines.push(\"\", \"Free plan includes 30 previews/day (each preview can include screenshots).\");\n }\n return mcpError(lines.join(\"\\n\"));\n}\n"],"mappings":"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","names":["data"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/findings.ts","../src/config.ts","../src/hosted.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\r\nimport { z } from \"zod\";\r\nimport { collapseWarnings, forClients, knownClientIds } from \"./findings.js\";\r\nimport {\r\n createSession,\r\n analyzeEmail,\r\n generateCompatibilityScore,\r\n generateFixPrompt,\r\n estimateAiFixTokens,\r\n structuralWarnings,\r\n diffResults,\r\n toPlainText,\r\n EMAIL_CLIENTS,\r\n MAX_HTML_SIZE,\r\n type Framework,\r\n type CSSWarning,\r\n type SourceLocation,\r\n} from \"@emailens/engine\";\r\n// Lazy-imported: @emailens/engine/server may not exist in all engine versions\r\nlet _checkDeliverability: ((domain: string) => Promise<unknown>) | null = null;\r\nimport { config } from \"./config.js\";\r\nimport { apiRequest, mcpError, noApiKeyError } from \"./hosted.js\";\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 formatEnum = z.enum([\"html\", \"jsx\", \"mjml\", \"maizzle\"]).optional();\r\n\r\n/**\r\n * How much compatibility detail to return.\r\n *\r\n * `summary` collapses the engine's per-client, per-selector warnings into one\r\n * entry per problem listing the clients it affects — the same information at a\r\n * fraction of the tokens. `full` is the engine's own shape, per client, with\r\n * fix snippets, for a caller that wants everything.\r\n */\r\nconst detailEnum = z\r\n .enum([\"summary\", \"full\"])\r\n .optional()\r\n .describe(\r\n \"'summary' (default) returns one entry per problem, listing the clients it affects — roughly 10x smaller. 'full' returns one entry per client with fix snippets.\",\r\n );\r\n\r\nconst clientsParam = z\r\n .array(z.string())\r\n .optional()\r\n .describe(\r\n \"Only report these client IDs (e.g. ['gmail-web','outlook-windows']). The fastest way to cut the response when you care about specific clients. Use list_clients for the IDs.\",\r\n );\r\n\r\n/**\r\n * Reject a client id the engine does not know.\r\n *\r\n * Filtering to a typo would return an empty list, which reads as \"this email\r\n * is fine for that client\" — the most misleading answer the tool can give.\r\n */\r\nfunction validateClients(clients: string[] | undefined) {\r\n if (!clients?.length) return undefined;\r\n const known = new Set(knownClientIds());\r\n const unknown = clients.filter((c) => !known.has(c));\r\n if (!unknown.length) return undefined;\r\n return mcpError(\r\n `Unknown client ${unknown.length > 1 ? \"IDs\" : \"ID\"}: ${unknown.join(\", \")}. Call list_clients for the valid IDs.`,\r\n );\r\n}\r\n\r\n/** The compatibility payload, at the requested level of detail. */\r\nfunction compatibilityPayload(\r\n warnings: CSSWarning[],\r\n detail: \"summary\" | \"full\" | undefined,\r\n clients: string[] | undefined,\r\n) {\r\n const scoped = forClients(warnings, clients);\r\n if (detail === \"full\") {\r\n return {\r\n warningCount: scoped.length,\r\n warnings: scoped.map((w) => ({\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 fixType: w.fixType,\r\n ...withPositions(w),\r\n })),\r\n };\r\n }\r\n const findings = collapseWarnings(scoped);\r\n return {\r\n warningCount: scoped.length,\r\n findingCount: findings.length,\r\n findings,\r\n };\r\n}\r\n\r\n/**\r\n * Do source positions refer to the code the caller handed us?\r\n *\r\n * Only for plain HTML. JSX, MJML and Maizzle are compiled before analysis, so a\r\n * position would point into generated output — worse than none, because an\r\n * agent would edit the wrong line with confidence.\r\n */\r\nfunction positionsApply(format?: string): boolean {\r\n return (format ?? \"html\") === \"html\";\r\n}\r\n\r\n/**\r\n * The position fields a finding carries, when it has them.\r\n *\r\n * Deliberately compact: this response is read by a model paying for every\r\n * token, and a newsletter can produce a thousand occurrences. The first one\r\n * gets a full position — enough to edit it — and the rest are reduced to line\r\n * numbers, which is all an assistant needs to go find them. Carrying every\r\n * occurrence in full nearly doubled the payload on a real fixture.\r\n */\r\nfunction withPositions<T extends {\r\n loc?: SourceLocation;\r\n locs?: SourceLocation[];\r\n locsTruncated?: boolean;\r\n}>(w: T) {\r\n if (!w.loc) return {};\r\n const { line, column, offset, length } = w.loc;\r\n const others = (w.locs ?? []).slice(1).map((l) => l.line);\r\n return {\r\n loc: { line, column, offset, length },\r\n ...(others.length ? { alsoAtLines: others } : {}),\r\n ...(w.locsTruncated ? { locsTruncated: true } : {}),\r\n };\r\n}\r\n\r\nfunction validateHtmlSize(html: string) {\r\n if (html.length > MAX_HTML_SIZE) {\r\n return mcpError(`HTML input exceeds ${Math.round(MAX_HTML_SIZE / 1024)}KB limit. Reduce the email size and try again.`);\r\n }\r\n return null;\r\n}\r\n\r\nconst server = new McpServer({\r\n name: \"emailens\",\r\n version: \"0.4.0\",\r\n});\r\n\r\n// ── Local Tool: preview_email ──────────────────────────────────────\r\n\r\nserver.registerTool(\r\n \"preview_email\",\r\n {\r\n title: \"Preview Email\",\r\n description:\r\n `Full email compatibility preview — transforms HTML for ${EMAIL_CLIENTS.length} email clients (Gmail, Outlook, Apple Mail, Yahoo, Samsung, Thunderbird, HEY, Proton Mail, AOL, Fastmail, Superhuman), analyzes CSS, generates scores, simulates dark mode, checks inbox preview and email size.`,\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(\"Optional client ID filter (e.g. ['gmail-web', 'outlook-windows'])\"),\r\n format: formatEnum.describe(\"Input format: 'html' (default), 'jsx', 'mjml', or 'maizzle'\"),\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 sizeError = validateHtmlSize(html);\r\n if (sizeError) return sizeError;\r\n\r\n const validClientIds = new Set(EMAIL_CLIENTS.map((c) => c.id));\r\n const framework = toFramework(format);\r\n const session = createSession(html, { framework });\r\n\r\n let transforms;\r\n if (clients) {\r\n const filter = clients.filter((c) => validClientIds.has(c));\r\n if (filter.length === 0) {\r\n return mcpError(\r\n JSON.stringify({ error: \"No valid client IDs provided\", validClientIds: Array.from(validClientIds) }, null, 2),\r\n );\r\n }\r\n transforms = filter.map((c) => session.transformForClient(c));\r\n } else {\r\n transforms = session.transformForAllClients();\r\n }\r\n\r\n const warnings = session.analyze();\r\n const scores = session.score(warnings);\r\n const inboxPreview = session.extractInboxPreview();\r\n const sizeReport = session.checkSize();\r\n\r\n const darkMode: Record<string, { html: string; warnings: CSSWarning[] }> = {};\r\n for (const t of transforms) {\r\n darkMode[t.clientId] = session.simulateDarkMode(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(scoreValues.reduce((a, b) => a + b.score, 0) / scoreValues.length)\r\n : 0;\r\n\r\n const plainText = toPlainText(html);\r\n\r\n const result: Record<string, unknown> = {\r\n overallScore,\r\n compatibilityScores: scores,\r\n cssWarnings: warnings.map((w) => ({\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 fixType: w.fixType,\r\n })),\r\n inboxPreview,\r\n sizeReport,\r\n ...(plainText ? { plainText } : {}),\r\n clientCount: transforms.length,\r\n darkModeWarnings: Object.entries(darkMode).reduce(\r\n (acc, [clientId, dm]) => {\r\n if (dm.warnings.length > 0) acc[clientId] = dm.warnings.length;\r\n return acc;\r\n },\r\n {} as Record<string, number>,\r\n ),\r\n };\r\n\r\n // Contextual tip: only when no API key and score indicates issues\r\n if (!config.isHosted && overallScore < 90) {\r\n result.tip =\r\n \"Capture real screenshots to verify these issues across clients. Add EMAILENS_API_KEY to your MCP config \\u2192 emailens.dev/settings/api-keys?ref=mcp\";\r\n }\r\n\r\n return { content: [{ type: \"text\" as const, text: JSON.stringify(result, null, 2) }] };\r\n },\r\n);\r\n\r\n// ── Local Tool: analyze_email (CSS-only, reverted from audit) ──────\r\n\r\nserver.registerTool(\r\n \"analyze_email\",\r\n {\r\n title: \"Analyze Email\",\r\n description:\r\n \"Quick CSS compatibility analysis — returns per-client scores and one finding per problem, listing the clients it affects. For HTML input each finding carries loc (line, column, offset) and alsoAtLines, so you can edit the exact source. Narrow with clients, or pass detail:'full' for the per-client breakdown with fix snippets. Use audit_email for a full quality report (spam, links, a11y, images, etc.).\",\r\n inputSchema: {\r\n html: z.string().describe(\"The email HTML source code\"),\r\n format: formatEnum.describe(\"Input format for framework-specific fix snippets\"),\r\n detail: detailEnum,\r\n clients: clientsParam,\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, detail, clients }) => {\r\n const sizeError = validateHtmlSize(html);\r\n if (sizeError) return sizeError;\r\n const clientError = validateClients(clients);\r\n if (clientError) return clientError;\r\n\r\n const warnings = analyzeEmail(html, toFramework(format), {\r\n positions: positionsApply(format),\r\n });\r\n // Scores stay whole-email: filtering to two clients should narrow what is\r\n // reported, not silently change what the email scores.\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(scoreValues.reduce((a, b) => a + b.score, 0) / scoreValues.length)\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 ...compatibilityPayload(warnings, detail, clients),\r\n },\r\n null,\r\n 2,\r\n ),\r\n },\r\n ],\r\n };\r\n },\r\n);\r\n\r\n// ── Local Tool: audit_email ────────────────────────────────────────\r\n\r\nserver.registerTool(\r\n \"audit_email\",\r\n {\r\n title: \"Audit Email\",\r\n description:\r\n \"Comprehensive email quality audit — CSS compatibility, spam scoring, link validation, accessibility, images, inbox preview, size (Gmail clipping), and template variables. For HTML input, findings tied to a specific element carry loc (line, column, offset) so you can edit the exact source. Compatibility is collapsed to one finding per problem listing the clients it affects; pass detail:'full' for the per-client breakdown with fix snippets. Use skip to omit checks and clients to narrow which clients are reported.\",\r\n inputSchema: {\r\n html: z.string().describe(\"The email HTML source code\"),\r\n format: formatEnum.describe(\"Input format for framework-specific fix snippets\"),\r\n skip: z\r\n .array(z.enum([\"spam\", \"links\", \"accessibility\", \"images\", \"compatibility\", \"inboxPreview\", \"size\", \"templateVariables\"]))\r\n .optional()\r\n .describe(\"Checks to skip (e.g. ['spam', 'images'])\"),\r\n detail: detailEnum,\r\n clients: clientsParam,\r\n },\r\n annotations: {\r\n title: \"Audit 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, skip, detail, clients }) => {\r\n const sizeError = validateHtmlSize(html);\r\n if (sizeError) return sizeError;\r\n const clientError = validateClients(clients);\r\n if (clientError) return clientError;\r\n\r\n const session = createSession(html, {\r\n framework: toFramework(format),\r\n positions: positionsApply(format),\r\n });\r\n const report = session.audit({ skip });\r\n\r\n const scoreValues = Object.values(report.compatibility.scores);\r\n const overallCompatibility =\r\n scoreValues.length > 0\r\n ? Math.round(scoreValues.reduce((a, b) => a + b.score, 0) / scoreValues.length)\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 overallCompatibility,\r\n compatibility: {\r\n scores: report.compatibility.scores,\r\n ...compatibilityPayload(report.compatibility.warnings, detail, clients),\r\n },\r\n spam: report.spam,\r\n links: report.links,\r\n accessibility: report.accessibility,\r\n images: report.images,\r\n inboxPreview: report.inboxPreview,\r\n size: report.size,\r\n templateVariables: report.templateVariables,\r\n overflow: report.overflow,\r\n visual: report.visual,\r\n },\r\n null,\r\n 2,\r\n ),\r\n },\r\n ],\r\n };\r\n },\r\n);\r\n\r\n// ── Local Tool: fix_email ──────────────────────────────────────────\r\n\r\nserver.registerTool(\r\n \"fix_email\",\r\n {\r\n title: \"Fix Email\",\r\n description:\r\n \"Generate a structured fix prompt for email compatibility issues. Returns markdown with the original code, detected issues (CSS or structural), fix snippets, and format-specific instructions. Use after preview_email or analyze_email.\",\r\n inputSchema: {\r\n html: z.string().describe(\"The email HTML source code to fix\"),\r\n format: formatEnum.describe(\"Input format — controls fix syntax\"),\r\n scope: z.enum([\"all\", \"current\"]).optional().describe(\"'all' (default) or 'current' (requires selectedClientId)\"),\r\n selectedClientId: z.string().optional().describe(\"Client ID to scope fixes to (e.g. 'outlook-windows')\"),\r\n },\r\n annotations: {\r\n title: \"Fix 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, scope, selectedClientId }) => {\r\n const sizeError = validateHtmlSize(html);\r\n if (sizeError) return sizeError;\r\n\r\n const framework = toFramework(format);\r\n const fixScope = scope === \"current\" ? \"current\" : \"all\";\r\n const inputFormat = format || \"html\";\r\n\r\n const warnings = analyzeEmail(html, framework);\r\n const scores = generateCompatibilityScore(warnings);\r\n\r\n if (warnings.length === 0) {\r\n return {\r\n content: [\r\n {\r\n type: \"text\" as const,\r\n text: JSON.stringify({ message: \"No compatibility issues found \\u2014 nothing to fix.\", overallScore: 100 }, null, 2),\r\n },\r\n ],\r\n };\r\n }\r\n\r\n const estimate = await estimateAiFixTokens({\r\n originalHtml: html,\r\n warnings,\r\n scores,\r\n scope: fixScope,\r\n selectedClientId,\r\n format: inputFormat,\r\n });\r\n\r\n const prompt = generateFixPrompt({\r\n originalHtml: html,\r\n warnings,\r\n scores,\r\n scope: fixScope,\r\n selectedClientId,\r\n format: inputFormat,\r\n });\r\n\r\n const structural = structuralWarnings(warnings);\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 totalWarnings: warnings.length,\r\n structuralWarnings: structural.length,\r\n cssWarnings: warnings.length - structural.length,\r\n tokenEstimate: {\r\n inputTokens: estimate.inputTokens,\r\n estimatedOutputTokens: estimate.estimatedOutputTokens,\r\n truncated: estimate.truncated,\r\n },\r\n note: \"Apply the fixes in the prompt below. Structural issues require HTML restructuring (tables, VML conditionals), not just CSS changes.\",\r\n },\r\n null,\r\n 2,\r\n ),\r\n },\r\n { type: \"text\" as const, text: prompt },\r\n ],\r\n };\r\n },\r\n);\r\n\r\n// ── Local Tool: list_clients ───────────────────────────────────────\r\n\r\nserver.registerTool(\r\n \"list_clients\",\r\n {\r\n title: \"List Email Clients\",\r\n description: \"List every supported email client with IDs, names, 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((c) => ({\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 ...(c.deprecated ? { deprecated: c.deprecated } : {}),\r\n })),\r\n null,\r\n 2,\r\n ),\r\n },\r\n ],\r\n };\r\n },\r\n);\r\n\r\n// ── Local Tool: diff_emails ────────────────────────────────────────\r\n\r\nserver.registerTool(\r\n \"diff_emails\",\r\n {\r\n title: \"Diff Emails\",\r\n description:\r\n \"Compare two email HTML versions — shows score changes, fixed issues, and newly introduced issues per client. Use after making fixes to verify improvements.\",\r\n inputSchema: {\r\n before: z.string().describe(\"Original email HTML\"),\r\n after: z.string().describe(\"Modified email HTML\"),\r\n format: formatEnum.describe(\"Input format for framework-specific analysis\"),\r\n },\r\n annotations: {\r\n title: \"Diff Emails\",\r\n readOnlyHint: true,\r\n destructiveHint: false,\r\n idempotentHint: true,\r\n openWorldHint: false,\r\n },\r\n },\r\n async ({ before, after, format }) => {\r\n const beforeSizeError = validateHtmlSize(before);\r\n if (beforeSizeError) return beforeSizeError;\r\n const afterSizeError = validateHtmlSize(after);\r\n if (afterSizeError) return afterSizeError;\r\n\r\n const framework = toFramework(format);\r\n\r\n const beforeWarnings = analyzeEmail(before, framework);\r\n const beforeScores = generateCompatibilityScore(beforeWarnings);\r\n\r\n const afterWarnings = analyzeEmail(after, framework);\r\n const afterScores = generateCompatibilityScore(afterWarnings);\r\n\r\n const results = diffResults(\r\n { scores: beforeScores, warnings: beforeWarnings },\r\n { scores: afterScores, warnings: afterWarnings },\r\n );\r\n\r\n let clientsImproved = 0;\r\n let clientsRegressed = 0;\r\n let clientsUnchanged = 0;\r\n let totalDelta = 0;\r\n\r\n const mappedResults = results.map((r) => {\r\n if (r.scoreDelta > 0) clientsImproved++;\r\n else if (r.scoreDelta < 0) clientsRegressed++;\r\n else clientsUnchanged++;\r\n totalDelta += r.scoreDelta;\r\n\r\n return {\r\n clientId: r.clientId,\r\n scoreBefore: r.scoreBefore,\r\n scoreAfter: r.scoreAfter,\r\n scoreDelta: r.scoreDelta,\r\n fixed: r.fixed.map((w) => ({ property: w.property, client: w.client, message: w.message })),\r\n introduced: r.introduced.map((w) => ({ property: w.property, client: w.client, message: w.message })),\r\n unchanged: r.unchanged.length,\r\n };\r\n });\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 results: mappedResults,\r\n summary: {\r\n clientsImproved,\r\n clientsRegressed,\r\n clientsUnchanged,\r\n avgScoreDelta: results.length > 0 ? Math.round((totalDelta / results.length) * 10) / 10 : 0,\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// ── Local Tool: check_deliverability ───────────────────────────────\r\n\r\nserver.registerTool(\r\n \"check_deliverability\",\r\n {\r\n title: \"Check Deliverability\",\r\n description:\r\n \"Check email deliverability for a domain — SPF, DKIM, DMARC, MX, and BIMI records. Returns a score (0-100) and actionable issues. Uses DNS lookups (no API key needed).\",\r\n inputSchema: {\r\n domain: z\r\n .string()\r\n .min(3)\r\n .regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i, \"Invalid domain format\")\r\n .describe(\"Domain to check (e.g. 'company.com')\"),\r\n },\r\n annotations: {\r\n title: \"Check Deliverability\",\r\n readOnlyHint: true,\r\n destructiveHint: false,\r\n idempotentHint: true,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ domain }) => {\r\n try {\r\n if (!_checkDeliverability) {\r\n const mod = await import(\"@emailens/engine/server\");\r\n _checkDeliverability = mod.checkDeliverability;\r\n }\r\n const report = await _checkDeliverability!(domain);\r\n return {\r\n content: [{ type: \"text\" as const, text: JSON.stringify(report, null, 2) }],\r\n };\r\n } catch (err) {\r\n const msg = (err as Error).message ?? String(err);\r\n if (msg.includes(\"Cannot find\") || msg.includes(\"cannot find\") || msg.includes(\"MODULE_NOT_FOUND\")) {\r\n return mcpError(\r\n \"check_deliverability requires @emailens/engine >=0.9.1 with the /server subpath. Update with: npm install @emailens/engine@latest\",\r\n );\r\n }\r\n return mcpError(`Deliverability check failed for \"${domain}\": ${msg}`);\r\n }\r\n },\r\n);\r\n\r\n// ── Hosted Tool: capture_screenshots ───────────────────────────────\r\n\r\nserver.registerTool(\r\n \"capture_screenshots\",\r\n {\r\n title: \"Capture Screenshots\",\r\n description:\r\n \"Capture real email screenshots across 21 clients (Gmail, Outlook, Apple Mail, etc.) with light and dark mode variants. Screenshots are rendered in real browsers and hosted on CDN. Requires EMAILENS_API_KEY env var \\u2014 free plan at emailens.dev?ref=mcp.\",\r\n inputSchema: {\r\n html: z.string().describe(\"The email HTML source code\"),\r\n format: formatEnum.describe(\"Input format\"),\r\n clients: z.array(z.string()).optional().describe(\"Filter to specific client IDs\"),\r\n modes: z\r\n .array(z.enum([\"light\", \"dark\"]))\r\n .optional()\r\n .describe(\"Screenshot modes (default: ['light'])\"),\r\n title: z.string().optional().describe(\"Name for the hosted preview\"),\r\n },\r\n annotations: {\r\n title: \"Capture Screenshots\",\r\n readOnlyHint: false,\r\n destructiveHint: false,\r\n idempotentHint: false,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ html, format, clients, modes, title }) => {\r\n if (!config.isHosted) {\r\n return noApiKeyError(\"capture_screenshots\");\r\n }\r\n\r\n const opts = { apiKey: config.apiKey!, apiUrl: config.apiUrl };\r\n\r\n // Step 1: Create hosted preview\r\n const previewResult = await apiRequest<{\r\n id: string;\r\n overallScore: number;\r\n }>(\"/api/preview\", {\r\n ...opts,\r\n body: { html, format, clients, title },\r\n timeoutMs: 30_000,\r\n });\r\n\r\n if (previewResult.isError) {\r\n return mcpError(previewResult.error!);\r\n }\r\n\r\n const previewId = previewResult.data!.id;\r\n\r\n // Step 2: Capture screenshots (with one retry on timeout)\r\n let screenshotResult = await apiRequest<{\r\n screenshots: Record<string, string>;\r\n screenshotStatus: string;\r\n captured: string[];\r\n failed: string[];\r\n }>(`/api/preview/${previewId}/screenshots`, {\r\n ...opts,\r\n body: { clients, modes },\r\n timeoutMs: 120_000,\r\n });\r\n\r\n // Retry once on timeout\r\n if (screenshotResult.isError && screenshotResult.error?.includes(\"timed out\")) {\r\n screenshotResult = await apiRequest(`/api/preview/${previewId}/screenshots`, {\r\n ...opts,\r\n body: { clients, modes },\r\n timeoutMs: 120_000,\r\n });\r\n }\r\n\r\n // If screenshots still failed, return preview analysis with error note\r\n if (screenshotResult.isError) {\r\n return {\r\n content: [\r\n {\r\n type: \"text\" as const,\r\n text: JSON.stringify(\r\n {\r\n previewId,\r\n previewUrl: `${config.apiUrl}/preview/${previewId}`,\r\n overallScore: previewResult.data!.overallScore,\r\n screenshots: {},\r\n screenshotStatus: \"failed\",\r\n captured: [],\r\n failed: [],\r\n note: `Preview created but screenshot capture failed: ${screenshotResult.error}. View the full preview at the URL above.`,\r\n },\r\n null,\r\n 2,\r\n ),\r\n },\r\n ],\r\n };\r\n }\r\n\r\n const ss = screenshotResult.data!;\r\n const result: Record<string, unknown> = {\r\n previewId,\r\n previewUrl: `${config.apiUrl}/preview/${previewId}`,\r\n overallScore: previewResult.data!.overallScore,\r\n screenshots: ss.screenshots,\r\n screenshotStatus: ss.screenshotStatus,\r\n captured: ss.captured,\r\n failed: ss.failed,\r\n };\r\n\r\n if (ss.failed?.length > 0) {\r\n result.note = `${ss.failed.length} of ${(ss.captured?.length ?? 0) + ss.failed.length} screenshots failed: ${ss.failed.join(\", \")}`;\r\n }\r\n\r\n return { content: [{ type: \"text\" as const, text: JSON.stringify(result, null, 2) }] };\r\n },\r\n);\r\n\r\n// ── Hosted Tool: share_preview ─────────────────────────────────────\r\n\r\nserver.registerTool(\r\n \"share_preview\",\r\n {\r\n title: \"Share Preview\",\r\n description:\r\n \"Create a shareable link for an email preview. Recipients see the full analysis without needing an account. Requires EMAILENS_API_KEY env var and Dev plan ($9/mo).\",\r\n inputSchema: {\r\n html: z.string().describe(\"The email HTML source code\"),\r\n title: z.string().optional().describe(\"Display title for the share page\"),\r\n format: formatEnum.describe(\"Input format\"),\r\n },\r\n annotations: {\r\n title: \"Share Preview\",\r\n readOnlyHint: false,\r\n destructiveHint: false,\r\n idempotentHint: false,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ html, title, format }) => {\r\n if (!config.isHosted) {\r\n return noApiKeyError(\"share_preview\", \"Shareable links require the Dev plan ($9/mo).\");\r\n }\r\n\r\n const result = await apiRequest<{ id: string; expiresAt: string | null }>(\"/api/share\", {\r\n apiKey: config.apiKey!,\r\n apiUrl: config.apiUrl,\r\n body: { html, title, format },\r\n });\r\n\r\n if (result.isError) {\r\n return mcpError(result.error!);\r\n }\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 shareUrl: `${config.apiUrl}/share/${result.data!.id}`,\r\n expiresAt: result.data!.expiresAt,\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","import { EMAIL_CLIENTS, type CSSWarning, type SourceLocation } from \"@emailens/engine\";\r\n\r\n/**\r\n * Turning the engine's per-client warnings into something a model can afford\r\n * to read.\r\n *\r\n * The engine reports per client because a score is per client, and per selector\r\n * because a fix is per selector. On an ordinary 11KB newsletter that is 347\r\n * warnings — which collapse to **46 distinct property-and-severity pairs**.\r\n * `border-radius` alone arrives twelve times: two clients that drop it, six\r\n * selectors that use it, the same sentence and the same VML workaround in every\r\n * one. Serialised with the fix snippets that is 286KB, about 73,000 tokens, for\r\n * an email the size of a README.\r\n *\r\n * A tool that cannot be called twice in a conversation gets called once and\r\n * then avoided, so this collapses the duplication:\r\n *\r\n * - **One entry per property and severity**, listing the clients it affects.\r\n * The clients are the data; repeating the prose once per client is not.\r\n * - **The message loses the client's name**, which is the only thing that\r\n * differed between the copies. It is stripped by exact match against the\r\n * client's own display name rather than by pattern, so it cannot eat a real\r\n * word.\r\n * - **No `fix` snippets.** They were 93KB of the 286KB, and `fix_email` exists\r\n * precisely to produce them for the issues a caller decides to act on.\r\n * - **Positions merge.** Every place the property breaks, across every client\r\n * and selector, in document order.\r\n *\r\n * `detail: \"full\"` returns the engine's own per-client shape, unchanged, for a\r\n * caller that genuinely wants it.\r\n */\r\n\r\nconst CLIENT_NAMES = new Map(EMAIL_CLIENTS.map((c) => [c.id, c.name]));\r\n\r\n/** One problem, and everywhere it lands. */\r\nexport interface CollapsedFinding {\r\n property: string;\r\n severity: CSSWarning[\"severity\"];\r\n /** Client ids affected, in the matrix's own order. */\r\n clients: string[];\r\n /** The engine's message with the client's name removed. */\r\n message: string;\r\n suggestion?: string;\r\n /** `structural` means the fix changes markup, not just CSS. */\r\n fixType?: CSSWarning[\"fixType\"];\r\n /** True when `fix_email` can produce a snippet for this. */\r\n hasFix?: boolean;\r\n loc?: Pick<SourceLocation, \"line\" | \"column\" | \"offset\" | \"length\">;\r\n alsoAtLines?: number[];\r\n locsTruncated?: boolean;\r\n}\r\n\r\n/**\r\n * Strip the leading client name from a message.\r\n *\r\n * Messages read \"Outlook (New) does not support …\" / \"Yahoo Mail has partial\r\n * support for …\". Matching the client's own display name exactly is safer than\r\n * a pattern: \"Mail\" and \"Outlook\" appear inside real sentences.\r\n */\r\nfunction withoutClientName(message: string, clientId: string): string {\r\n const name = CLIENT_NAMES.get(clientId);\r\n if (!name || !message.startsWith(name)) return message;\r\n const rest = message.slice(name.length).trimStart();\r\n return rest ? rest.charAt(0).toUpperCase() + rest.slice(1) : message;\r\n}\r\n\r\n/** Positions from every warning in a group, deduplicated, in document order. */\r\nfunction mergeLocations(group: CSSWarning[]): SourceLocation[] {\r\n const byOffset = new Map<number, SourceLocation>();\r\n for (const w of group) {\r\n for (const loc of w.locs ?? (w.loc ? [w.loc] : [])) {\r\n if (!byOffset.has(loc.offset)) byOffset.set(loc.offset, loc);\r\n }\r\n }\r\n return [...byOffset.values()].sort((a, b) => a.offset - b.offset);\r\n}\r\n\r\n/**\r\n * Collapse per-client, per-selector warnings into one entry each.\r\n *\r\n * Grouped by property, severity **and message** — two clients can both drop a\r\n * property for different reasons, and merging those would attribute a caveat\r\n * to a client whose note never said it.\r\n */\r\nexport function collapseWarnings(warnings: CSSWarning[]): CollapsedFinding[] {\r\n const groups = new Map<string, CSSWarning[]>();\r\n for (const w of warnings) {\r\n // A NUL separator, because it cannot occur in a property, a severity or\r\n // a message — a space could, and two different triples would collide.\r\n const key = [w.property, w.severity, withoutClientName(w.message, w.client)].join(\"\\u0000\");\r\n const seen = groups.get(key);\r\n if (seen) seen.push(w);\r\n else groups.set(key, [w]);\r\n }\r\n\r\n return [...groups.values()].map((group) => {\r\n const first = group[0];\r\n const clients = [...new Set(group.map((w) => w.client))];\r\n const locs = mergeLocations(group);\r\n // A suggestion can differ per client (5 of 46 groups on the reference\r\n // fixture). Keeping the first would silently attribute one client's advice\r\n // to another, so a group that disagrees says nothing and lets the caller\r\n // ask `fix_email`.\r\n const suggestions = new Set(group.map((w) => w.suggestion ?? \"\"));\r\n const suggestion = suggestions.size === 1 ? first.suggestion : undefined;\r\n\r\n return {\r\n property: first.property,\r\n severity: first.severity,\r\n clients,\r\n message: withoutClientName(first.message, first.client),\r\n ...(suggestion ? { suggestion } : {}),\r\n ...(first.fixType ? { fixType: first.fixType } : {}),\r\n ...(group.some((w) => w.fix) ? { hasFix: true } : {}),\r\n ...(locs.length\r\n ? {\r\n loc: {\r\n line: locs[0].line,\r\n column: locs[0].column,\r\n offset: locs[0].offset,\r\n length: locs[0].length,\r\n },\r\n // Lines, not positions, and each line once: two occurrences on\r\n // one line are two offsets but one place to look.\r\n ...(() => {\r\n const lines = [...new Set(locs.slice(1).map((l) => l.line))].filter(\r\n (line) => line !== locs[0].line,\r\n );\r\n return lines.length ? { alsoAtLines: lines } : {};\r\n })(),\r\n }\r\n : {}),\r\n ...(group.some((w) => w.locsTruncated) ? { locsTruncated: true } : {}),\r\n };\r\n });\r\n}\r\n\r\n/** Keep only the warnings for these clients. An empty or absent list keeps all. */\r\nexport function forClients(warnings: CSSWarning[], clients?: string[]): CSSWarning[] {\r\n if (!clients?.length) return warnings;\r\n const wanted = new Set(clients);\r\n return warnings.filter((w) => wanted.has(w.client));\r\n}\r\n\r\n/** Every client id the engine knows, for validating a caller's filter. */\r\nexport function knownClientIds(): string[] {\r\n return EMAIL_CLIENTS.map((c) => c.id);\r\n}\r\n","export const config = {\n get apiKey(): string | null {\n return process.env.EMAILENS_API_KEY ?? null;\n },\n get apiUrl(): string {\n return process.env.EMAILENS_API_URL ?? \"https://emailens.dev\";\n },\n get isHosted(): boolean {\n return this.apiKey !== null;\n },\n};\n","export interface ApiRequestOptions {\n apiKey: string;\n apiUrl: string;\n method?: \"GET\" | \"POST\" | \"PATCH\" | \"DELETE\";\n body?: unknown;\n timeoutMs?: number;\n}\n\nexport interface ApiResult<T = unknown> {\n data?: T;\n isError?: true;\n error?: string;\n}\n\nexport async function apiRequest<T = unknown>(\n path: string,\n options: ApiRequestOptions,\n): Promise<ApiResult<T>> {\n const { apiKey, apiUrl, method = \"GET\", body, timeoutMs = 30_000 } = options;\n const url = `${apiUrl}${path}`;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, {\n method: method !== \"GET\" ? method : body ? \"POST\" : \"GET\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n if (response.status === 401) {\n return {\n isError: true,\n error: \"Invalid or revoked API key. Generate a new one at emailens.dev/settings/api-keys?ref=mcp\",\n };\n }\n\n if (response.status === 403) {\n const data = (await response.json().catch(() => ({}))) as Record<string, unknown>;\n return {\n isError: true,\n error: data.upgrade\n ? \"This feature requires a plan upgrade. Visit emailens.dev/pricing?ref=mcp\"\n : \"Access denied. Check your plan at emailens.dev/pricing?ref=mcp\",\n };\n }\n\n if (response.status === 429) {\n const data = (await response.json().catch(() => ({}))) as Record<string, number>;\n return {\n isError: true,\n error: `Daily quota exceeded${data.limit ? ` (${data.limit - (data.remaining ?? 0)}/${data.limit})` : \"\"}. Resets tomorrow. Upgrade at emailens.dev/pricing?ref=mcp`,\n };\n }\n\n if (!response.ok) {\n const data = (await response.json().catch(() => ({}))) as Record<string, unknown>;\n return { isError: true, error: (data.error as string) ?? `API error: ${response.status}` };\n }\n\n const data = (await response.json()) as T;\n return { data };\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n return { isError: true, error: `Request timed out after ${timeoutMs / 1000}s. Try again or check emailens.dev status.` };\n }\n return { isError: true, error: `Network error: ${(err as Error).message}` };\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport function mcpError(message: string) {\n return {\n content: [{ type: \"text\" as const, text: message }],\n isError: true,\n };\n}\n\nexport function noApiKeyError(toolName: string, extraInfo?: string) {\n const lines = [\n `${toolName} requires an Emailens API key.`,\n \"\",\n \"1. Sign up free at emailens.dev?ref=mcp\",\n \"2. Create a key at emailens.dev/settings/api-keys?ref=mcp\",\n \"3. Add to your MCP config:\",\n \"\",\n ' \"env\": { \"EMAILENS_API_KEY\": \"ek_live_...\" }',\n ];\n if (extraInfo) {\n lines.push(\"\", extraInfo);\n } else {\n lines.push(\"\", \"Free plan includes 30 previews/day (each preview can include screenshots).\");\n }\n return mcpError(lines.join(\"\\n\"));\n}\n"],"mappings":";;;AAAA,SAAS,iBAAiB;AAC1B,SAAS,4BAA4B;AACrC,SAAS,SAAS;;;ACFlB,SAAS,qBAA2D;AAgCpE,IAAM,eAAe,IAAI,IAAI,cAAc,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AA2BrE,SAAS,kBAAkB,SAAiB,UAA0B;AACpE,QAAM,OAAO,aAAa,IAAI,QAAQ;AACtC,MAAI,CAAC,QAAQ,CAAC,QAAQ,WAAW,IAAI,EAAG,QAAO;AAC/C,QAAM,OAAO,QAAQ,MAAM,KAAK,MAAM,EAAE,UAAU;AAClD,SAAO,OAAO,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC,IAAI;AAC/D;AAGA,SAAS,eAAe,OAAuC;AAC7D,QAAM,WAAW,oBAAI,IAA4B;AACjD,aAAW,KAAK,OAAO;AACrB,eAAW,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,GAAG,IAAI,CAAC,IAAI;AAClD,UAAI,CAAC,SAAS,IAAI,IAAI,MAAM,EAAG,UAAS,IAAI,IAAI,QAAQ,GAAG;AAAA,IAC7D;AAAA,EACF;AACA,SAAO,CAAC,GAAG,SAAS,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AAClE;AASO,SAAS,iBAAiB,UAA4C;AAC3E,QAAM,SAAS,oBAAI,IAA0B;AAC7C,aAAW,KAAK,UAAU;AAGxB,UAAM,MAAM,CAAC,EAAE,UAAU,EAAE,UAAU,kBAAkB,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,KAAK,IAAQ;AAC1F,UAAM,OAAO,OAAO,IAAI,GAAG;AAC3B,QAAI,KAAM,MAAK,KAAK,CAAC;AAAA,QAChB,QAAO,IAAI,KAAK,CAAC,CAAC,CAAC;AAAA,EAC1B;AAEA,SAAO,CAAC,GAAG,OAAO,OAAO,CAAC,EAAE,IAAI,CAAC,UAAU;AACzC,UAAM,QAAQ,MAAM,CAAC;AACrB,UAAM,UAAU,CAAC,GAAG,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACvD,UAAM,OAAO,eAAe,KAAK;AAKjC,UAAM,cAAc,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC;AAChE,UAAM,aAAa,YAAY,SAAS,IAAI,MAAM,aAAa;AAE/D,WAAO;AAAA,MACL,UAAU,MAAM;AAAA,MAChB,UAAU,MAAM;AAAA,MAChB;AAAA,MACA,SAAS,kBAAkB,MAAM,SAAS,MAAM,MAAM;AAAA,MACtD,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,MACnC,GAAI,MAAM,UAAU,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;AAAA,MAClD,GAAI,MAAM,KAAK,CAAC,MAAM,EAAE,GAAG,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;AAAA,MACnD,GAAI,KAAK,SACL;AAAA,QACE,KAAK;AAAA,UACH,MAAM,KAAK,CAAC,EAAE;AAAA,UACd,QAAQ,KAAK,CAAC,EAAE;AAAA,UAChB,QAAQ,KAAK,CAAC,EAAE;AAAA,UAChB,QAAQ,KAAK,CAAC,EAAE;AAAA,QAClB;AAAA;AAAA;AAAA,QAGA,IAAI,MAAM;AACR,gBAAM,QAAQ,CAAC,GAAG,IAAI,IAAI,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,EAAE;AAAA,YAC3D,CAAC,SAAS,SAAS,KAAK,CAAC,EAAE;AAAA,UAC7B;AACA,iBAAO,MAAM,SAAS,EAAE,aAAa,MAAM,IAAI,CAAC;AAAA,QAClD,GAAG;AAAA,MACL,IACA,CAAC;AAAA,MACL,GAAI,MAAM,KAAK,CAAC,MAAM,EAAE,aAAa,IAAI,EAAE,eAAe,KAAK,IAAI,CAAC;AAAA,IACtE;AAAA,EACF,CAAC;AACH;AAGO,SAAS,WAAW,UAAwB,SAAkC;AACnF,MAAI,CAAC,SAAS,OAAQ,QAAO;AAC7B,QAAM,SAAS,IAAI,IAAI,OAAO;AAC9B,SAAO,SAAS,OAAO,CAAC,MAAM,OAAO,IAAI,EAAE,MAAM,CAAC;AACpD;AAGO,SAAS,iBAA2B;AACzC,SAAO,cAAc,IAAI,CAAC,MAAM,EAAE,EAAE;AACtC;;;AD/IA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAAA;AAAA,EACA;AAAA,OAIK;;;AElBA,IAAM,SAAS;AAAA,EACpB,IAAI,SAAwB;AAC1B,WAAO,QAAQ,IAAI,oBAAoB;AAAA,EACzC;AAAA,EACA,IAAI,SAAiB;AACnB,WAAO,QAAQ,IAAI,oBAAoB;AAAA,EACzC;AAAA,EACA,IAAI,WAAoB;AACtB,WAAO,KAAK,WAAW;AAAA,EACzB;AACF;;;ACIA,eAAsB,WACpB,MACA,SACuB;AACvB,QAAM,EAAE,QAAQ,QAAQ,SAAS,OAAO,MAAM,YAAY,IAAO,IAAI;AACrE,QAAM,MAAM,GAAG,MAAM,GAAG,IAAI;AAE5B,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,SAAS;AAE5D,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,QAAQ,WAAW,QAAQ,SAAS,OAAO,SAAS;AAAA,MACpD,SAAS;AAAA,QACP,eAAe,UAAU,MAAM;AAAA,QAC/B,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,MACpC,QAAQ,WAAW;AAAA,IACrB,CAAC;AAED,QAAI,SAAS,WAAW,KAAK;AAC3B,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAEA,QAAI,SAAS,WAAW,KAAK;AAC3B,YAAMC,QAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACpD,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAOA,MAAK,UACR,6EACA;AAAA,MACN;AAAA,IACF;AAEA,QAAI,SAAS,WAAW,KAAK;AAC3B,YAAMA,QAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACpD,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,uBAAuBA,MAAK,QAAQ,KAAKA,MAAK,SAASA,MAAK,aAAa,EAAE,IAAIA,MAAK,KAAK,MAAM,EAAE;AAAA,MAC1G;AAAA,IACF;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAMA,QAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACpD,aAAO,EAAE,SAAS,MAAM,OAAQA,MAAK,SAAoB,cAAc,SAAS,MAAM,GAAG;AAAA,IAC3F;AAEA,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,WAAO,EAAE,KAAK;AAAA,EAChB,SAAS,KAAK;AACZ,QAAK,IAAc,SAAS,cAAc;AACxC,aAAO,EAAE,SAAS,MAAM,OAAO,2BAA2B,YAAY,GAAI,6CAA6C;AAAA,IACzH;AACA,WAAO,EAAE,SAAS,MAAM,OAAO,kBAAmB,IAAc,OAAO,GAAG;AAAA,EAC5E,UAAE;AACA,iBAAa,KAAK;AAAA,EACpB;AACF;AAEO,SAAS,SAAS,SAAiB;AACxC,SAAO;AAAA,IACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,QAAQ,CAAC;AAAA,IAClD,SAAS;AAAA,EACX;AACF;AAEO,SAAS,cAAc,UAAkB,WAAoB;AAClE,QAAM,QAAQ;AAAA,IACZ,GAAG,QAAQ;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,MAAI,WAAW;AACb,UAAM,KAAK,IAAI,SAAS;AAAA,EAC1B,OAAO;AACL,UAAM,KAAK,IAAI,4EAA4E;AAAA,EAC7F;AACA,SAAO,SAAS,MAAM,KAAK,IAAI,CAAC;AAClC;;;AHhFA,IAAI,uBAAsE;AAI1E,SAAS,YAAY,QAAwC;AAC3D,MAAI,WAAW,SAAS,WAAW,UAAU,WAAW,UAAW,QAAO;AAC1E,SAAO;AACT;AAEA,IAAM,aAAa,EAAE,KAAK,CAAC,QAAQ,OAAO,QAAQ,SAAS,CAAC,EAAE,SAAS;AAUvE,IAAM,aAAa,EAChB,KAAK,CAAC,WAAW,MAAM,CAAC,EACxB,SAAS,EACT;AAAA,EACC;AACF;AAEF,IAAM,eAAe,EAClB,MAAM,EAAE,OAAO,CAAC,EAChB,SAAS,EACT;AAAA,EACC;AACF;AAQF,SAAS,gBAAgB,SAA+B;AACtD,MAAI,CAAC,SAAS,OAAQ,QAAO;AAC7B,QAAM,QAAQ,IAAI,IAAI,eAAe,CAAC;AACtC,QAAM,UAAU,QAAQ,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;AACnD,MAAI,CAAC,QAAQ,OAAQ,QAAO;AAC5B,SAAO;AAAA,IACL,kBAAkB,QAAQ,SAAS,IAAI,QAAQ,IAAI,KAAK,QAAQ,KAAK,IAAI,CAAC;AAAA,EAC5E;AACF;AAGA,SAAS,qBACP,UACA,QACA,SACA;AACA,QAAM,SAAS,WAAW,UAAU,OAAO;AAC3C,MAAI,WAAW,QAAQ;AACrB,WAAO;AAAA,MACL,cAAc,OAAO;AAAA,MACrB,UAAU,OAAO,IAAI,CAAC,OAAO;AAAA,QAC3B,QAAQ,EAAE;AAAA,QACV,UAAU,EAAE;AAAA,QACZ,UAAU,EAAE;AAAA,QACZ,SAAS,EAAE;AAAA,QACX,YAAY,EAAE;AAAA,QACd,KAAK,EAAE;AAAA,QACP,SAAS,EAAE;AAAA,QACX,GAAG,cAAc,CAAC;AAAA,MACpB,EAAE;AAAA,IACJ;AAAA,EACF;AACA,QAAM,WAAW,iBAAiB,MAAM;AACxC,SAAO;AAAA,IACL,cAAc,OAAO;AAAA,IACrB,cAAc,SAAS;AAAA,IACvB;AAAA,EACF;AACF;AASA,SAAS,eAAe,QAA0B;AAChD,UAAQ,UAAU,YAAY;AAChC;AAWA,SAAS,cAIN,GAAM;AACP,MAAI,CAAC,EAAE,IAAK,QAAO,CAAC;AACpB,QAAM,EAAE,MAAM,QAAQ,QAAQ,OAAO,IAAI,EAAE;AAC3C,QAAM,UAAU,EAAE,QAAQ,CAAC,GAAG,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI;AACxD,SAAO;AAAA,IACL,KAAK,EAAE,MAAM,QAAQ,QAAQ,OAAO;AAAA,IACpC,GAAI,OAAO,SAAS,EAAE,aAAa,OAAO,IAAI,CAAC;AAAA,IAC/C,GAAI,EAAE,gBAAgB,EAAE,eAAe,KAAK,IAAI,CAAC;AAAA,EACnD;AACF;AAEA,SAAS,iBAAiB,MAAc;AACtC,MAAI,KAAK,SAAS,eAAe;AAC/B,WAAO,SAAS,sBAAsB,KAAK,MAAM,gBAAgB,IAAI,CAAC,gDAAgD;AAAA,EACxH;AACA,SAAO;AACT;AAEA,IAAM,SAAS,IAAI,UAAU;AAAA,EAC3B,MAAM;AAAA,EACN,SAAS;AACX,CAAC;AAID,OAAO;AAAA,EACL;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,aACE,+DAA0DC,eAAc,MAAM;AAAA,IAChF,aAAa;AAAA,MACX,MAAM,EAAE,OAAO,EAAE,SAAS,4BAA4B;AAAA,MACtD,SAAS,EACN,MAAM,EAAE,OAAO,CAAC,EAChB,SAAS,EACT,SAAS,mEAAmE;AAAA,MAC/E,QAAQ,WAAW,SAAS,6DAA6D;AAAA,IAC3F;AAAA,IACA,aAAa;AAAA,MACX,OAAO;AAAA,MACP,cAAc;AAAA,MACd,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,MAChB,eAAe;AAAA,IACjB;AAAA,EACF;AAAA,EACA,OAAO,EAAE,MAAM,SAAS,OAAO,MAAM;AACnC,UAAM,YAAY,iBAAiB,IAAI;AACvC,QAAI,UAAW,QAAO;AAEtB,UAAM,iBAAiB,IAAI,IAAIA,eAAc,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAC7D,UAAM,YAAY,YAAY,MAAM;AACpC,UAAM,UAAU,cAAc,MAAM,EAAE,UAAU,CAAC;AAEjD,QAAI;AACJ,QAAI,SAAS;AACX,YAAM,SAAS,QAAQ,OAAO,CAAC,MAAM,eAAe,IAAI,CAAC,CAAC;AAC1D,UAAI,OAAO,WAAW,GAAG;AACvB,eAAO;AAAA,UACL,KAAK,UAAU,EAAE,OAAO,gCAAgC,gBAAgB,MAAM,KAAK,cAAc,EAAE,GAAG,MAAM,CAAC;AAAA,QAC/G;AAAA,MACF;AACA,mBAAa,OAAO,IAAI,CAAC,MAAM,QAAQ,mBAAmB,CAAC,CAAC;AAAA,IAC9D,OAAO;AACL,mBAAa,QAAQ,uBAAuB;AAAA,IAC9C;AAEA,UAAM,WAAW,QAAQ,QAAQ;AACjC,UAAM,SAAS,QAAQ,MAAM,QAAQ;AACrC,UAAM,eAAe,QAAQ,oBAAoB;AACjD,UAAM,aAAa,QAAQ,UAAU;AAErC,UAAM,WAAqE,CAAC;AAC5E,eAAW,KAAK,YAAY;AAC1B,eAAS,EAAE,QAAQ,IAAI,QAAQ,iBAAiB,EAAE,QAAQ;AAAA,IAC5D;AAEA,UAAM,cAAc,OAAO,OAAO,MAAM;AACxC,UAAM,eACJ,YAAY,SAAS,IACjB,KAAK,MAAM,YAAY,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,OAAO,CAAC,IAAI,YAAY,MAAM,IAC5E;AAEN,UAAM,YAAY,YAAY,IAAI;AAElC,UAAM,SAAkC;AAAA,MACtC;AAAA,MACA,qBAAqB;AAAA,MACrB,aAAa,SAAS,IAAI,CAAC,OAAO;AAAA,QAChC,QAAQ,EAAE;AAAA,QACV,UAAU,EAAE;AAAA,QACZ,UAAU,EAAE;AAAA,QACZ,SAAS,EAAE;AAAA,QACX,YAAY,EAAE;AAAA,QACd,KAAK,EAAE;AAAA,QACP,SAAS,EAAE;AAAA,MACb,EAAE;AAAA,MACF;AAAA,MACA;AAAA,MACA,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,MACjC,aAAa,WAAW;AAAA,MACxB,kBAAkB,OAAO,QAAQ,QAAQ,EAAE;AAAA,QACzC,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM;AACvB,cAAI,GAAG,SAAS,SAAS,EAAG,KAAI,QAAQ,IAAI,GAAG,SAAS;AACxD,iBAAO;AAAA,QACT;AAAA,QACA,CAAC;AAAA,MACH;AAAA,IACF;AAGA,QAAI,CAAC,OAAO,YAAY,eAAe,IAAI;AACzC,aAAO,MACL;AAAA,IACJ;AAEA,WAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,EACvF;AACF;AAIA,OAAO;AAAA,EACL;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,aACE;AAAA,IACF,aAAa;AAAA,MACX,MAAM,EAAE,OAAO,EAAE,SAAS,4BAA4B;AAAA,MACtD,QAAQ,WAAW,SAAS,kDAAkD;AAAA,MAC9E,QAAQ;AAAA,MACR,SAAS;AAAA,IACX;AAAA,IACA,aAAa;AAAA,MACX,OAAO;AAAA,MACP,cAAc;AAAA,MACd,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,MAChB,eAAe;AAAA,IACjB;AAAA,EACF;AAAA,EACA,OAAO,EAAE,MAAM,QAAQ,QAAQ,QAAQ,MAAM;AAC3C,UAAM,YAAY,iBAAiB,IAAI;AACvC,QAAI,UAAW,QAAO;AACtB,UAAM,cAAc,gBAAgB,OAAO;AAC3C,QAAI,YAAa,QAAO;AAExB,UAAM,WAAW,aAAa,MAAM,YAAY,MAAM,GAAG;AAAA,MACvD,WAAW,eAAe,MAAM;AAAA,IAClC,CAAC;AAGD,UAAM,SAAS,2BAA2B,QAAQ;AAElD,UAAM,cAAc,OAAO,OAAO,MAAM;AACxC,UAAM,eACJ,YAAY,SAAS,IACjB,KAAK,MAAM,YAAY,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,OAAO,CAAC,IAAI,YAAY,MAAM,IAC5E;AAEN,WAAO;AAAA,MACL,SAAS;AAAA,QACP;AAAA,UACE,MAAM;AAAA,UACN,MAAM,KAAK;AAAA,YACT;AAAA,cACE;AAAA,cACA;AAAA,cACA,GAAG,qBAAqB,UAAU,QAAQ,OAAO;AAAA,YACnD;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAIA,OAAO;AAAA,EACL;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,aACE;AAAA,IACF,aAAa;AAAA,MACX,MAAM,EAAE,OAAO,EAAE,SAAS,4BAA4B;AAAA,MACtD,QAAQ,WAAW,SAAS,kDAAkD;AAAA,MAC9E,MAAM,EACH,MAAM,EAAE,KAAK,CAAC,QAAQ,SAAS,iBAAiB,UAAU,iBAAiB,gBAAgB,QAAQ,mBAAmB,CAAC,CAAC,EACxH,SAAS,EACT,SAAS,0CAA0C;AAAA,MACtD,QAAQ;AAAA,MACR,SAAS;AAAA,IACX;AAAA,IACA,aAAa;AAAA,MACX,OAAO;AAAA,MACP,cAAc;AAAA,MACd,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,MAChB,eAAe;AAAA,IACjB;AAAA,EACF;AAAA,EACA,OAAO,EAAE,MAAM,QAAQ,MAAM,QAAQ,QAAQ,MAAM;AACjD,UAAM,YAAY,iBAAiB,IAAI;AACvC,QAAI,UAAW,QAAO;AACtB,UAAM,cAAc,gBAAgB,OAAO;AAC3C,QAAI,YAAa,QAAO;AAExB,UAAM,UAAU,cAAc,MAAM;AAAA,MAClC,WAAW,YAAY,MAAM;AAAA,MAC7B,WAAW,eAAe,MAAM;AAAA,IAClC,CAAC;AACD,UAAM,SAAS,QAAQ,MAAM,EAAE,KAAK,CAAC;AAErC,UAAM,cAAc,OAAO,OAAO,OAAO,cAAc,MAAM;AAC7D,UAAM,uBACJ,YAAY,SAAS,IACjB,KAAK,MAAM,YAAY,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,OAAO,CAAC,IAAI,YAAY,MAAM,IAC5E;AAEN,WAAO;AAAA,MACL,SAAS;AAAA,QACP;AAAA,UACE,MAAM;AAAA,UACN,MAAM,KAAK;AAAA,YACT;AAAA,cACE;AAAA,cACA,eAAe;AAAA,gBACb,QAAQ,OAAO,cAAc;AAAA,gBAC7B,GAAG,qBAAqB,OAAO,cAAc,UAAU,QAAQ,OAAO;AAAA,cACxE;AAAA,cACA,MAAM,OAAO;AAAA,cACb,OAAO,OAAO;AAAA,cACd,eAAe,OAAO;AAAA,cACtB,QAAQ,OAAO;AAAA,cACf,cAAc,OAAO;AAAA,cACrB,MAAM,OAAO;AAAA,cACb,mBAAmB,OAAO;AAAA,cAC1B,UAAU,OAAO;AAAA,cACjB,QAAQ,OAAO;AAAA,YACjB;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAIA,OAAO;AAAA,EACL;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,aACE;AAAA,IACF,aAAa;AAAA,MACX,MAAM,EAAE,OAAO,EAAE,SAAS,mCAAmC;AAAA,MAC7D,QAAQ,WAAW,SAAS,yCAAoC;AAAA,MAChE,OAAO,EAAE,KAAK,CAAC,OAAO,SAAS,CAAC,EAAE,SAAS,EAAE,SAAS,0DAA0D;AAAA,MAChH,kBAAkB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,sDAAsD;AAAA,IACzG;AAAA,IACA,aAAa;AAAA,MACX,OAAO;AAAA,MACP,cAAc;AAAA,MACd,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,MAChB,eAAe;AAAA,IACjB;AAAA,EACF;AAAA,EACA,OAAO,EAAE,MAAM,QAAQ,OAAO,iBAAiB,MAAM;AACnD,UAAM,YAAY,iBAAiB,IAAI;AACvC,QAAI,UAAW,QAAO;AAEtB,UAAM,YAAY,YAAY,MAAM;AACpC,UAAM,WAAW,UAAU,YAAY,YAAY;AACnD,UAAM,cAAc,UAAU;AAE9B,UAAM,WAAW,aAAa,MAAM,SAAS;AAC7C,UAAM,SAAS,2BAA2B,QAAQ;AAElD,QAAI,SAAS,WAAW,GAAG;AACzB,aAAO;AAAA,QACL,SAAS;AAAA,UACP;AAAA,YACE,MAAM;AAAA,YACN,MAAM,KAAK,UAAU,EAAE,SAAS,wDAAwD,cAAc,IAAI,GAAG,MAAM,CAAC;AAAA,UACtH;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,WAAW,MAAM,oBAAoB;AAAA,MACzC,cAAc;AAAA,MACd;AAAA,MACA;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA,QAAQ;AAAA,IACV,CAAC;AAED,UAAM,SAAS,kBAAkB;AAAA,MAC/B,cAAc;AAAA,MACd;AAAA,MACA;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA,QAAQ;AAAA,IACV,CAAC;AAED,UAAM,aAAa,mBAAmB,QAAQ;AAE9C,WAAO;AAAA,MACL,SAAS;AAAA,QACP;AAAA,UACE,MAAM;AAAA,UACN,MAAM,KAAK;AAAA,YACT;AAAA,cACE,eAAe,SAAS;AAAA,cACxB,oBAAoB,WAAW;AAAA,cAC/B,aAAa,SAAS,SAAS,WAAW;AAAA,cAC1C,eAAe;AAAA,gBACb,aAAa,SAAS;AAAA,gBACtB,uBAAuB,SAAS;AAAA,gBAChC,WAAW,SAAS;AAAA,cACtB;AAAA,cACA,MAAM;AAAA,YACR;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,QACA,EAAE,MAAM,QAAiB,MAAM,OAAO;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AACF;AAIA,OAAO;AAAA,EACL;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,aAAa;AAAA,IACb,aAAa;AAAA,MACX,OAAO;AAAA,MACP,cAAc;AAAA,MACd,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,MAChB,eAAe;AAAA,IACjB;AAAA,EACF;AAAA,EACA,YAAY;AACV,WAAO;AAAA,MACL,SAAS;AAAA,QACP;AAAA,UACE,MAAM;AAAA,UACN,MAAM,KAAK;AAAA,YACTA,eAAc,IAAI,CAAC,OAAO;AAAA,cACxB,IAAI,EAAE;AAAA,cACN,MAAM,EAAE;AAAA,cACR,UAAU,EAAE;AAAA,cACZ,QAAQ,EAAE;AAAA,cACV,iBAAiB,EAAE;AAAA,cACnB,GAAI,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,IAAI,CAAC;AAAA,YACrD,EAAE;AAAA,YACF;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAIA,OAAO;AAAA,EACL;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,aACE;AAAA,IACF,aAAa;AAAA,MACX,QAAQ,EAAE,OAAO,EAAE,SAAS,qBAAqB;AAAA,MACjD,OAAO,EAAE,OAAO,EAAE,SAAS,qBAAqB;AAAA,MAChD,QAAQ,WAAW,SAAS,8CAA8C;AAAA,IAC5E;AAAA,IACA,aAAa;AAAA,MACX,OAAO;AAAA,MACP,cAAc;AAAA,MACd,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,MAChB,eAAe;AAAA,IACjB;AAAA,EACF;AAAA,EACA,OAAO,EAAE,QAAQ,OAAO,OAAO,MAAM;AACnC,UAAM,kBAAkB,iBAAiB,MAAM;AAC/C,QAAI,gBAAiB,QAAO;AAC5B,UAAM,iBAAiB,iBAAiB,KAAK;AAC7C,QAAI,eAAgB,QAAO;AAE3B,UAAM,YAAY,YAAY,MAAM;AAEpC,UAAM,iBAAiB,aAAa,QAAQ,SAAS;AACrD,UAAM,eAAe,2BAA2B,cAAc;AAE9D,UAAM,gBAAgB,aAAa,OAAO,SAAS;AACnD,UAAM,cAAc,2BAA2B,aAAa;AAE5D,UAAM,UAAU;AAAA,MACd,EAAE,QAAQ,cAAc,UAAU,eAAe;AAAA,MACjD,EAAE,QAAQ,aAAa,UAAU,cAAc;AAAA,IACjD;AAEA,QAAI,kBAAkB;AACtB,QAAI,mBAAmB;AACvB,QAAI,mBAAmB;AACvB,QAAI,aAAa;AAEjB,UAAM,gBAAgB,QAAQ,IAAI,CAAC,MAAM;AACvC,UAAI,EAAE,aAAa,EAAG;AAAA,eACb,EAAE,aAAa,EAAG;AAAA,UACtB;AACL,oBAAc,EAAE;AAEhB,aAAO;AAAA,QACL,UAAU,EAAE;AAAA,QACZ,aAAa,EAAE;AAAA,QACf,YAAY,EAAE;AAAA,QACd,YAAY,EAAE;AAAA,QACd,OAAO,EAAE,MAAM,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,QAAQ,EAAE,QAAQ,SAAS,EAAE,QAAQ,EAAE;AAAA,QAC1F,YAAY,EAAE,WAAW,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,QAAQ,EAAE,QAAQ,SAAS,EAAE,QAAQ,EAAE;AAAA,QACpG,WAAW,EAAE,UAAU;AAAA,MACzB;AAAA,IACF,CAAC;AAED,WAAO;AAAA,MACL,SAAS;AAAA,QACP;AAAA,UACE,MAAM;AAAA,UACN,MAAM,KAAK;AAAA,YACT;AAAA,cACE,SAAS;AAAA,cACT,SAAS;AAAA,gBACP;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,eAAe,QAAQ,SAAS,IAAI,KAAK,MAAO,aAAa,QAAQ,SAAU,EAAE,IAAI,KAAK;AAAA,cAC5F;AAAA,YACF;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAIA,OAAO;AAAA,EACL;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,aACE;AAAA,IACF,aAAa;AAAA,MACX,QAAQ,EACL,OAAO,EACP,IAAI,CAAC,EACL,MAAM,sEAAsE,uBAAuB,EACnG,SAAS,sCAAsC;AAAA,IACpD;AAAA,IACA,aAAa;AAAA,MACX,OAAO;AAAA,MACP,cAAc;AAAA,MACd,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,MAChB,eAAe;AAAA,IACjB;AAAA,EACF;AAAA,EACA,OAAO,EAAE,OAAO,MAAM;AACpB,QAAI;AACF,UAAI,CAAC,sBAAsB;AACzB,cAAM,MAAM,MAAM,OAAO,yBAAyB;AAClD,+BAAuB,IAAI;AAAA,MAC7B;AACA,YAAM,SAAS,MAAM,qBAAsB,MAAM;AACjD,aAAO;AAAA,QACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE,CAAC;AAAA,MAC5E;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,MAAO,IAAc,WAAW,OAAO,GAAG;AAChD,UAAI,IAAI,SAAS,aAAa,KAAK,IAAI,SAAS,aAAa,KAAK,IAAI,SAAS,kBAAkB,GAAG;AAClG,eAAO;AAAA,UACL;AAAA,QACF;AAAA,MACF;AACA,aAAO,SAAS,oCAAoC,MAAM,MAAM,GAAG,EAAE;AAAA,IACvE;AAAA,EACF;AACF;AAIA,OAAO;AAAA,EACL;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,aACE;AAAA,IACF,aAAa;AAAA,MACX,MAAM,EAAE,OAAO,EAAE,SAAS,4BAA4B;AAAA,MACtD,QAAQ,WAAW,SAAS,cAAc;AAAA,MAC1C,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,SAAS,+BAA+B;AAAA,MAChF,OAAO,EACJ,MAAM,EAAE,KAAK,CAAC,SAAS,MAAM,CAAC,CAAC,EAC/B,SAAS,EACT,SAAS,uCAAuC;AAAA,MACnD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,6BAA6B;AAAA,IACrE;AAAA,IACA,aAAa;AAAA,MACX,OAAO;AAAA,MACP,cAAc;AAAA,MACd,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,MAChB,eAAe;AAAA,IACjB;AAAA,EACF;AAAA,EACA,OAAO,EAAE,MAAM,QAAQ,SAAS,OAAO,MAAM,MAAM;AACjD,QAAI,CAAC,OAAO,UAAU;AACpB,aAAO,cAAc,qBAAqB;AAAA,IAC5C;AAEA,UAAM,OAAO,EAAE,QAAQ,OAAO,QAAS,QAAQ,OAAO,OAAO;AAG7D,UAAM,gBAAgB,MAAM,WAGzB,gBAAgB;AAAA,MACjB,GAAG;AAAA,MACH,MAAM,EAAE,MAAM,QAAQ,SAAS,MAAM;AAAA,MACrC,WAAW;AAAA,IACb,CAAC;AAED,QAAI,cAAc,SAAS;AACzB,aAAO,SAAS,cAAc,KAAM;AAAA,IACtC;AAEA,UAAM,YAAY,cAAc,KAAM;AAGtC,QAAI,mBAAmB,MAAM,WAK1B,gBAAgB,SAAS,gBAAgB;AAAA,MAC1C,GAAG;AAAA,MACH,MAAM,EAAE,SAAS,MAAM;AAAA,MACvB,WAAW;AAAA,IACb,CAAC;AAGD,QAAI,iBAAiB,WAAW,iBAAiB,OAAO,SAAS,WAAW,GAAG;AAC7E,yBAAmB,MAAM,WAAW,gBAAgB,SAAS,gBAAgB;AAAA,QAC3E,GAAG;AAAA,QACH,MAAM,EAAE,SAAS,MAAM;AAAA,QACvB,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AAGA,QAAI,iBAAiB,SAAS;AAC5B,aAAO;AAAA,QACL,SAAS;AAAA,UACP;AAAA,YACE,MAAM;AAAA,YACN,MAAM,KAAK;AAAA,cACT;AAAA,gBACE;AAAA,gBACA,YAAY,GAAG,OAAO,MAAM,YAAY,SAAS;AAAA,gBACjD,cAAc,cAAc,KAAM;AAAA,gBAClC,aAAa,CAAC;AAAA,gBACd,kBAAkB;AAAA,gBAClB,UAAU,CAAC;AAAA,gBACX,QAAQ,CAAC;AAAA,gBACT,MAAM,kDAAkD,iBAAiB,KAAK;AAAA,cAChF;AAAA,cACA;AAAA,cACA;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,KAAK,iBAAiB;AAC5B,UAAM,SAAkC;AAAA,MACtC;AAAA,MACA,YAAY,GAAG,OAAO,MAAM,YAAY,SAAS;AAAA,MACjD,cAAc,cAAc,KAAM;AAAA,MAClC,aAAa,GAAG;AAAA,MAChB,kBAAkB,GAAG;AAAA,MACrB,UAAU,GAAG;AAAA,MACb,QAAQ,GAAG;AAAA,IACb;AAEA,QAAI,GAAG,QAAQ,SAAS,GAAG;AACzB,aAAO,OAAO,GAAG,GAAG,OAAO,MAAM,QAAQ,GAAG,UAAU,UAAU,KAAK,GAAG,OAAO,MAAM,wBAAwB,GAAG,OAAO,KAAK,IAAI,CAAC;AAAA,IACnI;AAEA,WAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,EACvF;AACF;AAIA,OAAO;AAAA,EACL;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,aACE;AAAA,IACF,aAAa;AAAA,MACX,MAAM,EAAE,OAAO,EAAE,SAAS,4BAA4B;AAAA,MACtD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,kCAAkC;AAAA,MACxE,QAAQ,WAAW,SAAS,cAAc;AAAA,IAC5C;AAAA,IACA,aAAa;AAAA,MACX,OAAO;AAAA,MACP,cAAc;AAAA,MACd,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,MAChB,eAAe;AAAA,IACjB;AAAA,EACF;AAAA,EACA,OAAO,EAAE,MAAM,OAAO,OAAO,MAAM;AACjC,QAAI,CAAC,OAAO,UAAU;AACpB,aAAO,cAAc,iBAAiB,+CAA+C;AAAA,IACvF;AAEA,UAAM,SAAS,MAAM,WAAqD,cAAc;AAAA,MACtF,QAAQ,OAAO;AAAA,MACf,QAAQ,OAAO;AAAA,MACf,MAAM,EAAE,MAAM,OAAO,OAAO;AAAA,IAC9B,CAAC;AAED,QAAI,OAAO,SAAS;AAClB,aAAO,SAAS,OAAO,KAAM;AAAA,IAC/B;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,QACP;AAAA,UACE,MAAM;AAAA,UACN,MAAM,KAAK;AAAA,YACT;AAAA,cACE,UAAU,GAAG,OAAO,MAAM,UAAU,OAAO,KAAM,EAAE;AAAA,cACnD,WAAW,OAAO,KAAM;AAAA,YAC1B;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAGA,IAAM,YAAY,IAAI,qBAAqB;AAC3C,MAAM,OAAO,QAAQ,SAAS;","names":["EMAIL_CLIENTS","data","EMAIL_CLIENTS"]}
|
package/package.json
CHANGED
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@emailens/mcp",
|
|
3
|
-
"mcpName": "io.github.emailens/mcp",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"description": "MCP server for email compatibility analysis
|
|
6
|
-
"type": "module",
|
|
7
|
-
"bin": {
|
|
8
|
-
"emailens-mcp": "./dist/index.js"
|
|
9
|
-
},
|
|
10
|
-
"exports": {
|
|
11
|
-
".": {
|
|
12
|
-
"types": "./dist/index.d.ts",
|
|
13
|
-
"import": "./dist/index.js"
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"main": "./dist/index.js",
|
|
17
|
-
"types": "./dist/index.d.ts",
|
|
18
|
-
"files": [
|
|
19
|
-
"dist",
|
|
20
|
-
"LICENSE"
|
|
21
|
-
],
|
|
22
|
-
"scripts": {
|
|
23
|
-
"build": "tsup",
|
|
24
|
-
"dev": "tsup --watch",
|
|
25
|
-
"start": "bun run dist/index.js",
|
|
26
|
-
"typecheck": "tsc --noEmit",
|
|
27
|
-
"prepublishOnly": "bun run build"
|
|
28
|
-
},
|
|
29
|
-
"dependencies": {
|
|
30
|
-
"@emailens/engine": "^0.
|
|
31
|
-
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
32
|
-
"zod": "^3.24.0"
|
|
33
|
-
},
|
|
34
|
-
"devDependencies": {
|
|
35
|
-
"@types/node": "^25.3.0",
|
|
36
|
-
"bun-types": "^1.3.9",
|
|
37
|
-
"tsup": "^8.4.0",
|
|
38
|
-
"typescript": "^5.9.3"
|
|
39
|
-
},
|
|
40
|
-
"keywords": [
|
|
41
|
-
"mcp",
|
|
42
|
-
"model-context-protocol",
|
|
43
|
-
"email",
|
|
44
|
-
"email-testing",
|
|
45
|
-
"compatibility",
|
|
46
|
-
"claude"
|
|
47
|
-
],
|
|
48
|
-
"license": "MIT",
|
|
49
|
-
"repository": {
|
|
50
|
-
"type": "git",
|
|
51
|
-
"url": "git+https://github.com/emailens/mcp.git"
|
|
52
|
-
},
|
|
53
|
-
"engines": {
|
|
54
|
-
"node": ">=18"
|
|
55
|
-
},
|
|
56
|
-
"homepage": "https://github.com/emailens/mcp#readme",
|
|
57
|
-
"bugs": {
|
|
58
|
-
"url": "https://github.com/emailens/mcp/issues"
|
|
59
|
-
}
|
|
60
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@emailens/mcp",
|
|
3
|
+
"mcpName": "io.github.emailens/mcp",
|
|
4
|
+
"version": "0.6.0",
|
|
5
|
+
"description": "MCP server for email compatibility analysis \u2014 preview, analyze, fix, diff, and capture screenshots across 21 email clients via Claude or any MCP-compatible tool.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"emailens-mcp": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"dev": "tsup --watch",
|
|
25
|
+
"start": "bun run dist/index.js",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"prepublishOnly": "bun run build"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@emailens/engine": "^0.10.2",
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
32
|
+
"zod": "^3.24.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^25.3.0",
|
|
36
|
+
"bun-types": "^1.3.9",
|
|
37
|
+
"tsup": "^8.4.0",
|
|
38
|
+
"typescript": "^5.9.3"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"mcp",
|
|
42
|
+
"model-context-protocol",
|
|
43
|
+
"email",
|
|
44
|
+
"email-testing",
|
|
45
|
+
"compatibility",
|
|
46
|
+
"claude"
|
|
47
|
+
],
|
|
48
|
+
"license": "MIT",
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/emailens/mcp.git"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=18"
|
|
55
|
+
},
|
|
56
|
+
"homepage": "https://github.com/emailens/mcp#readme",
|
|
57
|
+
"bugs": {
|
|
58
|
+
"url": "https://github.com/emailens/mcp/issues"
|
|
59
|
+
}
|
|
60
|
+
}
|