Skip to main content
Glama
ZatesloFL

Google Workspace MCP Server

by ZatesloFL

get_doc_content

Extracts text content from Google Docs or Drive files using the document_id. Retrieves native Google Docs content via Docs API and extracts text from Office files (.docx) stored in Drive via Drive API.

Instructions

Retrieves content of a Google Doc or a Drive file (like .docx) identified by document_id.

  • Native Google Docs: Fetches content via Docs API.

  • Office files (.docx, etc.) stored in Drive: Downloads via Drive API and extracts text.

Returns: str: The document content with metadata header.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
docs_serviceYes
document_idYes
drive_serviceYes
user_google_emailYes

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The complete handler function for 'get_doc_content' tool. Includes registration via @server.tool(), error handling decorator naming the tool, service requirements, and full logic to fetch Google Doc content (parsing elements, tables, tabs) or download/extract text from other Drive files.
    @server.tool()
    @handle_http_errors("get_doc_content", is_read_only=True, service_type="docs")
    @require_multiple_services([
        {"service_type": "drive", "scopes": "drive_read", "param_name": "drive_service"},
        {"service_type": "docs", "scopes": "docs_read", "param_name": "docs_service"}
    ])
    async def get_doc_content(
        drive_service,
        docs_service,
        user_google_email: str,
        document_id: str,
    ) -> str:
        """
        Retrieves content of a Google Doc or a Drive file (like .docx) identified by document_id.
        - Native Google Docs: Fetches content via Docs API.
        - Office files (.docx, etc.) stored in Drive: Downloads via Drive API and extracts text.
    
        Returns:
            str: The document content with metadata header.
        """
        logger.info(f"[get_doc_content] Invoked. Document/File ID: '{document_id}' for user '{user_google_email}'")
    
        # Step 2: Get file metadata from Drive
        file_metadata = await asyncio.to_thread(
            drive_service.files().get(
                fileId=document_id, fields="id, name, mimeType, webViewLink"
            ).execute
        )
        mime_type = file_metadata.get("mimeType", "")
        file_name = file_metadata.get("name", "Unknown File")
        web_view_link = file_metadata.get("webViewLink", "#")
    
        logger.info(f"[get_doc_content] File '{file_name}' (ID: {document_id}) has mimeType: '{mime_type}'")
    
        body_text = "" # Initialize body_text
    
        # Step 3: Process based on mimeType
        if mime_type == "application/vnd.google-apps.document":
            logger.info("[get_doc_content] Processing as native Google Doc.")
            doc_data = await asyncio.to_thread(
                docs_service.documents().get(
                    documentId=document_id,
                    includeTabsContent=True
                ).execute
            )
            # Tab header format constant
            TAB_HEADER_FORMAT = "\n--- TAB: {tab_name} ---\n"
    
            def extract_text_from_elements(elements, tab_name=None, depth=0):
                """Extract text from document elements (paragraphs, tables, etc.)"""
                # Prevent infinite recursion by limiting depth
                if depth > 5:
                    return ""
                text_lines = []
                if tab_name:
                    text_lines.append(TAB_HEADER_FORMAT.format(tab_name=tab_name))
    
                for element in elements:
                    if 'paragraph' in element:
                        paragraph = element.get('paragraph', {})
                        para_elements = paragraph.get('elements', [])
                        current_line_text = ""
                        for pe in para_elements:
                            text_run = pe.get('textRun', {})
                            if text_run and 'content' in text_run:
                                current_line_text += text_run['content']
                        if current_line_text.strip():
                            text_lines.append(current_line_text)
                    elif 'table' in element:
                        # Handle table content
                        table = element.get('table', {})
                        table_rows = table.get('tableRows', [])
                        for row in table_rows:
                            row_cells = row.get('tableCells', [])
                            for cell in row_cells:
                                cell_content = cell.get('content', [])
                                cell_text = extract_text_from_elements(cell_content, depth=depth + 1)
                                if cell_text.strip():
                                    text_lines.append(cell_text)
                return "".join(text_lines)
    
            def process_tab_hierarchy(tab, level=0):
                """Process a tab and its nested child tabs recursively"""
                tab_text = ""
    
                if 'documentTab' in tab:
                    tab_title = tab.get('documentTab', {}).get('title', 'Untitled Tab')
                    # Add indentation for nested tabs to show hierarchy
                    if level > 0:
                        tab_title = "    " * level + tab_title
                    tab_body = tab.get('documentTab', {}).get('body', {}).get('content', [])
                    tab_text += extract_text_from_elements(tab_body, tab_title)
    
                # Process child tabs (nested tabs)
                child_tabs = tab.get('childTabs', [])
                for child_tab in child_tabs:
                    tab_text += process_tab_hierarchy(child_tab, level + 1)
    
                return tab_text
    
            processed_text_lines = []
    
            # Process main document body
            body_elements = doc_data.get('body', {}).get('content', [])
            main_content = extract_text_from_elements(body_elements)
            if main_content.strip():
                processed_text_lines.append(main_content)
    
            # Process all tabs
            tabs = doc_data.get('tabs', [])
            for tab in tabs:
                tab_content = process_tab_hierarchy(tab)
                if tab_content.strip():
                    processed_text_lines.append(tab_content)
    
            body_text = "".join(processed_text_lines)
        else:
            logger.info(f"[get_doc_content] Processing as Drive file (e.g., .docx, other). MimeType: {mime_type}")
    
            export_mime_type_map = {
                    # Example: "application/vnd.google-apps.spreadsheet"z: "text/csv",
                    # Native GSuite types that are not Docs would go here if this function
                    # was intended to export them. For .docx, direct download is used.
            }
            effective_export_mime = export_mime_type_map.get(mime_type)
    
            request_obj = (
                drive_service.files().export_media(fileId=document_id, mimeType=effective_export_mime)
                if effective_export_mime
                else drive_service.files().get_media(fileId=document_id)
            )
    
            fh = io.BytesIO()
            downloader = MediaIoBaseDownload(fh, request_obj)
            loop = asyncio.get_event_loop()
            done = False
            while not done:
                status, done = await loop.run_in_executor(None, downloader.next_chunk)
    
            file_content_bytes = fh.getvalue()
    
            office_text = extract_office_xml_text(file_content_bytes, mime_type)
            if office_text:
                body_text = office_text
            else:
                try:
                    body_text = file_content_bytes.decode("utf-8")
                except UnicodeDecodeError:
                    body_text = (
                        f"[Binary or unsupported text encoding for mimeType '{mime_type}' - "
                        f"{len(file_content_bytes)} bytes]"
                    )
    
        header = (
            f'File: "{file_name}" (ID: {document_id}, Type: {mime_type})\n'
            f'Link: {web_view_link}\n\n--- CONTENT ---\n'
        )
        return header + body_text
  • The @server.tool() decorator registers the get_doc_content function as an MCP tool with the server.
    @server.tool()
