Skip to main content
Glama

distribute_elements

Evenly space selected elements horizontally or vertically in Excalidraw diagrams to organize layouts and improve visual alignment.

Instructions

Distribute elements evenly (horizontal or vertical)

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
elementIdsYes
directionYes

Implementation Reference

  • The actual working handler implementation - the distribute_elements tool is registered with inline logic that distributes elements horizontally or vertically by calculating equal gaps between sorted elements
    server.tool(
      'distribute_elements',
      'Distribute elements evenly (horizontal or vertical)',
      {
        elementIds: z.array(IdZ).min(3).max(LIMITS.MAX_ELEMENT_IDS),
        direction: z.enum(['horizontal', 'vertical']),
      },
      async ({ elementIds, direction }) => {
        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);
          }
    
          if (direction === 'horizontal') {
            const sorted = [...elements].sort((a, b) => a.x - b.x);
            const first = sorted[0]!;
            const last = sorted[sorted.length - 1]!;
            const totalSpan = (last.x + (last.width ?? 0)) - first.x;
            const totalWidth = sorted.reduce((s, e) => s + (e.width ?? 0), 0);
            const gap = (totalSpan - totalWidth) / (sorted.length - 1);
    
            let currentX = first.x;
            for (const el of sorted) {
              await client.updateElement(el.id, { x: currentX });
              currentX += (el.width ?? 0) + gap;
            }
          } else {
            const sorted = [...elements].sort((a, b) => a.y - b.y);
            const first = sorted[0]!;
            const last = sorted[sorted.length - 1]!;
            const totalSpan = (last.y + (last.height ?? 0)) - first.y;
            const totalHeight = sorted.reduce((s, e) => s + (e.height ?? 0), 0);
            const gap = (totalSpan - totalHeight) / (sorted.length - 1);
    
            let currentY = first.y;
            for (const el of sorted) {
              await client.updateElement(el.id, { y: currentY });
              currentY += (el.height ?? 0) + gap;
            }
          }
    
          return {
            content: [{
              type: 'text',
              text: JSON.stringify({ distributed: true, direction, elementIds }, null, 2),
            }],
          };
        } catch (err) {
          return { content: [{ type: 'text', text: `Error: ${(err as Error).message}` }], isError: true };
        }
      }
    );
  • Zod schema definition for distribute_elements input validation - requires elementIds array (min 3, max limit) and direction (horizontal or vertical)
    export const DistributeElementsSchema = z
      .object({
        elementIds: z
          .array(z.string().max(LIMITS.MAX_ID_LENGTH))
          .min(3)
          .max(LIMITS.MAX_ELEMENT_IDS),
        direction: z.enum(['horizontal', 'vertical']),
      })
      .strict();
  • Alternative standalone handler implementation with similar logic but using DistributeElementsSchema.parse for validation - appears to be unused by the main MCP server
    export async function distributeElementsTool(
      args: unknown,
      client: CanvasClient
    ) {
      const { elementIds, direction } = DistributeElementsSchema.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;
        })
      );
    
      if (direction === 'horizontal') {
        const sorted = [...elements].sort((a, b) => a.x - b.x);
        const first = sorted[0];
        const last = sorted[sorted.length - 1];
        const totalSpan = last.x + (last.width ?? 0) - first.x;
        const totalElementWidth = sorted.reduce(
          (sum, el) => sum + (el.width ?? 0),
          0
        );
        const gap = (totalSpan - totalElementWidth) / (sorted.length - 1);
    
        let currentX = first.x;
        for (const el of sorted) {
          if (el.x !== currentX) {
            await client.updateElement(el.id, { x: currentX });
          }
          currentX += (el.width ?? 0) + gap;
        }
      } else {
        const sorted = [...elements].sort((a, b) => a.y - b.y);
        const first = sorted[0];
        const last = sorted[sorted.length - 1];
        const totalSpan = last.y + (last.height ?? 0) - first.y;
        const totalElementHeight = sorted.reduce(
          (sum, el) => sum + (el.height ?? 0),
          0
        );
        const gap = (totalSpan - totalElementHeight) / (sorted.length - 1);
    
        let currentY = first.y;
        for (const el of sorted) {
          if (el.y !== currentY) {
            await client.updateElement(el.id, { y: currentY });
          }
          currentY += (el.height ?? 0) + gap;
        }
      }
    
      return { success: true, distributed: true, direction, elementIds };
    }
  • TypeScript type inferred from DistributeElementsSchema for type safety
    export type DistributeElements = z.infer<typeof DistributeElementsSchema>;
  • Sandbox registration for capability scanning - registers the tool with no-op handler
    server.tool('distribute_elements', 'Distribute elements evenly (horizontal or vertical)', { elementIds: z.array(IdZ).min(3).max(LIMITS.MAX_ELEMENT_IDS), direction: z.enum(['horizontal', 'vertical']) }, noop);
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions the action ('distribute evenly') but doesn't clarify side effects (e.g., whether it modifies element positions permanently, requires specific permissions, or has rate limits). For a mutation tool with zero annotation coverage, this is a significant gap in transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that front-loads the core action ('distribute elements evenly') and adds essential detail ('horizontal or vertical'). There is no wasted text, and it's appropriately sized for a tool with 2 parameters and no complex output schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 2 parameters with 0% schema coverage, no annotations, and no output schema, the description is incomplete. It covers the basic purpose but lacks details on behavioral traits, parameter nuances (e.g., what 'elementIds' represent), and expected outcomes. For a mutation tool in a context with many siblings, this leaves significant gaps for an agent to infer correct usage.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It adds meaning by explaining that 'direction' controls horizontal or vertical distribution, which aligns with the enum in the schema. However, it doesn't clarify 'elementIds' beyond implying multiple elements, and with 2 parameters total, the description provides partial but incomplete semantic context beyond the bare schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('distribute') and target ('elements'), specifying the distribution method ('evenly') and orientation options ('horizontal or vertical'). It distinguishes from siblings like 'align_elements' or 'group_elements' by focusing on spacing rather than alignment or grouping. However, it doesn't explicitly mention the resource context (e.g., UI elements, graphical objects), leaving some ambiguity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided on when to use this tool versus alternatives like 'align_elements' or 'group_elements'. The description implies usage for spacing elements but doesn't specify prerequisites, such as needing multiple selected elements, or exclusions, such as not working on locked elements. This lack of context makes it harder for an agent to choose appropriately among siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/debu-sinha/excalidraw-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server