Skip to main content
Glama

format_layout

Destructive

Apply layout controls to DOCX files by adjusting paragraph spacing, table row height, and cell padding. Google Docs supports paragraph spacing adjustments only.

Instructions

Apply layout controls (paragraph spacing, table row height, cell padding). Google Docs supports paragraph spacing only.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
file_pathNoPath to the DOCX file.
google_doc_idNoGoogle Doc ID or URL (alternative to file_path). Extract from URL: docs.google.com/document/d/{ID}/edit
strictNo
paragraph_spacingNo
row_heightNo
cell_paddingNo

Implementation Reference

  • The main handler function for the `format_layout` tool, which manages session resolution, validates layout parameters (paragraph spacing, row heights, cell padding), performs preflight checks if in strict mode, and executes the layout mutations on the document.
    export async function formatLayout(
      manager: SessionManager,
      params: FormatLayoutParams,
    ): Promise<ToolResponse> {
      try {
        const resolved = await resolveSessionForTool(manager, params, { toolName: 'format_layout' });
        if (!resolved.ok) return resolved.response;
        const { session, metadata } = resolved;
    
        const strict = params.strict ?? true;
        if (typeof strict !== 'boolean') {
          return err('VALIDATION_ERROR', 'strict must be a boolean.', 'Set strict to true or false.');
        }
    
        const paragraphSpacing = parseParagraphSpacingMutation(params.paragraph_spacing);
        if (isValidationError(paragraphSpacing)) {
          return err('VALIDATION_ERROR', paragraphSpacing.message, paragraphSpacing.hint);
        }
    
        const rowHeight = parseRowHeightMutation(params.row_height);
        if (isValidationError(rowHeight)) {
          return err('VALIDATION_ERROR', rowHeight.message, rowHeight.hint);
        }
    
        const cellPadding = parseCellPaddingMutation(params.cell_padding);
        if (isValidationError(cellPadding)) {
          return err('VALIDATION_ERROR', cellPadding.message, cellPadding.hint);
        }
    
        if (!paragraphSpacing && !rowHeight && !cellPadding) {
          return err(
            'VALIDATION_ERROR',
            'No layout operation was provided.',
            'Provide at least one of paragraph_spacing, row_height, or cell_padding.',
          );
        }
    
        if (paragraphSpacing && strict) {
          const missing = paragraphSpacing.paragraphIds.filter((id) => session.doc.getParagraphElementById(id) === null);
          if (missing.length > 0) {
            return err(
              'INVALID_SELECTOR',
              `paragraph_spacing references missing paragraph IDs: ${missing.join(', ')}`,
              'Set strict=false to ignore missing selectors and apply best-effort changes.',
            );
          }
        }
    
        if (strict && (rowHeight || cellPadding)) {
          // Preflight selectors against a cloned document so strict-mode failures
          // do not partially mutate the active session document.
          const snapshot = await session.doc.toBuffer({ cleanBookmarks: false });
          const previewDoc = await DocxDocument.load(snapshot.buffer);
    
          if (rowHeight) {
            const rowPreview = previewDoc.setTableRowHeight(rowHeight);
            if (rowPreview.missingTableIndexes.length > 0) {
              return err(
                'INVALID_SELECTOR',
                `row_height missing table indexes: ${rowPreview.missingTableIndexes.join(', ')}`,
                'Set strict=false to allow best-effort application.',
              );
            }
            if (rowPreview.missingRowIndexes.length > 0) {
              return err(
                'INVALID_SELECTOR',
                `row_height missing row indexes: ${rowPreview.missingRowIndexes
                  .map((x) => `${x.tableIndex}:${x.rowIndex}`)
                  .join(', ')}`,
                'Set strict=false to allow best-effort application.',
              );
            }
          }
    
          if (cellPadding) {
            const cellPreview = previewDoc.setTableCellPadding(cellPadding);
            if (cellPreview.missingTableIndexes.length > 0) {
              return err(
                'INVALID_SELECTOR',
                `cell_padding missing table indexes: ${cellPreview.missingTableIndexes.join(', ')}`,
                'Set strict=false to allow best-effort application.',
              );
            }
            if (cellPreview.missingRowIndexes.length > 0) {
              return err(
                'INVALID_SELECTOR',
                `cell_padding missing row indexes: ${cellPreview.missingRowIndexes
                  .map((x) => `${x.tableIndex}:${x.rowIndex}`)
                  .join(', ')}`,
                'Set strict=false to allow best-effort application.',
              );
            }
            if (cellPreview.missingCellIndexes.length > 0) {
              return err(
                'INVALID_SELECTOR',
                `cell_padding missing cell indexes: ${cellPreview.missingCellIndexes
                  .map((x) => `${x.tableIndex}:${x.rowIndex}:${x.cellIndex}`)
                  .join(', ')}`,
                'Set strict=false to allow best-effort application.',
              );
            }
          }
        }
    
        const paragraphCountBefore = session.doc.getParagraphs().length;
        const warnings: string[] = [];
    
        const paragraphSpacingResult = paragraphSpacing ? session.doc.setParagraphSpacing(paragraphSpacing) : null;
        const rowHeightResult = rowHeight ? session.doc.setTableRowHeight(rowHeight) : null;
        const cellPaddingResult = cellPadding ? session.doc.setTableCellPadding(cellPadding) : null;
    
        const paragraphCountAfter = session.doc.getParagraphs().length;
        if (paragraphCountAfter !== paragraphCountBefore) {
          return err(
            'INVARIANT_VIOLATION',
            `Layout formatting changed paragraph count (${paragraphCountBefore} -> ${paragraphCountAfter}).`,
            'Layout operations must only mutate OOXML spacing/geometry and must not insert spacer paragraphs.',
          );
        }
    
        if (paragraphSpacingResult && paragraphSpacingResult.missingParagraphIds.length > 0) {
          const message = `paragraph_spacing skipped missing paragraph IDs: ${paragraphSpacingResult.missingParagraphIds.join(', ')}`;
          if (strict) {
            return err('INVALID_SELECTOR', message, 'Set strict=false to allow best-effort application.');
          }
          warnings.push(message);
        }
        if (rowHeightResult) {
          if (rowHeightResult.missingTableIndexes.length > 0) {
            const message = `row_height missing table indexes: ${rowHeightResult.missingTableIndexes.join(', ')}`;
            if (strict) return err('INVALID_SELECTOR', message, 'Set strict=false to allow best-effort application.');
            warnings.push(message);
          }
          if (rowHeightResult.missingRowIndexes.length > 0) {
            const message = `row_height missing row indexes: ${rowHeightResult.missingRowIndexes
              .map((x) => `${x.tableIndex}:${x.rowIndex}`)
              .join(', ')}`;
            if (strict) return err('INVALID_SELECTOR', message, 'Set strict=false to allow best-effort application.');
            warnings.push(message);
          }
        }
        if (cellPaddingResult) {
          if (cellPaddingResult.missingTableIndexes.length > 0) {
            const message = `cell_padding missing table indexes: ${cellPaddingResult.missingTableIndexes.join(', ')}`;
            if (strict) return err('INVALID_SELECTOR', message, 'Set strict=false to allow best-effort application.');
            warnings.push(message);
          }
          if (cellPaddingResult.missingRowIndexes.length > 0) {
            const message = `cell_padding missing row indexes: ${cellPaddingResult.missingRowIndexes
              .map((x) => `${x.tableIndex}:${x.rowIndex}`)
              .join(', ')}`;
            if (strict) return err('INVALID_SELECTOR', message, 'Set strict=false to allow best-effort application.');
            warnings.push(message);
          }
          if (cellPaddingResult.missingCellIndexes.length > 0) {
            const message = `cell_padding missing cell indexes: ${cellPaddingResult.missingCellIndexes
              .map((x) => `${x.tableIndex}:${x.rowIndex}:${x.cellIndex}`)
              .join(', ')}`;
            if (strict) return err('INVALID_SELECTOR', message, 'Set strict=false to allow best-effort application.');
            warnings.push(message);
          }
        }
    
        const affectedParagraphs = paragraphSpacingResult?.affectedParagraphs ?? 0;
        const affectedRows = rowHeightResult?.affectedRows ?? 0;
        const affectedCells = cellPaddingResult?.affectedCells ?? 0;
        const totalAffected = affectedParagraphs + affectedRows + affectedCells;
        if (totalAffected > 0) {
          manager.markEdited(session);
        }
        manager.touch(session);
    
        return ok(mergeSessionResolutionMetadata({
          file_path: manager.normalizePath(session.originalPath),
          strict,
          mutation_summary: {
            affected_paragraphs: affectedParagraphs,
            affected_rows: affectedRows,
            affected_cells: affectedCells,
          },
          paragraph_spacing_result: paragraphSpacingResult ?? undefined,
          row_height_result: rowHeightResult ?? undefined,
          cell_padding_result: cellPaddingResult ?? undefined,
          no_spacer_paragraphs: paragraphCountBefore === paragraphCountAfter,
          paragraph_count_before: paragraphCountBefore,
          paragraph_count_after: paragraphCountAfter,
          warnings,
          message: totalAffected > 0
            ? 'Layout formatting applied with deterministic OOXML geometry mutations.'
            : 'No document nodes matched the provided selectors.',
        }, metadata));
      } catch (e: unknown) {
        const message = errorMessage(e);
        return err('FORMAT_LAYOUT_ERROR', `Failed to apply layout formatting: ${message}`, 'Check selector inputs and retry.');
      }
    }
  • Input parameter type definition for the `format_layout` tool, defining the structure of expected mutations.
    type FormatLayoutParams = {
      file_path?: string;
      strict?: boolean;
      paragraph_spacing?: ParagraphSpacingInput;
      row_height?: RowHeightInput;
      cell_padding?: CellPaddingInput;
    };
  • Registration and dispatch logic for the `format_layout` tool within the MCP server, routing requests between generic docx and Google Docs implementations.
    case 'format_layout':
      if (isGDocsRequest(args)) return await dispatchGDocs(sessions, args, 'format_layout');
      return await formatLayout(sessions, args as Parameters<typeof formatLayout>[1]);
