@motiblog/mcp 0.1.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/server.ts","../src/api-client.ts","../src/tools.ts","../src/export.ts","../../../packages/shared/src/constants/index.ts","../../../packages/shared/src/export/article-markdown.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { loadConfig } from './config';\nimport { buildServer } from './server';\n\n/**\n * MotiBlog MCP server — stdio entrypoint (local agents).\n *\n * Env:\n * MOTIBLOG_API_URL — API base (default http://localhost:3001)\n * MOTIBLOG_API_KEY — per-project API key (required; rotate in dashboard)\n * MOTIBLOG_PROJECT_ID — optional default project for tools that take project_id\n *\n * Remote agents: run src/http.ts instead (`pnpm --filter @motiblog/mcp start:http`).\n */\nasync function main(): Promise<void> {\n const config = loadConfig();\n const server: McpServer = buildServer(config);\n await server.connect(new StdioServerTransport());\n}\n\nmain().catch((err) => {\n // stderr is safe on stdio transport — stdout is protocol-only.\n console.error('[motiblog-mcp] fatal:', err);\n process.exit(1);\n});\n","export interface McpConfig {\n apiBaseUrl: string;\n apiKey: string;\n defaultProjectId?: string;\n}\n\nexport function loadConfig(env: NodeJS.ProcessEnv = process.env): McpConfig {\n const apiBaseUrl = (env.MOTIBLOG_API_URL || 'http://localhost:3001').replace(/\\/+$/, '');\n const apiKey = env.MOTIBLOG_API_KEY?.trim();\n\n if (!apiKey) {\n throw new Error(\n 'MOTIBLOG_API_KEY is required. Get a per-project key from the MotiBlog dashboard ' +\n '(Project → Blog API) or via POST /projects/:id/blog-api/rotate.',\n );\n }\n\n return {\n apiBaseUrl,\n apiKey,\n defaultProjectId: env.MOTIBLOG_PROJECT_ID?.trim() || undefined,\n };\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { MotiblogApiClient } from './api-client';\nimport type { McpConfig } from './config';\nimport { toolDefinitions, type ToolResult } from './tools';\n\n/**\n * Build the MotiBlog MCP server with the full tool surface.\n *\n * Shared by both entrypoints:\n * - src/index.ts (stdio, local agents)\n * - src/http.ts (streamable HTTP, remote agents)\n *\n * The config is injected rather than loaded once so HTTP mode can\n * authenticate each request with its own caller's API key.\n */\nexport function buildServer(config: McpConfig): McpServer {\n const client = new MotiblogApiClient(config.apiBaseUrl, config.apiKey);\n\n const server = new McpServer(\n { name: 'motiblog', version: '0.1.0' },\n {\n instructions:\n 'MotiBlog: agent-operated blog content engine. Typical loop: list_projects → ' +\n 'suggest_topics / list_content_plans → approve_content_plan → generate_article → ' +\n 'list_review_queue + get_article (check factCheckReport & seoScore) → update_article ' +\n 'if fixes are needed → approve_publication → publish_to_integration or export_blog ' +\n 'to ship content to your own codebase. Publication always passes the governed ' +\n 'approval gate — there is no bypass.',\n },\n );\n\n // registerTool's generic inference explodes on our union of zod shapes, so\n // bind through a deliberately loose signature — validation still happens at\n // runtime inside the SDK.\n const register = server.registerTool.bind(server) as unknown as (\n name: string,\n config: Record<string, unknown>,\n cb: (args: Record<string, unknown>) => Promise<{\n content: Array<{ type: 'text'; text: string }>;\n isError?: boolean;\n }>,\n ) => unknown;\n\n for (const tool of toolDefinitions) {\n register(\n tool.name,\n {\n title: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n ...(tool.annotations ? { annotations: tool.annotations } : {}),\n },\n async (args: Record<string, unknown>) => {\n let result: ToolResult;\n try {\n result = await tool.handler(args ?? {}, {\n client,\n defaultProjectId: config.defaultProjectId,\n });\n } catch (err) {\n result = {\n text: `Error: ${err instanceof Error ? err.message : String(err)}`,\n isError: true,\n };\n }\n return {\n content: [{ type: 'text', text: result.text }],\n ...(result.isError ? { isError: true } : {}),\n };\n },\n );\n }\n\n return server;\n}\n","import type {\n Article,\n ArticleSummary,\n ContentPlan,\n Integration,\n Keyword,\n PipelineLog,\n Project,\n ProprietaryFact,\n PublishLog,\n} from './types';\n\n/**\n * Thin HTTP client for the MotiBlog REST API.\n *\n * The global TransformInterceptor wraps successes as `{ data }` and the\n * HttpExceptionFilter returns `{ statusCode, message, errors? }` — both are\n * normalized here so tool handlers deal with plain resources and readable\n * error messages.\n */\nexport class MotiblogApiError extends Error {\n constructor(\n readonly status: number,\n readonly url: string,\n readonly body: unknown,\n message: string,\n ) {\n super(message);\n this.name = 'MotiblogApiError';\n }\n}\n\ninterface RequestOptions {\n query?: Record<string, string | number | boolean | undefined>;\n body?: unknown;\n}\n\nexport class MotiblogApiClient {\n constructor(\n private readonly apiBaseUrl: string,\n private readonly apiKey: string,\n private readonly fetchImpl: typeof fetch = fetch,\n ) {}\n\n async request<T>(method: string, path: string, opts: RequestOptions = {}): Promise<T> {\n // Idempotent methods get one retry on network failure — transient DNS /\n // TLS / socket hiccups otherwise surface as confusing \"fetch failed\" tool\n // errors even though a second attempt would succeed.\n const attempts = method === 'GET' ? 2 : 1;\n let lastErr: unknown;\n for (let attempt = 1; attempt <= attempts; attempt++) {\n try {\n return await this.requestOnce<T>(method, path, opts);\n } catch (err) {\n lastErr = err;\n // API-level errors (4xx/5xx with a response) are not transport flakes.\n if (err instanceof MotiblogApiError) throw err;\n if (attempt === attempts) break;\n await new Promise((r) => setTimeout(r, 400 * attempt));\n }\n }\n throw lastErr;\n }\n\n private async requestOnce<T>(method: string, path: string, opts: RequestOptions = {}): Promise<T> {\n let url = `${this.apiBaseUrl}${path}`;\n const search = new URLSearchParams();\n for (const [k, v] of Object.entries(opts.query ?? {})) {\n if (v !== undefined && v !== '') search.set(k, String(v));\n }\n const qs = search.toString();\n if (qs) url += `?${qs}`;\n\n const res = await this.fetchImpl(url, {\n method,\n headers: {\n 'x-api-key': this.apiKey,\n ...(opts.body !== undefined ? { 'content-type': 'application/json' } : {}),\n },\n body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,\n });\n\n const text = await res.text();\n let parsed: unknown;\n try {\n parsed = text ? JSON.parse(text) : undefined;\n } catch {\n parsed = text;\n }\n\n if (!res.ok) {\n const bodyObj =\n parsed && typeof parsed === 'object'\n ? (parsed as Record<string, unknown>)\n : {};\n const validation = bodyObj.errors as Record<string, string[]> | undefined;\n const detail = Array.isArray(bodyObj.message)\n ? (bodyObj.message as unknown[]).join('; ')\n : typeof bodyObj.message === 'string'\n ? bodyObj.message\n : typeof parsed === 'string' && parsed\n ? parsed\n : `HTTP ${res.status}`;\n const suffix = validation ? ` — ${JSON.stringify(validation)}` : '';\n throw new MotiblogApiError(res.status, url, parsed, `${detail}${suffix}`);\n }\n\n if (parsed && typeof parsed === 'object' && 'data' in (parsed as object)) {\n return (parsed as { data: T }).data;\n }\n return parsed as T;\n }\n\n // ── Projects ───────────────────────────────────────────────────────────────\n listProjects(): Promise<Project[]> {\n return this.request<Project[]>('GET', '/projects');\n }\n\n getProject(projectId: string): Promise<Project> {\n return this.request<Project>('GET', `/projects/${projectId}`);\n }\n\n pipelineStart(projectId: string): Promise<{ message: string }> {\n return this.request('POST', `/projects/${projectId}/pipeline/start`);\n }\n\n pipelineStatus(projectId: string): Promise<Record<string, unknown>> {\n return this.request('GET', `/projects/${projectId}/pipeline/status`);\n }\n\n // ── Content plans / topics ────────────────────────────────────────────────\n createContentPlanFromGap(projectId: string, topic: string): Promise<ContentPlan> {\n return this.request('POST', `/projects/${projectId}/content-plans/from-gap`, {\n body: { topic },\n });\n }\n\n listContentPlans(projectId: string): Promise<ContentPlan[]> {\n return this.request('GET', `/projects/${projectId}/content-plans`);\n }\n\n approveContentPlan(projectId: string, planId: string): Promise<ContentPlan> {\n return this.request(\n 'PATCH',\n `/projects/${projectId}/content-plans/${planId}/approve`,\n );\n }\n\n regenerateContentPlan(projectId: string, planId: string): Promise<ContentPlan> {\n return this.request(\n 'POST',\n `/projects/${projectId}/content-plans/${planId}/regenerate`,\n );\n }\n\n generateArticleFromPlan(projectId: string, planId: string): Promise<Article> {\n return this.request(\n 'POST',\n `/projects/${projectId}/content-plans/${planId}/generate`,\n );\n }\n\n getCalendar(projectId: string, start: string, end: string): Promise<unknown[]> {\n return this.request('GET', `/projects/${projectId}/calendar`, {\n query: { start, end },\n });\n }\n\n // ── Articles ──────────────────────────────────────────────────────────────\n listArticles(projectId: string, status?: string): Promise<ArticleSummary[]> {\n return this.request('GET', `/projects/${projectId}/articles`, {\n query: { status },\n });\n }\n\n getArticle(projectId: string, articleId: string): Promise<Article> {\n return this.request('GET', `/projects/${projectId}/articles/${articleId}`);\n }\n\n updateArticle(\n projectId: string,\n articleId: string,\n patch: { title?: string; content?: string; metaDescription?: string; status?: string },\n ): Promise<Article> {\n return this.request('PATCH', `/projects/${projectId}/articles/${articleId}`, {\n body: patch,\n });\n }\n\n scheduleArticle(\n projectId: string,\n articleId: string,\n scheduledFor: string | null,\n ): Promise<Article> {\n return this.request(\n 'PATCH',\n `/projects/${projectId}/articles/${articleId}/schedule`,\n { body: { scheduledFor } },\n );\n }\n\n approveAndPublish(projectId: string, articleId: string): Promise<Article> {\n return this.request(\n 'POST',\n `/projects/${projectId}/articles/${articleId}/approve-and-publish`,\n );\n }\n\n regenerateArticle(projectId: string, articleId: string): Promise<Article> {\n return this.request(\n 'POST',\n `/projects/${projectId}/articles/${articleId}/regenerate`,\n );\n }\n\n regenerateChapter(\n projectId: string,\n articleId: string,\n chapterIndex: number,\n ): Promise<Article> {\n return this.request(\n 'POST',\n `/projects/${projectId}/articles/${articleId}/regenerate-chapter`,\n { body: { chapterIndex } },\n );\n }\n\n getPipelineLogs(projectId: string, articleId: string): Promise<PipelineLog[]> {\n return this.request(\n 'GET',\n `/projects/${projectId}/articles/${articleId}/pipeline-logs`,\n );\n }\n\n listRefreshSuggestions(): Promise<unknown[]> {\n return this.request('GET', '/articles/refresh-suggestions');\n }\n\n // ── Integrations / publishing ─────────────────────────────────────────────\n listIntegrations(projectId: string): Promise<Integration[]> {\n return this.request('GET', `/projects/${projectId}/integrations`);\n }\n\n createIntegration(\n projectId: string,\n dto: { type: string; name: string; config: Record<string, unknown>; enabled?: boolean },\n ): Promise<Integration> {\n return this.request('POST', `/projects/${projectId}/integrations`, { body: dto });\n }\n\n testIntegration(\n projectId: string,\n integrationId: string,\n mode?: string,\n ): Promise<unknown> {\n return this.request(\n 'POST',\n `/projects/${projectId}/integrations/${integrationId}/test`,\n { query: { mode } },\n );\n }\n\n publishToIntegration(\n projectId: string,\n articleId: string,\n integrationId: string,\n ): Promise<unknown> {\n return this.request(\n 'POST',\n `/projects/${projectId}/articles/${articleId}/publish/${integrationId}`,\n );\n }\n\n retryPublish(\n projectId: string,\n articleId: string,\n integrationId: string,\n ): Promise<unknown> {\n return this.request(\n 'POST',\n `/projects/${projectId}/articles/${articleId}/publish/${integrationId}/retry`,\n );\n }\n\n listPublishLogs(\n projectId: string,\n articleId?: string,\n limit?: number,\n ): Promise<PublishLog[]> {\n const base = articleId\n ? `/projects/${projectId}/articles/${articleId}/publish-logs`\n : `/projects/${projectId}/publish-logs`;\n return this.request('GET', base, { query: { limit } });\n }\n\n // ── Keywords & proprietary facts (agent-supplied knowledge) ───────────────\n addKeyword(\n projectId: string,\n dto: { keyword: string; searchVolume?: number; difficulty?: number; intent?: string },\n ): Promise<Keyword> {\n return this.request('POST', `/projects/${projectId}/keywords`, { body: dto });\n }\n\n listKeywords(projectId: string): Promise<Keyword[]> {\n return this.request('GET', `/projects/${projectId}/keywords`);\n }\n\n listProductFacts(projectId: string, activeOnly?: boolean): Promise<ProprietaryFact[]> {\n return this.request('GET', `/projects/${projectId}/proprietary-facts`, {\n query: { active: activeOnly ? 'true' : undefined },\n });\n }\n\n createProductFact(\n projectId: string,\n dto: { fact: string; category?: string[] },\n ): Promise<ProprietaryFact> {\n return this.request('POST', `/projects/${projectId}/proprietary-facts`, {\n body: dto,\n });\n }\n\n updateProductFact(\n projectId: string,\n factId: string,\n dto: { fact?: string; category?: string[]; active?: boolean },\n ): Promise<ProprietaryFact> {\n return this.request(\n 'PATCH',\n `/projects/${projectId}/proprietary-facts/${factId}`,\n { body: dto },\n );\n }\n}\n","import { z } from 'zod';\nimport type { MotiblogApiClient } from './api-client';\nimport type { Article, ArticleSummary, ContentPlan } from './types';\nimport { exportArticlesToDir } from './export';\n\n/**\n * Tool surface for the MotiBlog MCP server.\n *\n * Design (docs/product/agent-first-direction.md): coordination verbs over the\n * content lifecycle — the agent steers (topics → review → approve → publish /\n * export) but never bypasses governance: publication always flows through the\n * API's typed approval gate with provenance.\n *\n * Every tool description is self-contained so a fresh agent session can pick\n * the right tool without extra docs.\n */\n\nexport interface ToolContext {\n client: MotiblogApiClient;\n defaultProjectId?: string;\n}\n\nexport interface ToolResult {\n text: string;\n isError?: boolean;\n}\n\nexport interface ToolDefinition {\n name: string;\n description: string;\n inputSchema: z.ZodRawShape;\n annotations?: {\n title?: string;\n readOnlyHint?: boolean;\n destructiveHint?: boolean;\n idempotentHint?: boolean;\n openWorldHint?: boolean;\n };\n handler: (args: Record<string, unknown>, ctx: ToolContext) => Promise<ToolResult>;\n}\n\nconst ARTICLE_STATUSES = [\n 'DRAFT',\n 'GENERATING',\n 'REVIEW',\n 'APPROVED',\n 'PUBLISHING',\n 'PUBLISHED',\n 'FAILED',\n 'SKIPPED',\n] as const;\n\nconst projectIdArg = {\n project_id: z\n .string()\n .optional()\n .describe('MotiBlog project id. Omit when MOTIBLOG_PROJECT_ID is configured.'),\n};\n\nfunction json(value: unknown): ToolResult {\n return { text: JSON.stringify(value, null, 2) };\n}\n\nfunction resolveProjectId(ctx: ToolContext, raw?: unknown): string {\n const id = (typeof raw === 'string' && raw.trim()) || ctx.defaultProjectId;\n if (!id) {\n throw new Error(\n 'No project specified: pass project_id or set the MOTIBLOG_PROJECT_ID environment variable.',\n );\n }\n return id;\n}\n\n/** Trim huge fields from full articles before returning them to the model. */\nfunction summarizeArticleForList(a: ArticleSummary | Article): Record<string, unknown> {\n return {\n id: a.id,\n title: a.title,\n slug: a.slug,\n status: a.status,\n wordCount: a.wordCount,\n seoScore: a.seoScore,\n scheduledFor: a.scheduledFor ?? null,\n publishedAt: a.publishedAt ?? null,\n topics: a.topics ?? [],\n };\n}\n\n// ── Digest ────────────────────────────────────────────────────────────────────\n\nexport interface DigestInput {\n project: {\n id: string;\n name: string;\n autoPublish?: boolean;\n requireApproval?: boolean;\n articlesUsed?: number;\n articlesLimit?: number;\n };\n articles: ArticleSummary[];\n plans: ContentPlan[];\n now?: Date;\n}\n\nexport interface Digest {\n project: {\n id: string;\n name: string;\n autonomy:\n | 'L1 — drafts daily, publish only via agent review'\n | 'L2 — auto-publish clean, review exceptions';\n quota: { used: number; limit: number };\n };\n articles: Record<string, number>;\n reviewQueue: Array<{ id: string; title: string; wordCount?: number }>;\n publishedLast7Days: Array<{ title: string; publishedAt: string }>;\n plansNext7Days: Array<{ date: string; title: string; status: string }>;\n latestPublished: { title: string; at: string } | null;\n checkedAt: string;\n}\n\n/** Pure aggregation so the morning check-in is one tool call, not N. */\nexport function buildDigest(input: DigestInput, now: Date = new Date()): Digest {\n const { project, articles, plans } = input;\n\n const statusCounts: Record<string, number> = {};\n for (const a of articles) statusCounts[a.status] = (statusCounts[a.status] ?? 0) + 1;\n\n const reviewQueue = articles\n .filter((a) => a.status === 'REVIEW')\n .map((a) => ({ id: a.id, title: a.title, wordCount: a.wordCount }));\n\n const sevenDaysAgo = now.getTime() - 7 * 24 * 3600 * 1000;\n const publishedLast7Days = articles\n .filter((a) => a.publishedAt && new Date(a.publishedAt).getTime() >= sevenDaysAgo)\n .sort((a, b) => new Date(b.publishedAt!).getTime() - new Date(a.publishedAt!).getTime())\n .map((a) => ({ title: a.title, publishedAt: String(a.publishedAt) }));\n\n const in7Days = now.getTime() + 7 * 24 * 3600 * 1000;\n const plansNext7Days = plans\n .filter(\n (p) =>\n p.scheduledDate &&\n new Date(p.scheduledDate).getTime() >= now.getTime() - 24 * 3600 * 1000 &&\n new Date(p.scheduledDate).getTime() <= in7Days &&\n p.status !== 'COMPLETED',\n )\n .sort((a, b) => String(a.scheduledDate).localeCompare(String(b.scheduledDate)))\n .map((p) => ({\n date: String(p.scheduledDate).slice(0, 10),\n title: p.title,\n status: p.status,\n }));\n\n const latest = publishedLast7Days[0] ?? null;\n\n return {\n project: {\n id: project.id,\n name: project.name,\n autonomy: project.autoPublish\n ? 'L2 — auto-publish clean, review exceptions'\n : 'L1 — drafts daily, publish only via agent review',\n quota: { used: project.articlesUsed ?? 0, limit: project.articlesLimit ?? 0 },\n },\n articles: statusCounts,\n reviewQueue,\n publishedLast7Days,\n plansNext7Days,\n latestPublished: latest ? { title: latest.title, at: latest.publishedAt } : null,\n checkedAt: now.toISOString(),\n };\n}\n\nexport const toolDefinitions: ToolDefinition[] = [\n // ── Discovery ──────────────────────────────────────────────────────────────\n {\n name: 'get_digest',\n description:\n \"One-call morning check-in for a project: article counts by lifecycle status, the REVIEW queue (what is waiting to be published, with word counts), posts published in the last 7 days, content plans due in the next 7 days, the latest published post, quota usage, and the project's current autonomy level (L1 = nothing ships without agent review; L2 = clean articles auto-publish). Start every check-in here.\",\n inputSchema: { ...projectIdArg },\n annotations: { readOnlyHint: true },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n const [project, articles, plans] = await Promise.all([\n ctx.client.getProject(projectId),\n ctx.client.listArticles(projectId),\n ctx.client.listContentPlans(projectId),\n ]);\n return json(\n buildDigest({ project, articles, plans }),\n );\n },\n },\n {\n name: 'list_projects',\n description:\n 'List every MotiBlog project the API key can access, with settings that matter to agents: requireApproval (REVIEW vs APPROVED after generation), autoPublish, factCheckStrict, selfHostedBlog, and article quota usage.',\n inputSchema: {},\n annotations: { readOnlyHint: true },\n async handler(_args, ctx) {\n return json(await ctx.client.listProjects());\n },\n },\n {\n name: 'get_project',\n description:\n 'Get one MotiBlog project in detail: pipeline settings (aiModel, language, internalLinks, banner), positioning inputs (businessProfile, productTruth, manualPositioning) and quota state.',\n inputSchema: { ...projectIdArg },\n annotations: { readOnlyHint: true },\n async handler(args, ctx) {\n return json(await ctx.client.getProject(resolveProjectId(ctx, args.project_id)));\n },\n },\n\n // ── Pipeline / generation ops ─────────────────────────────────────────────\n {\n name: 'start_pipeline',\n description:\n 'Start the autonomous pipeline for a project: site crawl/analysis → topic selection → article generation for scheduled plans. Returns immediately; poll get_pipeline_status or list_articles(status=GENERATING) to follow progress.',\n inputSchema: { ...projectIdArg },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n return json(await ctx.client.pipelineStart(projectId));\n },\n },\n {\n name: 'get_pipeline_status',\n description:\n 'Get current pipeline run state for a project: whether a run is active, its steps, and recent run history. Use after start_pipeline or generate_article.',\n inputSchema: { ...projectIdArg },\n annotations: { readOnlyHint: true },\n async handler(args, ctx) {\n return json(\n await ctx.client.pipelineStatus(resolveProjectId(ctx, args.project_id)),\n );\n },\n },\n\n // ── Topics & planning ─────────────────────────────────────────────────────\n {\n name: 'suggest_topics',\n description:\n \"Suggest blog topics for a project by adding entries to its content plan (one call per topic). This is the agent-side 'propose' step: topics land as DRAFT plan entries — nothing is generated until you approve_content_plan + generate_article. Prefer specific, keyword-like topics.\",\n inputSchema: {\n topic: z.string().min(1).describe('The proposed topic/title, e.g. \"How headless CMSs serve AI agents\"'),\n ...projectIdArg,\n },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n return json(await ctx.client.createContentPlanFromGap(projectId, String(args.topic)));\n },\n },\n {\n name: 'list_content_plans',\n description:\n 'List the content plan queue for a project with each entry status: DRAFT (proposed), APPROVED (cleared for generation), IN_PROGRESS, COMPLETED.',\n inputSchema: { ...projectIdArg },\n annotations: { readOnlyHint: true },\n async handler(args, ctx) {\n return json(await ctx.client.listContentPlans(resolveProjectId(ctx, args.project_id)));\n },\n },\n {\n name: 'approve_content_plan',\n description:\n 'Approve a DRAFT content-plan entry so it becomes eligible for generation (generate_article or the scheduled pipeline).',\n inputSchema: {\n plan_id: z.string().describe('Content plan entry id (from list_content_plans)'),\n ...projectIdArg,\n },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n return json(await ctx.client.approveContentPlan(projectId, String(args.plan_id)));\n },\n },\n {\n name: 'regenerate_content_plan',\n description:\n 'Ask AI to rewrite a content-plan entry (new title/summary/target keyword) — use when a suggested topic misses the mark. Only works while no article has been generated from it.',\n inputSchema: {\n plan_id: z.string().describe('Content plan entry id'),\n ...projectIdArg,\n },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n return json(await ctx.client.regenerateContentPlan(projectId, String(args.plan_id)));\n },\n },\n {\n name: 'generate_article',\n description:\n 'Generate an article from an APPROVED content-plan entry via the full pipeline (research → outline → draft → fact-check → polish → scoring). Returns the article in GENERATING state; review later via get_article once status reaches REVIEW or APPROVED.',\n inputSchema: {\n plan_id: z.string().describe('Content plan entry id (must be APPROVED)'),\n ...projectIdArg,\n },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n return json(await ctx.client.generateArticleFromPlan(projectId, String(args.plan_id)));\n },\n },\n {\n name: 'get_calendar',\n description:\n 'Get scheduled content-plan entries (with slim article previews) between two ISO dates, e.g. start_date=2026-09-01 end_date=2026-09-30.',\n inputSchema: {\n start_date: z.string().describe('ISO date, e.g. 2026-09-01'),\n end_date: z.string().describe('ISO date, e.g. 2026-09-30'),\n ...projectIdArg,\n },\n annotations: { readOnlyHint: true },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n return json(\n await ctx.client.getCalendar(\n projectId,\n String(args.start_date),\n String(args.end_date),\n ),\n );\n },\n },\n\n // ── Review loop ───────────────────────────────────────────────────────────\n {\n name: 'list_review_queue',\n description:\n `List articles awaiting attention, filtered by status. Lifecycle: DRAFT → GENERATING → REVIEW → APPROVED → PUBLISHING → PUBLISHED (also FAILED, SKIPPED). Default status=REVIEW (needs human/agent approval); pass APPROVED to see what is cleared for publishing.`,\n inputSchema: {\n status: z.enum(ARTICLE_STATUSES).optional().describe('Filter by lifecycle status (default REVIEW)'),\n ...projectIdArg,\n },\n annotations: { readOnlyHint: true },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n const status = typeof args.status === 'string' ? args.status : 'REVIEW';\n const articles = await ctx.client.listArticles(projectId, status);\n return json(articles.map(summarizeArticleForList));\n },\n },\n {\n name: 'get_article',\n description:\n 'Get the FULL article: markdown content plus quality signals — factCheckReport (verified/unverified claims with sources), topicGate verdict, seoScore/issues, publishedUrl. Set include_logs=true to append per-phase pipeline telemetry (tokens, cost, phase status). Use this during review before approving publication.',\n inputSchema: {\n article_id: z.string().describe('Article id'),\n include_logs: z.boolean().optional().describe('Include pipeline phase logs (default false)'),\n ...projectIdArg,\n },\n annotations: { readOnlyHint: true },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n const article = await ctx.client.getArticle(projectId, String(args.article_id));\n let logs: unknown = undefined;\n if (args.include_logs === true) {\n logs = await ctx.client.getPipelineLogs(projectId, String(args.article_id));\n }\n return json(logs ? { article, pipelineLogs: logs } : article);\n },\n },\n {\n name: 'update_article',\n description:\n 'Edit an article: replace the markdown content, retitle, change metaDescription, or move status (e.g. fix flagged claims from factCheckReport then set status=APPROVED; or send back to DRAFT). Editing content is how agents answer fact-check flags when no external source exists.',\n inputSchema: {\n article_id: z.string().describe('Article id'),\n content: z.string().optional().describe('Full replacement markdown body'),\n title: z.string().max(500).optional(),\n meta_description: z.string().max(320).optional(),\n status: z.enum(['DRAFT', 'REVIEW', 'APPROVED']).optional(),\n ...projectIdArg,\n },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n const patch: Record<string, unknown> = {};\n if (typeof args.content === 'string') patch.content = args.content;\n if (typeof args.title === 'string') patch.title = args.title;\n if (typeof args.meta_description === 'string') patch.metaDescription = args.meta_description;\n if (typeof args.status === 'string') patch.status = args.status;\n return json(await ctx.client.updateArticle(projectId, String(args.article_id), patch));\n },\n },\n {\n name: 'approve_publication',\n description:\n \"Approve an article for publication through the governed approval gate. publish_now=true also triggers publishing immediately (to enabled integrations, or the self-hosted blog when selfHostedBlog=true). Otherwise the article sits in APPROVED until the scheduler/autoPublish picks it up or publish_to_integration is called.\",\n inputSchema: {\n article_id: z.string().describe('Article id (must be in REVIEW/APPROVED/DRAFT-eligible state)'),\n publish_now: z.boolean().optional().describe('Also trigger publishing now (default false)'),\n ...projectIdArg,\n },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n const articleId = String(args.article_id);\n if (args.publish_now === true) {\n return json(await ctx.client.approveAndPublish(projectId, articleId));\n }\n return json(await ctx.client.updateArticle(projectId, articleId, { status: 'APPROVED' }));\n },\n },\n {\n name: 'schedule_publication',\n description:\n 'Set or clear a per-article scheduled publish time (ISO datetime). Pass null for clear_schedule=true to unschedule. Scheduled APPROVED articles are published automatically by the 15-minute scheduler when autoPublish is on.',\n inputSchema: {\n article_id: z.string().describe('Article id'),\n scheduled_for: z.string().optional().describe('ISO datetime, e.g. 2026-09-01T09:00:00Z'),\n clear_schedule: z.boolean().optional().describe('Clear any scheduled date instead of setting one'),\n ...projectIdArg,\n },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n const value =\n args.clear_schedule === true ? null : (args.scheduled_for as string | undefined) ?? null;\n return json(await ctx.client.scheduleArticle(projectId, String(args.article_id), value));\n },\n },\n\n // ── Regeneration ops ──────────────────────────────────────────────────────\n {\n name: 'regenerate_article',\n description:\n 'Wipe an article and rerun the ENTIRE pipeline from scratch (research → draft …). Content-destructive: prefer update_article for targeted edits or regenerate_chapter for one section.',\n inputSchema: {\n article_id: z.string().describe('Article id'),\n ...projectIdArg,\n },\n annotations: { destructiveHint: true },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n return json(await ctx.client.regenerateArticle(projectId, String(args.article_id)));\n },\n },\n {\n name: 'regenerate_chapter',\n description:\n 'Regenerate ONE chapter (0-based chapter_index) of an article; all other sections stay byte-identical. Cheaper and safer than regenerate_article.',\n inputSchema: {\n article_id: z.string().describe('Article id'),\n chapter_index: z.number().int().min(0).describe('Zero-based chapter index'),\n ...projectIdArg,\n },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n return json(\n await ctx.client.regenerateChapter(\n projectId,\n String(args.article_id),\n Number(args.chapter_index),\n ),\n );\n },\n },\n {\n name: 'get_pipeline_logs',\n description:\n 'Per-phase pipeline telemetry for one article: phase names, statuses, messages, token counts, and USD cost per phase. Diagnose FAILED articles or audit what the generator did.',\n inputSchema: {\n article_id: z.string().describe('Article id'),\n ...projectIdArg,\n },\n annotations: { readOnlyHint: true },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n return json(await ctx.client.getPipelineLogs(projectId, String(args.article_id)));\n },\n },\n {\n name: 'list_refresh_suggestions',\n description:\n 'Across ALL your projects: published articles whose Search Console stats suggest they are decaying and would benefit from a refresh. Feed these back into planning via suggest_topics or update_article.',\n inputSchema: {},\n annotations: { readOnlyHint: true },\n async handler(_args, ctx) {\n return json(await ctx.client.listRefreshSuggestions());\n },\n },\n\n // ── Publishing targets ────────────────────────────────────────────────────\n {\n name: 'list_integrations',\n description:\n 'List configured publishing targets (integrations) for a project: WEBHOOK, WORDPRESS, GHOST, WEBFLOW, SHOPIFY, DEVTO, SANITY, CUSTOM_API — with enabled state.',\n inputSchema: { ...projectIdArg },\n annotations: { readOnlyHint: true },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n const integrations = await ctx.client.listIntegrations(projectId);\n return json(integrations.map((i) => ({ ...i, config: '<redacted>' })));\n },\n },\n {\n name: 'create_webhook_integration',\n description:\n \"Register a webhook publishing target: MotiBlog POSTs the published article JSON to your endpoint (signed with X-Signature: sha256=<HMAC(secret)> when secret is set). Config shape: { url, secret?, method? }. This is how an agent's own infrastructure receives content pushes.\",\n inputSchema: {\n name: z.string().min(1).max(100).describe('Display name, e.g. \"my-site deploy hook\"'),\n url: z.string().url().describe('HTTPS endpoint receiving the article payload'),\n secret: z.string().optional().describe('HMAC secret used to sign deliveries'),\n method: z.enum(['POST', 'PUT', 'PATCH']).optional().describe('HTTP method (default POST)'),\n ...projectIdArg,\n },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n const config: Record<string, unknown> = { url: String(args.url) };\n if (typeof args.secret === 'string') config.secret = args.secret;\n if (typeof args.method === 'string') config.method = args.method;\n return json(\n await ctx.client.createIntegration(projectId, {\n type: 'WEBHOOK',\n name: String(args.name),\n config,\n enabled: true,\n }),\n );\n },\n },\n {\n name: 'test_integration',\n description:\n 'Test a publishing target connection. mode=ping (default) checks credentials/reachability only; mode=full may create and clean up a test item on the target.',\n inputSchema: {\n integration_id: z.string().describe('Integration id'),\n mode: z.enum(['ping', 'full']).optional(),\n ...projectIdArg,\n },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n return json(\n await ctx.client.testIntegration(\n projectId,\n String(args.integration_id),\n typeof args.mode === 'string' ? args.mode : undefined,\n ),\n );\n },\n },\n {\n name: 'publish_to_integration',\n description:\n 'Push an APPROVED article to one specific integration now (enqueues the publish job; transitions APPROVED → PUBLISHING → PUBLISHED). Check results via list_publish_logs.',\n inputSchema: {\n article_id: z.string().describe('Article id (should be APPROVED)'),\n integration_id: z.string().describe('Target integration id'),\n ...projectIdArg,\n },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n return json(\n await ctx.client.publishToIntegration(\n projectId,\n String(args.article_id),\n String(args.integration_id),\n ),\n );\n },\n },\n {\n name: 'retry_publish',\n description: 'Retry a failed publish attempt for an article/integration pair.',\n inputSchema: {\n article_id: z.string(),\n integration_id: z.string(),\n ...projectIdArg,\n },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n return json(\n await ctx.client.retryPublish(\n projectId,\n String(args.article_id),\n String(args.integration_id),\n ),\n );\n },\n },\n {\n name: 'list_publish_logs',\n description:\n 'Publishing history: SUCCESS/FAILED attempts with publishedUrl, provider error text and timestamps. Pass article_id for one article, or omit for the whole project (limit defaults server-side).',\n inputSchema: {\n article_id: z.string().optional(),\n limit: z.number().int().min(1).max(200).optional(),\n ...projectIdArg,\n },\n annotations: { readOnlyHint: true },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n return json(\n await ctx.client.listPublishLogs(\n projectId,\n typeof args.article_id === 'string' ? args.article_id : undefined,\n typeof args.limit === 'number' ? args.limit : undefined,\n ),\n );\n },\n },\n\n // ── Agent-supplied knowledge ───────────────────────────────────────────────\n {\n name: 'list_keywords',\n description:\n 'List tracked keywords for a project (search volume, difficulty, intent, status). Keywords drive topic relevance in generation.',\n inputSchema: { ...projectIdArg },\n annotations: { readOnlyHint: true },\n async handler(args, ctx) {\n return json(await ctx.client.listKeywords(resolveProjectId(ctx, args.project_id)));\n },\n },\n {\n name: 'add_keyword',\n description:\n 'Track a new SEO keyword for a project so future topic suggestions/generation can target it. intent is one of INFORMATIONAL, NAVIGATIONAL, TRANSACTIONAL, COMMERCIAL.',\n inputSchema: {\n keyword: z.string().min(1).describe('The keyword phrase'),\n search_volume: z.number().int().min(0).optional(),\n difficulty: z.number().int().min(0).max(100).optional(),\n intent: z.enum(['INFORMATIONAL', 'NAVIGATIONAL', 'TRANSACTIONAL', 'COMMERCIAL']).optional(),\n ...projectIdArg,\n },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n return json(\n await ctx.client.addKeyword(projectId, {\n keyword: String(args.keyword),\n ...(typeof args.search_volume === 'number' ? { searchVolume: args.search_volume } : {}),\n ...(typeof args.difficulty === 'number' ? { difficulty: args.difficulty } : {}),\n ...(typeof args.intent === 'string' ? { intent: args.intent } : {}),\n }),\n );\n },\n },\n {\n name: 'list_product_facts',\n description:\n 'List the project proprietary facts — ground-truth statements about the product (features, pricing, integrations) injected into prompts and enforced by strict fact-checking.',\n inputSchema: {\n active_only: z.boolean().optional().describe('Only active facts (default false = all)'),\n ...projectIdArg,\n },\n annotations: { readOnlyHint: true },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n return json(\n await ctx.client.listProductFacts(projectId, args.active_only === true),\n );\n },\n },\n {\n name: 'supply_product_fact',\n description:\n \"Supply a ground-truth fact about the customer's product (or update one via fact_id). Strict fact-checking uses these to verify claims; supply facts BEFORE generation for best results. Example: fact='Acme integrates natively with Shopify since v2.3', category=['integrations'].\",\n inputSchema: {\n fact: z.string().min(1).describe('A single verifiable statement of product truth'),\n category: z.array(z.string()).optional().describe('Tags, e.g. [\"pricing\",\"features\"]'),\n fact_id: z.string().optional().describe('Existing fact id to UPDATE instead of create'),\n active: z.boolean().optional().describe('Set active flag when updating'),\n ...projectIdArg,\n },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n const category = Array.isArray(args.category)\n ? (args.category as unknown[]).map(String)\n : undefined;\n if (typeof args.fact_id === 'string') {\n return json(\n await ctx.client.updateProductFact(projectId, args.fact_id, {\n ...(typeof args.fact === 'string' ? { fact: args.fact } : {}),\n ...(category ? { category } : {}),\n ...(typeof args.active === 'boolean' ? { active: args.active } : {}),\n }),\n );\n }\n return json(\n await ctx.client.createProductFact(projectId, {\n fact: String(args.fact),\n ...(category ? { category } : {}),\n }),\n );\n },\n },\n\n // ── Export (deploy-it-yourself) ───────────────────────────────────────────\n {\n name: 'export_blog',\n description:\n \"Export articles out of MotiBlog to local files so you can deploy them yourself into any codebase (Astro/Next/Jekyll/Hugo/plain git). Writes {out_dir}/{slug}/index.md with YAML frontmatter (title, slug, description, tags, publishedAt, banner, motiblogArticleId) + portable GitHub-Flavored-Markdown body, plus manifest.json. Default exports PUBLISHED posts; pass status to export drafts for review.\",\n inputSchema: {\n out_dir: z.string().min(1).describe('Directory to write into (created if missing; relative paths resolve against the MCP server cwd)'),\n status: z.enum(ARTICLE_STATUSES).optional().describe('Which lifecycle stage to export (default PUBLISHED)'),\n limit: z.number().int().min(1).max(500).optional().describe('Max articles to export (default 100)'),\n ...projectIdArg,\n },\n async handler(args, ctx) {\n const projectId = resolveProjectId(ctx, args.project_id);\n const status = typeof args.status === 'string' ? args.status : 'PUBLISHED';\n const limit = typeof args.limit === 'number' ? args.limit : 100;\n\n const summaries = await ctx.client.listArticles(projectId, status);\n const selected = summaries.slice(0, limit);\n const articles: Article[] = [];\n for (const summary of selected) {\n articles.push(await ctx.client.getArticle(projectId, summary.id));\n }\n\n const result = await exportArticlesToDir(String(args.out_dir), articles);\n return json({\n exportedTo: result.outDir,\n count: result.files.length,\n skippedEmpty: result.skipped,\n files: result.files.map((f) => f.path),\n nextStep:\n 'Commit/copy this folder into your target codebase. Each folder is self-contained static content ready for any SSG.',\n });\n },\n },\n];\n\n","import { promises as fs } from 'fs';\nimport { join, resolve } from 'path';\nimport { articleToMarkdown } from '@motiblog/shared';\nimport type { Article } from './types';\n\n/**\n * Blog export: pull articles out of MotiBlog as a portable, static-friendly\n * content folder an agent (or human) can drop into any codebase — Astro,\n * Next.js MDX, Jekyll, Hugo, plain files.\n *\n * Layout:\n * {outDir}/\n * manifest.json — machine-readable index of the export\n * {slug}/index.md — one folder per article (frontmatter + body)\n */\n\nexport interface ExportedFile {\n path: string;\n articleId: string;\n slug: string;\n title: string;\n}\n\nexport interface ExportResult {\n outDir: string;\n files: ExportedFile[];\n skipped: number;\n}\n\nexport function slugify(input: string, fallback: string): string {\n const slug = input\n .toLowerCase()\n .normalize('NFKD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 120);\n return slug || fallback;\n}\n\nfunction yamlString(value: string): string {\n return `\"${value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')}\"`;\n}\n\nexport function buildArticleMarkdown(article: Article): string {\n const frontmatter: string[] = ['---'];\n frontmatter.push(`title: ${yamlString(article.title)}`);\n frontmatter.push(`slug: ${yamlString(article.slug)}`);\n if (article.metaDescription) {\n frontmatter.push(`description: ${yamlString(article.metaDescription)}`);\n }\n if (article.publishedAt) {\n frontmatter.push(`publishedAt: ${yamlString(String(article.publishedAt))}`);\n }\n if (article.topics?.length) {\n frontmatter.push('tags:');\n for (const topic of article.topics) {\n frontmatter.push(` - ${yamlString(topic)}`);\n }\n }\n if (article.bannerUrl) {\n frontmatter.push(`banner: ${yamlString(String(article.bannerUrl))}`);\n }\n if (typeof article.seoScore === 'number') {\n frontmatter.push(`seoScore: ${article.seoScore}`);\n }\n if (article.wordCount !== undefined && article.wordCount !== null) {\n frontmatter.push(`wordCount: ${article.wordCount}`);\n }\n frontmatter.push(`motiblogArticleId: ${yamlString(article.id)}`);\n frontmatter.push('---', '');\n\n // Portable GFM body: single H1, inline <img> → markdown images, iframes → links.\n return `${frontmatter.join('\\n')}\\n${articleToMarkdown({\n title: article.title,\n content: article.content ?? '',\n })}`;\n}\n\nexport async function exportArticlesToDir(\n outDirInput: string,\n articles: Article[],\n): Promise<ExportResult> {\n const outDir = resolve(outDirInput);\n await fs.mkdir(outDir, { recursive: true });\n\n const files: ExportedFile[] = [];\n let skipped = 0;\n\n for (const article of articles) {\n if (!article.content || !article.content.trim()) {\n skipped += 1;\n continue;\n }\n const slug = slugify(article.slug || article.title, `article-${article.id}`);\n const dir = join(outDir, slug);\n await fs.mkdir(dir, { recursive: true });\n const file = join(dir, 'index.md');\n await fs.writeFile(file, buildArticleMarkdown(article), 'utf8');\n files.push({\n path: file,\n articleId: article.id,\n slug,\n title: article.title,\n });\n }\n\n const manifest = {\n exportedAt: new Date().toISOString(),\n count: files.length,\n skippedEmptyContent: skipped,\n articles: files.map((f) => ({\n file: f.path,\n articleId: f.articleId,\n slug: f.slug,\n title: f.title,\n })),\n };\n await fs.writeFile(\n join(outDir, 'manifest.json'),\n JSON.stringify(manifest, null, 2),\n 'utf8',\n );\n\n return { outDir, files, skipped };\n}\n","export const PROJECT_STATUSES = ['PENDING', 'ANALYZING', 'ACTIVE', 'PAUSED', 'ERROR'] as const;\nexport const AUTH_PROVIDERS = ['LOCAL', 'GOOGLE'] as const;\n\n/**\n * The trial, stated once, from what billing.service.ts actually creates and what\n * Stripe was observed to do with it: a subscription with `trial_period_days: 3`,\n * `payment_method_collection: 'always'`, and a one-time $1 line item.\n *\n * That $1 is charged AT SIGNUP, not at trial end. A subscription with a trial\n * still cuts an invoice the moment it is created — the recurring line sits on it\n * at $0 (\"Free trial for 1 x MotiBlog Pro\") — and one-time items attach to that\n * invoice rather than to the day-3 one. Verified against Stripe on 2026-08-20:\n * the signup invoice comes back `status=paid, total=$1.00`.\n *\n * So this is a PAID trial: $1 today, $39 on day 3 unless cancelled. It is not a\n * free trial and must never be described as one.\n *\n * Marketing copy must come from here — and must actually read from here. This\n * block previously claimed \"nothing is charged during the trial ... cancel\n * before day 3 and you pay nothing\", which was false in all three of its\n * clauses, and it reached the Terms of Service and the article generator's\n * guardrails before anyone charged a card against it.\n */\nexport const TRIAL = {\n days: 3,\n articles: 3,\n /** Charged once, at signup. Not deducted from the first month. */\n activationFeeUsd: 1,\n cardRequired: true,\n /** One line, for a CTA subtitle. */\n short: '$1 for three days and three articles. Renews at $39/mo unless you cancel.',\n /** Two sentences, for a pricing note or an FAQ answer. */\n long:\n 'Three days and three articles for $1, charged when you sign up. The trial converts automatically: on day 3 you are billed $39 for your first month unless you cancel before then. Cancelling during the trial stops the $39 — the $1 is not refunded.',\n} as const;\n\nexport const PLAN = {\n name: 'MotiBlog Pro',\n articlesPerMonth: 30,\n originalPrice: 99,\n monthly: { price: 39, interval: 'month' as const },\n yearly: { price: 390, interval: 'year' as const, monthlyEquivalent: 32.50, savings: 78 },\n features: [\n '30 articles a month, 2,500-4,000 words each',\n 'Keyword research from competitor gap analysis',\n 'Internal links woven in before every publish',\n 'Publishes to WordPress, Webflow, Ghost, Shopify and more',\n 'Search Console positions and impressions',\n 'Competitor tracking',\n 'Up to 10 projects',\n 'Support by email',\n ],\n} as const;\n\n// Keep backwards compat\nexport const PLANS = {\n PRO: { name: PLAN.name, articlesPerMonth: PLAN.articlesPerMonth, projects: 10, price: PLAN.monthly.price },\n} as const;\n","// ---------------------------------------------------------------------------\n// Markdown export contract\n//\n// \"Download .md\" / \"Copy Markdown\" produce PORTABLE GitHub-Flavored Markdown:\n// - exactly one H1: the article title (any body H1s are demoted to H2)\n// - inline <img> tags become markdown image syntax: ![alt](src)\n// - video iframes (YouTube/Vimeo) become a markdown link card pointing at\n// the watchable URL — raw <iframe> HTML never ships in a .md file\n// - code fences are left completely untouched\n// - everything already in markdown stays as-is\n//\n// The HTML export (.html) keeps the original content byte-for-byte; this\n// module only defines what MARKDOWN consumers (Obsidian, GitHub, editors)\n// receive.\n// ---------------------------------------------------------------------------\n\nexport interface ExportableArticle {\n title: string;\n /** Article body, markdown possibly containing inline HTML (img / iframe). */\n content: string;\n}\n\nfunction attr(tag: string, name: string): string {\n const m = tag.match(new RegExp(`${name}\\\\s*=\\\\s*[\"']([^\"']*)[\"']`, 'i'));\n return m?.[1] ?? '';\n}\n\n/** Turn an embed URL into the URL a human can open (YouTube/Vimeo aware). */\nexport function embedSrcToWatchUrl(src: string): string {\n const yt = src.match(/youtube(?:-nocookie)?\\.com\\/embed\\/([A-Za-z0-9_-]{5,})/i);\n if (yt) return `https://www.youtube.com/watch?v=${yt[1]}`;\n const vimeo = src.match(/player\\.vimeo\\.com\\/video\\/(\\d+)/i);\n if (vimeo) return `https://vimeo.com/${vimeo[1]}`;\n return src;\n}\n\n/** Apply a transform to content while leaving fenced code blocks untouched. */\nfunction outsideCodeFences(content: string, transform: (chunk: string) => string): string {\n const parts = content.split(/(```[\\s\\S]*?```|~~~[\\s\\S]*?~~~)/);\n return parts\n .map((part, i) => (i % 2 === 1 ? part : transform(part)))\n .join('');\n}\n\nfunction convertHtmlImages(chunk: string): string {\n return chunk.replace(/<img\\b[^>]*\\/?>(?:\\s*<\\/img>)?/gi, (tag) => {\n const src = attr(tag, 'src');\n if (!src) return '';\n const alt = attr(tag, 'alt');\n return `![${alt}](${src})`;\n });\n}\n\nfunction convertIframes(chunk: string): string {\n return chunk.replace(\n /<iframe\\b[^>]*>[\\s\\S]*?<\\/iframe>|<iframe\\b[^>]*\\/>/gi,\n (tag) => {\n const src = attr(tag, 'src');\n if (!src) return '';\n const url = embedSrcToWatchUrl(src);\n return `[▶ Watch the video](${url})`;\n },\n );\n}\n\nfunction enforceSingleH1(body: string): string {\n // Strip a leading H1 (the title is prepended separately)…\n const withoutLeading = body.replace(/^\\s*#(?!#)[^\\n]*\\n+/, '');\n // …and demote any remaining body H1s to H2 so the export has exactly one.\n return withoutLeading.replace(/^#(?!#)\\s?/gm, '## ');\n}\n\n/**\n * Convert a stored article into the portable Markdown export described in\n * the contract above. Pure and deterministic.\n */\nexport function articleToMarkdown(article: ExportableArticle): string {\n const converted = outsideCodeFences(article.content ?? '', (chunk) =>\n convertIframes(convertHtmlImages(chunk)),\n );\n const body = enforceSingleH1(converted).trim();\n return `# ${article.title}\\n\\n${body}\\n`;\n}\n"],"mappings":"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","names":["fs"]}
package/dist/lib.d.ts ADDED
@@ -0,0 +1,266 @@
1
+ import { IncomingMessage, ServerResponse } from 'http';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { z } from 'zod';
4
+
5
+ /**
6
+ * One MCP request over streamable HTTP, independent of who is listening.
7
+ *
8
+ * Deliberately its own module with no side effects on import. `http.ts` is an
9
+ * executable entrypoint — it calls `main()` at module scope — so anything that
10
+ * imports the handler from there would boot a second listener as a side
11
+ * effect. The API mounts this handler inside the existing NestJS process
12
+ * (`apps/api/src/mcp/mcp.middleware.ts`), which is what makes
13
+ * `POST api.motiblog.ai/mcp` a real endpoint rather than a second deployment.
14
+ */
15
+ declare function extractKey(req: IncomingMessage): string | null;
16
+ declare function reject(res: ServerResponse, status: number, message: string): void;
17
+ interface McpRequestOptions {
18
+ apiBaseUrl: string;
19
+ defaultProjectId?: string;
20
+ }
21
+ /**
22
+ * Path-agnostic on purpose: the caller owns the mount point. The standalone
23
+ * server checks for `/mcp` itself; when Express mounts this at `/mcp` the
24
+ * prefix is already stripped by the time we are called, so a path check here
25
+ * would reject every request.
26
+ *
27
+ * `parsedBody` matters when a host framework has already consumed the request
28
+ * stream. NestJS installs body-parser during `NestFactory.create`, so by the
29
+ * time our middleware runs the stream is drained and `req.body` holds the
30
+ * JSON. Passing it through is what stops the transport waiting forever on a
31
+ * stream that will never emit another byte.
32
+ */
33
+ declare function handleMcpRequest(req: IncomingMessage, res: ServerResponse, opts: McpRequestOptions, parsedBody?: unknown): Promise<void>;
34
+
35
+ interface McpConfig {
36
+ apiBaseUrl: string;
37
+ apiKey: string;
38
+ defaultProjectId?: string;
39
+ }
40
+
41
+ /**
42
+ * Build the MotiBlog MCP server with the full tool surface.
43
+ *
44
+ * Shared by both entrypoints:
45
+ * - src/index.ts (stdio, local agents)
46
+ * - src/http.ts (streamable HTTP, remote agents)
47
+ *
48
+ * The config is injected rather than loaded once so HTTP mode can
49
+ * authenticate each request with its own caller's API key.
50
+ */
51
+ declare function buildServer(config: McpConfig): McpServer;
52
+
53
+ /**
54
+ * Minimal shapes of the API resources this server surfaces to agents.
55
+ * Mirrors apps/api/prisma/schema.prisma — only fields useful in tool output
56
+ * are declared; unknown extra fields pass through untouched.
57
+ */
58
+ type ArticleStatus = 'DRAFT' | 'GENERATING' | 'REVIEW' | 'APPROVED' | 'PUBLISHING' | 'PUBLISHED' | 'FAILED' | 'SKIPPED';
59
+ interface Project {
60
+ id: string;
61
+ name: string;
62
+ url: string;
63
+ status: string;
64
+ autoPublish: boolean;
65
+ requireApproval: boolean;
66
+ language: string;
67
+ aiModel?: string;
68
+ factCheckStrict?: boolean;
69
+ selfHostedBlog?: boolean;
70
+ articlesUsed?: number;
71
+ articlesLimit?: number;
72
+ }
73
+ interface ContentPlan {
74
+ id: string;
75
+ projectId: string;
76
+ title: string;
77
+ summary?: string | null;
78
+ contentType?: string | null;
79
+ targetKeyword?: string | null;
80
+ status: 'DRAFT' | 'APPROVED' | 'IN_PROGRESS' | 'COMPLETED';
81
+ scheduledDate?: string | null;
82
+ funnelStage?: string | null;
83
+ }
84
+ interface ArticleSummary {
85
+ id: string;
86
+ title: string;
87
+ slug: string;
88
+ status: ArticleStatus;
89
+ wordCount?: number;
90
+ metaDescription?: string | null;
91
+ publishedAt?: string | null;
92
+ scheduledFor?: string | null;
93
+ topics?: string[];
94
+ seoScore?: number | null;
95
+ }
96
+ interface FactCheckReportItem {
97
+ claim?: string;
98
+ verdict?: string;
99
+ source?: string;
100
+ correction?: string;
101
+ [key: string]: unknown;
102
+ }
103
+ interface Article extends ArticleSummary {
104
+ content?: string;
105
+ generationPhase?: string | null;
106
+ factCheckReport?: {
107
+ items?: FactCheckReportItem[];
108
+ verified?: number;
109
+ unverified?: number;
110
+ } | null;
111
+ topicGate?: Record<string, unknown> | null;
112
+ publishedUrl?: string | null;
113
+ bannerUrl?: string | null;
114
+ updatedAt?: string;
115
+ createdAt?: string;
116
+ [key: string]: unknown;
117
+ }
118
+ interface PipelineLog {
119
+ id: string;
120
+ phase: string;
121
+ status: string;
122
+ message?: string | null;
123
+ tokensIn?: number | null;
124
+ tokensOut?: number | null;
125
+ costUsd?: number | null;
126
+ createdAt?: string;
127
+ [key: string]: unknown;
128
+ }
129
+ interface Integration {
130
+ id: string;
131
+ type: string;
132
+ name: string;
133
+ enabled: boolean;
134
+ lastUsedAt?: string | null;
135
+ config?: Record<string, unknown>;
136
+ }
137
+ interface PublishLog {
138
+ id: string;
139
+ articleId: string;
140
+ integrationId: string;
141
+ status: 'SUCCESS' | 'FAILED';
142
+ publishedUrl?: string | null;
143
+ externalId?: string | null;
144
+ error?: string | null;
145
+ createdAt?: string;
146
+ [key: string]: unknown;
147
+ }
148
+ interface Keyword {
149
+ id: string;
150
+ keyword: string;
151
+ searchVolume?: number | null;
152
+ difficulty?: number | null;
153
+ intent?: string | null;
154
+ status?: string | null;
155
+ [key: string]: unknown;
156
+ }
157
+ interface ProprietaryFact {
158
+ id: string;
159
+ fact: string;
160
+ category?: string[];
161
+ active?: boolean;
162
+ [key: string]: unknown;
163
+ }
164
+
165
+ interface RequestOptions {
166
+ query?: Record<string, string | number | boolean | undefined>;
167
+ body?: unknown;
168
+ }
169
+ declare class MotiblogApiClient {
170
+ private readonly apiBaseUrl;
171
+ private readonly apiKey;
172
+ private readonly fetchImpl;
173
+ constructor(apiBaseUrl: string, apiKey: string, fetchImpl?: typeof fetch);
174
+ request<T>(method: string, path: string, opts?: RequestOptions): Promise<T>;
175
+ private requestOnce;
176
+ listProjects(): Promise<Project[]>;
177
+ getProject(projectId: string): Promise<Project>;
178
+ pipelineStart(projectId: string): Promise<{
179
+ message: string;
180
+ }>;
181
+ pipelineStatus(projectId: string): Promise<Record<string, unknown>>;
182
+ createContentPlanFromGap(projectId: string, topic: string): Promise<ContentPlan>;
183
+ listContentPlans(projectId: string): Promise<ContentPlan[]>;
184
+ approveContentPlan(projectId: string, planId: string): Promise<ContentPlan>;
185
+ regenerateContentPlan(projectId: string, planId: string): Promise<ContentPlan>;
186
+ generateArticleFromPlan(projectId: string, planId: string): Promise<Article>;
187
+ getCalendar(projectId: string, start: string, end: string): Promise<unknown[]>;
188
+ listArticles(projectId: string, status?: string): Promise<ArticleSummary[]>;
189
+ getArticle(projectId: string, articleId: string): Promise<Article>;
190
+ updateArticle(projectId: string, articleId: string, patch: {
191
+ title?: string;
192
+ content?: string;
193
+ metaDescription?: string;
194
+ status?: string;
195
+ }): Promise<Article>;
196
+ scheduleArticle(projectId: string, articleId: string, scheduledFor: string | null): Promise<Article>;
197
+ approveAndPublish(projectId: string, articleId: string): Promise<Article>;
198
+ regenerateArticle(projectId: string, articleId: string): Promise<Article>;
199
+ regenerateChapter(projectId: string, articleId: string, chapterIndex: number): Promise<Article>;
200
+ getPipelineLogs(projectId: string, articleId: string): Promise<PipelineLog[]>;
201
+ listRefreshSuggestions(): Promise<unknown[]>;
202
+ listIntegrations(projectId: string): Promise<Integration[]>;
203
+ createIntegration(projectId: string, dto: {
204
+ type: string;
205
+ name: string;
206
+ config: Record<string, unknown>;
207
+ enabled?: boolean;
208
+ }): Promise<Integration>;
209
+ testIntegration(projectId: string, integrationId: string, mode?: string): Promise<unknown>;
210
+ publishToIntegration(projectId: string, articleId: string, integrationId: string): Promise<unknown>;
211
+ retryPublish(projectId: string, articleId: string, integrationId: string): Promise<unknown>;
212
+ listPublishLogs(projectId: string, articleId?: string, limit?: number): Promise<PublishLog[]>;
213
+ addKeyword(projectId: string, dto: {
214
+ keyword: string;
215
+ searchVolume?: number;
216
+ difficulty?: number;
217
+ intent?: string;
218
+ }): Promise<Keyword>;
219
+ listKeywords(projectId: string): Promise<Keyword[]>;
220
+ listProductFacts(projectId: string, activeOnly?: boolean): Promise<ProprietaryFact[]>;
221
+ createProductFact(projectId: string, dto: {
222
+ fact: string;
223
+ category?: string[];
224
+ }): Promise<ProprietaryFact>;
225
+ updateProductFact(projectId: string, factId: string, dto: {
226
+ fact?: string;
227
+ category?: string[];
228
+ active?: boolean;
229
+ }): Promise<ProprietaryFact>;
230
+ }
231
+
232
+ /**
233
+ * Tool surface for the MotiBlog MCP server.
234
+ *
235
+ * Design (docs/product/agent-first-direction.md): coordination verbs over the
236
+ * content lifecycle — the agent steers (topics → review → approve → publish /
237
+ * export) but never bypasses governance: publication always flows through the
238
+ * API's typed approval gate with provenance.
239
+ *
240
+ * Every tool description is self-contained so a fresh agent session can pick
241
+ * the right tool without extra docs.
242
+ */
243
+ interface ToolContext {
244
+ client: MotiblogApiClient;
245
+ defaultProjectId?: string;
246
+ }
247
+ interface ToolResult {
248
+ text: string;
249
+ isError?: boolean;
250
+ }
251
+ interface ToolDefinition {
252
+ name: string;
253
+ description: string;
254
+ inputSchema: z.ZodRawShape;
255
+ annotations?: {
256
+ title?: string;
257
+ readOnlyHint?: boolean;
258
+ destructiveHint?: boolean;
259
+ idempotentHint?: boolean;
260
+ openWorldHint?: boolean;
261
+ };
262
+ handler: (args: Record<string, unknown>, ctx: ToolContext) => Promise<ToolResult>;
263
+ }
264
+ declare const toolDefinitions: ToolDefinition[];
265
+
266
+ export { type McpConfig, type McpRequestOptions, MotiblogApiClient, buildServer, extractKey, handleMcpRequest, reject, toolDefinitions };