Expand MCP tool surface from 4 to 24 tools#26
Conversation
8c8162c to
c5a7147
Compare
Pre-merge hostile-review pass + blocker fixes (commit
|
Adds tools across templates (CRUD + validation), message search, bounces, suppressions, server info, and webhooks. Consolidates outbound stats into getDeliveryStats with an optional `stat` parameter and per-stat formatters. Why: - Initial release shipped only sendEmail, sendEmailWithTemplate, listTemplates, and getDeliveryStats. This brings parity with the broader Postmark API surface. - getDeliveryStats now exposes 13 stat breakdowns (overview, bounces, opens, clicks, platform/client/browser usage, click location, read times) without cluttering the tool list. - Adds validation guards (createWebhook requires ≥1 trigger; editTemplate requires ≥1 updated field) so misuse fails fast instead of silently. - Bumps engines.node to >=20 (MCP SDK requires ≥18; 16 was false-advertising). Smoke tested against a live Postmark account: 23/23 read-only checks (smoke-test.mjs) + 14/14 mutating lifecycle checks (smoke-test-mutating.mjs) including real sends and full create→edit→delete cycles for templates, webhooks, and suppressions. No residue left after cleanup.
Documents the new 21-tool layout, the consolidated getDeliveryStats with per-stat formatters, the smoke test commands, and a Gotchas section capturing tribal knowledge worth preserving: - Suppressions dump endpoint is eventually consistent - PostFirstOpenOnly vs EnableSmtpApiErrorHooks distinction - SDK method names that don't match the obvious guess (getEmailOpenClientUsage, getEmailOpenPlatformUsage, getClickLocation) Why include this in the PR: the gotchas were learned the hard way during this branch's audit, so capturing them helps the next person (or LLM) avoid the same wrong turns. Reviewers can drop the file before merge if they prefer to keep it untracked.
Runs message search, suppression check, and bounce history lookups in parallel for a recipient, then synthesizes a plain-English recommendation. Replaces what was previously a 5-step manual investigation (searchOutboundMessages → getMessageDetails → check events → listSuppressions → searchBounces) with a single tool call. Why this matters: the existing 21 tools mirror the Postmark REST API 1:1. This is the first composite tool — it demonstrates what AI-native tooling looks like by combining endpoints into outcomes. It's positioned as a proof-of-concept for a "Diagnostics" category that can grow (getAccountHealth, compareStats, etc.) in future iterations. Smoke tested: 25/25 (added two diagnoseDelivery cases — happy path with a real recent send, and an unknown-recipient case).
Postmark's bulk email API graduated from early access to GA, so wraps of /email/batch and /email/batchWithTemplates are now safe to ship in the official MCP. - sendBatch accepts up to 500 fully-independent messages (each with own recipient/subject/body) - sendBatchWithTemplate accepts a shared template + up to 500 recipients with per-recipient template models. Top-level `from` / `tag` apply as defaults but are overridable per recipient - Both share a formatBatchResults helper that splits the SDK response by ErrorCode and renders a per-message success/failure summary, capping success and failure lists for readable output on large batches Smoke tested: 25/25 read-only + 16/16 mutating including a real 3-message batch and a 2-recipient template batch. Cleanup verified clean. Pairs with the diagnoseDelivery commit to give the PR two flagship additions: one functional-completeness (batch sends), one composite/ AI-native (delivery diagnosis).
Adds the missing layoutTemplate parameter to createTemplate and editTemplate, and surfaces the binding in getTemplate and listTemplates output so the association is verifiable round-trip from the MCP. Without this, a template created via createTemplate is unbound from any Layout — renders without the masthead, footer, or shared CSS the rest of the account's templates inherit. We hit this dogfooding the MCP against KrateCMS: a tenant-admin-welcome template created over the wire looked nothing like its sibling templates because there was no way to attach a Layout. editTemplate accepts null on layoutTemplate to unbind. Postmark's API treats JSON null as "no change" — the documented way to clear a layout association is an empty string — so the MCP translates null -> "" before hitting the SDK so callers can use the natural JSON shape. Smoke test surfaced a separate, pre-existing bug while validating the binding round-trip: createTemplate's required subject param made it impossible to create Layout templates at all (Postmark rejects Subject on Layouts). Fixed by making subject conditionally required at the handler level — required for Standard, forbidden for Layout — with clear error messages either way. Mutating smoke test now covers the full binding lifecycle (create Layout -> create Standard with binding -> verify in get -> unbind via null -> verify cleared -> rebind -> verify in list -> cleanup), 23/23 passing.
The previous smoke-test.mjs and smoke-test-mutating.mjs files were
tracked in the repo with hard-coded verified-sender addresses, which
meant any local edit to point them at a different account could be
accidentally committed. Replace them with example-file templates
that follow the .env / .env.example pattern:
- smoke-test.example.mjs and smoke-test-mutating.example.mjs are
tracked and contain only placeholder addresses (recipient@example.com).
- smoke-test.mjs and smoke-test-mutating.mjs are gitignored so a
user-copy with personal addresses can never be accidentally added.
- smoke-test-mutating.example.mjs includes a startup guard that
refuses to run while the placeholders are still in place — prevents
sending mail from the example values.
- README and CLAUDE.md document the cp + edit + run workflow.
Workflow: cp smoke-test.example.mjs smoke-test.mjs, edit any addresses,
run npm run smoke (or node smoke-test-mutating.mjs for the full lifecycle
suite).
Two pre-merge blockers caught during a hostile-review pass: 1. Version was still 1.0.0 despite a breaking change (engines floor raised from >=16 to >=20) plus 20 net-new tools. Per semver, this is a major version bump. 2. The package.json `files` array shipped only LICENSE / README.md / index.js / package.json — but the README walks NPM users through `cp smoke-test.example.mjs smoke-test.mjs && npm run smoke`, and none of those files were in the published tarball. Added both *.example.mjs files plus CHANGELOG.md to the files array. CHANGELOG.md follows the Keep a Changelog format. The 2.0.0 entry documents all 20 new tools, the breaking engines change, the getDeliveryStats reformat, two real bug fixes (PostFirstOpenOnly field, engines floor accuracy), and the dropped node-fetch dep.
ceac412 to
ae9585f
Compare
- Adds a Changelog entry to the Useful Docs list so users have a clear pointer to what changed between versions. - Corrects the read-only smoke count comment from 24 to 25 — the example file was off by one against the actual test list.
The sendBatch / sendBatchWithTemplate tools wrap Postmark's batch email endpoints (/email/batch, /email/batchWithTemplates), not the separate bulk email API (/email/bulk). Earlier doc copy conflated the two — links pointed at the bulk-email docs while the implementation actually uses the batch endpoints. Fixed across: - CHANGELOG.md: corrected the 2.0.0 entry, added a note that bulk API wrapping is tracked as a v2.1 follow-up - README.md: corrected the sendBatch description, points at the bulk docs only as a "see also" for the unwrapped capability - CLAUDE.md: corrected the implementation note for future contributors Postmark's bulk email API is a distinct capability (high-volume sends) and remains an obvious v2.1 addition.
Postmark's bulk email API documentation (verified at the official URL) clarifies that /email/batch and /email/bulk are PARALLEL APIs, not one replacing the other: - /email/batch — synchronous, immediate per-message results, capped at 500 messages per call. This is what sendBatch / sendBatchWithTemplate wrap in v2.0. - /email/bulk — asynchronous, submit-a-job-and-poll-for-status, no message count cap (50 MB payload limit instead), subject to Postmark approval. This is the recently-GA capability and is NOT covered in v2.0 — tracked as a v2.1 follow-up. Earlier copy described bulk as "high-volume single-template sends", which was a partially-correct guess made without reading the docs. Now updated to reflect the verified async-vs-sync distinction. Affects: - CHANGELOG.md: tightened the 2.0.0 entry's note about bulk - README.md: corrected the sendBatch description's bulk pointer - CLAUDE.md: corrected the implementation note for future contributors The current sendBatch is not "legacy" — it's the synchronous half of Postmark's two-track send API. Wrapping bulk needs its own design pass because the SDK doesn't expose it and the async pattern likely needs two tools (submitBulk + getBulkStatus).
…smatches, expose messageStream filter The bounce-type enum exposed only 16 of Postmark's 22 documented bounce types, and two of those used the wrong case. Anyone trying to filter by the missing or miscased types would get a Zod validation error before the request reached the API. Surfaced during a final cross-check against the Postmark Bounce API docs. Added to the enum: - Subscribe, OpenRelayTest, Unknown, VirusNotification, Unconfirmed, Blocked Case corrections (now match Postmark's BounceType SDK enum): - DmarcPolicy → DMARCPolicy - DNSError → DnsError Also added the `messageStream` filter to searchBounces — Postmark's /bounces endpoint supports `messagestream` per the docs, and we already exposed it on searchOutboundMessages but missed it here. README's bounce documentation updated to list the full 22-value set. REVIEW_NOTES.md updated to reflect the corrected count. Smoke tests still pass: 25/25 read-only, 23/23 mutating.
Summary
Expands the official Postmark MCP server from 4 tools to 24, organized into eight categories. Adds full coverage of the server-token API surface, two flagship additions (batch email sends + AI-native delivery diagnosis), validation guards on mutating tools, polished stat formatters, and two smoke-test harnesses that run against a live Postmark account.
What's new (by category)
Email — 4 tools (was 2)
sendEmail,sendEmailWithTemplate(existing)sendBatch— sends up to 500 fully-independent messages in one API callsendBatchWithTemplate— sends up to 500 templated emails with per-recipienttemplateModel. Top-levelfrom/tagare defaults that can be overridden per recipientTemplates — 6 tools (was 1)
listTemplates(existing)getTemplate,createTemplate,editTemplate,deleteTemplate,validateTemplate— full CRUD + Mustachio validation withtestRenderModelsupportMessages — 2 tools (new)
searchOutboundMessages— recipient/tag/subject/status/date/messageStream filters, paginatedgetMessageDetails— full event timeline for a single MessageIDDiagnostics — 1 tool (new, composite)
diagnoseDelivery— answers "did my email reach X, and if not, why?" by running message search, suppression check, and bounce history in parallel for a recipient, then synthesizing a plain-English recommendation. Replaces a 5-step manual investigation with one tool callBounces — 3 tools (new)
searchBounces— full bounce-type enum, recipient/tag/messageID/date filtersgetBounceDump— raw SMTP dump (30-day retention)activateBounce— reactivates a deactivated addressSuppressions — 3 tools (new)
listSuppressions— by reason / origin / email / date, scoped to a streamcreateSuppressions/deleteSuppressions— up to 50 addresses per callStats & Server — 2 tools (was 1)
getDeliveryStats— unified stats tool with optionalstatparameter. Default returns a friendly summary; withstat: "<name>"returns a polished per-stat breakdown forsummary,overview,sent,bounces,spam,tracked,opens,openPlatforms,openClients,openReadTimes,clicks,clickBrowsers,clickPlatforms, orclickLocationgetServerInfo(new) — server name, color, tracking settings, configured webhook URLsWebhooks — 3 tools (new)
listWebhooks/createWebhook/deleteWebhook— full webhook lifecycle with per-trigger togglesBug fixes caught during the audit
getServerInfodisplayed wrong field — was readingEnableSmtpApiErrorHooksfor "First Open Only"; now correctly readsPostFirstOpenOnlygetOutboundStats(since merged intogetDeliveryStats) called nonexistent SDK methods —getEmailClientUsageandgetEmailPlatformUsagedon't exist; corrected togetEmailOpenClientUsageandgetEmailOpenPlatformUsage. AddedclickLocationviagetClickLocation(notgetClickLocationUsage, which also doesn't exist)package.jsondeclarednode >= 16but the MCP SDK requires>= 18. Bumped to>= 20(Node 18 hit EOL April 2025)Validation guards added
Mutating tools now fail-fast on misuse instead of silently doing nothing:
editTemplate— requires at least one updated fieldcreateWebhook— requires at least one trigger enabledcreateTemplate— requireshtmlBodyortextBodyvalidateTemplate— requiressubject,htmlBody, ortextBodysendEmailWithTemplate/sendBatchWithTemplate— require exactly one oftemplateId/templateAliasPolish & cleanup
maingetDeliveryStatsformatters — six module-level helpers (fmtInt,fmtPct,fmtPlatformUsage,fmtTopBreakdown,fmtDeliverySummary,fmtStatResponse) render counts with thousands separators, percentages, top-N sorted breakdowns, and aligned columnsz.number().int(); date strings validated againstYYYY-MM-DDregexnode-fetchdependency —getDeliveryStatspreviously bypassed the SDK with rawfetch; now uses the SDK throughout for consistent auth/error handlingPostFirstOpenOnlyvsEnableSmtpApiErrorHooksdistinction)Smoke testing
Two harnesses live in the repo and run against a live Postmark account:
npm run smoke) — 25 read-only checks covering every read tool plus all 13 distinctgetDeliveryStatsstat endpoints (14 enum values —summaryandoverviewshare an endpoint, formatted differently) plus both validation guardsBoth are in the branch but not in
package.json'sfilesarray — they ship in the repo for regression but not on npm. Reviewers can choose whether to keep, move undertests/, or drop before merge.Tool count summary
Test plan
npm installsucceeds with the droppednode-fetchdependencynpm startregisters all 24 tools (visible in startup log)npm run smokepasses 25/25 against a fresh Postmark server tokennode smoke-test-mutating.mjspasses 23/23 (editSENDERandRECIPIENTconstants for the reviewer's account)diagnoseDeliveryend-to-end — should pick up the test email from the mutating harness and report "Delivered" with a recommendationgetDeliveryStatswithstat: "summary"(default) and at least one breakdown statpackage.jsonengines.nodeof>=20matches your deployment baseline; revert to>=18if you'd rather match the MCP SDK floor exactlytests/, or remove before merge