Behavior3/5

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

With no annotations provided, the description carries the full burden. It discloses behavioral traits such as using different APIs (Docs API for native Google Docs, Drive API for Office files) and returning content with a metadata header, which adds context beyond basic retrieval. However, it misses details like rate limits, error handling, or permission requirements, which are important for a tool with multiple API integrations.

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 and front-loaded, starting with the core purpose. It uses bullet points for clarity on file handling and a clear 'Returns' section. However, the bullet points could be more integrated, and some redundancy exists (e.g., repeating 'Drive' in contexts), but overall it's efficient with minimal waste.

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 of handling multiple file types and APIs, the description is fairly complete. It explains the retrieval process and return format, and since an output schema exists, it doesn't need to detail return values. However, the lack of parameter explanations and some behavioral details (e.g., auth or errors) prevents a perfect score, but it's adequate for basic use.

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

Parameters2/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 mentions 'document_id' but does not explain the other three parameters ('docs_service', 'drive_service', 'user_google_email'), leaving their purposes unclear. This gap is significant given the 4 required parameters, reducing the tool's usability without additional context.

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 action ('Retrieves content'), the resource ('Google Doc or a Drive file'), and the identifier ('document_id'). It distinguishes from sibling tools like 'get_drive_file_content' by specifying it handles both native Google Docs and Office files, and from 'inspect_doc_structure' by focusing on content extraction rather than structural analysis.

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 usage by specifying the types of files it handles (Google Docs and Office files like .docx), which helps differentiate it from tools like 'get_drive_file_content' that might handle other file types. However, it lacks explicit guidance on when to use alternatives (e.g., 'get_drive_file_content' for non-text files) or prerequisites like authentication needs, leaving some ambiguity.

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