wp_seo_bulk_update_metadata
Update SEO metadata for multiple WordPress posts simultaneously with progress tracking and error handling.
Instructions
Update SEO metadata for multiple posts with progress tracking and error handling
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Site identifier for multi-site setups | |
| postIds | Yes | Array of WordPress post IDs to update | |
| updates | Yes | Metadata fields to update for all posts | |
| dryRun | No | Perform a dry run without making actual changes |
Implementation Reference
- src/tools/seo/SEOHandlers.ts:82-105 (handler)The main MCP tool handler function for 'wp_seo_bulk_update_metadata'. It parses input arguments, creates parameters, and delegates execution to the SEOTools.bulkUpdateMetadata method./** * Handle bulk metadata update request */ export async function handleBulkUpdateMetadata( client: WordPressClient, args: Record<string, unknown>, ): Promise<unknown> { const logger = LoggerFactory.tool("wp_seo_bulk_update_metadata"); try { const seoTools = getSEOToolsInstance(); const params: SEOToolParams = { postIds: args.postIds as number[], updates: args.updates as Partial<SEOMetadata>, dryRun: args.dryRun as boolean, site: args.site as string, }; return await seoTools.bulkUpdateMetadata(params); } catch (error) { logger.error("Failed to bulk update metadata", { error, args }); throw error; } }
- Defines the tool schema including name, description, and input validation schema for the wp_seo_bulk_update_metadata tool./** * Bulk update metadata for multiple posts */ export const bulkUpdateMetadataTool: Tool = { name: "wp_seo_bulk_update_metadata", description: "Update SEO metadata for multiple posts with progress tracking and error handling", inputSchema: { type: "object", properties: { postIds: { type: "array", items: { type: "number" }, description: "Array of WordPress post IDs to update", }, updates: { type: "object", properties: { title: { type: "string" }, description: { type: "string" }, focusKeyword: { type: "string" }, canonical: { type: "string" }, }, description: "Metadata fields to update for all posts", }, dryRun: { type: "boolean", description: "Perform a dry run without making actual changes", }, site: { type: "string", description: "Site identifier for multi-site setups", }, }, required: ["postIds", "updates"], }, };
- src/tools/seo/SEOTools.ts:378-399 (registration)Maps the tool name 'wp_seo_bulk_update_metadata' to its handler function handleBulkUpdateMetadata. Used by getTools() to register all SEO tools with handlers.private getHandlerForTool(toolName: string): unknown { const handlers: Record<string, unknown> = { wp_seo_analyze_content: handleAnalyzeContent, wp_seo_generate_metadata: handleGenerateMetadata, wp_seo_bulk_update_metadata: handleBulkUpdateMetadata, wp_seo_generate_schema: handleGenerateSchema, wp_seo_validate_schema: handleValidateSchema, wp_seo_suggest_internal_links: handleSuggestInternalLinks, wp_seo_site_audit: handlePerformSiteAudit, wp_seo_track_serp: handleTrackSERPPositions, wp_seo_keyword_research: handleKeywordResearch, wp_seo_test_integration: handleTestSEOIntegration, wp_seo_get_live_data: handleGetLiveSEOData, }; return ( handlers[toolName] || (() => { throw new Error(`Unknown SEO tool: ${toolName}`); }) ); }
- Core bulk processing logic delegated from SEOTools. Handles batching, individual post metadata updates, retries, progress reporting, and error collection.async bulkUpdateMetadata(params: SEOToolParams, progressCallback?: ProgressCallback): Promise<BulkOperationResult> { const startTime = Date.now(); this.logger.info("Starting bulk metadata update", { postIds: params.postIds?.length, dryRun: params.dryRun, batchSize: this.config.batchSize, }); if (!params.postIds?.length) { throw new Error("No post IDs provided for bulk operation"); } const progress: BulkProgress = { total: params.postIds.length, processed: 0, completed: 0, failed: 0, skipped: 0, currentBatch: 0, totalBatches: Math.ceil(params.postIds.length / this.config.batchSize), avgProcessingTime: 0, }; const errors: BulkOperationError[] = []; const batches = this.createBatches(params.postIds, this.config.batchSize); // Process each batch for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) { const batch = batches[batchIndex]; progress.currentBatch = batchIndex + 1; this.logger.debug(`Processing batch ${progress.currentBatch}/${progress.totalBatches}`, { batchSize: batch.length, postIds: batch, }); // Process batch items await Promise.all( batch.map(async (postId) => { const itemStartTime = Date.now(); try { await this.processMetadataUpdate(postId, params); progress.completed++; // Update average processing time const processingTime = Date.now() - itemStartTime; progress.avgProcessingTime = (progress.avgProcessingTime * progress.processed + processingTime) / (progress.processed + 1); } catch (_error) { const bulkError: BulkOperationError = { postId, error: _error instanceof Error ? _error.message : String(_error), attempts: 1, retryable: this.isRetryableError(_error), }; // Attempt retries if (bulkError.retryable) { const retryResult = await this.retryOperation( () => this.processMetadataUpdate(postId, params), bulkError, ); if (retryResult.success) { progress.completed++; } else { progress.failed++; errors.push(retryResult.error); } } else { progress.failed++; errors.push(bulkError); } } progress.processed++; }), ); // Calculate ETA if (progress.avgProcessingTime > 0 && progress.processed < progress.total) { const remainingItems = progress.total - progress.processed; const etaMs = remainingItems * progress.avgProcessingTime; progress.eta = new Date(Date.now() + etaMs); } else if (progress.processed > 0 && progress.processed < progress.total) { // Fallback ETA calculation even with minimal processing time const remainingItems = progress.total - progress.processed; const averageTime = progress.avgProcessingTime || 100; // Fallback to 100ms const etaMs = remainingItems * averageTime; progress.eta = new Date(Date.now() + etaMs); } // Call progress callback if (progressCallback && this.config.enableProgress) { progressCallback(progress); } // Small delay between batches to avoid overwhelming the server if (batchIndex < batches.length - 1) { await this.delay(100); } } const result: BulkOperationResult = { total: params.postIds.length, success: progress.completed, failed: progress.failed, skipped: progress.skipped, errors: errors.map((e) => ({ postId: e.postId, error: e.error })), processingTime: Date.now() - startTime, dryRun: params.dryRun || false, }; this.logger.info("Bulk metadata update completed", { ...result, successRate: ((result.success / result.total) * 100).toFixed(1) + "%", }); return result; }