@bulkpublishing/mcp-server 1.0.0

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.
Files changed (54) hide show
  1. package/README.md +170 -0
  2. package/dist/ai/engine.d.ts +12 -0
  3. package/dist/ai/engine.d.ts.map +1 -0
  4. package/dist/ai/engine.js +397 -0
  5. package/dist/ai/engine.js.map +1 -0
  6. package/dist/ai/prompts.d.ts +30 -0
  7. package/dist/ai/prompts.d.ts.map +1 -0
  8. package/dist/ai/prompts.js +207 -0
  9. package/dist/ai/prompts.js.map +1 -0
  10. package/dist/auth/context.d.ts +81 -0
  11. package/dist/auth/context.d.ts.map +1 -0
  12. package/dist/auth/context.js +68 -0
  13. package/dist/auth/context.js.map +1 -0
  14. package/dist/auth/validate.d.ts +13 -0
  15. package/dist/auth/validate.d.ts.map +1 -0
  16. package/dist/auth/validate.js +87 -0
  17. package/dist/auth/validate.js.map +1 -0
  18. package/dist/index.d.ts +22 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +78 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/tools/csv.d.ts +18 -0
  23. package/dist/tools/csv.d.ts.map +1 -0
  24. package/dist/tools/csv.js +673 -0
  25. package/dist/tools/csv.js.map +1 -0
  26. package/dist/tools/generation.d.ts +14 -0
  27. package/dist/tools/generation.d.ts.map +1 -0
  28. package/dist/tools/generation.js +291 -0
  29. package/dist/tools/generation.js.map +1 -0
  30. package/dist/tools/indexing.d.ts +11 -0
  31. package/dist/tools/indexing.d.ts.map +1 -0
  32. package/dist/tools/indexing.js +219 -0
  33. package/dist/tools/indexing.js.map +1 -0
  34. package/dist/tools/projects.d.ts +11 -0
  35. package/dist/tools/projects.d.ts.map +1 -0
  36. package/dist/tools/projects.js +181 -0
  37. package/dist/tools/projects.js.map +1 -0
  38. package/dist/tools/research.d.ts +12 -0
  39. package/dist/tools/research.d.ts.map +1 -0
  40. package/dist/tools/research.js +88 -0
  41. package/dist/tools/research.js.map +1 -0
  42. package/dist/tools/seo.d.ts +12 -0
  43. package/dist/tools/seo.d.ts.map +1 -0
  44. package/dist/tools/seo.js +164 -0
  45. package/dist/tools/seo.js.map +1 -0
  46. package/dist/tools/wordpress.d.ts +15 -0
  47. package/dist/tools/wordpress.d.ts.map +1 -0
  48. package/dist/tools/wordpress.js +447 -0
  49. package/dist/tools/wordpress.js.map +1 -0
  50. package/dist/types.d.ts +206 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +9 -0
  53. package/dist/types.js.map +1 -0
  54. package/package.json +53 -0
