marketing-post-generator-mcp
Version:
A powerful MCP server for AI-powered marketing blog post generation with Claude integration
394 lines (326 loc) ⢠11.8 kB
JavaScript
/**
* Basic Workflow Example - Marketing Post Generator MCP
*
* This example demonstrates a complete end-to-end workflow:
* 1. Initialize with a blog domain
* 2. Sample existing content to understand style
* 3. Analyze tone for consistency
* 4. Create a content plan
* 5. Generate a narrative for a post
* 6. Write a complete blog post
*
* Usage:
* node examples/basic-workflow.js [domain]
*
* Example:
* node examples/basic-workflow.js blog.stripe.com
*/
import { spawn } from 'child_process';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import fs from 'fs/promises';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
class MCPClient {
constructor() {
this.requestId = 1;
this.process = null;
}
async start() {
console.log('š Starting Marketing Post Generator MCP server...');
// Start the MCP server process
this.process = spawn('node', [join(__dirname, '../dist/index.js')], {
stdio: ['pipe', 'pipe', 'pipe'],
env: {
...process.env,
MCP_MODE: 'local',
MCP_TRANSPORT: 'stdio',
LOG_LEVEL: 'warn' // Reduce log noise
}
});
// Handle process errors
this.process.stderr.on('data', (data) => {
const message = data.toString();
if (!message.includes('INFO') && !message.includes('DEBUG')) {
console.error('Server error:', message);
}
});
// Wait for server to be ready
await new Promise(resolve => setTimeout(resolve, 2000));
console.log('ā
MCP server started successfully');
}
async callPrompt(name, args = {}) {
const request = {
jsonrpc: '2.0',
id: this.requestId++,
method: 'prompts/get',
params: {
name,
arguments: args
}
};
return this.sendRequest(request);
}
async callTool(name, args = {}) {
const request = {
jsonrpc: '2.0',
id: this.requestId++,
method: 'tools/call',
params: {
name,
arguments: args
}
};
return this.sendRequest(request);
}
async sendRequest(request) {
return new Promise((resolve, reject) => {
// Set up response handler
const timeout = setTimeout(() => {
reject(new Error('Request timeout'));
}, 60000); // 1 minute timeout
const responseHandler = (data) => {
try {
const response = JSON.parse(data.toString());
if (response.id === request.id) {
clearTimeout(timeout);
this.process.stdout.removeListener('data', responseHandler);
if (response.error) {
reject(new Error(response.error.message || 'MCP Error'));
} else {
resolve(response.result);
}
}
} catch (error) {
// Ignore parse errors, might be partial data
}
};
this.process.stdout.on('data', responseHandler);
// Send the request
this.process.stdin.write(JSON.stringify(request) + '\n');
});
}
async stop() {
if (this.process) {
this.process.kill('SIGTERM');
console.log('š MCP server stopped');
}
}
}
class WorkflowDemo {
constructor(domain = 'blog.stripe.com') {
this.domain = domain;
this.client = new MCPClient();
this.results = {};
}
async run() {
try {
console.log(`\nšÆ Starting basic workflow demo for: ${this.domain}\n`);
// Start MCP client
await this.client.start();
// Step 1: Initialize
await this.initialize();
// Step 2: Sample content
await this.sampleContent();
// Step 3: Analyze tone
await this.analyzeTone();
// Step 4: Create content plan
await this.createContentPlan();
// Step 5: Generate narrative
await this.generateNarrative();
// Step 6: Write blog post
await this.writeBlogPost();
// Display results summary
this.displaySummary();
} catch (error) {
console.error('ā Workflow failed:', error.message);
process.exit(1);
} finally {
await this.client.stop();
}
}
async initialize() {
console.log('1ļøā£ Initializing with domain...');
try {
const result = await this.client.callPrompt('init', {
domain: this.domain
});
this.results.init = result;
console.log('ā
Domain initialized successfully');
console.log(` š Created .postgen directory structure`);
} catch (error) {
throw new Error(`Initialization failed: ${error.message}`);
}
}
async sampleContent() {
console.log('\n2ļøā£ Sampling existing content...');
try {
const result = await this.client.callTool('sample', {
domain: this.domain,
sampleSize: 5,
maxRequestsPerSecond: 2
});
this.results.sample = result;
console.log(`ā
Sampled ${result.posts?.length || 0} posts`);
console.log(` šÆ Positioning: ${result.analysis?.positioning?.substring(0, 100)}...`);
console.log(` š£ļø Tone: ${result.analysis?.toneOfVoice?.substring(0, 100)}...`);
} catch (error) {
console.log(`ā ļø Sampling failed (continuing anyway): ${error.message}`);
this.results.sample = { error: error.message };
}
}
async analyzeTone() {
console.log('\n3ļøā£ Analyzing tone of voice...');
try {
const result = await this.client.callTool('generate_tone', {
source: this.domain,
detailLevel: 'detailed'
});
this.results.tone = result;
console.log('ā
Tone analysis completed');
console.log(` š Overall tone: ${result.toneAnalysis?.overallTone || 'N/A'}`);
console.log(` š„ Target audience: ${result.toneAnalysis?.targetAudience || 'N/A'}`);
} catch (error) {
console.log(`ā ļø Tone analysis failed (continuing anyway): ${error.message}`);
this.results.tone = { error: error.message };
}
}
async createContentPlan() {
console.log('\n4ļøā£ Creating content plan...');
try {
const result = await this.client.callTool('content_plan', {
domain: this.domain,
timeframe: 'month',
postCount: 4
});
this.results.contentPlan = result;
console.log(`ā
Created content plan with ${result.plan?.length || 0} posts`);
if (result.plan?.length > 0) {
console.log(' š Planned posts:');
result.plan.slice(0, 3).forEach((post, index) => {
console.log(` ${index + 1}. ${post.title}`);
});
if (result.plan.length > 3) {
console.log(` ... and ${result.plan.length - 3} more`);
}
}
} catch (error) {
throw new Error(`Content planning failed: ${error.message}`);
}
}
async generateNarrative() {
console.log('\n5ļøā£ Generating narrative for first post...');
try {
const firstPost = this.results.contentPlan.plan?.[0];
if (!firstPost) {
throw new Error('No posts available in content plan');
}
const result = await this.client.callTool('generate_narrative', {
postId: firstPost.id,
style: 'detailed'
});
this.results.narrative = result;
console.log('ā
Narrative generated');
console.log(` š Post: ${result.title}`);
console.log(` š Estimated words: ${result.metadata?.estimatedWordCount || 'N/A'}`);
console.log(` ā±ļø Reading time: ${result.metadata?.estimatedReadingTime || 'N/A'}`);
} catch (error) {
console.log(`ā ļø Narrative generation failed (trying direct post creation): ${error.message}`);
this.results.narrative = { error: error.message };
}
}
async writeBlogPost() {
console.log('\n6ļøā£ Writing blog post...');
try {
let result;
// Try to use narrative if available
if (this.results.narrative && !this.results.narrative.error) {
result = await this.client.callTool('write_post', {
narrativeId: this.results.narrative.postId,
wordCount: 1200,
style: 'informative'
});
} else {
// Fall back to creating from scratch
const firstPost = this.results.contentPlan.plan?.[0];
result = await this.client.callTool('write_post', {
title: firstPost?.title || 'The Future of Content Generation',
topic: firstPost?.topic || 'AI and Content Creation',
keywords: firstPost?.keywords || ['AI', 'content', 'automation'],
wordCount: 1200,
style: 'informative'
});
}
this.results.blogPost = result;
console.log('ā
Blog post generated');
console.log(` š Title: ${result.title}`);
console.log(` š Word count: ${result.metadata?.wordCount || 'N/A'}`);
console.log(` š File: ${result.filePath}`);
// Try to read and show excerpt
try {
const content = await fs.readFile(result.filePath, 'utf-8');
const lines = content.split('\n');
const firstParagraph = lines.find(line => line.trim() && !line.startsWith('#') && !line.includes(':'));
if (firstParagraph) {
console.log(` š Excerpt: ${firstParagraph.substring(0, 100)}...`);
}
} catch (readError) {
console.log(' š Post saved to file');
}
} catch (error) {
throw new Error(`Blog post generation failed: ${error.message}`);
}
}
displaySummary() {
console.log('\nš Workflow completed successfully!\n');
console.log('š Summary:');
console.log(` Domain: ${this.domain}`);
console.log(` Posts sampled: ${this.results.sample?.posts?.length || 'Failed'}`);
console.log(` Tone analyzed: ${this.results.tone?.error ? 'Failed' : 'Success'}`);
console.log(` Content plan: ${this.results.contentPlan?.plan?.length || 0} posts`);
console.log(` Narrative: ${this.results.narrative?.error ? 'Failed' : 'Generated'}`);
console.log(` Blog post: ${this.results.blogPost?.title ? 'Generated' : 'Failed'}`);
if (this.results.blogPost?.filePath) {
console.log('\nš Generated files:');
console.log(` š Blog post: ${this.results.blogPost.filePath}`);
console.log(` š Content plan: .postgen/content-plans/`);
console.log(` š Analysis: .postgen/analysis/`);
}
console.log('\nš Next steps:');
console.log(' 1. Review the generated blog post');
console.log(' 2. Edit and customize as needed');
console.log(' 3. Generate more posts from your content plan');
console.log(' 4. Explore the .postgen directory for all generated content');
}
}
// Main execution
async function main() {
const domain = process.argv[2] || 'blog.stripe.com';
console.log('Marketing Post Generator - Basic Workflow Demo');
console.log('=============================================');
// Validate domain format
if (!domain.includes('.')) {
console.error('ā Please provide a valid domain (e.g., blog.example.com)');
process.exit(1);
}
const demo = new WorkflowDemo(domain);
await demo.run();
}
// Handle process termination
process.on('SIGINT', () => {
console.log('\nš Demo interrupted by user');
process.exit(0);
});
process.on('SIGTERM', () => {
console.log('\nš Demo terminated');
process.exit(0);
});
// Run if this script is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
main().catch(error => {
console.error('š„ Demo failed:', error.message);
process.exit(1);
});
}
export default WorkflowDemo;