Skip to main content
Glama
ZatesloFL

Google Workspace MCP Server

by ZatesloFL

create_table_with_data

Generate and populate a table in Google Docs using a 2D list of data. Requires index from inspect_doc_structure for accurate document positioning. Ensures proper formatting and structure for reliable table creation.

Instructions

Creates a table and populates it with data in one reliable operation.

CRITICAL: YOU MUST CALL inspect_doc_structure FIRST TO GET THE INDEX!

MANDATORY WORKFLOW - DO THESE STEPS IN ORDER:

Step 1: ALWAYS call inspect_doc_structure first Step 2: Use the 'total_length' value from inspect_doc_structure as your index Step 3: Format data as 2D list: [["col1", "col2"], ["row1col1", "row1col2"]] Step 4: Call this function with the correct index and data

EXAMPLE DATA FORMAT: table_data = [ ["Header1", "Header2", "Header3"], # Row 0 - headers ["Data1", "Data2", "Data3"], # Row 1 - first data row ["Data4", "Data5", "Data6"] # Row 2 - second data row ]

CRITICAL INDEX REQUIREMENTS:

  • NEVER use index values like 1, 2, 10 without calling inspect_doc_structure first

  • ALWAYS get index from inspect_doc_structure 'total_length' field

  • Index must be a valid insertion point in the document

DATA FORMAT REQUIREMENTS:

  • Must be 2D list of strings only

  • Each inner list = one table row

  • All rows MUST have same number of columns

  • Use empty strings "" for empty cells, never None

  • Use debug_table_structure after creation to verify results

Args: user_google_email: User's Google email address document_id: ID of the document to update table_data: 2D list of strings - EXACT format: [["col1", "col2"], ["row1col1", "row1col2"]] index: Document position (MANDATORY: get from inspect_doc_structure 'total_length') bold_headers: Whether to make first row bold (default: true)

Returns: str: Confirmation with table details and link

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
bold_headersNo
document_idYes
indexYes
table_dataYes
user_google_emailYes

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • Main MCP tool handler for 'create_table_with_data'. Includes @server.tool() registration, input validation, error handling, and delegates core logic to TableOperationManager. Input schema defined by function parameters and type hints.
    @server.tool()
    @handle_http_errors("create_table_with_data", service_type="docs")
    @require_google_service("docs", "docs_write")
    async def create_table_with_data(
        service,
        user_google_email: str,
        document_id: str,
        table_data: list,
        index: int,
        bold_headers: bool = True,
    ) -> str:
        """
        Creates a table and populates it with data in one reliable operation.
    
        CRITICAL: YOU MUST CALL inspect_doc_structure FIRST TO GET THE INDEX!
    
        MANDATORY WORKFLOW - DO THESE STEPS IN ORDER:
    
        Step 1: ALWAYS call inspect_doc_structure first
        Step 2: Use the 'total_length' value from inspect_doc_structure as your index
        Step 3: Format data as 2D list: [["col1", "col2"], ["row1col1", "row1col2"]]
        Step 4: Call this function with the correct index and data
    
        EXAMPLE DATA FORMAT:
        table_data = [
            ["Header1", "Header2", "Header3"],    # Row 0 - headers
            ["Data1", "Data2", "Data3"],          # Row 1 - first data row
            ["Data4", "Data5", "Data6"]           # Row 2 - second data row
        ]
    
        CRITICAL INDEX REQUIREMENTS:
        - NEVER use index values like 1, 2, 10 without calling inspect_doc_structure first
        - ALWAYS get index from inspect_doc_structure 'total_length' field
        - Index must be a valid insertion point in the document
    
        DATA FORMAT REQUIREMENTS:
        - Must be 2D list of strings only
        - Each inner list = one table row
        - All rows MUST have same number of columns
        - Use empty strings "" for empty cells, never None
        - Use debug_table_structure after creation to verify results
    
        Args:
            user_google_email: User's Google email address
            document_id: ID of the document to update
            table_data: 2D list of strings - EXACT format: [["col1", "col2"], ["row1col1", "row1col2"]]
            index: Document position (MANDATORY: get from inspect_doc_structure 'total_length')
            bold_headers: Whether to make first row bold (default: true)
    
        Returns:
            str: Confirmation with table details and link
        """
        logger.debug(f"[create_table_with_data] Doc={document_id}, index={index}")
    
        # Input validation
        validator = ValidationManager()
    
        is_valid, error_msg = validator.validate_document_id(document_id)
        if not is_valid:
            return f"ERROR: {error_msg}"
    
        is_valid, error_msg = validator.validate_table_data(table_data)
        if not is_valid:
            return f"ERROR: {error_msg}"
    
        is_valid, error_msg = validator.validate_index(index, "Index")
        if not is_valid:
            return f"ERROR: {error_msg}"
    
        # Use TableOperationManager to handle the complex logic
        table_manager = TableOperationManager(service)
    
        # Try to create the table, and if it fails due to index being at document end, retry with index-1
        success, message, metadata = await table_manager.create_and_populate_table(
            document_id, table_data, index, bold_headers
        )
    
        # If it failed due to index being at or beyond document end, retry with adjusted index
        if not success and "must be less than the end index" in message:
            logger.debug(f"Index {index} is at document boundary, retrying with index {index - 1}")
            success, message, metadata = await table_manager.create_and_populate_table(
                document_id, table_data, index - 1, bold_headers
            )
    
        if success:
            link = f"https://docs.google.com/document/d/{document_id}/edit"
            rows = metadata.get('rows', 0)
            columns = metadata.get('columns', 0)
    
            return f"SUCCESS: {message}. Table: {rows}x{columns}, Index: {index}. Link: {link}"
        else:
            return f"ERROR: {message}"
  • Core helper method in TableOperationManager that implements the table creation and population logic. Handles multi-step process: create empty table, refresh structure, populate cells one-by-one with index correction to avoid shifting issues.
    async def create_and_populate_table(
        self,
        document_id: str,
        table_data: List[List[str]],
        index: int,
        bold_headers: bool = True
    ) -> Tuple[bool, str, Dict[str, Any]]:
        """
        Creates a table and populates it with data in a reliable multi-step process.
        
        This method extracts the complex logic from create_table_with_data tool function.
        
        Args:
            document_id: ID of the document to update
            table_data: 2D list of strings for table content
            index: Position to insert the table
            bold_headers: Whether to make the first row bold
            
        Returns:
            Tuple of (success, message, metadata)
        """
        logger.debug(f"Creating table at index {index}, dimensions: {len(table_data)}x{len(table_data[0]) if table_data and len(table_data) > 0 else 0}")
        
        # Validate input data
        is_valid, error_msg = validate_table_data(table_data)
        if not is_valid:
            return False, f"Invalid table data: {error_msg}", {}
            
        rows = len(table_data)
        cols = len(table_data[0])
        
        try:
            # Step 1: Create empty table
            await self._create_empty_table(document_id, index, rows, cols)
            
            # Step 2: Get fresh document structure to find actual cell positions
            fresh_tables = await self._get_document_tables(document_id)
            if not fresh_tables:
                return False, "Could not find table after creation", {}
                
            # Step 3: Populate each cell with proper index refreshing
            population_count = await self._populate_table_cells(
                document_id, table_data, bold_headers
            )
            
            metadata = {
                'rows': rows,
                'columns': cols,
                'populated_cells': population_count,
                'table_index': len(fresh_tables) - 1
            }
            
            return True, f"Successfully created {rows}x{cols} table and populated {population_count} cells", metadata
            
        except Exception as e:
            logger.error(f"Failed to create and populate table: {str(e)}")
            return False, f"Table creation failed: {str(e)}", {}
  • Input validation function for table_data parameter. Checks for 2D list format, consistent dimensions, size limits, no None values. Provides LLM-friendly error messages with examples.
    def validate_table_data(data: List[List[str]]) -> Tuple[bool, str]:
        """
        Validates table data format and provides specific error messages for LLMs.
        
        WHAT THIS CHECKS:
        - Data is a 2D list (list of lists)
        - All rows have consistent column counts
        - Dimensions are within Google Docs limits
        - No None or undefined values
        
        VALID FORMAT EXAMPLE:
        [
            ["Header1", "Header2"],     # Row 0 - 2 columns
            ["Data1", "Data2"],        # Row 1 - 2 columns  
            ["Data3", "Data4"]         # Row 2 - 2 columns
        ]
        
        INVALID FORMATS:
        - [["col1"], ["col1", "col2"]]  # Inconsistent column counts
        - ["col1", "col2"]              # Not 2D (missing inner lists)
        - [["col1", None]]              # Contains None values
        - [] or [[]]                    # Empty data
        
        Args:
            data: 2D array of data to validate
        
        Returns:
            Tuple of (is_valid, error_message_with_examples)
        """
        if not data:
            return False, "Data is empty. Use format: [['col1', 'col2'], ['row1col1', 'row1col2']]"
        
        if not isinstance(data, list):
            return False, f"Data must be a list, got {type(data).__name__}. Use format: [['col1', 'col2'], ['row1col1', 'row1col2']]"
        
        if not all(isinstance(row, list) for row in data):
            return False, f"Data must be a 2D list (list of lists). Each row must be a list. Check your format: {data}"
        
        # Check for consistent column count
        col_counts = [len(row) for row in data]
        if len(set(col_counts)) > 1:
            return False, f"All rows must have same number of columns. Found: {col_counts}. Fix your data format."
        
        # Check for reasonable size
        rows = len(data)
        cols = col_counts[0] if col_counts else 0
        
        if rows > 1000:
            return False, f"Too many rows ({rows}). Google Docs limit is 1000 rows."
        
        if cols > 20:
            return False, f"Too many columns ({cols}). Google Docs limit is 20 columns."
        
        return True, f"Valid table data: {rows}x{cols} table format"
  • MCP tool registration via @server.tool() decorator on the handler function, with error handling and auth requirements.
    @server.tool()
Behavior5/5

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

With no annotations provided, the description carries full burden and delivers comprehensive behavioral disclosure. It explains the critical index requirements, data format constraints, validation needs, and the reliable operation nature. It also mentions post-creation verification with debug_table_structure.

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

Conciseness4/5

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

The description is appropriately sized for a complex tool but could be more front-loaded. The critical information is emphasized with headers, but the workflow steps and examples are lengthy. Every sentence earns its place by providing essential guidance.

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

Completeness5/5

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

Given the complexity (5 parameters, 0% schema coverage, no annotations) and presence of output schema, the description is remarkably complete. It covers purpose, prerequisites, workflow, parameter semantics, constraints, examples, and post-operation verification, leaving no significant gaps for agent understanding.

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

Parameters5/5

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

With 0% schema description coverage, the description fully compensates by explaining all 5 parameters. It clarifies table_data format with detailed examples, explains index requirements and source, specifies bold_headers default behavior, and contextualizes document_id and user_google_email within the workflow.

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 clearly states the specific verb 'creates' and resource 'table', and distinguishes it from siblings by specifying it 'populates it with data in one reliable operation'. This differentiates it from other creation tools like create_doc or create_spreadsheet that don't mention data population.

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

Usage Guidelines5/5

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

The description provides explicit, mandatory workflow steps including when to use (ALWAYS call inspect_doc_structure first) and alternatives (use debug_table_structure after creation to verify results). It gives clear prerequisites and sequencing requirements.

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/ZatesloFL/google_workspace_mcp'

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