Skip to main content
Glama
GongRzhe

Office Word MCP Server

find_text_in_document

Locate specific text in Microsoft Word documents by searching with customizable options like case sensitivity and whole word matching.

Instructions

Find occurrences of specific text in a Word document.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
filenameYes
text_to_findYes
match_caseNo
whole_wordNo

Implementation Reference

  • Registers the 'find_text_in_document' tool using FastMCP's @mcp.tool() decorator. This wrapper function delegates execution to the implementation in extended_document_tools.
    @mcp.tool()
    def find_text_in_document(filename: str, text_to_find: str, match_case: bool = True,
                             whole_word: bool = False):
        """Find occurrences of specific text in a Word document."""
        return extended_document_tools.find_text_in_document(
            filename, text_to_find, match_case, whole_word
        )
  • Main handler function that performs input validation, ensures DOCX extension, calls the core find_text utility, formats results as JSON, and handles errors.
    async def find_text_in_document(filename: str, text_to_find: str, match_case: bool = True, whole_word: bool = False) -> str:
        """Find occurrences of specific text in a Word document.
        
        Args:
            filename: Path to the Word document
            text_to_find: Text to search for in the document
            match_case: Whether to match case (True) or ignore case (False)
            whole_word: Whether to match whole words only (True) or substrings (False)
        """
        filename = ensure_docx_extension(filename)
        
        if not os.path.exists(filename):
            return f"Document {filename} does not exist"
        
        if not text_to_find:
            return "Search text cannot be empty"
        
        try:
            
            result = find_text(filename, text_to_find, match_case, whole_word)
            return json.dumps(result, indent=2)
        except Exception as e:
            return f"Failed to search for text: {str(e)}"
  • Core helper function implementing the text search logic. Searches paragraphs and table cells, supports case-sensitive/insensitive and whole-word/substring matching, collects detailed occurrence information.
    def find_text(doc_path: str, text_to_find: str, match_case: bool = True, whole_word: bool = False) -> Dict[str, Any]:
        """
        Find all occurrences of specific text in a Word document.
        
        Args:
            doc_path: Path to the Word document
            text_to_find: Text to search for
            match_case: Whether to perform case-sensitive search
            whole_word: Whether to match whole words only
        
        Returns:
            Dictionary with search results
        """
        import os
        if not os.path.exists(doc_path):
            return {"error": f"Document {doc_path} does not exist"}
        
        if not text_to_find:
            return {"error": "Search text cannot be empty"}
        
        try:
            doc = Document(doc_path)
            results = {
                "query": text_to_find,
                "match_case": match_case,
                "whole_word": whole_word,
                "occurrences": [],
                "total_count": 0
            }
            
            # Search in paragraphs
            for i, para in enumerate(doc.paragraphs):
                # Prepare text for comparison
                para_text = para.text
                search_text = text_to_find
                
                if not match_case:
                    para_text = para_text.lower()
                    search_text = search_text.lower()
                
                # Find all occurrences (simple implementation)
                start_pos = 0
                while True:
                    if whole_word:
                        # For whole word search, we need to check word boundaries
                        words = para_text.split()
                        found = False
                        for word_idx, word in enumerate(words):
                            if (word == search_text or 
                                (not match_case and word.lower() == search_text.lower())):
                                results["occurrences"].append({
                                    "paragraph_index": i,
                                    "position": word_idx,
                                    "context": para.text[:100] + ("..." if len(para.text) > 100 else "")
                                })
                                results["total_count"] += 1
                                found = True
                        
                        # Break after checking all words
                        break
                    else:
                        # For substring search
                        pos = para_text.find(search_text, start_pos)
                        if pos == -1:
                            break
                        
                        results["occurrences"].append({
                            "paragraph_index": i,
                            "position": pos,
                            "context": para.text[:100] + ("..." if len(para.text) > 100 else "")
                        })
                        results["total_count"] += 1
                        start_pos = pos + len(search_text)
            
            # Search in tables
            for table_idx, table in enumerate(doc.tables):
                for row_idx, row in enumerate(table.rows):
                    for col_idx, cell in enumerate(row.cells):
                        for para_idx, para in enumerate(cell.paragraphs):
                            # Prepare text for comparison
                            para_text = para.text
                            search_text = text_to_find
                            
                            if not match_case:
                                para_text = para_text.lower()
                                search_text = search_text.lower()
                            
                            # Find all occurrences (simple implementation)
                            start_pos = 0
                            while True:
                                if whole_word:
                                    # For whole word search, check word boundaries
                                    words = para_text.split()
                                    found = False
                                    for word_idx, word in enumerate(words):
                                        if (word == search_text or 
                                            (not match_case and word.lower() == search_text.lower())):
                                            results["occurrences"].append({
                                                "location": f"Table {table_idx}, Row {row_idx}, Column {col_idx}",
                                                "position": word_idx,
                                                "context": para.text[:100] + ("..." if len(para.text) > 100 else "")
                                            })
                                            results["total_count"] += 1
                                            found = True
                                    
                                    # Break after checking all words
                                    break
                                else:
                                    # For substring search
                                    pos = para_text.find(search_text, start_pos)
                                    if pos == -1:
                                        break
                                    
                                    results["occurrences"].append({
                                        "location": f"Table {table_idx}, Row {row_idx}, Column {col_idx}",
                                        "position": pos,
                                        "context": para.text[:100] + ("..." if len(para.text) > 100 else "")
                                    })
                                    results["total_count"] += 1
                                    start_pos = pos + len(search_text)
            
            return results
        except Exception as e:
            return {"error": f"Failed to search for text: {str(e)}"}
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 states the tool finds text but doesn't describe what it returns (e.g., positions, counts, or matches), whether it's read-only or modifies the document, error handling for missing files, or performance implications. This leaves significant gaps for a tool with no annotation coverage.

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 directly states the tool's purpose without unnecessary words. It's front-loaded and wastes no space, making it easy to parse quickly.

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?

For a tool with 4 parameters, 0% schema description coverage, no annotations, and no output schema, the description is inadequate. It doesn't explain return values, error conditions, or parameter details, leaving the agent with insufficient information to use the tool effectively beyond a basic understanding of its purpose.

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?

The input schema has 0% description coverage, and the tool description doesn't explain any parameters beyond implying 'text_to_find' and 'filename'. It doesn't clarify the purpose of 'match_case' or 'whole_word', their default values, or how they affect search behavior, failing to compensate for the schema's lack of documentation.

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 tool's purpose with a specific verb ('Find') and resource ('occurrences of specific text in a Word document'). It distinguishes itself from siblings like 'search_and_replace' by focusing on finding rather than replacing, though it doesn't explicitly mention this distinction.

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?

The description provides no guidance on when to use this tool versus alternatives like 'search_and_replace' or 'get_document_text'. It doesn't mention prerequisites, such as whether the document must exist or be accessible, or any performance considerations for large documents.

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/GongRzhe/Office-Word-MCP-Server'

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