Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Connect Sequenzy to Claude Desktop, Claude Code, Codex, Cursor, Windsurf, VS Cod
- Supply localized template variants or queue AI translation for enabled locales.
- Create, edit, publish, unpublish, and delete landing pages.
- Create list-scoped saved signup forms and return client-safe static-site embeds.
- Create, edit, publish, unpublish, and delete saved signup popups, and return client-safe embed recipes.
- Connect and verify custom domains for published landing pages.
- Manage team invitations, inbox conversations, and outbound webhook endpoints.
- Generate email copy, subject lines, and multi-step sequences.
Expand Down Expand Up @@ -237,7 +238,7 @@ build a list as well as create it. Imports that apply `listIds` also need

## Tools

This server currently exposes 194 MCP tools.
This server currently exposes 202 MCP tools.

Tools reject arguments they do not declare instead of silently ignoring them.
Errors name the unsupported fields, list the supported arguments, and provide
Expand All @@ -250,7 +251,7 @@ sort options.
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| `get_account` | Get account info, available companies, current key permissions, and the API Keys management URL. |
| `select_company` | Set the active company for future tool calls. |
| `get_app_urls` | Build dashboard URLs for campaigns, landing pages, sequences, emails, settings, domains, and sent email details. |
| `get_app_urls` | Build dashboard URLs for campaigns, landing pages, popups, sequences, emails, settings, domains, and sent email details. |
| `create_company` | Create a new company or brand. |
| `get_company` | Read company details, product info, brand context, localization, reply-tracking settings, and current From/Reply-To defaults. |
| `update_company` | Edit product info, brand context, email theme, reply tracking, and account-wide From/Reply-To profile defaults or names. |
Expand Down Expand Up @@ -695,6 +696,30 @@ into the current theme. Pass an empty `tagIds` array to clear tags or an empty
a complete replacement, so read the current content with `list_forms` first
and retain exactly one email field and one submit button.

### Saved Popups

| Tool | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------- |
| `list_popups` | List saved popups with complete content, status, triggers, targeting, metrics, and dashboard URLs. |
| `get_popup` | Get one popup's complete builder content, lifecycle state, and metrics. |
| `create_popup` | Create and immediately publish a popup from a built-in template or complete content. |
| `update_popup` | Rename a popup or replace its complete content. |
| `publish_popup` | Publish a popup, optionally saving edits first, and return client-safe embed recipes. |
| `unpublish_popup` | Return a popup to draft, optionally saving edits first. |
| `delete_popup` | Permanently delete a popup; existing deployed embeds stop working immediately. |
| `get_popup_embed` | Return the versioned script URL and HTML, React, WordPress, and Shopify installation recipes for a published popup. |

Start with `list_popups` or `get_popup` before replacing `content`: popup
content updates replace the complete editor-compatible object instead of
merging nested fields. `create_popup` accepts either a built-in template or
complete content, never both. The service validates required form blocks,
click selectors, redirect URLs, and that targeted lists and tags belong to the
selected company.

Embed recipes contain only public URLs and the opaque popup ID, never a
Sequenzy API key. A popup must be published before `get_popup_embed` returns
installation code.

### Landing Pages

