html-ad-generator-mcp
Version:
MCP server for generating HTML ad templates from JSON input for Google Ads, Meta Ads, and Moment Science
405 lines • 16.1 kB
JavaScript
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { GoogleAdInputSchema, MetaAdInputSchema, MomentScienceAdInputSchema } from "./types/schemas.js";
import { validateAdContent } from "./validators/content.js";
import { processGoogleTemplate } from "./templates/google.js";
import { processMetaTemplate } from "./templates/meta.js";
import { processMomentScienceTemplate } from "./templates/moment-science.js";
import { getCharacterLimits } from "./validators/content.js";
// Create an MCP server
const server = new McpServer({
name: "html-ad-generator",
version: "0.1.0"
});
// Tool: generate_google_ad_html
server.tool("generate_google_ad_html", {
searchAd: z.object({
headlines: z.array(z.string()).min(1).max(5).describe("Array of 1-5 headlines (max 30 chars each)"),
descriptions: z.array(z.string()).min(1).max(5).describe("Array of 1-5 descriptions (max 90 chars each)")
}).describe("Search ad content (required)"),
displayAd: z.object({
headline: z.array(z.string()).min(1).max(5).describe("Array of 1-5 display ad headlines (max 30 chars each)"),
longHeadline: z.array(z.string()).min(1).max(5).describe("Array of 1-5 extended headlines (max 90 chars each)"),
description: z.array(z.string()).min(1).max(5).describe("Array of 1-5 display ad descriptions (max 90 chars each)"),
businessName: z.string().describe("Business name (max 25 chars)"),
imageUrl: z.string().url().optional().describe("Optional image URL")
}).describe("Display ad content (required)")
}, async (args) => {
try {
// Create input object with platform
const input = {
platform: 'google',
...args
};
// Validate input
const validation = validateAdContent(input);
if (!validation.valid) {
return {
content: [
{
type: "text",
text: JSON.stringify({
error: "Validation failed",
details: validation.errors
}, null, 2)
}
],
isError: true
};
}
// Process template
const parsed = GoogleAdInputSchema.parse(input);
const html = processGoogleTemplate(parsed);
// Return the generated HTML
return {
content: [
{
type: "text",
text: html
}
]
};
}
catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify({
error: "Failed to generate Google Ad HTML",
message: error instanceof Error ? error.message : "Unknown error"
}, null, 2)
}
],
isError: true
};
}
});
// Tool: generate_meta_ad_html
server.tool("generate_meta_ad_html", {
headline: z.array(z.string()).min(1).max(5).describe("Array of 1-5 ad headlines (max 40 chars each)"),
description: z.array(z.string()).min(1).max(5).describe("Array of 1-5 short descriptions (max 30 chars each)"),
primaryText: z.array(z.string()).min(1).max(5).describe("Array of 1-5 main ad texts (max 125 chars each)"),
cta: z.array(z.string()).min(1).max(5).describe("Array of 1-5 call-to-action button texts"),
businessName: z.string().optional().describe("Optional business/page name"),
profileImageUrl: z.string().url().optional().describe("Optional profile image URL"),
mainImageUrl: z.string().url().optional().describe("Optional main ad image URL")
}, async (args) => {
try {
// Create input object with platform
const input = {
platform: 'meta',
content: args
};
// Validate input
const validation = validateAdContent(input);
if (!validation.valid) {
return {
content: [
{
type: "text",
text: JSON.stringify({
error: "Validation failed",
details: validation.errors
}, null, 2)
}
],
isError: true
};
}
// Process template
const parsed = MetaAdInputSchema.parse(input);
const html = processMetaTemplate(parsed);
// Return the generated HTML
return {
content: [
{
type: "text",
text: html
}
]
};
}
catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify({
error: "Failed to generate Meta Ad HTML",
message: error instanceof Error ? error.message : "Unknown error"
}, null, 2)
}
],
isError: true
};
}
});
// Tool: generate_moment_science_ad_html
server.tool("generate_moment_science_ad_html", {
headline: z.array(z.string().max(90)).min(3).max(5).describe("Array of 3-5 ad headlines (max 90 chars each)"),
description: z.array(z.string().max(220)).min(3).max(5).describe("Array of 3-5 ad descriptions (max 220 chars each)"),
short_headline: z.array(z.string().max(60)).min(3).max(5).describe("Array of 3-5 short ad headlines (max 60 chars each)"),
short_description: z.array(z.string().max(140)).min(3).max(5).describe("Array of 3-5 short ad descriptions (max 140 chars each)"),
positive_cta: z.array(z.string().max(25)).min(3).max(5).describe("Array of 3-5 positive call-to-action button texts (max 25 chars each)"),
negative_cta: z.array(z.string().max(25)).min(3).max(5).describe("Array of 3-5 negative call-to-action button texts (max 25 chars each)"),
imageUrl: z.string().url().optional().describe("Optional image URL for the ad")
}, async (args) => {
try {
// Create input object with platform
const input = {
platform: 'moment-science',
content: args
};
// Validate input
const validation = validateAdContent(input);
if (!validation.valid) {
return {
content: [
{
type: "text",
text: JSON.stringify({
error: "Validation failed",
details: validation.errors
}, null, 2)
}
],
isError: true
};
}
// Process template
const parsed = MomentScienceAdInputSchema.parse(input);
const html = processMomentScienceTemplate(parsed);
// Return the generated HTML
return {
content: [
{
type: "text",
text: html
}
]
};
}
catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify({
error: "Failed to generate Moment Science Ad HTML",
message: error instanceof Error ? error.message : "Unknown error"
}, null, 2)
}
],
isError: true
};
}
});
// Tool: validate_ad_content (works for all platforms)
server.tool("validate_ad_content", {
platform: z.enum(['google', 'meta', 'moment-science']).describe("Ad platform"),
content: z.any().describe("Ad content to validate")
}, async ({ platform, content }) => {
const input = platform === 'google'
? { platform, ...content }
: { platform, content };
const validation = validateAdContent(input);
return {
content: [
{
type: "text",
text: JSON.stringify({
valid: validation.valid,
errors: validation.errors,
warnings: validation.warnings
}, null, 2)
}
]
};
});
// Tool: get_google_ad_schema
server.tool("get_google_ad_schema", {}, async () => {
const limits = getCharacterLimits('google');
const schema = {
searchAd: {
headlines: Array(5).fill("string (max 30 chars)"),
descriptions: Array(5).fill("string (max 90 chars)")
},
displayAd: {
headline: Array(5).fill("string (max 30 chars)"),
longHeadline: Array(5).fill("string (max 90 chars)"),
description: Array(5).fill("string (max 90 chars)"),
businessName: "string (max 25 chars)",
imageUrl: "string (optional)"
}
};
const example = {
searchAd: {
headlines: [
"High-Performance Sneakers",
"Huge Savings – Limited Time",
"Free Delivery on All Orders",
"Premium Quality Guaranteed",
"Shop Now & Save Big"
],
descriptions: [
"Experience unmatched comfort and style with our new range of sports sneakers.",
"Grab the exclusive offer today! Only while stocks last.",
"Made with premium materials for long-lasting durability.",
"Perfect for running, walking, and everyday comfort.",
"Available in multiple colors and sizes for everyone."
]
},
displayAd: {
headline: ["Mega Winter Sale", "Fashion Collection", "Limited Time Offer", "New Arrivals", "Premium Quality"],
longHeadline: [
"Stay Warm and Stylish – Discover the Limited Winter Collection",
"Premium Fashion at Unbeatable Prices This Season",
"Exclusive Designer Pieces Now Available Online",
"Transform Your Wardrobe with Our Latest Collection",
"Quality Fashion That Fits Your Style and Budget"
],
description: [
"Shop the latest trends in winter fashion before they're gone.",
"Discover premium quality clothing at amazing prices.",
"Find your perfect style with our curated collection.",
"Limited stock available - order now while supplies last.",
"Free shipping on orders over $50 - shop today!"
],
businessName: "TrendWorld",
imageUrl: "https://example.com/winter-sale.jpg"
}
};
return {
content: [
{
type: "text",
text: JSON.stringify({
schema,
characterLimits: limits,
exampleMultipleOptionsNote: "Provide 1–5 unique options for each content array to populate dropdowns dynamically.",
note: "Both searchAd and displayAd are required for Google ads. All content fields accept 1-5 values except businessName and imageUrl."
}, null, 2)
}
]
};
});
// Tool: get_meta_ad_schema
server.tool("get_meta_ad_schema", {}, async () => {
const limits = getCharacterLimits('meta');
const schema = {
headlines: Array(5).fill("string (max 40 chars)"),
descriptions: Array(5).fill("string (max 30 chars)"),
primaryTexts: Array(5).fill("string (max 125 chars)"),
ctas: Array(5).fill("string"),
businessName: "string (optional)",
profileImageUrl: "string (optional)",
mainImageUrl: "string (optional)"
};
const example = {
headlines: [
"Holiday Discounts Galore",
"Sign up and Save Big",
"Exclusive Launch Offer",
"Limited Time Deal",
"Premium Quality Products"
],
descriptions: [
"Offer valid till year end",
"Best price guarantee",
"Hurry, limited stock!",
"Free shipping available",
"Quality you can trust"
],
primaryTexts: [
"Enjoy unbeatable prices on our premium range. Stocks are running out fast!",
"Sign up today and enjoy free shipping plus mega discounts.",
"Introducing our latest arrivals – crafted with style and quality.",
"Don't miss out on these amazing deals. Shop now and save big on everything you love.",
"Transform your lifestyle with our premium collection. Experience the difference quality makes."
],
ctas: ["Shop Now", "Learn More", "Sign Up", "Get Started", "Discover More"],
businessName: "GlobalMart",
profileImageUrl: "https://example.com/profile.png",
mainImageUrl: "https://example.com/banner.jpg"
};
return {
content: [
{
type: "text",
text: JSON.stringify({
schema,
characterLimits: limits,
exampleMultipleOptionsNote: "Provide 1–5 unique options for each content array to populate dropdowns dynamically.",
note: "All content fields accept 1-5 values except businessName, profileImageUrl, and mainImageUrl which are single values."
}, null, 2)
}
]
};
});
// Tool: get_moment_science_ad_schema
server.tool("get_moment_science_ad_schema", {}, async () => {
const limits = getCharacterLimits('moment-science');
const schema = {
headline: "Array of 3-5 strings (max 90 chars each)",
description: "Array of 3-5 strings (max 220 chars each)",
short_headline: "Array of 3-5 strings (max 60 chars each)",
short_description: "Array of 3-5 strings (max 140 chars each)",
positive_cta: "Array of 3-5 strings (max 25 chars each)",
negative_cta: "Array of 3-5 strings (max 25 chars each)",
imageUrl: "string (optional)"
};
const example = {
headline: [
"Save 25% on an AARP Membership Today!",
"Join AARP and Save Big on Everything",
"Exclusive AARP Member Benefits Await"
],
description: [
"Pay only $15 for your first full year.",
"Get access to exclusive discounts and benefits.",
"Join millions of members saving money daily."
],
short_headline: [
"Save 25% on AARP Today!",
"Join AARP & Save Big",
"AARP Member Benefits"
],
short_description: [
"Pay only $15 for your first year.",
"Get exclusive discounts and benefits.",
"Join millions saving money daily."
],
positive_cta: [
"Join Now!",
"Sign Up Today",
"Get Started"
],
negative_cta: [
"No, Thanks",
"Maybe Later",
"Not Now"
],
imageUrl: "https://example.com/aarp-logo.jpg"
};
return {
content: [
{
type: "text",
text: JSON.stringify({
schema,
characterLimits: limits,
example,
note: "All content fields accept 3-5 values. The template creates a modal popup with Moment Science branding and numbered buttons to switch between variants."
}, null, 2)
}
]
};
});
// Start the server
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('HTML Ad Generator MCP server running on stdio');
//# sourceMappingURL=index.js.map