Пакетное обновление
bpm_batch_updateUpdates multiple records in a single OData v4 batch request, automatically resolving lookup fields and supporting continue_on_error for non-blocking error handling.
Instructions
Обновляет несколько записей в одном $batch (только OData v4). Lookup-поля резолвятся автоматически. Поддерживает continue_on_error.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| collection | Yes | ||
| updates | Yes | Массив обновлений [{id, data}] | |
| continue_on_error | No | Не прерывать batch на первой ошибке |
Implementation Reference
- src/tools/batch-tools.ts:131-201 (handler)The handler function for bpm_batch_update. Receives params with {collection, updates: [{id, data}], continue_on_error?}, resolves lookups for each update, builds PATCH batch requests, executes via OData $batch, and returns a summary with success/failure counts.
async (params): Promise<CallToolResult> => { if (!services.initialized) return notInitialized(); try { await services.authManager.ensureAuthenticated(); if (params.updates.length === 0) { return { content: [{ type: 'text', text: 'Массив обновлений пуст.' }], isError: true }; } const batchRequests: Array<{ method: 'PATCH'; url: string; body: Record<string, unknown> }> = []; for (let i = 0; i < params.updates.length; i++) { const update = params.updates[i]; try { const resolvedData = await services.lookupResolver.resolveDataLookups(params.collection, update.data); batchRequests.push({ method: 'PATCH', url: services.odataClient.buildRecordPath(params.collection, update.id), body: resolvedData, }); } catch (error) { return { content: [ { type: 'text', text: `Ошибка резолвинга lookup в обновлении #${i + 1} (ID: ${update.id}): ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } const result = await services.odataClient.executeBatch(batchRequests, params.continue_on_error ?? false); const succeeded = result.responses .map((r, i) => ({ index: i, ...r })) .filter((r) => r.status >= 200 && r.status < 300); const failed = result.responses .map((r, i) => ({ index: i, ...r })) .filter((r) => r.status >= 300); const lines = [ `Пакетное обновление в ${params.collection}:`, ` Всего запросов: ${params.updates.length}`, ` Успешно обновлено: ${succeeded.length}`, ` Ошибок: ${failed.length}`, ]; if (failed.length > 0) { lines.push('', 'Ошибки:'); failed.forEach((f) => lines.push(` #${f.index + 1} (id=${params.updates[f.index]?.id}): HTTP ${f.status} — ${JSON.stringify(f.body).slice(0, 300)}`) ); } return { content: [{ type: 'text', text: lines.join('\n') }], isError: failed.length > 0 && succeeded.length === 0, structuredContent: { collection: params.collection, total: params.updates.length, succeeded: succeeded.length, failed: failed.length, first_failed_index: failed.length > 0 ? failed[0].index : null, }, }; } catch (error) { const toolError = formatToolError(error, params.collection); return { content: [{ type: 'text', text: JSON.stringify(toolError, null, 2) }], isError: true }; } } ); - src/tools/batch-tools.ts:117-128 (schema)Input schema for bpm_batch_update: collection (string), updates (array of {id: string, data: Record<string, unknown>}), and optional continue_on_error (boolean).
inputSchema: { collection: z.string(), updates: z .array( z.object({ id: z.string(), data: z.record(z.string(), z.unknown()), }) ) .describe('Массив обновлений [{id, data}]'), continue_on_error: z.boolean().optional().describe('Не прерывать batch на первой ошибке'), }, - src/tools/batch-tools.ts:109-201 (registration)Registration of bpm_batch_update tool via server.registerTool() within the registerBatchTools function, pulling metadata from the tool registry.
// bpm_batch_update { const meta = getTool('bpm_batch_update'); server.registerTool( meta.name, { title: meta.title, description: meta.description, inputSchema: { collection: z.string(), updates: z .array( z.object({ id: z.string(), data: z.record(z.string(), z.unknown()), }) ) .describe('Массив обновлений [{id, data}]'), continue_on_error: z.boolean().optional().describe('Не прерывать batch на первой ошибке'), }, annotations: meta.annotations, }, async (params): Promise<CallToolResult> => { if (!services.initialized) return notInitialized(); try { await services.authManager.ensureAuthenticated(); if (params.updates.length === 0) { return { content: [{ type: 'text', text: 'Массив обновлений пуст.' }], isError: true }; } const batchRequests: Array<{ method: 'PATCH'; url: string; body: Record<string, unknown> }> = []; for (let i = 0; i < params.updates.length; i++) { const update = params.updates[i]; try { const resolvedData = await services.lookupResolver.resolveDataLookups(params.collection, update.data); batchRequests.push({ method: 'PATCH', url: services.odataClient.buildRecordPath(params.collection, update.id), body: resolvedData, }); } catch (error) { return { content: [ { type: 'text', text: `Ошибка резолвинга lookup в обновлении #${i + 1} (ID: ${update.id}): ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } const result = await services.odataClient.executeBatch(batchRequests, params.continue_on_error ?? false); const succeeded = result.responses .map((r, i) => ({ index: i, ...r })) .filter((r) => r.status >= 200 && r.status < 300); const failed = result.responses .map((r, i) => ({ index: i, ...r })) .filter((r) => r.status >= 300); const lines = [ `Пакетное обновление в ${params.collection}:`, ` Всего запросов: ${params.updates.length}`, ` Успешно обновлено: ${succeeded.length}`, ` Ошибок: ${failed.length}`, ]; if (failed.length > 0) { lines.push('', 'Ошибки:'); failed.forEach((f) => lines.push(` #${f.index + 1} (id=${params.updates[f.index]?.id}): HTTP ${f.status} — ${JSON.stringify(f.body).slice(0, 300)}`) ); } return { content: [{ type: 'text', text: lines.join('\n') }], isError: failed.length > 0 && succeeded.length === 0, structuredContent: { collection: params.collection, total: params.updates.length, succeeded: succeeded.length, failed: failed.length, first_failed_index: failed.length > 0 ? failed[0].index : null, }, }; } catch (error) { const toolError = formatToolError(error, params.collection); return { content: [{ type: 'text', text: JSON.stringify(toolError, null, 2) }], isError: true }; } } ); - src/tools/registry.ts:196-204 (helper)Metadata definition in the TOOLS registry: name 'bpm_batch_update', title 'Пакетное обновление', description, annotations (idempotentHint: true), blurb, and category 'batch'.
{ name: 'bpm_batch_update', title: 'Пакетное обновление', description: 'Обновляет несколько записей в одном $batch (только OData v4). Lookup-поля резолвятся автоматически. Поддерживает continue_on_error.', annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }, blurb: 'пакетное обновление (OData v4)', category: 'batch', }, - src/tools/registry.ts:328-332 (helper)The getTool() function used at registration time to look up the ToolDescriptor for 'bpm_batch_update' from the TOOLS array.
export function getTool(name: string): ToolDescriptor { const t = TOOLS.find((x) => x.name === name); if (!t) throw new Error(`Tool not registered in registry: ${name}`); return t; }