Behavior4/5

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

Annotations declare destructiveHint=true, and the description aligns with this via the verb 'Apply'. The description adds crucial behavioral context beyond annotations: the platform-specific limitation that Google Docs only supports paragraph spacing, which predicts partial failure or ignored parameters when using other features on Google Docs.

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?

Two sentences total with zero redundancy. First sentence front-loads the action and scope; second sentence provides the critical platform constraint. Every word earns its place with no filler or tautology.

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

Completeness3/5

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

For a destructive tool with 6 parameters, 3 nested objects, and platform-specific behavior (DOCX vs Google Docs), the description covers the primary functional scope and platform limitation but leaves gaps. It omits explanation of the 'strict' parameter, does not clarify that file_path and google_doc_id are mutually exclusive alternatives, and provides no indication of success/failure behavior or return values.

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

Parameters4/5

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

With only 33% schema description coverage (file_path and google_doc_id described), the description compensates effectively by naming the three complex nested parameters (paragraph_spacing, row_height, cell_padding) and explaining their semantic purpose. However, it fails to explain the 'strict' boolean parameter, leaving its behavioral significance undocumented.

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

Purpose5/5

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

The description uses specific verb 'Apply' with explicit resource 'layout controls' and enumerates the three specific control types (paragraph spacing, table row height, cell padding). The added constraint 'Google Docs supports paragraph spacing only' effectively distinguishes scope from sibling formatting tools like clear_formatting or replace_text.

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

Usage Guidelines3/5

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

The description provides implicit usage guidance through the Google Docs platform limitation, warning that row_height and cell_padding only work for DOCX files. However, it lacks explicit when-to-use comparisons with sibling tools (e.g., when to use this versus clear_formatting) or guidance on choosing between file_path and google_doc_id parameters.

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/UseJunior/safe-docx'

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