@emailens/mcp 0.11.0 → 0.12.1

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 CHANGED
@@ -69,7 +69,7 @@ claude mcp add emailens -- npx -y @emailens/mcp
69
69
  }
70
70
  ```
71
71
 
72
- Get your free API key at [emailens.dev/settings/api-keys](https://emailens.dev/settings/api-keys).
72
+ Get your free API key at [app.emailens.dev/dashboard/api](https://app.emailens.dev/dashboard/api?utm_source=mcp).
73
73
 
74
74
  ### Remote (no install)
75
75
 
@@ -184,6 +184,10 @@ The last three cover what a light desktop preview cannot show: text that disappe
184
184
  | `clients` | string[] | No | Only report these client IDs |
185
185
  | `skip` | string[] | No | Checks to skip (e.g. `["spam", "images"]`) |
186
186
  | `targetingPolicy` | enum | No | `"progressive"` (default), `"strict"`, or `"lenient"` |
187
+ | `emailType` | enum | No | `"transactional"` (no unsubscribe link required) or `"marketing"`. Omitted: guessed from the content |
188
+ | `previousHtml` | string | No | The design to hold this version to, usually your first draft. Omitted: the first version of this email audited in the session. What the edits removed comes back as `designRemovals` |
189
+
190
+ Fix `error` and `warning` findings. `info` findings are not problems: the email falls back safely there, so never remove responsive (`@media`) or dark-mode CSS to clear them.
187
191
 
188
192
  #### `fix_email`
189
193
 
@@ -232,7 +236,7 @@ Capture real email screenshots across 21 clients in real browsers. Screenshots a
232
236
  | `modes` | string[] | No | `["light"]`, `["dark"]`, or `["light", "dark"]` |
233
237
  | `title` | string | No | Name for the preview |
234
238
 
235
- Free plan: 30 previews/day. [Sign up](https://emailens.dev?ref=mcp)
239
+ Free plan: 30 previews/day. [Sign up](https://emailens.dev/?utm_source=mcp)
236
240
 
237
241
  #### `share_preview`
238
242
 
package/dist/index.js CHANGED
@@ -121,6 +121,7 @@ import {
121
121
  estimateAiFixTokens,
122
122
  diffResults,
123
123
  toPlainText,
124
+ checkDesignRetention,
124
125
  EMAIL_CLIENTS as EMAIL_CLIENTS2,
125
126
  MAX_HTML_SIZE
126
127
  } from "@emailens/engine";
@@ -138,6 +139,34 @@ var config = {
138
139
  }
139
140
  };
140
141
 
142
+ // src/drafts.ts
143
+ var MAX_DRAFTS = 200;
144
+ var SAME_EMAIL = 0.7;
145
+ var drafts = /* @__PURE__ */ new Map();
146
+ function title(html) {
147
+ return (html.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1] ?? "").trim().toLowerCase();
148
+ }
149
+ function words(html) {
150
+ const text = html.replace(/<!--[\s\S]*?-->/g, " ").replace(/<(style|script|head)[\s\S]*?<\/\1>/gi, " ").replace(/<[^>]+>/g, " ").toLowerCase();
151
+ return new Set(text.match(/[\p{L}\p{N}]{3,}/gu) ?? []);
152
+ }
153
+ function overlap(a, b) {
154
+ if (!a.size || !b.size) return 0;
155
+ let shared = 0;
156
+ for (const w of a) if (b.has(w)) shared++;
157
+ return shared / (a.size + b.size - shared);
158
+ }
159
+ function firstDraft(html) {
160
+ const key = title(html);
161
+ const seen = words(html);
162
+ const list = drafts.get(key) ?? [];
163
+ const best = list.map((d) => ({ d, score: overlap(d.words, seen) })).sort((a, b) => b.score - a.score)[0];
164
+ if (best && best.score >= SAME_EMAIL) return best.d.html;
165
+ if ([...drafts.values()].reduce((n, l) => n + l.length, 0) >= MAX_DRAFTS) drafts.clear();
166
+ drafts.set(key, [...list, { words: seen, html }]);
167
+ return html;
168
+ }
169
+
141
170
  // src/hosted.ts
142
171
  async function apiRequest(path, options) {
143
172
  const { apiKey, apiUrl, method = "GET", body, timeoutMs = 3e4 } = options;
@@ -157,21 +186,21 @@ async function apiRequest(path, options) {
157
186
  if (response.status === 401) {
158
187
  return {
159
188
  isError: true,
160
- error: "Invalid or revoked API key. Generate a new one at emailens.dev/settings/api-keys?ref=mcp"
189
+ error: "Invalid or revoked API key. Generate a new one at app.emailens.dev/dashboard/api?utm_source=mcp"
161
190
  };
162
191
  }
163
192
  if (response.status === 403) {
164
193
  const data2 = await response.json().catch(() => ({}));
165
194
  return {
166
195
  isError: true,
167
- error: data2.upgrade ? "This feature requires a plan upgrade. Visit emailens.dev/pricing?ref=mcp" : "Access denied. Check your plan at emailens.dev/pricing?ref=mcp"
196
+ error: data2.upgrade ? "This feature requires a plan upgrade. Visit emailens.dev/pricing?utm_source=mcp" : "Access denied. Check your plan at emailens.dev/pricing?utm_source=mcp"
168
197
  };
169
198
  }
170
199
  if (response.status === 429) {
171
200
  const data2 = await response.json().catch(() => ({}));
172
201
  return {
173
202
  isError: true,
174
- error: `Daily quota exceeded${data2.limit ? ` (${data2.limit - (data2.remaining ?? 0)}/${data2.limit})` : ""}. Resets tomorrow. Upgrade at emailens.dev/pricing?ref=mcp`
203
+ error: `Daily quota exceeded${data2.limit ? ` (${data2.limit - (data2.remaining ?? 0)}/${data2.limit})` : ""}. Resets tomorrow. Upgrade at emailens.dev/pricing?utm_source=mcp`
175
204
  };
176
205
  }
177
206
  if (!response.ok) {
@@ -199,8 +228,8 @@ function noApiKeyError(toolName, extraInfo) {
199
228
  const lines = [
200
229
  `${toolName} requires an Emailens API key.`,
201
230
  "",
202
- "1. Sign up free at emailens.dev?ref=mcp",
203
- "2. Create a key at emailens.dev/settings/api-keys?ref=mcp",
231
+ "1. Sign up free at emailens.dev?utm_source=mcp",
232
+ "2. Create a key at app.emailens.dev/dashboard/api?utm_source=mcp",
204
233
  "3. Add to your MCP config:",
205
234
  "",
206
235
  ' "env": { "EMAILENS_API_KEY": "ek_live_..." }'
@@ -306,7 +335,7 @@ function validateHtmlSize(html) {
306
335
  }
307
336
  var server = new McpServer({
308
337
  name: "emailens",
309
- version: "0.11.0"
338
+ version: "0.12.1"
310
339
  });
311
340
  server.registerTool(
312
341
  "preview_email",
@@ -381,8 +410,8 @@ server.registerTool(
381
410
  {}
382
411
  )
383
412
  };
384
- if (!config.isHosted && overallScore < 90) {
385
- result.tip = "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";
413
+ if (!config.isHosted) {
414
+ result.tip = overallScore < 90 ? "Capture real screenshots to verify these issues across clients. Add EMAILENS_API_KEY to your MCP config \u2192 app.emailens.dev/dashboard/api?utm_source=mcp" : "Checked by Emailens against 21 email clients. Confirm on real Outlook, Gmail and Apple Mail \u2192 emailens.dev/real-devices?utm_source=mcp";
386
415
  }
387
416
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
388
417
  }
@@ -391,7 +420,7 @@ server.registerTool(
391
420
  "analyze_email",
392
421
  {
393
422
  title: "Analyze Email",
394
- description: "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.).",
423
+ description: "Fix error and warning findings. Info findings are not problems (the email falls back safely there): never remove responsive (@media) or dark-mode CSS to clear them. 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.).",
395
424
  inputSchema: {
396
425
  html: z.string().describe("The email source: HTML, or an MJML / Maizzle / React Email template when `format` says so"),
397
426
  format: formatEnum.describe("Source format: 'html' (default), 'jsx' (React Email), 'mjml', or 'maizzle' (HTML template or Vue single-file component). Anything but 'html' is compiled before analysis and sets the syntax of the fix snippets. A pasted Vue file with no format is compiled as maizzle."),
@@ -442,14 +471,16 @@ server.registerTool(
442
471
  "audit_email",
443
472
  {
444
473
  title: "Audit Email",
445
- description: "Comprehensive email quality audit: CSS compatibility, spam scoring, link validation, accessibility, images, inbox preview, size (Gmail clipping), template variables, content overflow, visual fallbacks, dark-mode and mobile text contrast, design consistency, structural faults in Outlook-only VML (which lives inside conditional comments and is invisible to every other check), style survival: CSS a client parses correctly and then discards, such as a stylesheet past Gmail's 16 KB ceiling or every rule after a `}}`, which no support matrix can express, and client targeting: HowToTarget hacks such as Gmail u+.body or MSO conditionals, which do not score as css-hack. 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, targetingPolicy to change how targeted scopes affect scores, and clients to narrow which clients are reported.",
474
+ description: "Fix error and warning findings, and restore anything listed in designRemovals. Info findings are not problems (the email falls back safely there): never remove responsive (@media), dark-mode CSS or rounded corners to clear them. designRemovals compares this version with the first one audited for the same email (or with previousHtml): design the edits deleted, such as @media rules, dark mode, corners, gradients, VML fallbacks or images. Comprehensive email quality audit: CSS compatibility, spam scoring, link validation, accessibility, images, inbox preview, size (Gmail clipping), template variables, content overflow, visual fallbacks, dark-mode and mobile text contrast, design consistency, structural faults in Outlook-only VML (which lives inside conditional comments and is invisible to every other check), style survival: CSS a client parses correctly and then discards, such as a stylesheet past Gmail's 16 KB ceiling or every rule after a `}}`, which no support matrix can express, and client targeting: HowToTarget hacks such as Gmail u+.body or MSO conditionals, which do not score as css-hack. 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, targetingPolicy to change how targeted scopes affect scores, and clients to narrow which clients are reported.",
446
475
  inputSchema: {
447
476
  html: z.string().describe("The email source: HTML, or an MJML / Maizzle / React Email template when `format` says so"),
448
477
  format: formatEnum.describe("Source format: 'html' (default), 'jsx' (React Email), 'mjml', or 'maizzle' (HTML template or Vue single-file component). Anything but 'html' is compiled before analysis and sets the syntax of the fix snippets. A pasted Vue file with no format is compiled as maizzle."),
449
478
  skip: z.array(skipEnum).optional().describe("Checks to skip (e.g. ['spam', 'images'])"),
450
479
  detail: detailEnum,
451
480
  clients: clientsParam,
452
- targetingPolicy: targetingPolicyEnum
481
+ targetingPolicy: targetingPolicyEnum,
482
+ emailType: z.enum(["marketing", "transactional"]).optional().describe("'transactional' (receipts, password resets, codes, alerts: no unsubscribe link required) or 'marketing'. Omitted: guessed from the content, which misses many transactional emails."),
483
+ previousHtml: z.string().optional().describe("The design to hold this version to, usually your first draft. Omitted: the first version of this email audited in this session (matched by <title> and text).")
453
484
  },
454
485
  annotations: {
455
486
  title: "Audit Email",
@@ -459,7 +490,7 @@ server.registerTool(
459
490
  openWorldHint: false
460
491
  }
461
492
  },
462
- async ({ html, format, skip, detail, clients, targetingPolicy }) => {
493
+ async ({ html, format, skip, detail, clients, targetingPolicy, emailType, previousHtml }) => {
463
494
  const sizeError = validateHtmlSize(html);
464
495
  if (sizeError) return sizeError;
465
496
  const clientError = validateClients(clients);
@@ -471,8 +502,10 @@ server.registerTool(
471
502
  positions: positionsApply(resolveSourceFormat(html, format)),
472
503
  targetingPolicy
473
504
  });
474
- const report = session.audit({ skip });
505
+ const report = session.audit({ skip, ...emailType ? { spam: { emailType } } : {} });
475
506
  const overallCompatibility = worstScore(report.compatibility.scores);
507
+ const baseline = previousHtml ? await toHtml(previousHtml, format) : { ok: true, html: firstDraft(source.html) };
508
+ const designRemovals = baseline.ok ? checkDesignRetention(baseline.html, source.html) : [];
476
509
  return {
477
510
  content: [
478
511
  {
@@ -480,6 +513,7 @@ server.registerTool(
480
513
  text: JSON.stringify(
481
514
  {
482
515
  overallCompatibility,
516
+ designRemovals,
483
517
  compatibility: {
484
518
  scores: report.compatibility.scores,
485
519
  ...compatibilityPayload(report.compatibility.warnings, detail, clients)
@@ -745,7 +779,7 @@ server.registerTool(
745
779
  "capture_screenshots",
746
780
  {
747
781
  title: "Capture Screenshots",
748
- 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.",
782
+ 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?utm_source=mcp.",
749
783
  inputSchema: {
750
784
  html: z.string().describe("The email source: HTML, or an MJML / Maizzle / React Email template when `format` says so"),
751
785
  format: formatEnum.describe("Input format"),
@@ -761,14 +795,14 @@ server.registerTool(
761
795
  openWorldHint: true
762
796
  }
763
797
  },
764
- async ({ html, format, clients, modes, title }) => {
798
+ async ({ html, format, clients, modes, title: title2 }) => {
765
799
  if (!config.isHosted) {
766
800
  return noApiKeyError("capture_screenshots");
767
801
  }
768
802
  const opts = { apiKey: config.apiKey, apiUrl: config.apiUrl };
769
803
  const previewResult = await apiRequest("/api/preview", {
770
804
  ...opts,
771
- body: { html, format, clients, title },
805
+ body: { html, format, clients, title: title2, source: "mcp" },
772
806
  timeoutMs: 3e4
773
807
  });
774
808
  if (previewResult.isError) {
@@ -844,14 +878,14 @@ server.registerTool(
844
878
  openWorldHint: true
845
879
  }
846
880
  },
847
- async ({ html, title, format }) => {
881
+ async ({ html, title: title2, format }) => {
848
882
  if (!config.isHosted) {
849
883
  return noApiKeyError("share_preview", "Shareable links require the Dev plan ($9/mo).");
850
884
  }
851
885
  const result = await apiRequest("/api/share", {
852
886
  apiKey: config.apiKey,
853
887
  apiUrl: config.apiUrl,
854
- body: { html, title, format }
888
+ body: { html, title: title2, format }
855
889
  });
856
890
  if (result.isError) {
857
891
  return mcpError(result.error);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/findings.ts","../src/compile.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 { resolveSourceFormat, toHtml } from \"./compile.js\";\r\nimport {\r\n createSession,\r\n analyzeEmail,\r\n generateCompatibilityScore,\r\n simulateDarkMode,\r\n transformForAllClients,\r\n transformForClient,\r\n generateFixPrompt,\r\n estimateAiFixTokens,\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\n/** The headline is the worst client. A mean of 21 clients hides the one that fails. */\r\nfunction worstScore(scores: Record<string, { score: number }>): number {\r\n const values = Object.values(scores);\r\n if (values.length === 0) return 0;\r\n return Math.min(...values.map((s) => s.score));\r\n}\r\n\r\n/**\r\n * The language the source is written in.\r\n *\r\n * Anything but `html` is compiled before analysis and also selects the syntax\r\n * the fix snippets are written in.\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 targetingPolicyEnum = z\r\n .enum([\"progressive\", \"strict\", \"lenient\"])\r\n .optional()\r\n .describe(\r\n \"'progressive' (default) suppresses compatibility false-positives inside client-targeted scopes and only flags deprecated or dangerous hacks. 'strict' treats targeting as non-standard and reports every hack. 'lenient' suppresses false positives and ignores non-fatal targeting warnings.\",\r\n );\r\n\r\nconst skipEnum = z.enum([\r\n \"spam\",\r\n \"links\",\r\n \"accessibility\",\r\n \"images\",\r\n \"compatibility\",\r\n \"inboxPreview\",\r\n \"size\",\r\n \"templateVariables\",\r\n \"overflow\",\r\n \"visual\",\r\n \"darkContrast\",\r\n \"mobileContrast\",\r\n \"design\",\r\n \"vml\",\r\n \"styleSurvival\",\r\n \"targeting\",\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.11.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 source: HTML, or an MJML / Maizzle / React Email template when `format` says so\"),\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(\"Source format: 'html' (default), 'jsx' (React Email), 'mjml', or 'maizzle' (HTML template or Vue single-file component). Anything but 'html' is compiled before analysis and sets the syntax of the fix snippets. A pasted Vue file with no format is compiled as maizzle.\"),\r\n targetingPolicy: targetingPolicyEnum,\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, targetingPolicy }) => {\r\n const sizeError = validateHtmlSize(html);\r\n if (sizeError) return sizeError;\r\n\r\n const source = await toHtml(html, format);\r\n if (!source.ok) return mcpError(source.message);\r\n\r\n const validClientIds = new Set(EMAIL_CLIENTS.map((c) => c.id));\r\n const framework = toFramework(resolveSourceFormat(html, format));\r\n const session = createSession(source.html, { framework, targetingPolicy });\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) => transformForClient(source.html, c, framework));\r\n } else {\r\n transforms = transformForAllClients(source.html, framework);\r\n }\r\n\r\n const warnings = session.analyze();\r\n const scores = generateCompatibilityScore(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] = simulateDarkMode(source.html, t.clientId);\r\n }\r\n\r\n const overallScore = worstScore(scores);\r\n\r\n const plainText = toPlainText(source.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 source: HTML, or an MJML / Maizzle / React Email template when `format` says so\"),\r\n format: formatEnum.describe(\"Source format: 'html' (default), 'jsx' (React Email), 'mjml', or 'maizzle' (HTML template or Vue single-file component). Anything but 'html' is compiled before analysis and sets the syntax of the fix snippets. A pasted Vue file with no format is compiled as maizzle.\"),\r\n detail: detailEnum,\r\n clients: clientsParam,\r\n targetingPolicy: targetingPolicyEnum,\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, targetingPolicy }) => {\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 source = await toHtml(html, format);\r\n if (!source.ok) return mcpError(source.message);\r\n\r\n const warnings = analyzeEmail(source.html, toFramework(resolveSourceFormat(html, format)), {\r\n positions: positionsApply(resolveSourceFormat(html, format)),\r\n targetingPolicy,\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 overallScore = worstScore(scores);\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), template variables, content overflow, visual fallbacks, dark-mode and mobile text contrast, design consistency, structural faults in Outlook-only VML (which lives inside conditional comments and is invisible to every other check), style survival: CSS a client parses correctly and then discards, such as a stylesheet past Gmail's 16 KB ceiling or every rule after a `}}`, which no support matrix can express, and client targeting: HowToTarget hacks such as Gmail u+.body or MSO conditionals, which do not score as css-hack. 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, targetingPolicy to change how targeted scopes affect scores, and clients to narrow which clients are reported.\",\r\n inputSchema: {\r\n html: z.string().describe(\"The email source: HTML, or an MJML / Maizzle / React Email template when `format` says so\"),\r\n format: formatEnum.describe(\"Source format: 'html' (default), 'jsx' (React Email), 'mjml', or 'maizzle' (HTML template or Vue single-file component). Anything but 'html' is compiled before analysis and sets the syntax of the fix snippets. A pasted Vue file with no format is compiled as maizzle.\"),\r\n skip: z\r\n .array(skipEnum)\r\n .optional()\r\n .describe(\"Checks to skip (e.g. ['spam', 'images'])\"),\r\n detail: detailEnum,\r\n clients: clientsParam,\r\n targetingPolicy: targetingPolicyEnum,\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, targetingPolicy }) => {\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 source = await toHtml(html, format);\r\n if (!source.ok) return mcpError(source.message);\r\n\r\n const session = createSession(source.html, {\r\n framework: toFramework(resolveSourceFormat(html, format)),\r\n positions: positionsApply(resolveSourceFormat(html, format)),\r\n targetingPolicy,\r\n });\r\n const report = session.audit({ skip });\r\n\r\n const overallCompatibility = worstScore(report.compatibility.scores);\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 vml: report.vml,\r\n darkContrast: report.darkContrast,\r\n mobileContrast: report.mobileContrast,\r\n design: report.design,\r\n styleSurvival: report.styleSurvival,\r\n targeting: report.targeting,\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 source: HTML, or an MJML / Maizzle / React Email template when `format` says so: the source to fix\"),\r\n format: formatEnum.describe(\"Source format: 'html' (default), 'jsx' (React Email), 'mjml', or 'maizzle' (HTML template or Vue single-file component). Anything but 'html' is compiled before analysis and sets the syntax of the fix snippets. A pasted Vue file with no format is compiled as maizzle.\"),\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 // The findings come from the compiled output, because that is what a\r\n // client renders. The prompt keeps the caller's own source, because that is\r\n // the file they will edit; a fix written against generated HTML is not a\r\n // fix they can apply.\r\n const source = await toHtml(html, format);\r\n if (!source.ok) return mcpError(source.message);\r\n\r\n const resolved = resolveSourceFormat(html, format) as\r\n | \"html\"\r\n | \"jsx\"\r\n | \"mjml\"\r\n | \"maizzle\";\r\n const framework = toFramework(resolved);\r\n const fixScope = scope === \"current\" ? \"current\" : \"all\";\r\n const inputFormat = resolved;\r\n\r\n const warnings = analyzeEmail(source.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 = warnings.filter((w) => w.fixType === \"structural\");\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 source\"),\r\n after: z.string().describe(\"Modified email source, in the same format\"),\r\n format: formatEnum.describe(\"Source format: 'html' (default), 'jsx' (React Email), 'mjml', or 'maizzle' (HTML template or Vue single-file component). Anything but 'html' is compiled before analysis and sets the syntax of the fix snippets. A pasted Vue file with no format is compiled as maizzle.\"),\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 beforeSource = await toHtml(before, format);\r\n if (!beforeSource.ok) return mcpError(beforeSource.message);\r\n const afterSource = await toHtml(after, format);\r\n if (!afterSource.ok) return mcpError(afterSource.message);\r\n\r\n const framework = toFramework(\r\n resolveSourceFormat(before, format) === \"maizzle\" ||\r\n resolveSourceFormat(after, format) === \"maizzle\"\r\n ? (format ?? \"maizzle\")\r\n : (format ?? \"html\"),\r\n );\r\n\r\n const beforeWarnings = analyzeEmail(beforeSource.html, framework);\r\n const beforeScores = generateCompatibilityScore(beforeWarnings);\r\n\r\n const afterWarnings = analyzeEmail(afterSource.html, 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 source: HTML, or an MJML / Maizzle / React Email template when `format` says so\"),\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 source: HTML, or an MJML / Maizzle / React Email template when `format` says so\"),\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// ── Hosted Tool: sandbox_get_inbox ──────────────────────────────────\r\n\r\nserver.registerTool(\r\n \"sandbox_get_inbox\",\r\n {\r\n title: \"Get Sandbox Inbox\",\r\n description:\r\n \"Get your active Emailens sandbox test address (or create a new one). Send emails here via SMTP (smtp.emailens.dev:2525) or inbound domain routing. Requires EMAILENS_API_KEY.\",\r\n inputSchema: {\r\n name: z.string().optional().describe(\"Optional inbox name (e.g. 'Auth E2E Test')\"),\r\n },\r\n annotations: {\r\n title: \"Get Sandbox Inbox\",\r\n readOnlyHint: false,\r\n destructiveHint: false,\r\n idempotentHint: false,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ name }) => {\r\n if (!config.isHosted) {\r\n return noApiKeyError(\"sandbox_get_inbox\", \"Email sandboxing requires an Emailens account and API key.\");\r\n }\r\n\r\n const result = await apiRequest<{\r\n inbox?: { id: string; name: string; createdAt: string; lastReceivedAt: string | null };\r\n address?: string;\r\n }>(\"/api/sandbox/v1/inbox\", {\r\n apiKey: config.apiKey!,\r\n apiUrl: config.apiUrl,\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(result.data, null, 2),\r\n },\r\n ],\r\n };\r\n },\r\n);\r\n\r\n// ── Hosted Tool: sandbox_wait_for_email ─────────────────────────────\r\n\r\nserver.registerTool(\r\n \"sandbox_wait_for_email\",\r\n {\r\n title: \"Wait for Sandbox Email\",\r\n description:\r\n \"Poll and wait for a matching incoming test email in the sandbox. Automatically extracts 4-8 digit OTP codes and action links for zero-regex test assertions. Requires EMAILENS_API_KEY.\",\r\n inputSchema: {\r\n inboxId: z.string().optional().describe(\"Optional inbox ID filter\"),\r\n subject: z.string().optional().describe(\"Substring match for email subject (case-insensitive)\"),\r\n sender: z.string().optional().describe(\"Substring match for sender address (case-insensitive)\"),\r\n timeoutSeconds: z.number().optional().describe(\"Max seconds to wait (default: 30, max: 60)\"),\r\n },\r\n annotations: {\r\n title: \"Wait for Sandbox Email\",\r\n readOnlyHint: true,\r\n destructiveHint: false,\r\n idempotentHint: false,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ inboxId, subject, sender, timeoutSeconds = 30 }) => {\r\n if (!config.isHosted) {\r\n return noApiKeyError(\"sandbox_wait_for_email\", \"Email sandboxing requires an Emailens account and API key.\");\r\n }\r\n\r\n const params = new URLSearchParams();\r\n if (inboxId) params.set(\"inboxId\", inboxId);\r\n if (subject) params.set(\"subject\", subject);\r\n if (sender) params.set(\"sender\", sender);\r\n params.set(\"timeout\", String(timeoutSeconds));\r\n\r\n const result = await apiRequest<{\r\n message: {\r\n id: string;\r\n subject: string;\r\n sender: string;\r\n otp?: string | null;\r\n actionUrl?: string | null;\r\n links?: Array<{ href: string; text?: string }>;\r\n receivedAt: string;\r\n };\r\n }>(`/api/sandbox/v1/messages/wait?${params.toString()}`, {\r\n apiKey: config.apiKey!,\r\n apiUrl: config.apiUrl,\r\n timeoutMs: (timeoutSeconds + 5) * 1000,\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(result.data?.message ?? result.data, null, 2),\r\n },\r\n ],\r\n };\r\n },\r\n);\r\n\r\n// ── Hosted Tool: sandbox_list_messages ──────────────────────────────\r\n\r\nserver.registerTool(\r\n \"sandbox_list_messages\",\r\n {\r\n title: \"List Sandbox Messages\",\r\n description:\r\n \"List recently received test emails in your sandbox inboxes. Requires EMAILENS_API_KEY.\",\r\n inputSchema: {\r\n inboxId: z.string().optional().describe(\"Optional inbox ID filter\"),\r\n },\r\n annotations: {\r\n title: \"List Sandbox Messages\",\r\n readOnlyHint: true,\r\n destructiveHint: false,\r\n idempotentHint: true,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ inboxId }) => {\r\n if (!config.isHosted) {\r\n return noApiKeyError(\"sandbox_list_messages\", \"Email sandboxing requires an Emailens account and API key.\");\r\n }\r\n\r\n const params = inboxId ? `?inboxId=${encodeURIComponent(inboxId)}` : \"\";\r\n const result = await apiRequest<{ messages: unknown[] }>(`/api/sandbox/messages${params}`, {\r\n apiKey: config.apiKey!,\r\n apiUrl: config.apiUrl,\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(result.data, null, 2),\r\n },\r\n ],\r\n };\r\n },\r\n);\r\n\r\n// ── Hosted Tool: sandbox_get_message ────────────────────────────────\r\n\r\nserver.registerTool(\r\n \"sandbox_get_message\",\r\n {\r\n title: \"Get Sandbox Message\",\r\n description:\r\n \"Get complete captured email details including HTML/text parts, extracted OTP, magic links, headers, and spam analysis. Requires EMAILENS_API_KEY.\",\r\n inputSchema: {\r\n messageId: z.string().describe(\"ID of the sandbox message\"),\r\n },\r\n annotations: {\r\n title: \"Get Sandbox Message\",\r\n readOnlyHint: true,\r\n destructiveHint: false,\r\n idempotentHint: true,\r\n openWorldHint: true,\r\n },\r\n },\r\n async ({ messageId }) => {\r\n if (!config.isHosted) {\r\n return noApiKeyError(\"sandbox_get_message\", \"Email sandboxing requires an Emailens account and API key.\");\r\n }\r\n\r\n const result = await apiRequest<{ message: unknown }>(`/api/sandbox/messages/${encodeURIComponent(messageId)}`, {\r\n apiKey: config.apiKey!,\r\n apiUrl: config.apiUrl,\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(result.data, null, 2),\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","import { compile } from \"@emailens/engine/compile\";\n\n/**\n * Getting a template to HTML before anything looks at it.\n *\n * The `format` parameter used to do nothing but choose the syntax of the fix\n * snippets, while the source was handed to the analyzer as-is. That is a quiet\n * and expensive lie: cheerio parses `<mjml><mj-section>…` without complaint,\n * finds no CSS in it, and the tools answer **zero findings**. An assistant\n * reads that as a clean bill of health and says the campaign is fine. A wrong\n * answer delivered confidently is worse than an error.\n *\n * So a non-HTML format is compiled first, with the same compilers the CLI uses.\n * They are optional peer dependencies of the engine (MJML alone is 56MB, and\n * this server is usually run through `npx`), so a caller can land on a machine\n * that has none of them. That case gets the engine's own \"install this\" message\n * plus the way out that needs nothing installed: compile it yourself and send\n * the HTML.\n */\n\nexport type SourceResult = { ok: true; html: string } | { ok: false; message: string };\n\nconst COMPILED: ReadonlySet<string> = new Set([\"jsx\", \"mjml\", \"maizzle\"]);\n\n/**\n * Is this \"the package isn't here\" rather than \"the template is wrong\"?\n *\n * The engine says `MJML compilation requires \"mjml\"` for a missing compiler\n * and `Sandbox strategy \"isolated-vm\" requires the \"isolated-vm\" package` when\n * the compiler is present but its sandbox is not, which is what React Email\n * hits first. Both are the same problem for the caller, so one pattern covers\n * them. Getting this wrong is cheap in one direction only: a misclassified\n * template error gains an irrelevant sentence, while a misclassified missing\n * package would tell someone to debug markup that is fine.\n */\nfunction isMissingCompiler(message: string): boolean {\n return /requires (?:the )?\"/.test(message);\n}\n\n/**\n * Compile `source` when `format` names a template language, or pass it through\n * when it is already HTML.\n */\nfunction isVueSfc(source: string): boolean {\n return /<template[\\s>]/i.test(source) && /<\\/template>/i.test(source);\n}\n\n/** An omitted format on a Vue SFC is maizzle. An explicit format is kept. */\nexport function resolveSourceFormat(source: string, format?: string): string {\n if (format) return format;\n return isVueSfc(source) ? \"maizzle\" : \"html\";\n}\n\nexport async function toHtml(source: string, format?: string): Promise<SourceResult> {\n const resolved = resolveSourceFormat(source, format);\n if (resolved === \"html\") return { ok: true, html: source };\n if (!COMPILED.has(resolved)) {\n return { ok: false, message: `Unknown format \"${resolved}\". Use html, jsx, mjml, or maizzle.` };\n }\n\n try {\n return { ok: true, html: await compile(source, resolved as \"jsx\" | \"mjml\" | \"maizzle\") };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (isMissingCompiler(message)) {\n return {\n ok: false,\n message:\n `${message}\\n\\nThat has to happen where this MCP server runs, which may not be somewhere ` +\n `you can install to. If it is not, compile the template yourself and send the resulting ` +\n `HTML with format \"html\".`,\n };\n }\n return { ok: false, message };\n }\n}\n","export const config = {\r\n get apiKey(): string | null {\r\n return process.env.EMAILENS_API_KEY ?? null;\r\n },\r\n get apiUrl(): string {\r\n // The app host, not the apex: emailens.dev redirects /api/* here, and a\r\n // redirect to another host drops the Authorization header on the way.\r\n return process.env.EMAILENS_API_URL ?? \"https://app.emailens.dev\";\r\n },\r\n get isHosted(): boolean {\r\n return this.apiKey !== null;\r\n },\r\n};\r\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":["EMAIL_CLIENTS","data","EMAIL_CLIENTS"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/findings.ts","../src/compile.ts","../src/config.ts","../src/drafts.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 { collapseWarnings, forClients, knownClientIds } from \"./findings.js\";\nimport { resolveSourceFormat, toHtml } from \"./compile.js\";\nimport {\n createSession,\n analyzeEmail,\n generateCompatibilityScore,\n simulateDarkMode,\n transformForAllClients,\n transformForClient,\n generateFixPrompt,\n estimateAiFixTokens,\n diffResults,\n toPlainText,\n checkDesignRetention,\n EMAIL_CLIENTS,\n MAX_HTML_SIZE,\n type Framework,\n type CSSWarning,\n type SourceLocation,\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 { firstDraft } from \"./drafts.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\n/** The headline is the worst client. A mean of 21 clients hides the one that fails. */\nfunction worstScore(scores: Record<string, { score: number }>): number {\n const values = Object.values(scores);\n if (values.length === 0) return 0;\n return Math.min(...values.map((s) => s.score));\n}\n\n/**\n * The language the source is written in.\n *\n * Anything but `html` is compiled before analysis and also selects the syntax\n * the fix snippets are written in.\n */\nconst formatEnum = z.enum([\"html\", \"jsx\", \"mjml\", \"maizzle\"]).optional();\n\n/**\n * How much compatibility detail to return.\n *\n * `summary` collapses the engine's per-client, per-selector warnings into one\n * entry per problem listing the clients it affects: the same information at a\n * fraction of the tokens. `full` is the engine's own shape, per client, with\n * fix snippets, for a caller that wants everything.\n */\nconst detailEnum = z\n .enum([\"summary\", \"full\"])\n .optional()\n .describe(\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.\",\n );\n\nconst targetingPolicyEnum = z\n .enum([\"progressive\", \"strict\", \"lenient\"])\n .optional()\n .describe(\n \"'progressive' (default) suppresses compatibility false-positives inside client-targeted scopes and only flags deprecated or dangerous hacks. 'strict' treats targeting as non-standard and reports every hack. 'lenient' suppresses false positives and ignores non-fatal targeting warnings.\",\n );\n\nconst skipEnum = z.enum([\n \"spam\",\n \"links\",\n \"accessibility\",\n \"images\",\n \"compatibility\",\n \"inboxPreview\",\n \"size\",\n \"templateVariables\",\n \"overflow\",\n \"visual\",\n \"darkContrast\",\n \"mobileContrast\",\n \"design\",\n \"vml\",\n \"styleSurvival\",\n \"targeting\",\n]);\n\nconst clientsParam = z\n .array(z.string())\n .optional()\n .describe(\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.\",\n );\n\n/**\n * Reject a client id the engine does not know.\n *\n * Filtering to a typo would return an empty list, which reads as \"this email\n * is fine for that client\"; the most misleading answer the tool can give.\n */\nfunction validateClients(clients: string[] | undefined) {\n if (!clients?.length) return undefined;\n const known = new Set(knownClientIds());\n const unknown = clients.filter((c) => !known.has(c));\n if (!unknown.length) return undefined;\n return mcpError(\n `Unknown client ${unknown.length > 1 ? \"IDs\" : \"ID\"}: ${unknown.join(\", \")}. Call list_clients for the valid IDs.`,\n );\n}\n\n/** The compatibility payload, at the requested level of detail. */\nfunction compatibilityPayload(\n warnings: CSSWarning[],\n detail: \"summary\" | \"full\" | undefined,\n clients: string[] | undefined,\n) {\n const scoped = forClients(warnings, clients);\n if (detail === \"full\") {\n return {\n warningCount: scoped.length,\n warnings: scoped.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 ...withPositions(w),\n })),\n };\n }\n const findings = collapseWarnings(scoped);\n return {\n warningCount: scoped.length,\n findingCount: findings.length,\n findings,\n };\n}\n\n/**\n * Do source positions refer to the code the caller handed us?\n *\n * Only for plain HTML. JSX, MJML and Maizzle are compiled before analysis, so a\n * position would point into generated output: worse than none, because an\n * agent would edit the wrong line with confidence.\n */\nfunction positionsApply(format?: string): boolean {\n return (format ?? \"html\") === \"html\";\n}\n\n/**\n * The position fields a finding carries, when it has them.\n *\n * Deliberately compact: this response is read by a model paying for every\n * token, and a newsletter can produce a thousand occurrences. The first one\n * gets a full position, enough to edit it, and the rest are reduced to line\n * numbers, which is all an assistant needs to go find them. Carrying every\n * occurrence in full nearly doubled the payload on a real fixture.\n */\nfunction withPositions<T extends {\n loc?: SourceLocation;\n locs?: SourceLocation[];\n locsTruncated?: boolean;\n}>(w: T) {\n if (!w.loc) return {};\n const { line, column, offset, length } = w.loc;\n const others = (w.locs ?? []).slice(1).map((l) => l.line);\n return {\n loc: { line, column, offset, length },\n ...(others.length ? { alsoAtLines: others } : {}),\n ...(w.locsTruncated ? { locsTruncated: true } : {}),\n };\n}\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.12.1\",\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 ${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.`,\n inputSchema: {\n html: z.string().describe(\"The email source: HTML, or an MJML / Maizzle / React Email template when `format` says so\"),\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(\"Source format: 'html' (default), 'jsx' (React Email), 'mjml', or 'maizzle' (HTML template or Vue single-file component). Anything but 'html' is compiled before analysis and sets the syntax of the fix snippets. A pasted Vue file with no format is compiled as maizzle.\"),\n targetingPolicy: targetingPolicyEnum,\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, targetingPolicy }) => {\n const sizeError = validateHtmlSize(html);\n if (sizeError) return sizeError;\n\n const source = await toHtml(html, format);\n if (!source.ok) return mcpError(source.message);\n\n const validClientIds = new Set(EMAIL_CLIENTS.map((c) => c.id));\n const framework = toFramework(resolveSourceFormat(html, format));\n const session = createSession(source.html, { framework, targetingPolicy });\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) => transformForClient(source.html, c, framework));\n } else {\n transforms = transformForAllClients(source.html, framework);\n }\n\n const warnings = session.analyze();\n const scores = generateCompatibilityScore(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] = simulateDarkMode(source.html, t.clientId);\n }\n\n const overallScore = worstScore(scores);\n\n const plainText = toPlainText(source.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 when no API key: the fix route on issues, and on a clean\n // result the name of who checked it, so success is attributable too.\n if (!config.isHosted) {\n result.tip = overallScore < 90\n ? \"Capture real screenshots to verify these issues across clients. Add EMAILENS_API_KEY to your MCP config \\u2192 app.emailens.dev/dashboard/api?utm_source=mcp\"\n : \"Checked by Emailens against 21 email clients. Confirm on real Outlook, Gmail and Apple Mail \\u2192 emailens.dev/real-devices?utm_source=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 \"Fix error and warning findings. Info findings are not problems (the email falls back safely there): never remove responsive (@media) or dark-mode CSS to clear them. 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.).\",\n inputSchema: {\n html: z.string().describe(\"The email source: HTML, or an MJML / Maizzle / React Email template when `format` says so\"),\n format: formatEnum.describe(\"Source format: 'html' (default), 'jsx' (React Email), 'mjml', or 'maizzle' (HTML template or Vue single-file component). Anything but 'html' is compiled before analysis and sets the syntax of the fix snippets. A pasted Vue file with no format is compiled as maizzle.\"),\n detail: detailEnum,\n clients: clientsParam,\n targetingPolicy: targetingPolicyEnum,\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, detail, clients, targetingPolicy }) => {\n const sizeError = validateHtmlSize(html);\n if (sizeError) return sizeError;\n const clientError = validateClients(clients);\n if (clientError) return clientError;\n\n const source = await toHtml(html, format);\n if (!source.ok) return mcpError(source.message);\n\n const warnings = analyzeEmail(source.html, toFramework(resolveSourceFormat(html, format)), {\n positions: positionsApply(resolveSourceFormat(html, format)),\n targetingPolicy,\n });\n // Scores stay whole-email: filtering to two clients should narrow what is\n // reported, not silently change what the email scores.\n const scores = generateCompatibilityScore(warnings);\n\n const overallScore = worstScore(scores);\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(\n {\n overallScore,\n scores,\n ...compatibilityPayload(warnings, detail, clients),\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 \"Fix error and warning findings, and restore anything listed in designRemovals. Info findings are not problems (the email falls back safely there): never remove responsive (@media), dark-mode CSS or rounded corners to clear them. designRemovals compares this version with the first one audited for the same email (or with previousHtml): design the edits deleted, such as @media rules, dark mode, corners, gradients, VML fallbacks or images. Comprehensive email quality audit: CSS compatibility, spam scoring, link validation, accessibility, images, inbox preview, size (Gmail clipping), template variables, content overflow, visual fallbacks, dark-mode and mobile text contrast, design consistency, structural faults in Outlook-only VML (which lives inside conditional comments and is invisible to every other check), style survival: CSS a client parses correctly and then discards, such as a stylesheet past Gmail's 16 KB ceiling or every rule after a `}}`, which no support matrix can express, and client targeting: HowToTarget hacks such as Gmail u+.body or MSO conditionals, which do not score as css-hack. 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, targetingPolicy to change how targeted scopes affect scores, and clients to narrow which clients are reported.\",\n inputSchema: {\n html: z.string().describe(\"The email source: HTML, or an MJML / Maizzle / React Email template when `format` says so\"),\n format: formatEnum.describe(\"Source format: 'html' (default), 'jsx' (React Email), 'mjml', or 'maizzle' (HTML template or Vue single-file component). Anything but 'html' is compiled before analysis and sets the syntax of the fix snippets. A pasted Vue file with no format is compiled as maizzle.\"),\n skip: z\n .array(skipEnum)\n .optional()\n .describe(\"Checks to skip (e.g. ['spam', 'images'])\"),\n detail: detailEnum,\n clients: clientsParam,\n targetingPolicy: targetingPolicyEnum,\n emailType: z\n .enum([\"marketing\", \"transactional\"])\n .optional()\n .describe(\"'transactional' (receipts, password resets, codes, alerts: no unsubscribe link required) or 'marketing'. Omitted: guessed from the content, which misses many transactional emails.\"),\n previousHtml: z\n .string()\n .optional()\n .describe(\"The design to hold this version to, usually your first draft. Omitted: the first version of this email audited in this session (matched by <title> and text).\"),\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, detail, clients, targetingPolicy, emailType, previousHtml }) => {\n const sizeError = validateHtmlSize(html);\n if (sizeError) return sizeError;\n const clientError = validateClients(clients);\n if (clientError) return clientError;\n\n const source = await toHtml(html, format);\n if (!source.ok) return mcpError(source.message);\n\n const session = createSession(source.html, {\n framework: toFramework(resolveSourceFormat(html, format)),\n positions: positionsApply(resolveSourceFormat(html, format)),\n targetingPolicy,\n });\n const report = session.audit({ skip, ...(emailType ? { spam: { emailType } } : {}) });\n\n const overallCompatibility = worstScore(report.compatibility.scores);\n const baseline = previousHtml ? (await toHtml(previousHtml, format)) : { ok: true as const, html: firstDraft(source.html) };\n const designRemovals = baseline.ok ? checkDesignRetention(baseline.html, source.html) : [];\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(\n {\n overallCompatibility,\n designRemovals,\n compatibility: {\n scores: report.compatibility.scores,\n ...compatibilityPayload(report.compatibility.warnings, detail, clients),\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 overflow: report.overflow,\n visual: report.visual,\n vml: report.vml,\n darkContrast: report.darkContrast,\n mobileContrast: report.mobileContrast,\n design: report.design,\n styleSurvival: report.styleSurvival,\n targeting: report.targeting,\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 source: HTML, or an MJML / Maizzle / React Email template when `format` says so: the source to fix\"),\n format: formatEnum.describe(\"Source format: 'html' (default), 'jsx' (React Email), 'mjml', or 'maizzle' (HTML template or Vue single-file component). Anything but 'html' is compiled before analysis and sets the syntax of the fix snippets. A pasted Vue file with no format is compiled as maizzle.\"),\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 // The findings come from the compiled output, because that is what a\n // client renders. The prompt keeps the caller's own source, because that is\n // the file they will edit; a fix written against generated HTML is not a\n // fix they can apply.\n const source = await toHtml(html, format);\n if (!source.ok) return mcpError(source.message);\n\n const resolved = resolveSourceFormat(html, format) as\n | \"html\"\n | \"jsx\"\n | \"mjml\"\n | \"maizzle\";\n const framework = toFramework(resolved);\n const fixScope = scope === \"current\" ? \"current\" : \"all\";\n const inputFormat = resolved;\n\n const warnings = analyzeEmail(source.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 = warnings.filter((w) => w.fixType === \"structural\");\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 every supported email client 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 source\"),\n after: z.string().describe(\"Modified email source, in the same format\"),\n format: formatEnum.describe(\"Source format: 'html' (default), 'jsx' (React Email), 'mjml', or 'maizzle' (HTML template or Vue single-file component). Anything but 'html' is compiled before analysis and sets the syntax of the fix snippets. A pasted Vue file with no format is compiled as maizzle.\"),\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 beforeSource = await toHtml(before, format);\n if (!beforeSource.ok) return mcpError(beforeSource.message);\n const afterSource = await toHtml(after, format);\n if (!afterSource.ok) return mcpError(afterSource.message);\n\n const framework = toFramework(\n resolveSourceFormat(before, format) === \"maizzle\" ||\n resolveSourceFormat(after, format) === \"maizzle\"\n ? (format ?? \"maizzle\")\n : (format ?? \"html\"),\n );\n\n const beforeWarnings = analyzeEmail(beforeSource.html, framework);\n const beforeScores = generateCompatibilityScore(beforeWarnings);\n\n const afterWarnings = analyzeEmail(afterSource.html, 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 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?utm_source=mcp.\",\n inputSchema: {\n html: z.string().describe(\"The email source: HTML, or an MJML / Maizzle / React Email template when `format` says so\"),\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, source: \"mcp\" },\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 source: HTML, or an MJML / Maizzle / React Email template when `format` says so\"),\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// ── Hosted Tool: sandbox_get_inbox ──────────────────────────────────\n\nserver.registerTool(\n \"sandbox_get_inbox\",\n {\n title: \"Get Sandbox Inbox\",\n description:\n \"Get your active Emailens sandbox test address (or create a new one). Send emails here via SMTP (smtp.emailens.dev:2525) or inbound domain routing. Requires EMAILENS_API_KEY.\",\n inputSchema: {\n name: z.string().optional().describe(\"Optional inbox name (e.g. 'Auth E2E Test')\"),\n },\n annotations: {\n title: \"Get Sandbox Inbox\",\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n },\n async ({ name }) => {\n if (!config.isHosted) {\n return noApiKeyError(\"sandbox_get_inbox\", \"Email sandboxing requires an Emailens account and API key.\");\n }\n\n const result = await apiRequest<{\n inbox?: { id: string; name: string; createdAt: string; lastReceivedAt: string | null };\n address?: string;\n }>(\"/api/sandbox/v1/inbox\", {\n apiKey: config.apiKey!,\n apiUrl: config.apiUrl,\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(result.data, null, 2),\n },\n ],\n };\n },\n);\n\n// ── Hosted Tool: sandbox_wait_for_email ─────────────────────────────\n\nserver.registerTool(\n \"sandbox_wait_for_email\",\n {\n title: \"Wait for Sandbox Email\",\n description:\n \"Poll and wait for a matching incoming test email in the sandbox. Automatically extracts 4-8 digit OTP codes and action links for zero-regex test assertions. Requires EMAILENS_API_KEY.\",\n inputSchema: {\n inboxId: z.string().optional().describe(\"Optional inbox ID filter\"),\n subject: z.string().optional().describe(\"Substring match for email subject (case-insensitive)\"),\n sender: z.string().optional().describe(\"Substring match for sender address (case-insensitive)\"),\n timeoutSeconds: z.number().optional().describe(\"Max seconds to wait (default: 30, max: 60)\"),\n },\n annotations: {\n title: \"Wait for Sandbox Email\",\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n },\n async ({ inboxId, subject, sender, timeoutSeconds = 30 }) => {\n if (!config.isHosted) {\n return noApiKeyError(\"sandbox_wait_for_email\", \"Email sandboxing requires an Emailens account and API key.\");\n }\n\n const params = new URLSearchParams();\n if (inboxId) params.set(\"inboxId\", inboxId);\n if (subject) params.set(\"subject\", subject);\n if (sender) params.set(\"sender\", sender);\n params.set(\"timeout\", String(timeoutSeconds));\n\n const result = await apiRequest<{\n message: {\n id: string;\n subject: string;\n sender: string;\n otp?: string | null;\n actionUrl?: string | null;\n links?: Array<{ href: string; text?: string }>;\n receivedAt: string;\n };\n }>(`/api/sandbox/v1/messages/wait?${params.toString()}`, {\n apiKey: config.apiKey!,\n apiUrl: config.apiUrl,\n timeoutMs: (timeoutSeconds + 5) * 1000,\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(result.data?.message ?? result.data, null, 2),\n },\n ],\n };\n },\n);\n\n// ── Hosted Tool: sandbox_list_messages ──────────────────────────────\n\nserver.registerTool(\n \"sandbox_list_messages\",\n {\n title: \"List Sandbox Messages\",\n description:\n \"List recently received test emails in your sandbox inboxes. Requires EMAILENS_API_KEY.\",\n inputSchema: {\n inboxId: z.string().optional().describe(\"Optional inbox ID filter\"),\n },\n annotations: {\n title: \"List Sandbox Messages\",\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async ({ inboxId }) => {\n if (!config.isHosted) {\n return noApiKeyError(\"sandbox_list_messages\", \"Email sandboxing requires an Emailens account and API key.\");\n }\n\n const params = inboxId ? `?inboxId=${encodeURIComponent(inboxId)}` : \"\";\n const result = await apiRequest<{ messages: unknown[] }>(`/api/sandbox/messages${params}`, {\n apiKey: config.apiKey!,\n apiUrl: config.apiUrl,\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(result.data, null, 2),\n },\n ],\n };\n },\n);\n\n// ── Hosted Tool: sandbox_get_message ────────────────────────────────\n\nserver.registerTool(\n \"sandbox_get_message\",\n {\n title: \"Get Sandbox Message\",\n description:\n \"Get complete captured email details including HTML/text parts, extracted OTP, magic links, headers, and spam analysis. Requires EMAILENS_API_KEY.\",\n inputSchema: {\n messageId: z.string().describe(\"ID of the sandbox message\"),\n },\n annotations: {\n title: \"Get Sandbox Message\",\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n async ({ messageId }) => {\n if (!config.isHosted) {\n return noApiKeyError(\"sandbox_get_message\", \"Email sandboxing requires an Emailens account and API key.\");\n }\n\n const result = await apiRequest<{ message: unknown }>(`/api/sandbox/messages/${encodeURIComponent(messageId)}`, {\n apiKey: config.apiKey!,\n apiUrl: config.apiUrl,\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(result.data, null, 2),\n },\n ],\n };\n },\n);\n\n// ── Start ────────────────────────────────────────────────────────────\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\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","import { compile } from \"@emailens/engine/compile\";\n\n/**\n * Getting a template to HTML before anything looks at it.\n *\n * The `format` parameter used to do nothing but choose the syntax of the fix\n * snippets, while the source was handed to the analyzer as-is. That is a quiet\n * and expensive lie: cheerio parses `<mjml><mj-section>…` without complaint,\n * finds no CSS in it, and the tools answer **zero findings**. An assistant\n * reads that as a clean bill of health and says the campaign is fine. A wrong\n * answer delivered confidently is worse than an error.\n *\n * So a non-HTML format is compiled first, with the same compilers the CLI uses.\n * They are optional peer dependencies of the engine (MJML alone is 56MB, and\n * this server is usually run through `npx`), so a caller can land on a machine\n * that has none of them. That case gets the engine's own \"install this\" message\n * plus the way out that needs nothing installed: compile it yourself and send\n * the HTML.\n */\n\nexport type SourceResult = { ok: true; html: string } | { ok: false; message: string };\n\nconst COMPILED: ReadonlySet<string> = new Set([\"jsx\", \"mjml\", \"maizzle\"]);\n\n/**\n * Is this \"the package isn't here\" rather than \"the template is wrong\"?\n *\n * The engine says `MJML compilation requires \"mjml\"` for a missing compiler\n * and `Sandbox strategy \"isolated-vm\" requires the \"isolated-vm\" package` when\n * the compiler is present but its sandbox is not, which is what React Email\n * hits first. Both are the same problem for the caller, so one pattern covers\n * them. Getting this wrong is cheap in one direction only: a misclassified\n * template error gains an irrelevant sentence, while a misclassified missing\n * package would tell someone to debug markup that is fine.\n */\nfunction isMissingCompiler(message: string): boolean {\n return /requires (?:the )?\"/.test(message);\n}\n\n/**\n * Compile `source` when `format` names a template language, or pass it through\n * when it is already HTML.\n */\nfunction isVueSfc(source: string): boolean {\n return /<template[\\s>]/i.test(source) && /<\\/template>/i.test(source);\n}\n\n/** An omitted format on a Vue SFC is maizzle. An explicit format is kept. */\nexport function resolveSourceFormat(source: string, format?: string): string {\n if (format) return format;\n return isVueSfc(source) ? \"maizzle\" : \"html\";\n}\n\nexport async function toHtml(source: string, format?: string): Promise<SourceResult> {\n const resolved = resolveSourceFormat(source, format);\n if (resolved === \"html\") return { ok: true, html: source };\n if (!COMPILED.has(resolved)) {\n return { ok: false, message: `Unknown format \"${resolved}\". Use html, jsx, mjml, or maizzle.` };\n }\n\n try {\n return { ok: true, html: await compile(source, resolved as \"jsx\" | \"mjml\" | \"maizzle\") };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (isMissingCompiler(message)) {\n return {\n ok: false,\n message:\n `${message}\\n\\nThat has to happen where this MCP server runs, which may not be somewhere ` +\n `you can install to. If it is not, compile the template yourself and send the resulting ` +\n `HTML with format \"html\".`,\n };\n }\n return { ok: false, message };\n }\n}\n","export const config = {\r\n get apiKey(): string | null {\r\n return process.env.EMAILENS_API_KEY ?? null;\r\n },\r\n get apiUrl(): string {\r\n // The app host, not the apex: emailens.dev redirects /api/* here, and a\r\n // redirect to another host drops the Authorization header on the way.\r\n return process.env.EMAILENS_API_URL ?? \"https://app.emailens.dev\";\r\n },\r\n get isHosted(): boolean {\r\n return this.apiKey !== null;\r\n },\r\n};\r\n","/**\r\n * The first version of each email audited in this session: the design its\r\n * later revisions are held to. An agent fixing findings rarely passes its own\r\n * draft back, so the server remembers it.\r\n *\r\n * Matched by <title> and then by text, because an agent rewrites markup but\r\n * keeps the words: a revision shares nearly all its text with the draft,\r\n * another email with the same title (often just the product name) does not.\r\n * A revision that rewrites the copy becomes a new draft: a missed comparison,\r\n * never a false one.\r\n * ponytail: in-memory, per process, capped; lost on restart, which only means\r\n * the next audit becomes the draft.\r\n */\r\n\r\nconst MAX_DRAFTS = 200;\r\n// Jaccard on words. Measured on the benchmark (359 emails, one title for all):\r\n// two different emails of one product reach 0.48, a fix revision keeps ~all.\r\nconst SAME_EMAIL = 0.7;\r\nconst drafts = new Map<string, { words: Set<string>; html: string }[]>();\r\n\r\nfunction title(html: string): string {\r\n return (html.match(/<title[^>]*>([\\s\\S]*?)<\\/title>/i)?.[1] ?? \"\").trim().toLowerCase();\r\n}\r\n\r\nfunction words(html: string): Set<string> {\r\n const text = html\r\n .replace(/<!--[\\s\\S]*?-->/g, \" \")\r\n .replace(/<(style|script|head)[\\s\\S]*?<\\/\\1>/gi, \" \")\r\n .replace(/<[^>]+>/g, \" \")\r\n .toLowerCase();\r\n return new Set(text.match(/[\\p{L}\\p{N}]{3,}/gu) ?? []);\r\n}\r\n\r\nfunction overlap(a: Set<string>, b: Set<string>): number {\r\n if (!a.size || !b.size) return 0;\r\n let shared = 0;\r\n for (const w of a) if (b.has(w)) shared++;\r\n return shared / (a.size + b.size - shared);\r\n}\r\n\r\n/** The draft this html revises, or this html itself when it is a new email. */\r\nexport function firstDraft(html: string): string {\r\n const key = title(html);\r\n const seen = words(html);\r\n const list = drafts.get(key) ?? [];\r\n const best = list\r\n .map((d) => ({ d, score: overlap(d.words, seen) }))\r\n .sort((a, b) => b.score - a.score)[0];\r\n if (best && best.score >= SAME_EMAIL) return best.d.html;\r\n if ([...drafts.values()].reduce((n, l) => n + l.length, 0) >= MAX_DRAFTS) drafts.clear();\r\n drafts.set(key, [...list, { words: seen, html }]);\r\n return html;\r\n}\r\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 app.emailens.dev/dashboard/api?utm_source=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?utm_source=mcp\"\n : \"Access denied. Check your plan at emailens.dev/pricing?utm_source=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?utm_source=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?utm_source=mcp\",\n \"2. Create a key at app.emailens.dev/dashboard/api?utm_source=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":["EMAIL_CLIENTS","data","EMAIL_CLIENTS","title"]}
