cms_prepare_bulk_update_blocks
Prepare bulk updates for CMS blocks by specifying action parameters in a JSON object.
Instructions
Prepare a bulk update for CMS blocks.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| params | No | Action parameters as a JSON object |
Implementation Reference
- src/actions/cms.ts:224-276 (handler)The main handler function for the 'cms.prepare_bulk_update_blocks' tool. Parses input, enforces allowed fields, resolves matching blocks via the helper function, creates a plan, and returns plan details with sample diffs.
// ── Prepare Bulk Update Blocks ──────────────────────────────────────── { name: 'cms.prepare_bulk_update_blocks', description: 'Prepare a bulk update for CMS blocks.', riskTier: RiskTier.Risk, requiresAuth: true, handler: async (params: Record<string, unknown>, context: ActionContext) => { const validated = CmsPrepareBulkUpdateBlocksSchema.parse(params); guardrails.enforceAllowedFields( Object.keys(validated.updates), config.allowedCmsBlockUpdateFields, 'CMS block update', ); const client = context.getClient(); const blocks = await resolveMatchingBlocks(client, validated.match); const sampleDiffs = blocks.slice(0, 5).map((b: Record<string, unknown>) => { const diff: Record<string, { from: unknown; to: unknown }> = {}; for (const [field, newValue] of Object.entries(validated.updates)) { diff[field] = { from: b[field], to: newValue }; } return { block_id: b['id'], title: b['title'], changes: diff }; }); const blockIdentifiers: Record<number, string> = {}; for (const b of blocks) { blockIdentifiers[b['id'] as number] = String(b['identifier'] || ''); } const plan = planStore.create( 'cms.commit_bulk_update_blocks', { block_ids: blocks.map((b: Record<string, unknown>) => b['id']), block_identifiers: blockIdentifiers, updates: validated.updates, scope: validated.scope, }, blocks.length, config.planExpiryMinutes, sampleDiffs, ); return { plan_id: plan.plan_id, expires_at: plan.expires_at, affected_count: blocks.length, sample_diffs: sampleDiffs, message: 'CMS block update plan created. Call cms.commit_bulk_update_blocks to execute.', }; }, }, - src/validation/schemas.ts:203-210 (schema)Zod schema for the 'cms_prepare_bulk_update_blocks' tool. Validates input: 'match' (optional block_ids array and/or identifier string), 'updates' (record of fields to update), and 'scope' (store scope).
export const CmsPrepareBulkUpdateBlocksSchema = z.object({ match: z.object({ block_ids: z.array(z.number().int()).optional(), identifier: z.string().optional(), }), updates: z.record(z.unknown()), scope: StoreScopeSchema, }); - src/actions/cms.ts:22-329 (registration)Registration of the tool inside the createCmsActions() function which returns an array of ActionDefinitions. The tool is registered with name 'cms.prepare_bulk_update_blocks', description, risk tier 'Risk', requiresAuth true, and its handler.
export function createCmsActions( planStore: PlanStore, guardrails: Guardrails, config: McpConfig, ): ActionDefinition[] { return [ // ── Search Pages ────────────────────────────────────────────────────── { name: 'cms.search_pages', description: 'Search CMS pages by query string.', riskTier: RiskTier.Safe, requiresAuth: true, handler: async (params: Record<string, unknown>, context: ActionContext) => { const validated = CmsSearchPagesSchema.parse(params); const client = context.getClient(); const filterGroups: Array<{ filters: Array<{ field: string; value: string; conditionType?: string }> }> = []; if (validated.query) { filterGroups.push({ filters: [{ field: 'title', value: `%${validated.query}%`, conditionType: 'like' }], }); } const searchParams = client.buildSearchParams({ filterGroups: filterGroups.length > 0 ? filterGroups : undefined, pageSize: validated.page_size, currentPage: validated.current_page, }); const storeCode = validated.scope?.store_view_code; return await client.get('/V1/cmsPage/search', searchParams, storeCode); }, }, // ── Get Page ────────────────────────────────────────────────────────── { name: 'cms.get_page', description: 'Get a CMS page by ID.', riskTier: RiskTier.Safe, requiresAuth: true, handler: async (params: Record<string, unknown>, context: ActionContext) => { const validated = CmsGetPageSchema.parse(params); const client = context.getClient(); const storeCode = validated.scope?.store_view_code; return await client.get(`/V1/cmsPage/${validated.page_id}`, undefined, storeCode); }, }, // ── Prepare Bulk Update Pages ───────────────────────────────────────── { name: 'cms.prepare_bulk_update_pages', description: 'Prepare a bulk update for CMS pages.', riskTier: RiskTier.Risk, requiresAuth: true, handler: async (params: Record<string, unknown>, context: ActionContext) => { const validated = CmsPrepareBulkUpdatePagesSchema.parse(params); guardrails.enforceAllowedFields( Object.keys(validated.updates), config.allowedCmsPageUpdateFields, 'CMS page update', ); const client = context.getClient(); // Resolve matching pages const pages = await resolveMatchingPages(client, validated.match); const sampleDiffs = pages.slice(0, 5).map((p: Record<string, unknown>) => { const diff: Record<string, { from: unknown; to: unknown }> = {}; for (const [field, newValue] of Object.entries(validated.updates)) { diff[field] = { from: p[field], to: newValue }; } return { page_id: p['id'], title: p['title'], changes: diff }; }); // Store page identifiers so commit can include them in the PUT payload // (required by Magento for pages whose identifier is reserved in config, e.g. CMS Home Page) const pageIdentifiers: Record<number, string> = {}; for (const p of pages) { pageIdentifiers[p['id'] as number] = String(p['identifier'] || ''); } const plan = planStore.create( 'cms.commit_bulk_update_pages', { page_ids: pages.map((p: Record<string, unknown>) => p['id']), page_identifiers: pageIdentifiers, updates: validated.updates, scope: validated.scope, }, pages.length, config.planExpiryMinutes, sampleDiffs, ); return { plan_id: plan.plan_id, expires_at: plan.expires_at, affected_count: pages.length, sample_diffs: sampleDiffs, message: 'CMS page update plan created. Call cms.commit_bulk_update_pages to execute.', }; }, }, // ── Commit Bulk Update Pages ────────────────────────────────────────── { name: 'cms.commit_bulk_update_pages', description: 'Execute a previously prepared CMS page bulk update.', riskTier: RiskTier.Risk, requiresAuth: true, handler: async (params: Record<string, unknown>, context: ActionContext) => { const validated = CmsCommitBulkUpdatePagesSchema.parse(params); guardrails.requireConfirmation(RiskTier.Risk, params); const plan = planStore.consume(validated.plan_id); if (!plan) { throw new Error('Plan not found or expired.'); } const payload = plan.payload as { page_ids: number[]; page_identifiers?: Record<number, string>; updates: Record<string, unknown>; scope?: { store_view_code?: string }; }; const client = context.getClient(); const storeCode = payload.scope?.store_view_code; let successCount = 0; const errors: Array<{ page_id: number; error: string }> = []; for (const pageId of payload.page_ids) { try { // Include identifier in payload — required by Magento for pages whose // identifier is reserved in config (e.g. CMS Home Page "home") const identifier = payload.page_identifiers?.[pageId]; const pagePayload: Record<string, unknown> = { id: pageId, ...payload.updates }; if (identifier) { pagePayload.identifier = identifier; } await client.put(`/V1/cmsPage/${pageId}`, { page: pagePayload, }, storeCode); successCount++; } catch (err) { errors.push({ page_id: pageId, error: err instanceof Error ? err.message : String(err) }); } } return { message: `Updated ${successCount}/${payload.page_ids.length} CMS pages.`, success_count: successCount, error_count: errors.length, errors: errors.length > 0 ? errors : undefined, }; }, }, // ── Search Blocks ───────────────────────────────────────────────────── { name: 'cms.search_blocks', description: 'Search CMS blocks by query string.', riskTier: RiskTier.Safe, requiresAuth: true, handler: async (params: Record<string, unknown>, context: ActionContext) => { const validated = CmsSearchBlocksSchema.parse(params); const client = context.getClient(); const filterGroups: Array<{ filters: Array<{ field: string; value: string; conditionType?: string }> }> = []; if (validated.query) { filterGroups.push({ filters: [{ field: 'title', value: `%${validated.query}%`, conditionType: 'like' }], }); } const searchParams = client.buildSearchParams({ filterGroups: filterGroups.length > 0 ? filterGroups : undefined, pageSize: validated.page_size, currentPage: validated.current_page, }); const storeCode = validated.scope?.store_view_code; return await client.get('/V1/cmsBlock/search', searchParams, storeCode); }, }, // ── Get Block ───────────────────────────────────────────────────────── { name: 'cms.get_block', description: 'Get a CMS block by ID.', riskTier: RiskTier.Safe, requiresAuth: true, handler: async (params: Record<string, unknown>, context: ActionContext) => { const validated = CmsGetBlockSchema.parse(params); const client = context.getClient(); const storeCode = validated.scope?.store_view_code; return await client.get(`/V1/cmsBlock/${validated.block_id}`, undefined, storeCode); }, }, // ── Prepare Bulk Update Blocks ──────────────────────────────────────── { name: 'cms.prepare_bulk_update_blocks', description: 'Prepare a bulk update for CMS blocks.', riskTier: RiskTier.Risk, requiresAuth: true, handler: async (params: Record<string, unknown>, context: ActionContext) => { const validated = CmsPrepareBulkUpdateBlocksSchema.parse(params); guardrails.enforceAllowedFields( Object.keys(validated.updates), config.allowedCmsBlockUpdateFields, 'CMS block update', ); const client = context.getClient(); const blocks = await resolveMatchingBlocks(client, validated.match); const sampleDiffs = blocks.slice(0, 5).map((b: Record<string, unknown>) => { const diff: Record<string, { from: unknown; to: unknown }> = {}; for (const [field, newValue] of Object.entries(validated.updates)) { diff[field] = { from: b[field], to: newValue }; } return { block_id: b['id'], title: b['title'], changes: diff }; }); const blockIdentifiers: Record<number, string> = {}; for (const b of blocks) { blockIdentifiers[b['id'] as number] = String(b['identifier'] || ''); } const plan = planStore.create( 'cms.commit_bulk_update_blocks', { block_ids: blocks.map((b: Record<string, unknown>) => b['id']), block_identifiers: blockIdentifiers, updates: validated.updates, scope: validated.scope, }, blocks.length, config.planExpiryMinutes, sampleDiffs, ); return { plan_id: plan.plan_id, expires_at: plan.expires_at, affected_count: blocks.length, sample_diffs: sampleDiffs, message: 'CMS block update plan created. Call cms.commit_bulk_update_blocks to execute.', }; }, }, // ── Commit Bulk Update Blocks ───────────────────────────────────────── { name: 'cms.commit_bulk_update_blocks', description: 'Execute a previously prepared CMS block bulk update.', riskTier: RiskTier.Risk, requiresAuth: true, handler: async (params: Record<string, unknown>, context: ActionContext) => { const validated = CmsCommitBulkUpdateBlocksSchema.parse(params); guardrails.requireConfirmation(RiskTier.Risk, params); const plan = planStore.consume(validated.plan_id); if (!plan) { throw new Error('Plan not found or expired.'); } const payload = plan.payload as { block_ids: number[]; block_identifiers?: Record<number, string>; updates: Record<string, unknown>; scope?: { store_view_code?: string }; }; const client = context.getClient(); const storeCode = payload.scope?.store_view_code; let successCount = 0; const errors: Array<{ block_id: number; error: string }> = []; for (const blockId of payload.block_ids) { try { const identifier = payload.block_identifiers?.[blockId]; const blockPayload: Record<string, unknown> = { id: blockId, ...payload.updates }; if (identifier) { blockPayload.identifier = identifier; } await client.put(`/V1/cmsBlock/${blockId}`, { block: blockPayload, }, storeCode); successCount++; } catch (err) { errors.push({ block_id: blockId, error: err instanceof Error ? err.message : String(err) }); } } return { message: `Updated ${successCount}/${payload.block_ids.length} CMS blocks.`, success_count: successCount, error_count: errors.length, errors: errors.length > 0 ? errors : undefined, }; }, }, ]; - src/actions/cms.ts:360-384 (helper)The resolveMatchingBlocks helper function used by the handler to fetch CMS blocks matching given block_ids and/or identifier from the Magento API.
async function resolveMatchingBlocks( client: MagentoRestClient, match: { block_ids?: number[]; identifier?: string }, ): Promise<Array<Record<string, unknown>>> { const filterGroups: Array<{ filters: Array<{ field: string; value: string; conditionType?: string }> }> = []; if (match.block_ids && match.block_ids.length > 0) { filterGroups.push({ filters: [{ field: 'block_id', value: match.block_ids.join(','), conditionType: 'in' }], }); } if (match.identifier) { filterGroups.push({ filters: [{ field: 'identifier', value: `%${match.identifier}%`, conditionType: 'like' }], }); } const searchParams = client.buildSearchParams({ filterGroups: filterGroups.length > 0 ? filterGroups : undefined, pageSize: 200, }); const result = await client.get<{ items: Array<Record<string, unknown>> }>('/V1/cmsBlock/search', searchParams); return result.items || []; }