Skip to main content
Glama
safety.ts4.21 kB
import crypto from 'crypto'; import type { WPConfig } from './config.js'; export type JsonPatchOp = { op: 'replace' | 'insert' | 'delete' | 'patch'; path: string; // e.g., "/title", "/content", "/status" value?: any; }; export type ApplyContentOptions = { postId: number; operations: JsonPatchOp[]; force?: boolean; }; export type GutenbergBlock = { blockName: string; attrs: Record<string, any>; innerBlocks?: GutenbergBlock[]; }; export type CreateContentOptions = { postType: 'post' | 'page' | string; title: string; content?: string; excerpt?: string; status?: 'draft' | 'publish' | 'private'; blocks?: GutenbergBlock[]; // For creating posts with blocks }; export type WpRequest = (endpoint: string, options?: RequestInit & { timeoutMs?: number }) => Promise<any>; export async function applyContentChanges( wpRequest: WpRequest, config: WPConfig, opts: ApplyContentOptions ) { const idempotencyKey = makeIdempotencyKey(); // 1) Plan const plan = await wpRequest('/wpnav/v1/content/plan', { method: 'POST', body: JSON.stringify({ post_id: opts.postId, operations: opts.operations, idempotency_key: idempotencyKey, }), }); const planId = plan?.plan_id ?? plan?.id ?? plan?.planId; if (!planId) { throw new Error(`PLAN_FAILED: Could not retrieve plan_id from response`); } // 2) Diff (json format; small context) const diff = await wpRequest('/wpnav/v1/content/diff', { method: 'POST', body: JSON.stringify({ plan_id: planId, format: 'json', context_lines: 3, }), }); // 3) Apply const apply = await wpRequest('/wpnav/v1/content/apply', { method: 'POST', body: JSON.stringify({ plan_id: planId, idempotency_key: idempotencyKey, force: !!opts.force, }), }); return { plan, diff, apply }; } export async function applyContentCreation( wpRequest: WpRequest, config: WPConfig, opts: CreateContentOptions ) { const idempotencyKey = makeIdempotencyKey(); // Build operations from create options const operations: JsonPatchOp[] = [ { op: 'replace', path: '/title', value: opts.title }, ]; if (opts.content) { operations.push({ op: 'replace', path: '/content', value: opts.content }); } if (opts.excerpt) { operations.push({ op: 'replace', path: '/excerpt', value: opts.excerpt }); } if (opts.status) { operations.push({ op: 'replace', path: '/status', value: opts.status }); } // Add Gutenberg block operations if provided if (opts.blocks && opts.blocks.length > 0) { opts.blocks.forEach((block, index) => { operations.push({ op: 'insert', path: `/blocks/${index}`, value: { type: 'block', blockName: block.blockName, attrs: block.attrs, innerBlocks: block.innerBlocks || [], innerContent: [], children: [], }, }); }); } // 1) Plan (with post_id: 0 for creates) const plan = await wpRequest('/wpnav/v1/content/plan', { method: 'POST', body: JSON.stringify({ post_id: 0, // Sentinel: indicates create mode post_type: opts.postType, operations, idempotency_key: idempotencyKey, }), }); const planId = plan?.plan_id ?? plan?.id ?? plan?.planId; if (!planId) { throw new Error(`PLAN_FAILED: Could not retrieve plan_id from response`); } // 2) Diff (same as updates) const diff = await wpRequest('/wpnav/v1/content/diff', { method: 'POST', body: JSON.stringify({ plan_id: planId, format: 'json', context_lines: 3, }), }); // 3) Apply (creates the post) const apply = await wpRequest('/wpnav/v1/content/apply', { method: 'POST', body: JSON.stringify({ plan_id: planId, idempotency_key: idempotencyKey, force: true, // Required for critical-risk operations like creates }), }); return { plan, diff, apply }; } export function makeIdempotencyKey(): string { try { return crypto.randomBytes(16).toString('hex'); } catch { // Fallback (non‑crypto) if environment restricts return `${Date.now()}-${Math.random().toString(36).slice(2)}`; } }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/littlebearapps/wp-navigator-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server