package/package.json CHANGED
@@ -1,65 +1,65 @@
1
- {
2
- "name": "@emailens/mcp",
3
- "mcpName": "io.github.emailens/mcp",
4
- "version": "0.11.0",
5
- "description": "MCP server for email compatibility analysis: preview, analyze, fix, diff and capture screenshots across 21 email clients, from HTML, MJML, Maizzle or React Email sources, 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
- "test": "bun test",
28
- "prepublishOnly": "bun run build"
29
- },
30
- "dependencies": {
31
- "@emailens/engine": "^0.15.0",
32
- "@modelcontextprotocol/sdk": "^1.30.0",
33
- "zod": "^3.25.76"
34
- },
35
- "devDependencies": {
36
- "@types/node": "^25.9.5",
37
- "bun-types": "^1.4.0",
38
- "mjml": "^5.4.0",
39
- "tsup": "^8.5.1",
40
- "typescript": "^5.9.3"
41
- },
42
- "keywords": [
43
- "mcp",
44
- "model-context-protocol",
45
- "email",
46
- "email-testing",
47
- "compatibility",
48
- "claude",
49
- "mjml",
50
- "maizzle",
51
- "react-email"
52
- ],
53
- "license": "MIT",
54
- "repository": {
55
- "type": "git",
56
- "url": "git+https://github.com/emailens/mcp.git"
57
- },
58
- "engines": {
59
- "node": ">=18"
60
- },
61
- "homepage": "https://github.com/emailens/mcp#readme",
62
- "bugs": {
63
- "url": "https://github.com/emailens/mcp/issues"
64
- }
1
+ {
2
+ "name": "@emailens/mcp",
3
+ "mcpName": "io.github.emailens/mcp",
4
+ "version": "0.12.1",
5
+ "description": "MCP server for email compatibility analysis: preview, analyze, fix, diff and capture screenshots across 21 email clients, from HTML, MJML, Maizzle or React Email sources, 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
+ "test": "bun test",
28
+ "prepublishOnly": "bun run build"
29
+ },
30
+ "dependencies": {
31
+ "@emailens/engine": "^0.16.4",
32
+ "@modelcontextprotocol/sdk": "^1.30.0",
33
+ "zod": "^3.25.76"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^25.9.5",
37
+ "bun-types": "^1.4.0",
38
+ "mjml": "^5.4.0",
39
+ "tsup": "^8.5.1",
40
+ "typescript": "^5.9.3"
41
+ },
42
+ "keywords": [
43
+ "mcp",
44
+ "model-context-protocol",
45
+ "email",
46
+ "email-testing",
47
+ "compatibility",
48
+ "claude",
49
+ "mjml",
50
+ "maizzle",
51
+ "react-email"
52
+ ],
53
+ "license": "MIT",
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "git+https://github.com/emailens/mcp.git"
57
+ },
58
+ "engines": {
59
+ "node": ">=18"
60
+ },
61
+ "homepage": "https://github.com/emailens/mcp#readme",
62
+ "bugs": {
63
+ "url": "https://github.com/emailens/mcp/issues"
64
+ }
65
65
  }