align_elements
Align multiple diagram elements horizontally or vertically to create organized layouts in Excalidraw diagrams.
Instructions
Align elements (left, center, right, top, middle, bottom)
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| elementIds | Yes | ||
| alignment | Yes |
Implementation Reference
- src/mcp/tools/align-elements.ts:4-88 (handler)The main handler function alignElementsTool that implements the align_elements tool logic. It parses input args, fetches elements, and performs alignment operations (left, center, right, top, middle, bottom) by updating element coordinates.export async function alignElementsTool( args: unknown, client: CanvasClient ) { const { elementIds, alignment } = AlignElementsSchema.parse(args); const elements = await Promise.all( elementIds.map(async (id) => { const el = await client.getElement(id); if (!el) throw new Error(`Element ${id} not found`); return el; }) ); switch (alignment) { case 'left': { const minX = Math.min(...elements.map((el) => el.x)); for (const el of elements) { if (el.x !== minX) { await client.updateElement(el.id, { x: minX }); } } break; } case 'right': { const maxRight = Math.max( ...elements.map((el) => el.x + (el.width ?? 0)) ); for (const el of elements) { const newX = maxRight - (el.width ?? 0); if (el.x !== newX) { await client.updateElement(el.id, { x: newX }); } } break; } case 'center': { const avgCenterX = elements.reduce((sum, el) => sum + el.x + (el.width ?? 0) / 2, 0) / elements.length; for (const el of elements) { const newX = avgCenterX - (el.width ?? 0) / 2; if (el.x !== newX) { await client.updateElement(el.id, { x: newX }); } } break; } case 'top': { const minY = Math.min(...elements.map((el) => el.y)); for (const el of elements) { if (el.y !== minY) { await client.updateElement(el.id, { y: minY }); } } break; } case 'bottom': { const maxBottom = Math.max( ...elements.map((el) => el.y + (el.height ?? 0)) ); for (const el of elements) { const newY = maxBottom - (el.height ?? 0); if (el.y !== newY) { await client.updateElement(el.id, { y: newY }); } } break; } case 'middle': { const avgCenterY = elements.reduce((sum, el) => sum + el.y + (el.height ?? 0) / 2, 0) / elements.length; for (const el of elements) { const newY = avgCenterY - (el.height ?? 0) / 2; if (el.y !== newY) { await client.updateElement(el.id, { y: newY }); } } break; } } return { success: true, aligned: true, alignment, elementIds }; }
- src/mcp/schemas/element.ts:130-145 (schema)Zod schema definition for AlignElements input validation, specifying elementIds array (min 2, max) and alignment enum values.export const AlignElementsSchema = z .object({ elementIds: z .array(z.string().max(LIMITS.MAX_ID_LENGTH)) .min(2) .max(LIMITS.MAX_ELEMENT_IDS), alignment: z.enum([ 'left', 'center', 'right', 'top', 'middle', 'bottom', ]), }) .strict();
- src/mcp/schemas/element.ts:246-246 (schema)Type inference for AlignElements from the schema, enabling TypeScript type checking.export type AlignElements = z.infer<typeof AlignElementsSchema>;
- src/mcp/index.ts:302-365 (registration)MCP server tool registration for align_elements with inline handler implementation, including input schema validation and the alignment logic.// --- Tool: align_elements --- server.tool( 'align_elements', 'Align elements (left, center, right, top, middle, bottom)', { elementIds: z.array(IdZ).min(2).max(LIMITS.MAX_ELEMENT_IDS), alignment: z.enum(['left', 'center', 'right', 'top', 'middle', 'bottom']), }, async ({ elementIds, alignment }) => { try { const elements = []; for (const eid of elementIds) { const el = await client.getElement(eid); if (!el) throw new Error(`Element ${eid} not found`); if (el.locked) throw new Error(`Element ${eid} is locked`); elements.push(el); } switch (alignment) { case 'left': { const minX = Math.min(...elements.map(e => e.x)); for (const el of elements) await client.updateElement(el.id, { x: minX }); break; } case 'right': { const maxRight = Math.max(...elements.map(e => e.x + (e.width ?? 0))); for (const el of elements) await client.updateElement(el.id, { x: maxRight - (el.width ?? 0) }); break; } case 'center': { const centers = elements.map(e => e.x + (e.width ?? 0) / 2); const avg = centers.reduce((a, b) => a + b, 0) / centers.length; for (const el of elements) await client.updateElement(el.id, { x: avg - (el.width ?? 0) / 2 }); break; } case 'top': { const minY = Math.min(...elements.map(e => e.y)); for (const el of elements) await client.updateElement(el.id, { y: minY }); break; } case 'bottom': { const maxBottom = Math.max(...elements.map(e => e.y + (e.height ?? 0))); for (const el of elements) await client.updateElement(el.id, { y: maxBottom - (el.height ?? 0) }); break; } case 'middle': { const middles = elements.map(e => e.y + (e.height ?? 0) / 2); const avgY = middles.reduce((a, b) => a + b, 0) / middles.length; for (const el of elements) await client.updateElement(el.id, { y: avgY - (el.height ?? 0) / 2 }); break; } } return { content: [{ type: 'text', text: JSON.stringify({ aligned: true, alignment, elementIds }, null, 2), }], }; } catch (err) { return { content: [{ type: 'text', text: `Error: ${(err as Error).message}` }], isError: true }; } } );
- src/mcp/tools/index.ts:13-13 (helper)Export statement that makes the alignElementsTool handler available to other modules.export { alignElementsTool } from './align-elements.js';