Skip to main content
Glama
index.ts12.3 kB
/** * Batch Operations Tools * * Tools for performing batch get, update, and delete operations * across multiple WordPress content types in a single call. * * @package WP_Navigator_Pro * @since 2.6.0 */ import { toolRegistry } from '../../tool-registry/registry.js'; import { ToolCategory } from '../../tool-registry/types.js'; import { validateRequired, validateId, validateEnum } from '../../tool-registry/utils.js'; import { BATCH_CONTENT_TYPES, CONTENT_TYPE_ENDPOINTS, MAX_BATCH_SIZE, BatchContentType, BatchItemResult, BatchResponse, BatchUpdateItem, mapHttpErrorToCode, } from './types.js'; /** * Register all batch operation tools */ export function registerBatchTools() { // ============================================ // wpnav_batch_get - Fetch multiple items by ID // ============================================ toolRegistry.register({ definition: { name: 'wpnav_batch_get', description: `Fetch multiple WordPress items by ID in a single call. Supports ${BATCH_CONTENT_TYPES.join(', ')}. Returns per-item success/error status. Max ${MAX_BATCH_SIZE} items per request.`, inputSchema: { type: 'object', properties: { type: { type: 'string', enum: [...BATCH_CONTENT_TYPES], description: 'Content type to fetch', }, ids: { type: 'array', items: { type: 'number' }, description: `Array of WordPress IDs to fetch (max ${MAX_BATCH_SIZE})`, maxItems: MAX_BATCH_SIZE, }, fields: { type: 'array', items: { type: 'string' }, description: 'Optional: Fields to return (e.g., ["id", "title", "status"]). Reduces response size.', }, }, required: ['type', 'ids'], }, }, handler: async (args, context) => { validateRequired(args, ['type', 'ids']); const contentType = validateEnum(args.type, BATCH_CONTENT_TYPES, 'type') as BatchContentType; // Validate and limit IDs if (!Array.isArray(args.ids) || args.ids.length === 0) { return { content: [ { type: 'text', text: JSON.stringify( { error: 'validation_failed', code: 'INVALID_IDS', message: 'ids must be a non-empty array of numbers', }, null, 2 ), }, ], isError: true, }; } const ids = args.ids.slice(0, MAX_BATCH_SIZE).map((id: any) => validateId(id, 'Item')); const endpoint = CONTENT_TYPE_ENDPOINTS[contentType]; // Build _fields parameter if specified const fieldsParam = args.fields?.length ? `_fields=${args.fields.join(',')}` : ''; // Execute parallel requests const results: BatchItemResult[] = await Promise.all( ids.map(async (id: number) => { try { const url = fieldsParam ? `${endpoint}/${id}?${fieldsParam}` : `${endpoint}/${id}`; const data = await context.wpRequest(url); return { id, success: true, data }; } catch (error: any) { return { id, success: false, error: mapHttpErrorToCode(error), }; } }) ); const succeeded = results.filter((r) => r.success).length; const response: BatchResponse = { operation: 'get', type: contentType, total_requested: ids.length, succeeded, failed: ids.length - succeeded, results, }; return { content: [{ type: 'text', text: context.clampText(JSON.stringify(response, null, 2)) }], }; }, category: ToolCategory.BATCH, }); // ============================================ // wpnav_batch_update - Update multiple items // ============================================ toolRegistry.register({ definition: { name: 'wpnav_batch_update', description: `Update multiple WordPress items in a single call. Supports ${BATCH_CONTENT_TYPES.join(', ')}. Requires WPNAV_ENABLE_WRITES=1. Max ${MAX_BATCH_SIZE} items per request.`, inputSchema: { type: 'object', properties: { type: { type: 'string', enum: [...BATCH_CONTENT_TYPES], description: 'Content type to update', }, updates: { type: 'array', items: { type: 'object', properties: { id: { type: 'number', description: 'WordPress ID' }, data: { type: 'object', additionalProperties: true, description: 'Fields to update', }, }, required: ['id', 'data'], }, description: `Array of updates (max ${MAX_BATCH_SIZE})`, maxItems: MAX_BATCH_SIZE, }, }, required: ['type', 'updates'], }, }, handler: async (args, context) => { validateRequired(args, ['type', 'updates']); const contentType = validateEnum(args.type, BATCH_CONTENT_TYPES, 'type') as BatchContentType; // Validate updates array if (!Array.isArray(args.updates) || args.updates.length === 0) { return { content: [ { type: 'text', text: JSON.stringify( { error: 'validation_failed', code: 'INVALID_UPDATES', message: 'updates must be a non-empty array of {id, data} objects', }, null, 2 ), }, ], isError: true, }; } const updates: BatchUpdateItem[] = args.updates.slice(0, MAX_BATCH_SIZE).map((u: any) => ({ id: validateId(u.id, 'Item'), data: u.data || {}, })); const endpoint = CONTENT_TYPE_ENDPOINTS[contentType]; // Execute updates (sequentially to respect concurrency guard) const results: BatchItemResult[] = []; for (const update of updates) { try { const data = await context.wpRequest(`${endpoint}/${update.id}`, { method: 'POST', body: JSON.stringify(update.data), }); results.push({ id: update.id, success: true, data }); } catch (error: any) { results.push({ id: update.id, success: false, error: mapHttpErrorToCode(error), }); } } const succeeded = results.filter((r) => r.success).length; const response: BatchResponse = { operation: 'update', type: contentType, total_requested: updates.length, succeeded, failed: updates.length - succeeded, results, }; return { content: [{ type: 'text', text: context.clampText(JSON.stringify(response, null, 2)) }], isError: succeeded === 0 && updates.length > 0, }; }, category: ToolCategory.BATCH, }); // ============================================ // wpnav_batch_delete - Delete multiple items // ============================================ toolRegistry.register({ definition: { name: 'wpnav_batch_delete', description: `DELETE multiple WordPress items in a single call. HIGH RISK: Requires confirm_destructive=true and WPNAV_ENABLE_WRITES=1. Supports ${BATCH_CONTENT_TYPES.join(', ')}. Max ${MAX_BATCH_SIZE} items.`, inputSchema: { type: 'object', properties: { type: { type: 'string', enum: [...BATCH_CONTENT_TYPES], description: 'Content type to delete', }, ids: { type: 'array', items: { type: 'number' }, description: `Array of WordPress IDs to delete (max ${MAX_BATCH_SIZE})`, maxItems: MAX_BATCH_SIZE, }, force: { type: 'boolean', description: 'Force permanent deletion (skip trash). Default: false', default: false, }, reassign: { type: 'number', description: 'For users only: User ID to reassign content to before deletion', }, confirm_destructive: { type: 'boolean', description: 'REQUIRED: Must be true to confirm this destructive batch operation', }, }, required: ['type', 'ids', 'confirm_destructive'], }, }, handler: async (args, context) => { validateRequired(args, ['type', 'ids', 'confirm_destructive']); // Safety guard: require explicit confirmation if (args.confirm_destructive !== true) { return { content: [ { type: 'text', text: JSON.stringify( { error: 'confirmation_required', code: 'CONFIRM_DESTRUCTIVE_REQUIRED', message: 'Batch delete requires confirm_destructive=true. This operation CANNOT be undone if force=true.', hint: 'Set confirm_destructive: true to proceed with deletion', }, null, 2 ), }, ], isError: true, }; } const contentType = validateEnum(args.type, BATCH_CONTENT_TYPES, 'type') as BatchContentType; // Validate IDs if (!Array.isArray(args.ids) || args.ids.length === 0) { return { content: [ { type: 'text', text: JSON.stringify( { error: 'validation_failed', code: 'INVALID_IDS', message: 'ids must be a non-empty array of numbers', }, null, 2 ), }, ], isError: true, }; } const ids = args.ids.slice(0, MAX_BATCH_SIZE).map((id: any) => validateId(id, 'Item')); const endpoint = CONTENT_TYPE_ENDPOINTS[contentType]; const force = args.force === true; // Build query params const params = new URLSearchParams(); params.append('force', String(force)); // For users, require reassign parameter if (contentType === 'users') { if (!args.reassign) { return { content: [ { type: 'text', text: JSON.stringify( { error: 'validation_failed', code: 'REASSIGN_REQUIRED', message: 'Deleting users requires a reassign parameter to transfer their content', hint: 'Provide reassign: <user_id> to reassign posts/pages to another user', }, null, 2 ), }, ], isError: true, }; } params.append('reassign', String(args.reassign)); } const queryString = params.toString(); // Execute deletes (sequentially to respect concurrency guard) const results: BatchItemResult[] = []; for (const id of ids) { try { const data = await context.wpRequest(`${endpoint}/${id}?${queryString}`, { method: 'DELETE', }); results.push({ id, success: true, data }); } catch (error: any) { results.push({ id, success: false, error: mapHttpErrorToCode(error), }); } } const succeeded = results.filter((r) => r.success).length; const response: BatchResponse = { operation: 'delete', type: contentType, total_requested: ids.length, succeeded, failed: ids.length - succeeded, results, }; return { content: [{ type: 'text', text: context.clampText(JSON.stringify(response, null, 2)) }], isError: succeeded === 0 && ids.length > 0, }; }, category: ToolCategory.BATCH, }); }

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