Skip to main content
Glama

add_table_column_tool

Add new columns to a Google Sheets table with support for multiple column types, dropdown validation, and custom positions. Extend tables dynamically.

Instructions

Add new columns to an existing table in Google Sheets.

This tool extends an existing table with additional columns.
New columns can have different data types and validation rules.
Supports adding multiple columns at once with proper positioning.

Args:
    spreadsheet_name: Name of the spreadsheet
    sheet_name: Name of the sheet containing the table
    table_name: Name of the table to add columns to
    column_names: List of column names to add
    column_types: List of column types corresponding to column_names
    positions: List of positions to insert columns (0-based index, empty list for end)
    dropdown_columns: List of column names that should have dropdown validation
    dropdown_values: List of comma-separated dropdown options for each dropdown column

Returns:
    JSON string with success status and column addition details

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
spreadsheet_nameYesThe name of the Google Spreadsheet
sheet_nameYesThe name of the sheet containing the table
table_nameYesName of the table to add columns to
column_namesYesList of column names (e.g., ['Status', 'Priority', 'Notes'])
column_typesYesList of column types: DOUBLE, CURRENCY, PERCENT, DATE, TIME, DATE_TIME, TEXT, BOOLEAN, DROPDOWN
positionsNoList of positions to insert columns (0-based index, empty list for end)
dropdown_columnsNoList of column names that should have dropdown validation
dropdown_valuesNoComma-separated dropdown options for each dropdown column

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • Main handler function that adds columns to a table in Google Sheets. Validates inputs, checks for conflicts, builds API requests (InsertRangeRequest + UpdateTableRequest), and executes them via the Sheets API.
    def add_table_column_handler(
        drive_service,
        sheets_service,
        spreadsheet_name: str,
        sheet_name: str,
        table_name: str,
        column_names: List[str],
        column_types: List[str],
        positions: List[int] = [],
        dropdown_columns: List[str] = [],
        dropdown_values: List[str] = []
    ) -> str:
        """
        Add new columns to an existing table in Google Sheets.
        
        Available column types:
        - TEXT: Plain text data
        - DOUBLE: Numeric data with decimals
        - CURRENCY: Monetary values ($#,##0.00)
        - PERCENT: Percentage values (0.00%)
        - DATE: Date values (yyyy-mm-dd)
        - TIME: Time values (hh:mm:ss)
        - DATE_TIME: Date and time values
        - BOOLEAN: True/false values
        - DROPDOWN: Selection from predefined options
        - COLUMN_TYPE_UNSPECIFIED: Defaults to TEXT
        
        According to the official Google Sheets API documentation, to add columns to a table:
        1. Use InsertRangeRequest to insert new columns in the sheet (API handles position shifting)
        2. Use UpdateTableRequest to update the table range and column properties
        
        The table will automatically recognize the new columns with proper types and validation.
        The Google Sheets API automatically handles position shifting for existing columns.
        
        Args:
            drive_service: Google Drive service instance
            sheets_service: Google Sheets service instance
            spreadsheet_name: Name of the spreadsheet
            sheet_name: Name of the sheet containing the table
            table_name: Name of the table to add columns to
            column_names: List of column names to add
            column_types: List of column types corresponding to column_names
            positions: List of positions to insert columns (0-based index, empty list for end)
            dropdown_columns: List of column names that should have dropdown validation
            dropdown_values: List of comma-separated dropdown options strings
        
        Returns:
            str: Success message with column addition details
        """
        try:
            # Validate inputs
            if not table_name or table_name.strip() == "":
                return compact_json_response({
                    "success": False,
                    "message": "Table name is required."
                })
            
            if not column_names or len(column_names) == 0:
                return compact_json_response({
                    "success": False,
                    "message": "Column name is required."
                })
            
            if not column_types or len(column_types) == 0:
                return compact_json_response({
                    "success": False,
                    "message": "Column type is required."
                })
            
            # Validate input lengths match
            if len(column_names) != len(column_types):
                return compact_json_response({
                    "success": False,
                    "message": f"Number of column names ({len(column_names)}) must match number of column types ({len(column_types)})."
                })
            
            # Validate positions if provided
            if positions and len(positions) != len(column_names):
                return compact_json_response({
                    "success": False,
                    "message": f"Number of positions ({len(positions)}) must match number of columns ({len(column_names)})."
                })
            
            # Validate position values are non-negative
            if positions:
                invalid_positions = [pos for pos in positions if pos < 0]
                if invalid_positions:
                    return compact_json_response({
                        "success": False,
                        "message": f"Invalid positions found: {invalid_positions}. Positions must be 0-based non-negative integers."
                    })
            
            # Validate dropdown parameters
            if dropdown_columns and dropdown_values:
                if len(dropdown_columns) != len(dropdown_values):
                    return compact_json_response({
                        "success": False,
                        "message": f"Number of dropdown columns ({len(dropdown_columns)}) must match number of dropdown values ({len(dropdown_values)})."
                    })
            
            # Validate column names and types
            validated_column_names = []
            validated_column_types = []
            invalid_columns = []
            
            for i, (col_name, col_type) in enumerate(zip(column_names, column_types)):
                # Validate column name
                col_name_validation = validate_column_name(col_name)
                if not col_name_validation["valid"]:
                    invalid_columns.append({"index": i, "name": col_name, "error": col_name_validation["error"]})
                    continue
                
                # Validate column type
                col_type_validation = validate_column_type(col_type)
                if not col_type_validation["valid"]:
                    invalid_columns.append({"index": i, "name": col_name, "error": col_type_validation["error"]})
                    continue
                
                validated_column_names.append(col_name_validation["cleaned_name"])
                validated_column_types.append(col_type_validation["cleaned_type"])
            
            if invalid_columns:
                error_messages = [f"Column {item['index']} '{item['name']}': {item['error']}" for item in invalid_columns]
                return compact_json_response({
                    "success": False,
                    "message": f"Invalid columns: {'; '.join(error_messages)}",
                    "invalid_columns": invalid_columns
                })
            
            if not validated_column_names:
                return compact_json_response({
                    "success": False,
                    "message": "No valid columns provided after validation."
                })
            
            # Check for duplicate column names
            seen_names = set()
            duplicate_columns = []
            for col_name in validated_column_names:
                if col_name in seen_names:
                    duplicate_columns.append(col_name)
                else:
                    seen_names.add(col_name)
            
            if duplicate_columns:
                return compact_json_response({
                    "success": False,
                    "message": f"Duplicate column names found: {', '.join(duplicate_columns)}"
                })
            
            # Get spreadsheet ID
            spreadsheet_id = get_spreadsheet_id_by_name(drive_service, spreadsheet_name)
            if not spreadsheet_id:
                return compact_json_response({
                    "success": False,
                    "message": f"Spreadsheet '{spreadsheet_name}' not found."
                })
            
            # Get sheet ID
            sheet_ids = get_sheet_ids_by_names(sheets_service, spreadsheet_id, [sheet_name])
            sheet_id = sheet_ids.get(sheet_name)
            if sheet_id is None:
                return compact_json_response({
                    "success": False,
                    "message": f"Sheet '{sheet_name}' not found in spreadsheet '{spreadsheet_name}'."
                })
            
            # Get table ID
            table_ids = get_table_ids_by_names(sheets_service, spreadsheet_id, sheet_name, [table_name])
            table_id = table_ids.get(table_name)
            if not table_id:
                return compact_json_response({
                    "success": False,
                    "message": f"Table '{table_name}' not found in sheet '{sheet_name}'."
                })
            
            # Get current table information
            try:
                table_info = get_table_info(sheets_service, spreadsheet_id, table_id)
            except Exception as e:
                return compact_json_response({
                    "success": False,
                    "message": f"Could not retrieve table information: {str(e)}"
                })
            
            # Check for conflicts with existing column names
            existing_column_names = [col.get("name", "") for col in table_info.get("columns", [])]
            conflicting_columns = []
            for col_name in validated_column_names:
                if col_name in existing_column_names:
                    conflicting_columns.append(col_name)
            
            if conflicting_columns:
                return compact_json_response({
                    "success": False,
                    "message": f"Column names already exist in table: {', '.join(conflicting_columns)}"
                })
            
            # Create a mapping of column names to their dropdown options
            dropdown_mapping = {}
            if dropdown_columns and dropdown_values:
                # Create mapping by matching column names
                for col_name, options_str in zip(dropdown_columns, dropdown_values):
                    if col_name not in validated_column_names:
                        return compact_json_response({
                            "success": False,
                            "message": f"Dropdown column '{col_name}' not found in column_names: {validated_column_names}."
                        })
                    options = [opt.strip() for opt in options_str.split(",") if opt.strip()]
                    dropdown_mapping[col_name] = options
            
            # Get current table range
            current_range = table_info.get("range", {})
            current_start_col = current_range.get("startColumnIndex", 0)
            current_end_col = current_range.get("endColumnIndex", 0)
            current_start_row = current_range.get("startRowIndex", 0)
            current_end_row = current_range.get("endRowIndex", 0)
            
            # Get existing column objects for processing
            existing_columns = table_info.get("columns", [])
            
            # Calculate insertion positions
            if positions:
                # Use provided positions (0-based)
                insertion_positions = positions.copy()
                # Validate positions don't exceed current table column count
                # Allow positions up to the current column count (for inserting at the end)
                max_position = len(existing_columns)
                invalid_positions = [pos for pos in insertion_positions if pos > max_position]
                if invalid_positions:
                    return compact_json_response({
                        "success": False,
                        "message": f"Invalid positions found: {invalid_positions}. Positions must be 0-based and not exceed current column count ({max_position})."
                    })
                # Sort positions in descending order (rightmost to leftmost) for proper insertion
                insertion_positions.sort(reverse=True)
            else:
                # Insert at the end
                insertion_positions = [len(existing_columns)] * len(validated_column_names)
            
            # Create requests according to official API documentation
            requests = []
            
            # 1. Insert new columns using InsertRangeRequest (rightmost to leftmost)
            for pos in insertion_positions:
                # Ensure position is within valid range
                if pos < 0:
                    pos = 0
                if pos > len(existing_columns):
                    pos = len(existing_columns)
    
                insert_request = {
                    "insertRange": {
                        "range": {
                            "sheetId": sheet_id,
                            "startRowIndex": 0,
                            "endRowIndex": current_end_row,
                            "startColumnIndex": pos,
                            "endColumnIndex": pos + 1
                        },
                        "shiftDimension": "COLUMNS"
                    }
                }
                requests.append(insert_request)
            
            # 2. Build complete column properties array for UpdateTableRequest
            # Start with existing columns and their updated indices after insertions
            full_column_properties = []
            
            def create_column_property(col_name, col_type, col_index):
                """Helper function to create column property with dropdown validation."""
                column_property = {
                    "columnIndex": col_index,
                    "columnName": col_name,
                    "columnType": map_column_type(col_type)
                }
                
                # Add data validation for dropdown columns
                if col_name in dropdown_mapping:
                    options = dropdown_mapping[col_name]
                    if options:
                        column_property["dataValidationRule"] = {
                            "condition": {
                                "type": "ONE_OF_LIST",
                                "values": [{"userEnteredValue": opt} for opt in options]
                            }
                        }
                
                return column_property
            
            # Create new column properties with their final positions
            new_column_properties = []
            for i, (col_name, col_type) in enumerate(zip(validated_column_names, validated_column_types)):
                if positions:
                    # Use the original position for new columns (positions are already sorted)
                    col_index = positions[i]
                else:
                    # For end insertions, use the position after existing columns
                    col_index = len(existing_columns) + i
                
                new_column_properties.append(create_column_property(col_name, col_type, col_index))
            
            # Build the complete column properties array
            # First, add existing columns with their updated indices
            existing_col_index = 0
            new_col_index = 0
            
            # Sort new columns by their target positions for proper insertion
            if positions:
                # Create pairs of (position, column_property) and sort by position
                new_cols_with_positions = list(zip(positions, new_column_properties))
                new_cols_with_positions.sort(key=lambda x: x[0])
                new_column_properties = [col_prop for _, col_prop in new_cols_with_positions]
                new_positions = [pos for pos, _ in new_cols_with_positions]
            else:
                new_positions = list(range(len(existing_columns), len(existing_columns) + len(validated_column_names)))
            
            # Convert existing columns back to API format and build the complete array
            api_existing_columns = []
            for col_info in existing_columns:
                api_col_prop = {
                    "columnIndex": col_info.get("index", 0),
                    "columnName": col_info.get("name", ""),
                    "columnType": col_info.get("type", "TEXT")
                }
                # Preserve dataValidationRule if it exists
                if "dataValidationRule" in col_info:
                    api_col_prop["dataValidationRule"] = col_info["dataValidationRule"]
                api_existing_columns.append(api_col_prop)
            
            # Build the complete array by inserting new columns at their positions
            full_column_properties = api_existing_columns.copy()
            
            # Insert new columns at their target positions (rightmost to leftmost to avoid index shifting)
            for i in range(len(new_column_properties) - 1, -1, -1):
                insert_pos = new_positions[i]
                col_prop = new_column_properties[i]
                # Update the column index to match the final position
                col_prop["columnIndex"] = insert_pos
                full_column_properties.insert(insert_pos, col_prop)
            
            # Update all existing column indices to reflect their new positions after insertions
            for i, col_prop in enumerate(full_column_properties):
                col_prop["columnIndex"] = i
            
            # Update table with complete column properties
            new_end_col = current_end_col + len(validated_column_names)
            update_table_request = {
                "updateTable": {
                    "table": {
                        "tableId": table_id,
                        "range": {
                            "sheetId": sheet_id,
                            "startRowIndex": current_start_row,
                            "endRowIndex": current_end_row,
                            "startColumnIndex": current_start_col,
                            "endColumnIndex": new_end_col
                        },
                        "columnProperties": full_column_properties
                    },
                    "fields": "range,columnProperties"
                }
            }
            requests.append(update_table_request)
            
            # Execute the requests
            response = sheets_service.spreadsheets().batchUpdate(
                spreadsheetId=spreadsheet_id,
                body={"requests": requests}
            ).execute()
            
            # Extract response information
            replies = response.get("replies", [])
            if replies:
                response_data = {
                    "success": True,
                    "spreadsheet_name": spreadsheet_name,
                    "sheet_name": sheet_name,
                    "table_name": table_name,
                    "added_columns": validated_column_names,
                    "column_types": validated_column_types,
                    "columns_added": len(validated_column_names),
                    "new_column_count": len(existing_columns) + len(validated_column_names),
                    "insertion_positions": positions if positions else ["end"] * len(validated_column_names),
                    "message": f"Successfully added {len(validated_column_names)} column(s) to table '{table_name}' in '{sheet_name}'"
                }
                
                return compact_json_response(response_data)
            else:
                return compact_json_response({
                    "success": False,
                    "message": "Failed to add columns - no response data from API"
                })
            
        except HttpError as error:
            return compact_json_response({
                "success": False,
                "message": f"Google Sheets API error: {str(error)}"
            })
        except Exception as e:
            return compact_json_response({
                "success": False,
                "message": f"Error adding table columns: {str(e)}"
            }) 
  • MCP tool definition with Pydantic Field schemas for all parameters: spreadsheet_name, sheet_name, table_name, column_names, column_types, positions, dropdown_columns, dropdown_values.
    @mcp.tool()
    def add_table_column_tool(
        spreadsheet_name: str = Field(..., description="The name of the Google Spreadsheet"),
        sheet_name: str = Field(..., description="The name of the sheet containing the table"),
        table_name: str = Field(..., description="Name of the table to add columns to"),
        column_names: List[str] = Field(..., description="List of column names (e.g., ['Status', 'Priority', 'Notes'])"),
        column_types: List[str] = Field(..., description="List of column types: DOUBLE, CURRENCY, PERCENT, DATE, TIME, DATE_TIME, TEXT, BOOLEAN, DROPDOWN"),
        positions: List[int] = Field(default=[], description="List of positions to insert columns (0-based index, empty list for end)"),
        dropdown_columns: List[str] = Field(default=[], description="List of column names that should have dropdown validation"),
        dropdown_values: List[str] = Field(default=[], description="Comma-separated dropdown options for each dropdown column")
    ) -> str:
  • Tool registered via @mcp.tool() decorator on 'add_table_column_tool' function, imported from handler module at line 36.
    @mcp.tool()
    def add_table_column_tool(
        spreadsheet_name: str = Field(..., description="The name of the Google Spreadsheet"),
        sheet_name: str = Field(..., description="The name of the sheet containing the table"),
        table_name: str = Field(..., description="Name of the table to add columns to"),
        column_names: List[str] = Field(..., description="List of column names (e.g., ['Status', 'Priority', 'Notes'])"),
        column_types: List[str] = Field(..., description="List of column types: DOUBLE, CURRENCY, PERCENT, DATE, TIME, DATE_TIME, TEXT, BOOLEAN, DROPDOWN"),
        positions: List[int] = Field(default=[], description="List of positions to insert columns (0-based index, empty list for end)"),
        dropdown_columns: List[str] = Field(default=[], description="List of column names that should have dropdown validation"),
        dropdown_values: List[str] = Field(default=[], description="Comma-separated dropdown options for each dropdown column")
    ) -> str:
        """
        Add new columns to an existing table in Google Sheets.
        
        This tool extends an existing table with additional columns.
        New columns can have different data types and validation rules.
        Supports adding multiple columns at once with proper positioning.
        
        Args:
            spreadsheet_name: Name of the spreadsheet
            sheet_name: Name of the sheet containing the table
            table_name: Name of the table to add columns to
            column_names: List of column names to add
            column_types: List of column types corresponding to column_names
            positions: List of positions to insert columns (0-based index, empty list for end)
            dropdown_columns: List of column names that should have dropdown validation
            dropdown_values: List of comma-separated dropdown options for each dropdown column
        
        Returns:
            JSON string with success status and column addition details
        """
        sheets_service, drive_service = _get_google_services()
        return add_table_column_handler(drive_service, sheets_service, spreadsheet_name, sheet_name, table_name, column_names, column_types, positions, dropdown_columns, dropdown_values)
  • Validates column names (empty check, length limit 100, invalid characters) and column types (validates against allowed types list).
    def validate_column_name(name: str) -> Dict[str, Any]:
        """
        Validate a column name according to Google Sheets rules.
        
        Args:
            name: Column name to validate
        
        Returns:
            Dictionary with validation result
        """
        if not name or name.strip() == "":
            return {"valid": False, "error": "Column name cannot be empty"}
        
        # Remove leading/trailing whitespace
        name = name.strip()
        
        # Check length (Google Sheets limit is 100 characters)
        if len(name) > 100:
            return {"valid": False, "error": f"Column name '{name}' is too long (max 100 characters)"}
        
        # Check for invalid characters
        invalid_chars = ['[', ']', '*', '?', '/', '\\']
        for char in invalid_chars:
            if char in name:
                return {"valid": False, "error": f"Column name '{name}' contains invalid character '{char}'"}
        
        return {"valid": True, "cleaned_name": name}
  • Maps user-friendly column type strings to Google Sheets API column types with backward-compatible legacy support.
    def map_column_type(col_type: str) -> str:
        """
        Map user-friendly column types to Google Sheets API types.
        
        Args:
            col_type: User-friendly column type
        
        Returns:
            str: Google Sheets API column type
        """
        api_type_mapping = {
            "COLUMN_TYPE_UNSPECIFIED": "COLUMN_TYPE_UNSPECIFIED",
            "DOUBLE": "DOUBLE",
            "CURRENCY": "CURRENCY",
            "PERCENT": "PERCENT",
            "DATE": "DATE",
            "TIME": "TIME",
            "DATE_TIME": "DATE_TIME",
            "TEXT": "TEXT",
            "BOOLEAN": "BOOLEAN",
            "DROPDOWN": "DROPDOWN",
            "NONE": "TEXT",  # Legacy support
            # Legacy mappings for backward compatibility
            "NUMBER": "DOUBLE",
            "CHECKBOX": "BOOLEAN"
        }
        
        # Handle case-insensitive input
        col_type_upper = col_type.upper() if col_type else "TEXT"
        return api_type_mapping.get(col_type_upper, "TEXT")
Behavior3/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. It mentions support for different data types and validation rules, but does not disclose side effects, permissions, or error scenarios. It adds some context beyond the schema but is not thorough.

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 concise with a clear title, brief paragraph, and structured Args/Returns. No redundant information, and every sentence serves a purpose.

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

Completeness4/5

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

Given the complexity (8 parameters) and the presence of an output schema, the description adequately covers the tool's functionality. It explains multi-column addition and positioning, though it lacks explicit prerequisites or error handling details.

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 100%, so baseline is 3. The description summarizes parameters but does not add significant new meaning beyond what the schema already provides for individual parameters.

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 starts with 'Add new columns to an existing table in Google Sheets', clearly stating the verb and resource. It distinguishes itself from siblings like delete_table_column_tool and update_table_column_name_tool by focusing on addition.

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 implies use for adding columns but does not explicitly state when to use this tool versus alternatives like update_table_column_type_tool. No exclusionary language or alternative guidance is provided.

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/henilcalagiya/google-sheets-mcp'

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