suparank 1.2.5 → 1.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/suparank.js +85 -536
- package/credentials.example.json +36 -18
- package/mcp-client/config.js +37 -0
- package/mcp-client/handlers/action.js +33 -0
- package/mcp-client/handlers/backend.js +43 -0
- package/mcp-client/handlers/index.js +9 -0
- package/mcp-client/handlers/orchestrator.js +850 -0
- package/mcp-client/index.js +33 -0
- package/mcp-client/publishers/ghost.js +105 -0
- package/mcp-client/publishers/image.js +306 -0
- package/mcp-client/publishers/index.js +20 -0
- package/mcp-client/publishers/webhook.js +76 -0
- package/mcp-client/publishers/wordpress.js +220 -0
- package/mcp-client/server.js +220 -0
- package/mcp-client/services/api.js +101 -0
- package/mcp-client/services/credentials.js +149 -0
- package/mcp-client/services/index.js +11 -0
- package/mcp-client/services/project.js +40 -0
- package/mcp-client/services/session-state.js +201 -0
- package/mcp-client/services/stats.js +50 -0
- package/mcp-client/tools/definitions.js +679 -0
- package/mcp-client/tools/discovery.js +132 -0
- package/mcp-client/tools/index.js +22 -0
- package/mcp-client/utils/content.js +126 -0
- package/mcp-client/utils/formatting.js +71 -0
- package/mcp-client/utils/index.js +10 -0
- package/mcp-client/utils/logging.js +38 -0
- package/mcp-client/utils/paths.js +134 -0
- package/mcp-client/workflow/index.js +10 -0
- package/mcp-client/workflow/planner.js +513 -0
- package/package.json +8 -19
- package/mcp-client.js +0 -3693
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Suparank MCP - Workflow Planner
|
|
3
|
+
*
|
|
4
|
+
* Builds multi-step workflow plans for content creation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { log } from '../utils/logging.js'
|
|
8
|
+
import { hasCredential, getExternalMCPs, getCompositionHints } from '../services/credentials.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validate project configuration
|
|
12
|
+
* @param {object} config - Project configuration from database
|
|
13
|
+
* @returns {{ warnings: string[] }} Validation result
|
|
14
|
+
* @throws {Error} If required fields are missing
|
|
15
|
+
*/
|
|
16
|
+
export function validateProjectConfig(config) {
|
|
17
|
+
const errors = []
|
|
18
|
+
|
|
19
|
+
if (!config) {
|
|
20
|
+
throw new Error('Project configuration not found. Please configure your project in the dashboard.')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Check required fields
|
|
24
|
+
if (!config.content?.default_word_count) {
|
|
25
|
+
errors.push('Word count: Not set → Dashboard → Project Settings → Content')
|
|
26
|
+
} else if (typeof config.content.default_word_count !== 'number' || config.content.default_word_count < 100) {
|
|
27
|
+
errors.push('Word count: Must be at least 100 words')
|
|
28
|
+
} else if (config.content.default_word_count > 10000) {
|
|
29
|
+
errors.push('Word count: Maximum 10,000 words supported')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!config.brand?.voice) {
|
|
33
|
+
errors.push('Brand voice: Not set → Dashboard → Project Settings → Brand')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!config.site?.niche) {
|
|
37
|
+
errors.push('Niche: Not set → Dashboard → Project Settings → Site')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Warnings (non-blocking but helpful)
|
|
41
|
+
const warnings = []
|
|
42
|
+
if (!config.seo?.primary_keywords?.length) {
|
|
43
|
+
warnings.push('No primary keywords set - content may lack SEO focus')
|
|
44
|
+
}
|
|
45
|
+
if (!config.brand?.target_audience) {
|
|
46
|
+
warnings.push('No target audience set - content may be too generic')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (errors.length > 0) {
|
|
50
|
+
throw new Error(`Project configuration incomplete:\n${errors.map(e => ` - ${e}`).join('\n')}`)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { warnings }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build a workflow plan for content creation
|
|
58
|
+
* @param {string} request - Content request description
|
|
59
|
+
* @param {number} count - Number of articles to create
|
|
60
|
+
* @param {string[]} publishTo - Platforms to publish to
|
|
61
|
+
* @param {boolean} withImages - Whether to generate images
|
|
62
|
+
* @param {object} project - Project configuration from database
|
|
63
|
+
* @returns {object} Workflow plan object
|
|
64
|
+
*/
|
|
65
|
+
export function buildWorkflowPlan(request, count, publishTo, withImages, project) {
|
|
66
|
+
const steps = []
|
|
67
|
+
const hasGhost = hasCredential('ghost')
|
|
68
|
+
const hasWordPress = hasCredential('wordpress')
|
|
69
|
+
const hasImageGen = hasCredential('image')
|
|
70
|
+
|
|
71
|
+
// Get project config from database - MUST be dynamic, no hardcoding
|
|
72
|
+
const config = project?.config
|
|
73
|
+
|
|
74
|
+
// Validate configuration with helpful messages
|
|
75
|
+
const { warnings } = validateProjectConfig(config)
|
|
76
|
+
if (warnings.length > 0) {
|
|
77
|
+
log(`Config warnings: ${warnings.join('; ')}`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Extract all settings from project.config (database schema)
|
|
81
|
+
const targetWordCount = config.content?.default_word_count
|
|
82
|
+
|
|
83
|
+
// LOG ALL CONFIG VALUES FOR DEBUGGING
|
|
84
|
+
log('=== PROJECT CONFIG VALUES ===')
|
|
85
|
+
log(`Word Count Target: ${targetWordCount}`)
|
|
86
|
+
log(`Reading Level: ${config.content?.reading_level}`)
|
|
87
|
+
log(`Brand Voice: ${config.brand?.voice}`)
|
|
88
|
+
log(`Target Audience: ${config.brand?.target_audience}`)
|
|
89
|
+
log(`Primary Keywords: ${config.seo?.primary_keywords?.join(', ')}`)
|
|
90
|
+
log(`Include Images: ${config.content?.include_images}`)
|
|
91
|
+
log('=============================')
|
|
92
|
+
|
|
93
|
+
// CRITICAL: Validate word count is set
|
|
94
|
+
if (!targetWordCount || targetWordCount < 100) {
|
|
95
|
+
log(`WARNING: Word count not properly set! Got: ${targetWordCount}`)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const readingLevel = config.content?.reading_level
|
|
99
|
+
const includeImages = config.content?.include_images
|
|
100
|
+
const brandVoice = config.brand?.voice
|
|
101
|
+
const targetAudience = config.brand?.target_audience
|
|
102
|
+
const differentiators = config.brand?.differentiators || []
|
|
103
|
+
const visualStyle = config.visual_style?.image_aesthetic
|
|
104
|
+
const brandColors = config.visual_style?.colors || []
|
|
105
|
+
const primaryKeywords = config.seo?.primary_keywords || []
|
|
106
|
+
const geoFocus = config.seo?.geo_focus
|
|
107
|
+
const niche = config.site?.niche
|
|
108
|
+
const siteName = config.site?.name
|
|
109
|
+
const siteUrl = config.site?.url
|
|
110
|
+
const siteDescription = config.site?.description
|
|
111
|
+
|
|
112
|
+
// Calculate required images: 1 cover + 1 per 300 words (only if includeImages is true)
|
|
113
|
+
const shouldGenerateImages = withImages && includeImages && hasImageGen
|
|
114
|
+
const contentImageCount = shouldGenerateImages ? Math.floor(targetWordCount / 300) : 0
|
|
115
|
+
const totalImages = shouldGenerateImages ? 1 + contentImageCount : 0 // cover + inline images
|
|
116
|
+
|
|
117
|
+
// Format reading level for display (stored as number, display as "Grade X")
|
|
118
|
+
const readingLevelDisplay = readingLevel ? `Grade ${readingLevel}` : 'Not set'
|
|
119
|
+
|
|
120
|
+
// Format keywords for display
|
|
121
|
+
const keywordsDisplay = primaryKeywords.length > 0 ? primaryKeywords.join(', ') : 'No keywords set'
|
|
122
|
+
|
|
123
|
+
// Determine publish targets
|
|
124
|
+
let targets = publishTo || []
|
|
125
|
+
if (targets.length === 0 || targets.includes('all')) {
|
|
126
|
+
targets = []
|
|
127
|
+
if (hasGhost) targets.push('ghost')
|
|
128
|
+
if (hasWordPress) targets.push('wordpress')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let stepNum = 0
|
|
132
|
+
|
|
133
|
+
// Build dynamic MCP hints from local credentials (user-configured in credentials.json)
|
|
134
|
+
const externalMcps = getExternalMCPs()
|
|
135
|
+
const keywordResearchHints = getCompositionHints('keyword_research')
|
|
136
|
+
|
|
137
|
+
let mcpInstructions = ''
|
|
138
|
+
if (externalMcps.length > 0) {
|
|
139
|
+
const mcpList = externalMcps.map(m => `- **${m.name}**: ${m.available_tools?.join(', ') || 'tools available'}`).join('\n')
|
|
140
|
+
mcpInstructions = `\n**External MCPs Available (from your credentials.json):**\n${mcpList}`
|
|
141
|
+
if (keywordResearchHints) {
|
|
142
|
+
mcpInstructions += `\n\n**Integration Hint:** ${keywordResearchHints}`
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ═══════════════════════════════════════════════════════════
|
|
147
|
+
// RESEARCH PHASE
|
|
148
|
+
// ═══════════════════════════════════════════════════════════
|
|
149
|
+
|
|
150
|
+
stepNum++
|
|
151
|
+
steps.push({
|
|
152
|
+
step: stepNum,
|
|
153
|
+
type: 'llm_execute',
|
|
154
|
+
action: 'keyword_research',
|
|
155
|
+
instruction: `Research keywords for: "${request}"
|
|
156
|
+
|
|
157
|
+
**Project Context (from database):**
|
|
158
|
+
- Site: ${siteName} (${siteUrl})
|
|
159
|
+
- Niche: ${niche}
|
|
160
|
+
- Description: ${siteDescription || 'Not set'}
|
|
161
|
+
- Primary keywords: ${keywordsDisplay}
|
|
162
|
+
- Geographic focus: ${geoFocus || 'Global'}
|
|
163
|
+
${mcpInstructions}
|
|
164
|
+
|
|
165
|
+
**Deliverables:**
|
|
166
|
+
- 1 primary keyword to target (lower difficulty preferred)
|
|
167
|
+
- 3-5 secondary/LSI keywords
|
|
168
|
+
- 2-3 question-based keywords for FAQ section`,
|
|
169
|
+
store: 'keywords'
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
// Step 2: SEO Strategy & Content Brief
|
|
173
|
+
stepNum++
|
|
174
|
+
steps.push({
|
|
175
|
+
step: stepNum,
|
|
176
|
+
type: 'llm_execute',
|
|
177
|
+
action: 'seo_strategy',
|
|
178
|
+
instruction: `Create SEO strategy and content brief for: "${request}"
|
|
179
|
+
|
|
180
|
+
**Using Keywords from Step 1:**
|
|
181
|
+
- Use the primary keyword you identified
|
|
182
|
+
- Incorporate secondary/LSI keywords naturally
|
|
183
|
+
|
|
184
|
+
**Project Context:**
|
|
185
|
+
- Site: ${siteName}
|
|
186
|
+
- Niche: ${niche}
|
|
187
|
+
- Target audience: ${targetAudience || 'Not specified'}
|
|
188
|
+
- Brand voice: ${brandVoice}
|
|
189
|
+
- Geographic focus: ${geoFocus || 'Global'}
|
|
190
|
+
|
|
191
|
+
**Deliverables:**
|
|
192
|
+
1. **Search Intent Analysis** - What is the user trying to accomplish?
|
|
193
|
+
2. **Competitor Gap Analysis** - What are top 3 ranking pages missing?
|
|
194
|
+
3. **Content Brief:**
|
|
195
|
+
- Recommended content type (guide/listicle/how-to/comparison)
|
|
196
|
+
- Unique angle to differentiate from competitors
|
|
197
|
+
- Key points to cover that competitors miss
|
|
198
|
+
4. **On-Page SEO Checklist:**
|
|
199
|
+
- Title tag format
|
|
200
|
+
- Meta description template
|
|
201
|
+
- Header structure (H1, H2, H3)
|
|
202
|
+
- Internal linking opportunities`,
|
|
203
|
+
store: 'seo_strategy'
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
// Step 3: Topical Map (Content Architecture)
|
|
207
|
+
stepNum++
|
|
208
|
+
steps.push({
|
|
209
|
+
step: stepNum,
|
|
210
|
+
type: 'llm_execute',
|
|
211
|
+
action: 'topical_map',
|
|
212
|
+
instruction: `Design content architecture for: "${request}"
|
|
213
|
+
|
|
214
|
+
**Build a Pillar-Cluster Structure:**
|
|
215
|
+
- Main pillar topic (this article)
|
|
216
|
+
- Supporting cluster articles (future content opportunities)
|
|
217
|
+
|
|
218
|
+
**Project Context:**
|
|
219
|
+
- Site: ${siteName}
|
|
220
|
+
- Niche: ${niche}
|
|
221
|
+
- Primary keywords: ${keywordsDisplay}
|
|
222
|
+
|
|
223
|
+
**Deliverables:**
|
|
224
|
+
1. **Pillar Page Concept** - What should this main article establish?
|
|
225
|
+
2. **Cluster Topics** - 5-7 related subtopics for future articles
|
|
226
|
+
3. **Internal Linking Plan** - How these articles connect
|
|
227
|
+
4. **Content Gaps** - What topics are missing in this niche?
|
|
228
|
+
|
|
229
|
+
Note: Focus on the CURRENT article structure, but identify opportunities for a content cluster.`,
|
|
230
|
+
store: 'topical_map'
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
// Step 4: Content Calendar (only for multi-article requests)
|
|
234
|
+
if (count > 1) {
|
|
235
|
+
stepNum++
|
|
236
|
+
steps.push({
|
|
237
|
+
step: stepNum,
|
|
238
|
+
type: 'llm_execute',
|
|
239
|
+
action: 'content_calendar',
|
|
240
|
+
instruction: `Plan content calendar for ${count} articles about: "${request}"
|
|
241
|
+
|
|
242
|
+
**Project Context:**
|
|
243
|
+
- Site: ${siteName}
|
|
244
|
+
- Niche: ${niche}
|
|
245
|
+
- Articles to create: ${count}
|
|
246
|
+
|
|
247
|
+
**Deliverables:**
|
|
248
|
+
1. **Article Sequence** - Order to create articles (foundational → specific)
|
|
249
|
+
2. **Topic List** - ${count} specific titles/topics
|
|
250
|
+
3. **Keyword Assignment** - Primary keyword for each article
|
|
251
|
+
4. **Publishing Cadence** - Recommended frequency
|
|
252
|
+
|
|
253
|
+
Note: This guides the creation of all ${count} articles in this session.`,
|
|
254
|
+
store: 'content_calendar'
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ═══════════════════════════════════════════════════════════
|
|
259
|
+
// CREATION PHASE
|
|
260
|
+
// ═══════════════════════════════════════════════════════════
|
|
261
|
+
|
|
262
|
+
// Step N: Content Planning with SEO Meta
|
|
263
|
+
stepNum++
|
|
264
|
+
steps.push({
|
|
265
|
+
step: stepNum,
|
|
266
|
+
type: 'llm_execute',
|
|
267
|
+
action: 'content_planning',
|
|
268
|
+
instruction: `Create a detailed content outline with SEO meta:
|
|
269
|
+
|
|
270
|
+
**Project Requirements (from database):**
|
|
271
|
+
- Site: ${siteName}
|
|
272
|
+
- Target audience: ${targetAudience || 'Not specified'}
|
|
273
|
+
- Brand voice: ${brandVoice}
|
|
274
|
+
- Brand differentiators: ${differentiators.length > 0 ? differentiators.join(', ') : 'Not set'}
|
|
275
|
+
- Word count: **${targetWordCount} words MINIMUM** (this is required!)
|
|
276
|
+
- Reading level: **${readingLevelDisplay}** (use simple sentences, avoid jargon)
|
|
277
|
+
|
|
278
|
+
**You MUST create:**
|
|
279
|
+
|
|
280
|
+
1. **SEO Meta Title** (50-60 characters, include primary keyword)
|
|
281
|
+
2. **SEO Meta Description** (150-160 characters, compelling, include keyword)
|
|
282
|
+
3. **URL Slug** (lowercase, hyphens, keyword-rich)
|
|
283
|
+
4. **Content Outline:**
|
|
284
|
+
- H1: Main title
|
|
285
|
+
- 6-8 H2 sections (to achieve ${targetWordCount} words)
|
|
286
|
+
- H3 subsections where needed
|
|
287
|
+
- FAQ section with 4-5 questions
|
|
288
|
+
|
|
289
|
+
${shouldGenerateImages ? `**Image Placeholders:** Mark where ${contentImageCount} inline images should go (1 every ~300 words)
|
|
290
|
+
Use format: [IMAGE: description of what image should show]` : '**Note:** Images disabled for this project.'}`,
|
|
291
|
+
store: 'outline'
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
// Step: Write Content
|
|
295
|
+
stepNum++
|
|
296
|
+
steps.push({
|
|
297
|
+
step: stepNum,
|
|
298
|
+
type: 'llm_execute',
|
|
299
|
+
action: 'content_write',
|
|
300
|
+
instruction: `Write the COMPLETE article following your outline.
|
|
301
|
+
|
|
302
|
+
**MANDATORY WORD COUNT: ${targetWordCount} WORDS MINIMUM**
|
|
303
|
+
This is a strict requirement from the project settings.
|
|
304
|
+
The article will be REJECTED if under ${targetWordCount} words.
|
|
305
|
+
|
|
306
|
+
**Project Requirements (from Supabase database - DO NOT IGNORE):**
|
|
307
|
+
- Word count: **${targetWordCount} words** (MINIMUM - not a suggestion!)
|
|
308
|
+
- Reading level: **${readingLevelDisplay}** - Simple sentences, short paragraphs
|
|
309
|
+
- Brand voice: ${brandVoice}
|
|
310
|
+
- Target audience: ${targetAudience || 'General readers'}
|
|
311
|
+
|
|
312
|
+
**To reach ${targetWordCount} words, you MUST:**
|
|
313
|
+
- Write 8-10 substantial H2 sections (each 200-400 words)
|
|
314
|
+
- Include detailed examples, statistics, and actionable advice
|
|
315
|
+
- Add comprehensive FAQ section (5-8 questions)
|
|
316
|
+
- Expand each point with thorough explanations
|
|
317
|
+
|
|
318
|
+
**Content Structure:**
|
|
319
|
+
- Engaging hook in first 2 sentences
|
|
320
|
+
- All H2/H3 sections from your outline (expand each thoroughly!)
|
|
321
|
+
- Statistics, examples, and actionable tips in EVERY section
|
|
322
|
+
${shouldGenerateImages ? '- Image placeholders: [IMAGE: description] where images should go' : ''}
|
|
323
|
+
- FAQ section with 5-8 Q&As (detailed answers, not one-liners)
|
|
324
|
+
- Strong conclusion with clear CTA
|
|
325
|
+
|
|
326
|
+
**After writing ${targetWordCount}+ words, call 'save_content' with:**
|
|
327
|
+
- title: Your SEO-optimized title
|
|
328
|
+
- content: The full article (markdown)
|
|
329
|
+
- keywords: Array of target keywords
|
|
330
|
+
- meta_description: Your 150-160 char meta description
|
|
331
|
+
|
|
332
|
+
STOP! Before calling save_content, verify you have ${targetWordCount}+ words.
|
|
333
|
+
Count the words. If under ${targetWordCount}, ADD MORE CONTENT.`,
|
|
334
|
+
store: 'article'
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
// ═══════════════════════════════════════════════════════════
|
|
338
|
+
// OPTIMIZATION PHASE
|
|
339
|
+
// ═══════════════════════════════════════════════════════════
|
|
340
|
+
|
|
341
|
+
// Quality Check - Pre-publish QA
|
|
342
|
+
stepNum++
|
|
343
|
+
steps.push({
|
|
344
|
+
step: stepNum,
|
|
345
|
+
type: 'llm_execute',
|
|
346
|
+
action: 'quality_check',
|
|
347
|
+
instruction: `Perform quality check on the article you just saved.
|
|
348
|
+
|
|
349
|
+
**Quality Checklist:**
|
|
350
|
+
|
|
351
|
+
1. **SEO Check:**
|
|
352
|
+
- Primary keyword in H1, first 100 words, URL slug
|
|
353
|
+
- Secondary keywords distributed naturally
|
|
354
|
+
- Meta title 50-60 characters
|
|
355
|
+
- Meta description 150-160 characters
|
|
356
|
+
- Proper header hierarchy (H1 → H2 → H3)
|
|
357
|
+
|
|
358
|
+
2. **Content Quality:**
|
|
359
|
+
- Word count meets requirement (${targetWordCount}+ words)
|
|
360
|
+
- Reading level appropriate (${readingLevelDisplay})
|
|
361
|
+
- No grammar or spelling errors
|
|
362
|
+
- Factual accuracy (no made-up statistics)
|
|
363
|
+
|
|
364
|
+
3. **Brand Consistency:**
|
|
365
|
+
- Voice matches: ${brandVoice}
|
|
366
|
+
- Speaks to: ${targetAudience || 'target audience'}
|
|
367
|
+
- Aligns with ${siteName} brand
|
|
368
|
+
|
|
369
|
+
4. **Engagement:**
|
|
370
|
+
- Strong hook in introduction
|
|
371
|
+
- Clear value proposition
|
|
372
|
+
- Actionable takeaways
|
|
373
|
+
- Compelling CTA in conclusion
|
|
374
|
+
|
|
375
|
+
**Report any issues found and suggest fixes. If major issues exist, fix them before proceeding.**`,
|
|
376
|
+
store: 'quality_report'
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
// GEO Optimize - AI Search Engine Optimization
|
|
380
|
+
stepNum++
|
|
381
|
+
steps.push({
|
|
382
|
+
step: stepNum,
|
|
383
|
+
type: 'llm_execute',
|
|
384
|
+
action: 'geo_optimize',
|
|
385
|
+
instruction: `Optimize article for AI search engines (ChatGPT, Perplexity, Google SGE, Claude).
|
|
386
|
+
|
|
387
|
+
**GEO (Generative Engine Optimization) Checklist:**
|
|
388
|
+
|
|
389
|
+
1. **Structured Answers:**
|
|
390
|
+
- Clear, direct answers to common questions
|
|
391
|
+
- Definition boxes for key terms
|
|
392
|
+
- TL;DR sections for complex topics
|
|
393
|
+
|
|
394
|
+
2. **Citation-Worthy Content:**
|
|
395
|
+
- Original statistics or data points
|
|
396
|
+
- Expert quotes or authoritative sources
|
|
397
|
+
- Unique insights not found elsewhere
|
|
398
|
+
|
|
399
|
+
3. **LLM-Friendly Structure:**
|
|
400
|
+
- Bulleted lists for easy extraction
|
|
401
|
+
- Tables for comparisons
|
|
402
|
+
- Step-by-step numbered processes
|
|
403
|
+
|
|
404
|
+
4. **Semantic Clarity:**
|
|
405
|
+
- Clear topic sentences per paragraph
|
|
406
|
+
- Explicit cause-effect relationships
|
|
407
|
+
- Avoid ambiguous pronouns
|
|
408
|
+
|
|
409
|
+
**Target AI Engines:**
|
|
410
|
+
- ChatGPT (conversational answers)
|
|
411
|
+
- Perplexity (citation-heavy)
|
|
412
|
+
- Google SGE (structured snippets)
|
|
413
|
+
- Claude (comprehensive analysis)
|
|
414
|
+
|
|
415
|
+
**Review the saved article and suggest specific improvements to make it more likely to be cited by AI search engines.**`,
|
|
416
|
+
store: 'geo_report'
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
// ═══════════════════════════════════════════════════════════
|
|
420
|
+
// PUBLISHING PHASE
|
|
421
|
+
// ═══════════════════════════════════════════════════════════
|
|
422
|
+
|
|
423
|
+
// Generate Images (if enabled in project settings AND credentials available)
|
|
424
|
+
if (shouldGenerateImages) {
|
|
425
|
+
// Format brand colors for image style guidance
|
|
426
|
+
const colorsDisplay = brandColors.length > 0 ? brandColors.join(', ') : 'Not specified'
|
|
427
|
+
|
|
428
|
+
stepNum++
|
|
429
|
+
steps.push({
|
|
430
|
+
step: stepNum,
|
|
431
|
+
type: 'llm_execute',
|
|
432
|
+
action: 'generate_images',
|
|
433
|
+
instruction: `Generate ${totalImages} images for the article:
|
|
434
|
+
|
|
435
|
+
**Required Images:**
|
|
436
|
+
1. **Cover/Hero Image** - Main article header (16:9 aspect ratio)
|
|
437
|
+
${Array.from({length: contentImageCount}, (_, i) => `${i + 2}. **Section Image ${i + 1}** - For content section ${i + 1} (16:9 aspect ratio)`).join('\n')}
|
|
438
|
+
|
|
439
|
+
**For each image, call 'generate_image' tool with:**
|
|
440
|
+
- prompt: Detailed description based on article content
|
|
441
|
+
- style: ${visualStyle || 'professional minimalist'}
|
|
442
|
+
- aspect_ratio: 16:9
|
|
443
|
+
|
|
444
|
+
**Visual Style (from project database):**
|
|
445
|
+
- Image aesthetic: ${visualStyle || 'Not specified'}
|
|
446
|
+
- Brand colors: ${colorsDisplay}
|
|
447
|
+
- Keep consistent with ${siteName} brand identity
|
|
448
|
+
|
|
449
|
+
**Image Style Guide:**
|
|
450
|
+
- Professional, clean aesthetic
|
|
451
|
+
- Relevant to the section topic
|
|
452
|
+
- No text in images
|
|
453
|
+
- Consistent style across all images
|
|
454
|
+
|
|
455
|
+
After generating, note the URLs - they will be saved automatically for publishing.`,
|
|
456
|
+
image_count: totalImages,
|
|
457
|
+
store: 'images'
|
|
458
|
+
})
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Step: Publish
|
|
462
|
+
if (targets.length > 0) {
|
|
463
|
+
stepNum++
|
|
464
|
+
steps.push({
|
|
465
|
+
step: stepNum,
|
|
466
|
+
type: 'action',
|
|
467
|
+
action: 'publish',
|
|
468
|
+
instruction: `Publish the article to: ${targets.join(', ')}
|
|
469
|
+
|
|
470
|
+
Call 'publish_content' tool - it will automatically use:
|
|
471
|
+
- Saved article title and content
|
|
472
|
+
- SEO meta description
|
|
473
|
+
- Generated images (cover + inline)
|
|
474
|
+
- Target keywords as tags`,
|
|
475
|
+
targets: targets
|
|
476
|
+
})
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return {
|
|
480
|
+
workflow_id: `wf_${Date.now()}`,
|
|
481
|
+
request: request,
|
|
482
|
+
total_articles: count,
|
|
483
|
+
current_article: 1,
|
|
484
|
+
total_steps: steps.length,
|
|
485
|
+
current_step: 1,
|
|
486
|
+
// All settings come from project.config (database) - no hardcoded values
|
|
487
|
+
project_info: {
|
|
488
|
+
name: siteName,
|
|
489
|
+
url: siteUrl,
|
|
490
|
+
niche: niche
|
|
491
|
+
},
|
|
492
|
+
settings: {
|
|
493
|
+
target_word_count: targetWordCount,
|
|
494
|
+
reading_level: readingLevel,
|
|
495
|
+
reading_level_display: readingLevelDisplay,
|
|
496
|
+
brand_voice: brandVoice,
|
|
497
|
+
target_audience: targetAudience,
|
|
498
|
+
include_images: includeImages,
|
|
499
|
+
total_images: totalImages,
|
|
500
|
+
content_images: contentImageCount,
|
|
501
|
+
visual_style: visualStyle,
|
|
502
|
+
primary_keywords: primaryKeywords,
|
|
503
|
+
geo_focus: geoFocus
|
|
504
|
+
},
|
|
505
|
+
available_integrations: {
|
|
506
|
+
external_mcps: externalMcps.map(m => m.name),
|
|
507
|
+
ghost: hasGhost,
|
|
508
|
+
wordpress: hasWordPress,
|
|
509
|
+
image_generation: hasImageGen
|
|
510
|
+
},
|
|
511
|
+
steps: steps
|
|
512
|
+
}
|
|
513
|
+
}
|
package/package.json
CHANGED
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "suparank",
|
|
3
|
-
"version": "1.2.
|
|
4
|
-
"description": "AI-powered SEO content creation MCP - generate and publish optimized blog posts with
|
|
5
|
-
"main": "mcp-client.js",
|
|
3
|
+
"version": "1.2.7",
|
|
4
|
+
"description": "AI-powered SEO content creation MCP - generate and publish optimized blog posts with your AI assistant",
|
|
6
5
|
"type": "module",
|
|
7
6
|
"bin": {
|
|
8
7
|
"suparank": "./bin/suparank.js"
|
|
9
8
|
},
|
|
10
9
|
"files": [
|
|
11
10
|
"bin/",
|
|
12
|
-
"mcp-client
|
|
11
|
+
"mcp-client/",
|
|
13
12
|
"credentials.example.json",
|
|
14
|
-
"README.md"
|
|
15
|
-
"LICENSE"
|
|
13
|
+
"README.md"
|
|
16
14
|
],
|
|
17
|
-
"scripts": {
|
|
18
|
-
"start": "node mcp-client.js",
|
|
19
|
-
"test": "node bin/suparank.js test"
|
|
20
|
-
},
|
|
21
15
|
"keywords": [
|
|
22
16
|
"mcp",
|
|
23
17
|
"model-context-protocol",
|
|
@@ -30,18 +24,13 @@
|
|
|
30
24
|
"blog",
|
|
31
25
|
"wordpress",
|
|
32
26
|
"ghost",
|
|
33
|
-
"content-generation"
|
|
34
|
-
"anthropic",
|
|
35
|
-
"openai"
|
|
27
|
+
"content-generation"
|
|
36
28
|
],
|
|
37
|
-
"author": "Suparank
|
|
29
|
+
"author": "Suparank",
|
|
38
30
|
"license": "MIT",
|
|
39
31
|
"repository": {
|
|
40
32
|
"type": "git",
|
|
41
|
-
"url": "
|
|
42
|
-
},
|
|
43
|
-
"bugs": {
|
|
44
|
-
"url": "https://github.com/Suparank/Suparank-MCP/issues"
|
|
33
|
+
"url": "https://github.com/Suparank/Suparank-MCP.git"
|
|
45
34
|
},
|
|
46
35
|
"homepage": "https://suparank.io",
|
|
47
36
|
"dependencies": {
|
|
@@ -49,6 +38,6 @@
|
|
|
49
38
|
"marked": "^15.0.12"
|
|
50
39
|
},
|
|
51
40
|
"engines": {
|
|
52
|
-
"node": ">=
|
|
41
|
+
"node": ">=20.0.0"
|
|
53
42
|
}
|
|
54
43
|
}
|