@@ -0,0 +1,207 @@
1
+ /**
2
+ * BPAI MCP Server — Prompt Templates
3
+ *
4
+ * Adapted from src/services/ai/prompts.ts for server-side MCP usage.
5
+ * Contains the master generation prompt, humanizer, SEO metadata generator,
6
+ * and internal linking builder.
7
+ */
8
+ // ============================================
9
+ // ARTICLE GENERATION PROMPT
10
+ // ============================================
11
+ export const ROBUST_PROMPT_TEMPLATE = `
12
+ (Act as an expert SEO content writer. Create a comprehensive, high-ranking blog post for the keyword: "{{keyword}}".
13
+
14
+ ⚠️ CRITICAL OUTPUT RULE: Return ONLY raw Markdown text. NEVER return JSON. NEVER wrap your response in { } braces or a code block.
15
+
16
+ KEYWORD INSTRUCTIONS:
17
+ 1. Target Keyword: {{keyword}} (Use natural variations, do NOT overstuff).
18
+ 2. Secondary Keywords: Identify and naturally integrate 3-5 LSI/secondary keywords relevant to the topic to improve semantic reach.
19
+ 3. Slug: Use the exact target keyword as the slug, lowercased and hyphenated.
20
+ 4. **KEYWORD PLACEMENT (STRICT):**
21
+ - The exact target keyword MUST appear in the first 25% of the article (intro or first section).
22
+ - The exact target keyword MUST appear in at least one H2 header near the beginning of the article.
23
+
24
+ {{template_instruction}}
25
+
26
+ DATA & CONTEXT INTEGRITY:
27
+ You will be provided with "Context" or "Research Data" in the user prompt. You MUST prioritize this data for facts, statistics, and specific details. Do NOT simulate a web search. Use the provided real-world data to back your claims.
28
+
29
+ {{title_instruction}}
30
+ {{h1_instruction}}
31
+
32
+ OUTPUT FORMAT (MANDATORY):
33
+ YOU MUST RETURN RAW MARKDOWN TEXT ONLY. DO NOT RETURN JSON.
34
+ - Start directly with the title using # (H1).
35
+ - Use ## for main sections (H2).
36
+ - Use ### for subsections (H3).
37
+ - Use tables, lists, and bold text for readability.
38
+ - NO JSON format. NO code blocks. NO { "title": } structures. Just raw markdown text.
39
+ - At the very END of the article, add the meta description in this format:
40
+ META_DESCRIPTION: [Your 155-char max meta description here]
41
+
42
+ CONTENT SEPARATION (CRITICAL):
43
+ - The article body must contain ONLY the article content itself.
44
+ - DO NOT include any "Meta title", "Slug", "Title Tag", or metadata sections inside the article.
45
+ - Meta titles, slugs, and meta descriptions are handled SEPARATELY by the system.
46
+ - Your output = H1 title + article body + META_DESCRIPTION line at the end. Nothing else.
47
+
48
+ REQUIREMENTS:
49
+ 1. Title & Meta: Title under 60 characters. Meta description 150-160 characters.
50
+ 2. Content Structure (INVERTED PYRAMID): Give the answer immediately. Surface answers first, details later. Remove fluff.
51
+ 3. Readability: Simple, accessible language. Paragraphs 2-3 sentences max. Frequent visual breaks.
52
+ 4. TL;DR: 3-4 bullet points (~75 words) that fully answer the user's query.
53
+ 5. Intro: ~50-100 words, keyword mentioned, direct answer visible.
54
+
55
+ 6. INTRO OPENER (Use Opener Seed: {{opener_seed}}):
56
+ Strategy A — COLD STAT OPENER: Lead with a surprising number.
57
+ Strategy B — CONTRARIAN CLAIM: Bold, slightly provocative statement.
58
+ Strategy C — MINI-STORY: A single vivid sentence.
59
+ Strategy D — DIRECT ANSWER FIRST: Answer the query in sentence one.
60
+ Strategy E — QUESTION HOOK: One sharp question.
61
+ Strategy F — TENSION/PROBLEM: Name the specific pain.
62
+ Strategy G — QUOTE OR ANECDOTE: A real quote or 1-line anecdote.
63
+ Strategy H — BEFORE/AFTER CONTRAST: Transformation in one sentence.
64
+
65
+ 7. Components: DEPTH & EXPANSION. Explain concepts in depth. Include at least one data table.
66
+ {{internal_linking_instructions}}
67
+
68
+ 8. CRITICAL RULES:
69
+ - THE CURRENT YEAR IS 2026.
70
+ - NEVER use em-dashes.
71
+ - Use clear headings (H1, H2, H3).
72
+ - Include tables, lists, bold text for readability.
73
+ - Mention specific brands, models, features, and pricing tiers.
74
+ - Reference external sources with natural anchor text (NOT [1] bracket numbers).
75
+ - NEVER fabricate URLs.
76
+
77
+ 9. FORBIDDEN AI TERMINOLOGY:
78
+ Never use: delve, comprehensive, leverage, harness, seamlessly, robust, landscape, multifaceted, pivotal, realm, intricate, tapestry, synergy, ecosystem, embark, embrace, illuminate, paradigm, culmination, quest, pinnacle, beacon, foster, catalyze, meticulous, nuanced, transformative, holistic, resonate, underscore, underpins, streamline, incorporate, facilitate, encompass, exemplify, epitomize, and all related AI-sounding words.
79
+
80
+ 10. BANNED STRUCTURAL PATTERNS:
81
+ - Three consecutive short "You" sentences.
82
+ - Starting sentences with "By" + gerund.
83
+ - "it's not about X, it's about Y" patterns.
84
+ - Generic closers ("And that's why X matters.").
85
+ - Motivational speech tone.
86
+ `;
87
+ // ============================================
88
+ // SEO METADATA GENERATOR
89
+ // ============================================
90
+ export const SEO_GENERATOR_PROMPT = `You are an expert SEO strategist. Generate optimized metadata for a blog article.
91
+
92
+ CRITICAL: Return ONLY valid JSON. No explanations, no markdown, no code blocks.
93
+
94
+ For the given topic/keyword, return this EXACT JSON structure:
95
+ {
96
+ "title": "Article H1 heading",
97
+ "titleTag": "SEO title for search results (50-60 chars max)",
98
+ "slug": "url-friendly-slug",
99
+ "mainKeyword": "primary target keyword phrase",
100
+ "secondaryKeywords": "related, lsi, keywords",
101
+ "metaDescription": "Action-oriented meta description (150-160 chars)"
102
+ }
103
+
104
+ Rules:
105
+ - THE YEAR IS 2026
106
+ - No forbidden AI words
107
+ - Meta description must lead with a verb
108
+ - Slug is keyword lowercased and hyphenated`;
109
+ // ============================================
110
+ // HUMANIZE PROMPT
111
+ // ============================================
112
+ export const HUMANIZE_PROMPT = `
113
+ You are a senior human editor performing a FINAL anti-AI detection pass on a blog article.
114
+ Your job is to rewrite any sentence or section that reads like AI-generated text, while preserving every fact, link, and piece of data.
115
+
116
+ INPUT CONTENT:
117
+ {{content}}
118
+
119
+ === KEY RULES ===
120
+ 1. Replace all banned AI words (delve, comprehensive, leverage, harness, etc.)
121
+ 2. Replace all banned phrases ("in today's", "it's important to note", etc.)
122
+ 3. Remove ALL em-dashes and replace with periods, commas, or semicolons
123
+ 4. Mix sentence lengths (short punchy + medium)
124
+ 5. No triplet patterns (3+ sentences starting with same word)
125
+ 6. No "By + gerund" sentence starters
126
+ 7. Use contractions (don't, it's, they're)
127
+ 8. Vary paragraph openings
128
+ 9. Preserve ALL links, data, formatting, and content depth
129
+ 10. Output must be at least as long as input
130
+
131
+ === OUTPUT ===
132
+ Return the FULL humanized article in Markdown. Nothing else.
133
+ `;
134
+ // ============================================
135
+ // INTERNAL LINKING BUILDER
136
+ // ============================================
137
+ export function buildInternalLinkingPrompt(urls, maxLinks) {
138
+ if (!urls || urls.length === 0)
139
+ return '';
140
+ const linkCount = Math.min(Math.max(maxLinks || 3, 1), 10);
141
+ // Shuffle for variety across articles
142
+ const shuffled = [...urls];
143
+ for (let i = shuffled.length - 1; i > 0; i--) {
144
+ const j = Math.floor(Math.random() * (i + 1));
145
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
146
+ }
147
+ const limited = shuffled.slice(0, 150);
148
+ return `
149
+ === INTERNAL LINKING REQUIREMENTS (MANDATORY) ===
150
+ You MUST include EXACTLY ${linkCount} internal links to other pages on this site.
151
+
152
+ INSTRUCTIONS:
153
+ 1. Choose ${linkCount} URLs from the "Candidate URLs" list below.
154
+ 2. Insert them NATURALLY into the content proper.
155
+ 3. DO NOT create a "Related Posts" section. The links must be inside the paragraphs.
156
+ 4. Use DESCRIPTIVE anchor text.
157
+ 5. Distribute the links evenly throughout the article.
158
+
159
+ CANDIDATE URLS (Choose ${linkCount} from this list):
160
+ ${limited.map(url => ` - ${url}`).join('\n')}
161
+ `;
162
+ }
163
+ // ============================================
164
+ // RESEARCH PROMPT
165
+ // ============================================
166
+ export const RESEARCH_PROMPT = `You are a research assistant. Search the web for accurate, current information about the given topic.
167
+
168
+ Focus on:
169
+ 1. Recent data, statistics, and trends (2025-2026)
170
+ 2. Expert opinions and quotes
171
+ 3. Specific product names, pricing, and features
172
+ 4. Competitor comparisons
173
+ 5. Common questions people ask about this topic
174
+
175
+ Return your findings as a structured research brief with citations. Include source URLs where available.`;
176
+ // ============================================
177
+ // PROMPT BUILDER HELPERS
178
+ // ============================================
179
+ const OPENER_STRATEGIES = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
180
+ export function buildGenerationPrompt(options) {
181
+ const openerSeed = OPENER_STRATEGIES[(options.openerSeedIndex || Math.floor(Math.random() * 8)) % 8];
182
+ let systemPrompt = ROBUST_PROMPT_TEMPLATE
183
+ .replace(/\{\{keyword\}\}/g, options.keyword)
184
+ .replace('{{opener_seed}}', openerSeed)
185
+ .replace('{{template_instruction}}', options.templateInstruction || '')
186
+ .replace('{{title_instruction}}', options.titleInstruction || '')
187
+ .replace('{{h1_instruction}}', options.h1Instruction || '')
188
+ .replace('{{internal_linking_instructions}}', options.internalLinkUrls
189
+ ? buildInternalLinkingPrompt(options.internalLinkUrls, options.maxInternalLinks || 5)
190
+ : '');
191
+ // Build user prompt
192
+ let userPrompt = `Write a ${options.wordCount || 2500}-word article about "${options.keyword}".`;
193
+ if (options.tone) {
194
+ userPrompt += `\n\nTone: ${options.tone}`;
195
+ }
196
+ if (options.knowledgeBaseContext) {
197
+ systemPrompt = `${options.knowledgeBaseContext}\n\n${systemPrompt}`;
198
+ }
199
+ if (options.researchContext) {
200
+ userPrompt += `\n\n=== RESEARCH DATA (USE THIS AS PRIMARY SOURCE) ===\n${options.researchContext}`;
201
+ }
202
+ if (options.ctaSnippets && options.ctaSnippets.length > 0) {
203
+ userPrompt += `\n\n=== CTA BLOCKS (Insert these naturally into the article) ===\n${options.ctaSnippets.join('\n\n')}`;
204
+ }
205
+ return { systemPrompt, userPrompt };
206
+ }
207
+ //# sourceMappingURL=prompts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/ai/prompts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * MCP Server — User Context
3
+ *
4
+ * Holds the authenticated user's full context after token validation.
5
+ * All tools read from this singleton instead of requiring inline keys.
6
+ */
7
+ import type { ApiKeys } from '../types.js';
8
+ export interface UserContext {
9
+ authenticated: boolean;
10
+ user: {
11
+ id: string;
12
+ name: string;
13
+ email: string;
14
+ };
15
+ settings: {
16
+ api_keys: ApiKeys;
17
+ model_settings: Record<string, any>;
18
+ global_prompts: {
19
+ strategy?: string;
20
+ customSystem?: string;
21
+ masterSystem?: string;
22
+ userTemplate?: string;
23
+ additionalInstructions?: string;
24
+ };
25
+ multi_key_config: Record<string, any>;
26
+ master_prompt: string | null;
27
+ };
28
+ projects: ProjectContext[];
29
+ activeProjectId: string | null;
30
+ knowledgeBase: KBItem[];
31
+ }
32
+ export interface ProjectContext {
33
+ id: string;
34
+ name: string;
35
+ description?: string;
36
+ color?: string;
37
+ created_at: string;
38
+ settings: {
39
+ internal_linking?: {
40
+ enabled: boolean;
41
+ sitemapUrls: string[];
42
+ maxLinksPerPost: number;
43
+ sourceUrl?: string;
44
+ includedPaths?: string[];
45
+ };
46
+ cross_linking?: {
47
+ enabled: boolean;
48
+ baseUrl: string;
49
+ maxLinksPerPost: number;
50
+ };
51
+ wordpress?: {
52
+ connected: boolean;
53
+ siteUrl: string;
54
+ username: string;
55
+ appPassword: string;
56
+ };
57
+ };
58
+ }
59
+ export interface KBItem {
60
+ id: string;
61
+ title: string;
62
+ content: string;
63
+ project_id: string | null;
64
+ type: string;
65
+ }
66
+ export declare function setUserContext(ctx: UserContext): void;
67
+ export declare function getUserContext(): UserContext;
68
+ /**
69
+ * Get the API keys for a specific provider.
70
+ * Falls back to env vars if the user's stored key is empty.
71
+ */
72
+ export declare function getApiKey(provider: string): string | undefined;
73
+ /**
74
+ * Get the active project's settings, or a specific project by ID.
75
+ */
76
+ export declare function getProjectSettings(projectId?: string): ProjectContext | undefined;
77
+ /**
78
+ * Get knowledge base items for the active project (includes global items).
79
+ */
80
+ export declare function getKnowledgeBaseContext(projectId?: string): string;
81
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/auth/context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,WAAW;IACxB,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,EAAE;QACF,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,QAAQ,EAAE;QACN,QAAQ,EAAE,OAAO,CAAC;QAClB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACpC,cAAc,EAAE;YACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,YAAY,CAAC,EAAE,MAAM,CAAC;YACtB,YAAY,CAAC,EAAE,MAAM,CAAC;YACtB,YAAY,CAAC,EAAE,MAAM,CAAC;YACtB,sBAAsB,CAAC,EAAE,MAAM,CAAC;SACnC,CAAC;QACF,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACtC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;KAChC,CAAC;IACF,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,aAAa,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE;QACN,gBAAgB,CAAC,EAAE;YACf,OAAO,EAAE,OAAO,CAAC;YACjB,WAAW,EAAE,MAAM,EAAE,CAAC;YACtB,eAAe,EAAE,MAAM,CAAC;YACxB,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;SAC5B,CAAC;QACF,aAAa,CAAC,EAAE;YACZ,OAAO,EAAE,OAAO,CAAC;YACjB,OAAO,EAAE,MAAM,CAAC;YAChB,eAAe,EAAE,MAAM,CAAC;SAC3B,CAAC;QACF,SAAS,CAAC,EAAE;YACR,SAAS,EAAE,OAAO,CAAC;YACnB,OAAO,EAAE,MAAM,CAAC;YAChB,QAAQ,EAAE,MAAM,CAAC;YACjB,WAAW,EAAE,MAAM,CAAC;SACvB,CAAC;KACL,CAAC;CACL;AAED,MAAM,WAAW,MAAM;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;CAChB;AAkBD,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAErD;AAED,wBAAgB,cAAc,IAAI,WAAW,CAE5C;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAiB9D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAIjF;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CASlE"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * MCP Server — User Context
3
+ *
4
+ * Holds the authenticated user's full context after token validation.
5
+ * All tools read from this singleton instead of requiring inline keys.
6
+ */
7
+ // Singleton — set once on startup, read by all tools
8
+ let _context = {
9
+ authenticated: false,
10
+ user: { id: '', name: '', email: '' },
11
+ settings: {
12
+ api_keys: {},
13
+ model_settings: {},
14
+ global_prompts: {},
15
+ multi_key_config: {},
16
+ master_prompt: null,
17
+ },
18
+ projects: [],
19
+ activeProjectId: null,
20
+ knowledgeBase: [],
21
+ };
22
+ export function setUserContext(ctx) {
23
+ _context = ctx;
24
+ }
25
+ export function getUserContext() {
26
+ return _context;
27
+ }
28
+ /**
29
+ * Get the API keys for a specific provider.
30
+ * Falls back to env vars if the user's stored key is empty.
31
+ */
32
+ export function getApiKey(provider) {
33
+ const keys = _context.settings.api_keys;
34
+ const stored = keys[provider];
35
+ if (stored)
36
+ return stored;
37
+ // Fallback to environment variables
38
+ const envMap = {
39
+ openai: 'OPENAI_API_KEY',
40
+ claude: 'CLAUDE_API_KEY',
41
+ deepseek: 'DEEPSEEK_API_KEY',
42
+ grok: 'GROK_API_KEY',
43
+ gemini: 'GEMINI_API_KEY',
44
+ perplexity: 'PERPLEXITY_API_KEY',
45
+ kimi: 'KIMI_API_KEY',
46
+ };
47
+ return process.env[envMap[provider] || ''];
48
+ }
49
+ /**
50
+ * Get the active project's settings, or a specific project by ID.
51
+ */
52
+ export function getProjectSettings(projectId) {
53
+ const targetId = projectId || _context.activeProjectId;
54
+ if (!targetId)
55
+ return _context.projects[0]; // fallback to first project
56
+ return _context.projects.find(p => p.id === targetId);
57
+ }
58
+ /**
59
+ * Get knowledge base items for the active project (includes global items).
60
+ */
61
+ export function getKnowledgeBaseContext(projectId) {
62
+ const targetId = projectId || _context.activeProjectId;
63
+ const items = _context.knowledgeBase.filter(kb => kb.project_id === targetId || kb.project_id === null);
64
+ if (items.length === 0)
65
+ return '';
66
+ return items.map(kb => `[${kb.type || 'context'}] ${kb.title}:\n${kb.content}`).join('\n\n---\n\n');
67
+ }
68
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/auth/context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAiEH,qDAAqD;AACrD,IAAI,QAAQ,GAAgB;IACxB,aAAa,EAAE,KAAK;IACpB,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;IACrC,QAAQ,EAAE;QACN,QAAQ,EAAE,EAAE;QACZ,cAAc,EAAE,EAAE;QAClB,cAAc,EAAE,EAAE;QAClB,gBAAgB,EAAE,EAAE;QACpB,aAAa,EAAE,IAAI;KACtB;IACD,QAAQ,EAAE,EAAE;IACZ,eAAe,EAAE,IAAI;IACrB,aAAa,EAAE,EAAE;CACpB,CAAC;AAEF,MAAM,UAAU,cAAc,CAAC,GAAgB;IAC3C,QAAQ,GAAG,GAAG,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,cAAc;IAC1B,OAAO,QAAQ,CAAC;AACpB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,QAAgB;IACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,QAAkC,CAAC;IAClE,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9B,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,oCAAoC;IACpC,MAAM,MAAM,GAA2B;QACnC,MAAM,EAAE,gBAAgB;QACxB,MAAM,EAAE,gBAAgB;QACxB,QAAQ,EAAE,kBAAkB;QAC5B,IAAI,EAAE,cAAc;QACpB,MAAM,EAAE,gBAAgB;QACxB,UAAU,EAAE,oBAAoB;QAChC,IAAI,EAAE,cAAc;KACvB,CAAC;IAEF,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;AAC/C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAkB;IACjD,MAAM,QAAQ,GAAG,SAAS,IAAI,QAAQ,CAAC,eAAe,CAAC;IACvD,IAAI,CAAC,QAAQ;QAAE,OAAO,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,4BAA4B;IACxE,OAAO,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC;AAC1D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,uBAAuB,CAAC,SAAkB;IACtD,MAAM,QAAQ,GAAG,SAAS,IAAI,QAAQ,CAAC,eAAe,CAAC;IACvD,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CACvC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,KAAK,QAAQ,IAAI,EAAE,CAAC,UAAU,KAAK,IAAI,CAC7D,CAAC;IAEF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAElC,OAAO,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI,SAAS,KAAK,EAAE,CAAC,KAAK,MAAM,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;AACxG,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * MCP Server — Token Validation
3
+ *
4
+ * On startup, validates the BPAI_API_KEY against the auth edge function,
5
+ * loads the full user context, and makes it available to all tools.
6
+ */
7
+ import type { UserContext } from './context.js';
8
+ /**
9
+ * Validate the BPAI API key and load user context.
10
+ * Called once on server startup. Throws on failure.
11
+ */
12
+ export declare function authenticate(): Promise<UserContext>;
13
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/auth/validate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,WAAW,EAA0B,MAAM,cAAc,CAAC;AAKxE;;;GAGG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,WAAW,CAAC,CA2FzD"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * MCP Server — Token Validation
3
+ *
4
+ * On startup, validates the BPAI_API_KEY against the auth edge function,
5
+ * loads the full user context, and makes it available to all tools.
6
+ */
7
+ import { setUserContext } from './context.js';
8
+ // The auth endpoint deployed on the BPAI Netlify site
9
+ const AUTH_ENDPOINT = process.env.BPAI_AUTH_URL || 'https://bulkpublishing.ai/api/mcp-auth';
10
+ /**
11
+ * Validate the BPAI API key and load user context.
12
+ * Called once on server startup. Throws on failure.
13
+ */
14
+ export async function authenticate() {
15
+ const apiKey = process.env.BPAI_API_KEY;
16
+ if (!apiKey) {
17
+ throw new Error('BPAI_API_KEY not set. Generate your MCP API key at bulkpublishing.ai → Settings → API Access, ' +
18
+ 'then add it to your MCP client config as BPAI_API_KEY.');
19
+ }
20
+ console.error('[Auth] Validating API key...');
21
+ try {
22
+ const response = await fetch(AUTH_ENDPOINT, {
23
+ method: 'POST',
24
+ headers: {
25
+ 'Content-Type': 'application/json',
26
+ 'Authorization': `Bearer ${apiKey}`,
27
+ },
28
+ });
29
+ if (!response.ok) {
30
+ const body = await response.json().catch(() => ({}));
31
+ const message = body.message || body.error || `HTTP ${response.status}`;
32
+ if (response.status === 401) {
33
+ throw new Error(`Authentication failed: ${message}. ` +
34
+ 'Check that your BPAI_API_KEY is correct and not expired.');
35
+ }
36
+ if (response.status === 403) {
37
+ throw new Error(`Subscription inactive: ${message}. ` +
38
+ 'Visit bulkpublishing.ai/checkout to reactivate your plan.');
39
+ }
40
+ throw new Error(`Auth endpoint error: ${message}`);
41
+ }
42
+ const data = await response.json();
43
+ // Build user context from response
44
+ const context = {
45
+ authenticated: true,
46
+ user: data.user,
47
+ settings: {
48
+ api_keys: data.settings.api_keys || {},
49
+ model_settings: data.settings.model_settings || {},
50
+ global_prompts: data.settings.global_prompts || {},
51
+ multi_key_config: data.settings.multi_key_config || {},
52
+ master_prompt: data.settings.master_prompt || null,
53
+ },
54
+ projects: (data.projects || []).map((p) => ({
55
+ id: p.id,
56
+ name: p.name,
57
+ description: p.description,
58
+ color: p.color,
59
+ created_at: p.created_at,
60
+ settings: p.settings || {},
61
+ })),
62
+ activeProjectId: data.projects?.[0]?.id || null,
63
+ knowledgeBase: (data.knowledge_base || []).map((kb) => ({
64
+ id: kb.id,
65
+ title: kb.title,
66
+ content: kb.content,
67
+ project_id: kb.project_id,
68
+ type: kb.type,
69
+ })),
70
+ };
71
+ // Store in singleton
72
+ setUserContext(context);
73
+ console.error(`[Auth] Authenticated as ${context.user.name} (${context.user.email})`);
74
+ console.error(`[Auth] ${context.projects.length} projects loaded`);
75
+ console.error(`[Auth] ${context.knowledgeBase.length} knowledge base items loaded`);
76
+ console.error(`[Auth] Providers with keys: ${Object.keys(context.settings.api_keys).filter(k => context.settings.api_keys[k]).join(', ') || 'none (use env vars)'}`);
77
+ return context;
78
+ }
79
+ catch (error) {
80
+ if (error.message.includes('fetch failed') || error.cause?.code === 'ENOTFOUND') {
81
+ throw new Error('Could not reach the BPAI auth server. Check your internet connection. ' +
82
+ 'If the issue persists, visit status.bulkpublishing.ai for service status.');
83
+ }
84
+ throw error;
85
+ }
86
+ }
87
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/auth/validate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Bulk Publishing AI — MCP Server
4
+ *
5
+ * A Model Context Protocol server that exposes BPAI's content generation,
6
+ * WordPress publishing, SEO, and indexing capabilities as tools callable
7
+ * from any MCP-compatible client (Claude Desktop, Antigravity, Cursor, etc.).
8
+ *
9
+ * Transport: stdio (standard MCP transport)
10
+ *
11
+ * Usage:
12
+ * node dist/index.js
13
+ * npx tsx src/index.ts (development)
14
+ *
15
+ * Required environment variables:
16
+ * BPAI_API_KEY — Your Bulk Publishing AI API key (from Settings → API Access)
17
+ *
18
+ * Optional overrides:
19
+ * BPAI_AUTH_URL — Custom auth endpoint (default: https://bulkpublishing.ai/api/mcp-auth)
20
+ */
21
+ export {};
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;GAkBG"}
package/dist/index.js ADDED
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Bulk Publishing AI — MCP Server
4
+ *
5
+ * A Model Context Protocol server that exposes BPAI's content generation,
6
+ * WordPress publishing, SEO, and indexing capabilities as tools callable
7
+ * from any MCP-compatible client (Claude Desktop, Antigravity, Cursor, etc.).
8
+ *
9
+ * Transport: stdio (standard MCP transport)
10
+ *
11
+ * Usage:
12
+ * node dist/index.js
13
+ * npx tsx src/index.ts (development)
14
+ *
15
+ * Required environment variables:
16
+ * BPAI_API_KEY — Your Bulk Publishing AI API key (from Settings → API Access)
17
+ *
18
+ * Optional overrides:
19
+ * BPAI_AUTH_URL — Custom auth endpoint (default: https://bulkpublishing.ai/api/mcp-auth)
20
+ */
21
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
22
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
23
+ // Auth
24
+ import { authenticate } from './auth/validate.js';
25
+ // Tool registrations
26
+ import { registerGenerationTools } from './tools/generation.js';
27
+ import { registerResearchTools } from './tools/research.js';
28
+ import { registerWordPressTools } from './tools/wordpress.js';
29
+ import { registerIndexingTools } from './tools/indexing.js';
30
+ import { registerSEOTools } from './tools/seo.js';
31
+ import { registerProjectTools } from './tools/projects.js';
32
+ import { registerCSVTools } from './tools/csv.js';
33
+ // ============================================
34
+ // SERVER SETUP
35
+ // ============================================
36
+ const server = new McpServer({
37
+ name: 'Bulk Publishing AI',
38
+ version: '1.0.0',
39
+ }, {
40
+ capabilities: {
41
+ tools: {},
42
+ },
43
+ });
44
+ // ============================================
45
+ // REGISTER ALL TOOL GROUPS
46
+ // ============================================
47
+ registerGenerationTools(server);
48
+ registerResearchTools(server);
49
+ registerWordPressTools(server);
50
+ registerIndexingTools(server);
51
+ registerSEOTools(server);
52
+ registerProjectTools(server);
53
+ registerCSVTools(server);
54
+ // ============================================
55
+ // START SERVER
56
+ // ============================================
57
+ async function main() {
58
+ // Step 1: Authenticate with BPAI account
59
+ try {
60
+ await authenticate();
61
+ }
62
+ catch (error) {
63
+ console.error(`[BPAI MCP Server] Authentication failed: ${error.message}`);
64
+ process.exit(1);
65
+ }
66
+ // Step 2: Connect MCP transport
67
+ const transport = new StdioServerTransport();
68
+ await server.connect(transport);
69
+ // Log to stderr (stdout is reserved for MCP protocol messages)
70
+ console.error('[BPAI MCP Server] Started successfully');
71
+ console.error('[BPAI MCP Server] Available tool groups: generation, research, wordpress, indexing, seo, projects, csv');
72
+ console.error('[BPAI MCP Server] Transport: stdio');
73
+ }
74
+ main().catch((error) => {
75
+ console.error('[BPAI MCP Server] Fatal error:', error);
76
+ process.exit(1);
77
+ });
78
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO;AACP,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,qBAAqB;AACrB,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,+CAA+C;AAC/C,eAAe;AACf,+CAA+C;AAE/C,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IACzB,IAAI,EAAE,oBAAoB;IAC1B,OAAO,EAAE,OAAO;CACnB,EAAE;IACC,YAAY,EAAE;QACV,KAAK,EAAE,EAAE;KACZ;CACJ,CAAC,CAAC;AAEH,+CAA+C;AAC/C,2BAA2B;AAC3B,+CAA+C;AAE/C,uBAAuB,CAAC,MAAM,CAAC,CAAC;AAChC,qBAAqB,CAAC,MAAM,CAAC,CAAC;AAC9B,sBAAsB,CAAC,MAAM,CAAC,CAAC;AAC/B,qBAAqB,CAAC,MAAM,CAAC,CAAC;AAC9B,gBAAgB,CAAC,MAAM,CAAC,CAAC;AACzB,oBAAoB,CAAC,MAAM,CAAC,CAAC;AAC7B,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAEzB,+CAA+C;AAC/C,eAAe;AACf,+CAA+C;AAE/C,KAAK,UAAU,IAAI;IACf,yCAAyC;IACzC,IAAI,CAAC;QACD,MAAM,YAAY,EAAE,CAAC;IACzB,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,4CAA4C,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,gCAAgC;IAChC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,+DAA+D;IAC/D,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACxD,OAAO,CAAC,KAAK,CAAC,wGAAwG,CAAC,CAAC;IACxH,OAAO,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;AACxD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,KAAK,CAAC,CAAC;IACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * BPAI MCP Server — CSV Import / Export Tools
3
+ *
4
+ * MCP tool handlers for:
5
+ * - import_csv: Read a local CSV and auto-detect columns
6
+ * - export_csv: Write articles to a CSV file
7
+ * - export_markdown: Write articles as individual .md files
8
+ * - export_json: Write articles as structured JSON
9
+ * - export_bulk: Combined multi-format export with filtering
10
+ *
11
+ * Security:
12
+ * - All operations run on the user's local machine (filesystem only)
13
+ * - No database or remote calls; articles are provided inline by the MCP client
14
+ * - Auth check on every tool call via getUserContext()
15
+ */
16
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
17
+ export declare function registerCSVTools(server: McpServer): void;
18
+ //# sourceMappingURL=csv.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csv.d.ts","sourceRoot":"","sources":["../../src/tools/csv.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA6QzE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,QAsbjD"}