| Tool | Description |
Expand Down
7 changes: 7 additions & 0 deletions src/app-urls.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ describe("buildSequenzyAppUrls", () => {
companyId: "comp_123",
campaignId: "camp_123",
landingPageId: "lp_123",
popupId: "popup_123",
sequenceId: "seq_123",
templateId: "email_123",
emailSendId: "send_123",
Expand All @@ -25,6 +26,9 @@ describe("buildSequenzyAppUrls", () => {
expect(appUrls.urls.landingPage).toBe(
"https://app.example.com/dashboard/company/comp_123/landing-pages/lp_123"
);
expect(appUrls.urls.popup).toBe(
"https://app.example.com/dashboard/company/comp_123/popups/popup_123"
);
expect(appUrls.urls.sequence).toBe(
"https://app.example.com/dashboard/company/comp_123/sequences/seq_123"
);
Expand All @@ -46,6 +50,9 @@ describe("buildSequenzyAppUrls", () => {
expect(appUrls.routeTemplates.landingPage).toBe(
"/dashboard/company/{companyId}/landing-pages/{landingPageId}"
);
expect(appUrls.routeTemplates.popup).toBe(
"/dashboard/company/{companyId}/popups/{popupId}"
);
expect(appUrls.settingsTabValues).toContain("integrations");
});
});
12 changes: 12 additions & 0 deletions src/app-urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export const routeTemplates = {
campaignList: "/dashboard/company/{companyId}/campaign/list/{status}",
landingPages: "/dashboard/company/{companyId}/landing-pages",
landingPage: "/dashboard/company/{companyId}/landing-pages/{landingPageId}",
popups: "/dashboard/company/{companyId}/popups",
popup: "/dashboard/company/{companyId}/popups/{popupId}",
sequences: "/dashboard/company/{companyId}/sequences",
sequence: "/dashboard/company/{companyId}/sequences/{sequenceId}",
sequenceList: "/dashboard/company/{companyId}/sequences/list/{status}",
Expand All @@ -53,6 +55,7 @@ export interface AppUrlInput {
companyId?: string | null;
campaignId?: string | null;
landingPageId?: string | null;
popupId?: string | null;
sequenceId?: string | null;
emailId?: string | null;
templateId?: string | null;
Expand Down Expand Up @@ -136,6 +139,7 @@ export function buildSequenzyAppUrls(
urls.dashboard = joinUrl(appUrl, companyPath(companyId));
urls.campaigns = joinUrl(appUrl, companyPath(companyId, "/campaign"));
urls.landingPages = joinUrl(appUrl, companyPath(companyId, "/landing-pages"));
urls.popups = joinUrl(appUrl, companyPath(companyId, "/popups"));
urls.sequences = joinUrl(appUrl, companyPath(companyId, "/sequences"));
urls.settings = joinUrl(appUrl, companyPath(companyId, "/settings"));
urls.emails = joinUrl(appUrl, companyPath(companyId, "/emails"));
Expand Down Expand Up @@ -165,6 +169,14 @@ export function buildSequenzyAppUrls(
);
}

const popupId = clean(input.popupId);
if (popupId) {
urls.popup = joinUrl(
appUrl,
companyPath(companyId, `/popups/${pathSegment(popupId)}`)
);
}

const sequenceId = clean(input.sequenceId);
if (sequenceId) {
urls.sequence = joinUrl(
Expand Down
14 changes: 9 additions & 5 deletions src/tools/definitions/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ The response shows 'companies' (all available) and 'selectedCompanyId' (currentl
{
name: "get_app_urls",
description:
"Generate Sequenzy dashboard URLs for known resource IDs. Use this when the user asks where to review or edit a generated sequence, campaign, template, or company settings. If companyId is omitted, the selected/current company is used when available.",
"Generate Sequenzy dashboard URLs for known resource IDs. Use this when the user asks where to review or edit a generated sequence, campaign, landing page, popup, template, or company settings. If companyId is omitted, the selected/current company is used when available.",
inputSchema: {
type: "object",
properties: {
Expand All @@ -52,6 +52,10 @@ The response shows 'companies' (all available) and 'selectedCompanyId' (currentl
type: "string",
description: "Landing page ID for the landing page editor URL.",
},
popupId: {
type: "string",
description: "Popup ID for the popup editor URL.",
},
sequenceId: {
type: "string",
description: "Sequence ID for the sequence editor URL.",
Expand Down Expand Up @@ -664,7 +668,7 @@ The response shows 'companies' (all available) and 'selectedCompanyId' (currentl
{
name: "list_websites",
description:
"List configured sending domains with separate DNS verification, selected home-transport readiness, and SPF, DKIM, and MAIL FROM status",
"List configured sending domains with separate DNS verification, sending readiness, and SPF, DKIM, and MAIL FROM status",
inputSchema: {
type: "object",
properties: {
Expand Down Expand Up @@ -724,7 +728,7 @@ The response shows 'companies' (all available) and 'selectedCompanyId' (currentl
{
name: "check_website",
description:
"Read a sending domain's separate DNS verification, selected home-transport readiness, and SPF, DKIM, MAIL FROM diagnostics. Use verify_sending_domain to run a fresh DNS check.",
"Read a sending domain's separate DNS verification, sending readiness, and SPF, DKIM, MAIL FROM diagnostics. Use verify_sending_domain to run a fresh DNS check.",
inputSchema: {
type: "object",
properties: {
Expand All @@ -744,7 +748,7 @@ The response shows 'companies' (all available) and 'selectedCompanyId' (currentl
{
name: "verify_sending_domain",
description:
"Run a fresh DNS check for a configured sending domain and return DNS verification separately from selected home-transport readiness. A DNS-verified domain may still be activating in SES.",
"Run a fresh DNS check for a configured sending domain and return DNS verification separately from sending readiness. A DNS-verified domain may still be activating; when readyToSend is false, read readiness.reason rather than the DKIM/SPF/MAIL FROM record statuses, which describe DNS only.",
inputSchema: {
type: "object",
properties: {
Expand Down Expand Up @@ -1024,7 +1028,7 @@ Use cases:
- 'event_tracking': Tracking CUSTOM events only (not payment events - those come from the integration)
- 'ecommerce': Connecting a custom e-commerce platform via the Commerce API (sync products, push orders/checkouts, power abandoned cart + back-in-stock automations)

Before protected server-side API work, use create_api_key and save the key to .env as SEQUENZY_API_KEY. Static-site saved forms are the exception: use list_forms/create_form/get_form_embed and never place a secret key in browser code.`,
Before protected server-side API work, use create_api_key and save the key to .env as SEQUENZY_API_KEY. Saved forms and popups are the exceptions: use list_forms/create_form/get_form_embed or list_popups/create_popup/get_popup_embed, and never place a secret key in browser code.`,
inputSchema: {
type: "object",
properties: {
Expand Down
2 changes: 2 additions & 0 deletions src/tools/definitions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { imageAssetToolDefinitions } from "./image-assets.js";
import { inboxToolDefinitions } from "./inbox.js";
import { integrationToolDefinitions } from "./integrations.js";
import { landingPageToolDefinitions } from "./landing-pages.js";
import { savedPopupToolDefinitions } from "./popups.js";
import { productToolDefinitions } from "./products.js";
import { renderToolDefinitions } from "./render.js";
import { sequenceGoalToolDefinitions } from "./sequence-goals.js";
Expand Down Expand Up @@ -39,6 +40,7 @@ export const toolDefinitions: Tool[] = [
...campaignToolDefinitions,
...renderToolDefinitions,
...savedFormToolDefinitions,
...savedPopupToolDefinitions,
...landingPageToolDefinitions,
...imageAssetToolDefinitions,
...sequenceBasicToolDefinitions,
Expand Down
157 changes: 157 additions & 0 deletions src/tools/definitions/popups.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import type { Tool } from "@modelcontextprotocol/sdk/types.js";

import {
popupContentDescription,
popupTemplateDescription,
} from "../internal.js";

const companyIdProperty = {
type: "string",
description:
"Company ID. If not provided, uses the currently selected company.",
} as const;

const popupIdProperty = {
type: "string",
description: "Saved popup ID returned by list_popups or create_popup.",
} as const;

const popupContentProperty = {
type: "object",
description: popupContentDescription,
additionalProperties: true,
} as const;

const popupMutationProperties = {
companyId: companyIdProperty,
popupId: popupIdProperty,
name: {
type: "string",
description: "Optional popup name update.",
},
content: popupContentProperty,
} as const;

export const savedPopupToolDefinitions: Tool[] = [
{
name: "list_popups",
description:
"List saved signup popups with their complete content, trigger, targeting, status, metrics, and dashboard URLs.",
inputSchema: {
type: "object",
properties: { companyId: companyIdProperty },
additionalProperties: false,
},
},
{
name: "get_popup",
description:
"Get one saved popup's complete content, trigger, targeting, status, metrics, and dashboard URL.",
inputSchema: {
type: "object",
properties: {
companyId: companyIdProperty,
popupId: popupIdProperty,
},
required: ["popupId"],
additionalProperties: false,
},
},
{
name: "create_popup",
description:
"Create and immediately publish a saved signup popup. Start from a template or provide complete popup content, but not both. The returned embed is client-safe and contains no API key.",
inputSchema: {
type: "object",
properties: {
companyId: companyIdProperty,
name: {
type: "string",
description: "Internal popup name.",
},
template: {
type: "string",
enum: [
"newsletter-modal",
"discount-offer",
"countdown-launch",
"minimal-slide-in",
"exit-lead-magnet",
"live-demo",
"launch-modal",
"paper-digest",
"stark-takeover",
"top-bar",
"announcement-bar",
"fullscreen-welcome",
],
description: popupTemplateDescription,
},
content: popupContentProperty,
},
required: ["name"],
additionalProperties: false,
},
},
{
name: "update_popup",
description:
"Update a popup's name or replace its complete content. Read the current popup first before changing content.",
inputSchema: {
type: "object",
properties: popupMutationProperties,
required: ["popupId"],
additionalProperties: false,
},
},
{
name: "publish_popup",
description:
"Publish a popup, optionally saving a name or complete content update first. Returns client-safe embed code.",
inputSchema: {
type: "object",
properties: popupMutationProperties,
required: ["popupId"],
additionalProperties: false,
},
},
{
name: "unpublish_popup",
description:
"Unpublish a popup, optionally saving a name or complete content update first. Existing embeds remain installed but load a no-op script until republished.",
inputSchema: {
type: "object",
properties: popupMutationProperties,
required: ["popupId"],
additionalProperties: false,
},
},
{
name: "delete_popup",
description:
"Permanently delete a popup, including a published popup. Existing deployed embeds stop working immediately.",
inputSchema: {
type: "object",
properties: {
companyId: companyIdProperty,
popupId: popupIdProperty,
},
required: ["popupId"],
additionalProperties: false,
},
},
{
name: "get_popup_embed",
description:
"Get a published popup's versioned script URL and HTML, React, WordPress, and Shopify embed recipes. The snippets are client-safe and contain no API key.",
inputSchema: {
type: "object",
properties: {
companyId: companyIdProperty,
popupId: popupIdProperty,
},
required: ["popupId"],
additionalProperties: false,
},
},
];
6 changes: 6 additions & 0 deletions src/tools/descriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ export const landingPageContentDescription =
export const landingPageTemplateDescription =
"Optional template key for default content, such as from-scratch, waitlist, lead-magnet, launch, demo-request, webinar, newsletter, product-hunt, pricing-offer, agency-lead-gen, or feature-announcement.";

export const popupTemplateDescription =
"Optional popup template key: newsletter-modal, discount-offer, countdown-launch, minimal-slide-in, exit-lead-magnet, live-demo, launch-modal, paper-digest, stark-takeover, top-bar, announcement-bar, or fullscreen-welcome.";

export const popupContentDescription =
'Complete Sequenzy popup content JSON. Use this for an exact full replacement after reading the current content with get_popup. It must include version: 1, surface: "popup", template, presentation, placement, theme, settings, trigger, targeting, schedule, frequency, visual, and blocks. The main form must retain exactly one required email field and one submit button. Click triggers require trigger.clickSelector. settings.listIds and tagIds must belong to the selected company, and redirect success actions require an HTTP or HTTPS URL.';

export const ADD_SUBSCRIBERS_TO_LIST_EMAIL_LIMIT = 500;
export const SEQUENCE_ENROLLMENT_TARGET_LIMIT = 500;

Expand Down
1 change: 1 addition & 0 deletions src/tools/handlers/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export async function handleAccountTools(
companyId,
campaignId: optionalString(args, "campaignId"),
landingPageId: optionalString(args, "landingPageId"),
popupId: optionalString(args, "popupId"),
sequenceId: optionalString(args, "sequenceId"),
emailId:
optionalString(args, "emailId") ?? optionalString(args, "templateId"),
Expand Down
2 changes: 2 additions & 0 deletions src/tools/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { handleSavedFormTools } from "./forms.js";
import { handleImageAssetTools } from "./image-assets.js";
import { handleIntegrationTools } from "./integrations.js";
import { handleLandingPageTools } from "./landing-pages.js";
import { handleSavedPopupTools } from "./popups.js";
import { handleProductTools } from "./products.js";
import { handleRenderTools } from "./render.js";
import { handleSequenceTools } from "./sequences.js";
Expand All @@ -24,6 +25,7 @@ export const toolHandlers = [
handleCampaignTools,
handleRenderTools,
handleSavedFormTools,
handleSavedPopupTools,
handleLandingPageTools,
handleImageAssetTools,
handleSequenceTools,
Expand Down
Loading