UNPKG

marketing-post-generator-mcp

Version:

A powerful MCP server for AI-powered marketing blog post generation with Claude integration

210 lines (200 loc) 8.34 kB
import { createLogger } from '../utils/logger.js'; import * as fs from 'fs/promises'; import * as path from 'path'; import * as crypto from 'crypto'; export class SummarizeTool { logger; searchService; constructor(searchService, logger) { this.logger = logger || createLogger({ level: 'info', format: 'simple' }); this.searchService = searchService; } getToolDefinition() { return { name: 'marketing_post_generator_mcp__summarize', description: 'Generate a concise summary of a blog post from its URL. This tool fetches the content of a specific blog post and creates an AI-generated summary that captures the main points and key insights.', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'The URL of the blog post to summarize', }, }, required: ['url'], }, }; } async execute(args, claudeService) { const { url } = args; try { this.logger.info('Starting summarize tool execution', { url }); // Validate URL format this.validateUrl(url); // Validate .postgen directory exists await this.validatePostgenDirectory(); // Generate URL hash for caching const urlHash = this.generateUrlHash(url); const summaryPath = path.join(process.cwd(), '.postgen', 'summaries', `${urlHash}.json`); // Check for existing summary (cache hit) const existingSummary = await this.checkExistingSummary(summaryPath); if (existingSummary) { this.logger.info('Returning cached summary', { url, cached: true }); return this.formatResponse(existingSummary); } // Fetch blog post content using SearchService this.logger.info('Fetching blog post content', { url }); const content = await this.searchService.fetchContent(url); if (!content) { throw new Error(`Unable to fetch content from URL: ${url}. Please verify the URL is accessible and contains blog content.`); } this.logger.info('Blog post content fetched successfully', { url, contentLength: content.length, }); // Extract title from content (basic implementation) const title = this.extractTitleFromContent(content, url); // Generate summary with Claude this.logger.info('Generating summary with Claude', { url }); const summary = await this.generateSummary(title, content, claudeService); // Prepare result const result = { url, title, summary, timestamp: new Date().toISOString(), }; // Save result await this.saveResult(result, summaryPath); this.logger.info('Summarize tool execution completed successfully', { url }); return this.formatResponse(result); } catch (error) { this.logger.error('Summarize tool execution failed', { url, error: error instanceof Error ? error.message : String(error), }); throw new Error(`Failed to summarize blog post from ${url}: ${error instanceof Error ? error.message : String(error)}`); } } validateUrl(url) { try { const urlObj = new URL(url); if (!['http:', 'https:'].includes(urlObj.protocol)) { throw new Error('URL must use HTTP or HTTPS protocol'); } } catch (error) { throw new Error(`Invalid URL format: ${url}`); } } async validatePostgenDirectory() { const postgenDir = path.join(process.cwd(), '.postgen'); try { await fs.access(postgenDir); } catch (error) { throw new Error('The .postgen directory does not exist. Please run the init tool first to set up the project structure.'); } // Ensure summaries directory exists const summariesDir = path.join(postgenDir, 'summaries'); try { await fs.access(summariesDir); } catch (error) { await fs.mkdir(summariesDir, { recursive: true }); this.logger.info('Created summaries directory', { summariesDir }); } } generateUrlHash(url) { return crypto.createHash('md5').update(url).digest('hex'); } async checkExistingSummary(summaryPath) { try { const existingContent = await fs.readFile(summaryPath, 'utf-8'); return JSON.parse(existingContent); } catch (error) { // No existing summary found return null; } } async generateSummary(title, content, claudeService) { const summaryPrompt = this.buildSummaryPrompt(title, content); const response = await claudeService.generateContent(summaryPrompt, { maxTokens: 1024, temperature: 0.3, }); return response.content.trim(); } buildSummaryPrompt(title, content) { // Truncate content if too long to fit in prompt const maxContentLength = 4000; const truncatedContent = content.length > maxContentLength ? content.substring(0, maxContentLength) + '...' : content; return `You are a professional content analyst tasked with creating concise, informative summaries of blog posts. Please analyze the following blog post and create a summary that captures: 1. The main thesis or key message 2. Important supporting points or arguments 3. Key insights, findings, or conclusions 4. Practical implications or actionable takeaways Title: ${title} Content: ${truncatedContent} Instructions: - Write a concise summary in 2-4 paragraphs - Focus on the most important insights and key points - Use clear, professional language - Avoid redundancy and filler content - Capture the essence that would be valuable to someone deciding whether to read the full post Summary:`; } extractTitleFromContent(content, url) { // Try to extract title from the first few lines of content const lines = content.split('\n').slice(0, 10); for (const line of lines) { const trimmed = line.trim(); // Look for lines that could be titles (not too short, not too long, first in content) if (trimmed.length > 10 && trimmed.length < 200 && !trimmed.includes(' ')) { return trimmed; } } // Fallback: extract from URL try { const urlObj = new URL(url); const pathParts = urlObj.pathname.split('/').filter((part) => part.length > 0); if (pathParts.length > 0) { const lastPart = pathParts[pathParts.length - 1]; if (lastPart) { // Convert URL slug to title const title = lastPart .replace(/[-_]/g, ' ') .replace(/\.(html?|php|aspx?)$/i, '') .split(' ') .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(' '); return title; } } } catch (error) { this.logger.debug('Failed to extract title from URL', { url, error }); } return 'Untitled Post'; } async saveResult(result, summaryPath) { await fs.writeFile(summaryPath, JSON.stringify(result, null, 2)); this.logger.info('Summary result saved', { summaryPath, url: result.url }); } formatResponse(result) { return `# Blog Post Summary ## Original Post - **Title**: ${result.title} - **URL**: ${result.url} - **Summarized**: ${new Date(result.timestamp).toLocaleDateString()} ## Summary ${result.summary} --- **Note**: This summary has been cached and can be accessed again without re-processing the content.`; } } //# sourceMappingURL=SummarizeTool.js.map