Skip to main content
Glama
marc-hanheide

PDF Redaction MCP Server

redact_text

Search for and mark sensitive text in PDF documents for redaction. This tool identifies specified text strings and overlays them with redaction annotations before final saving.

Instructions

Redact specific texts in a loaded PDF.

This tool searches for all instances of the specified texts in the PDF and adds redaction annotations over them. The redactions are not yet applied to the document - use save_redacted_pdf to apply and save. Only texts that haven't been previously redacted will be processed.

Args: pdf_path: Path to the PDF file (must be already loaded) texts_to_redact: List of text strings to search for and redact fill_color: RGB color tuple (0-1 range) for the redaction box. Default is black. ctx: MCP context for logging

Returns: Summary of redaction operations

Raises: ToolError: If the PDF is not loaded or redaction fails

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pdf_pathYesPath to the loaded PDF file
texts_to_redactYesList of text strings to search for and redact
fill_colorNoRGB color for redaction (values 0-1). Default is black (0,0,0)

Implementation Reference

  • The primary handler function for the 'redact_text' MCP tool, decorated with @mcp.tool for registration. It performs text search and redaction annotation addition on loaded PDF documents using PyMuPDF (fitz). Includes input schema via Annotated types, tracks previous redactions to avoid duplicates, and returns a detailed summary.
    @mcp.tool async def redact_text( pdf_path: Annotated[str, Field(description="Path to the loaded PDF file")], texts_to_redact: Annotated[list[str], Field(description="List of text strings to search for and redact")], fill_color: Annotated[tuple[float, float, float], Field( description="RGB color for redaction (values 0-1). Default is black (0,0,0)" )] = (0, 0, 0), ctx: Context = None ) -> str: """Redact specific texts in a loaded PDF. This tool searches for all instances of the specified texts in the PDF and adds redaction annotations over them. The redactions are not yet applied to the document - use save_redacted_pdf to apply and save. Only texts that haven't been previously redacted will be processed. Args: pdf_path: Path to the PDF file (must be already loaded) texts_to_redact: List of text strings to search for and redact fill_color: RGB color tuple (0-1 range) for the redaction box. Default is black. ctx: MCP context for logging Returns: Summary of redaction operations Raises: ToolError: If the PDF is not loaded or redaction fails """ try: path = Path(pdf_path).resolve() path_str = str(path) await ctx.info(f"Redacting texts in: {path}") # Check if PDF is loaded if path_str not in _loaded_pdfs: raise ToolError( f"PDF not loaded. Please load it first using load_pdf: {path}" ) doc = _loaded_pdfs[path_str] # Initialize redaction tracking for this PDF if not exists if path_str not in _applied_redactions: _applied_redactions[path_str] = [] # Validate color values if not all(0 <= c <= 1 for c in fill_color): raise ToolError("RGB color values must be between 0 and 1") # Filter out already redacted texts already_redacted = [] new_texts = [] for text in texts_to_redact: if text in _applied_redactions[path_str]: already_redacted.append(text) else: new_texts.append(text) if not new_texts: skipped_msg = f"All {len(texts_to_redact)} text(s) have already been redacted. No new redactions added." await ctx.info(skipped_msg) return skipped_msg total_redactions = 0 text_summaries = [] # Process each text to redact for text_to_redact in new_texts: text_redaction_count = 0 page_redactions = [] # Search and redact on each page for page_num, page in enumerate(doc, start=1): # Search for the text text_instances = page.search_for(text_to_redact) if text_instances: # Add redaction annotations for each instance for inst in text_instances: page.add_redact_annot(inst, fill=fill_color) text_redaction_count += 1 total_redactions += 1 page_redactions.append(f"Page {page_num}: {len(text_instances)} instance(s)") if text_redaction_count > 0: # Track this redaction _applied_redactions[path_str].append(text_to_redact) text_summaries.append( f" '{text_to_redact}': {text_redaction_count} instance(s) across {len(page_redactions)} page(s)" ) else: text_summaries.append(f" '{text_to_redact}': No instances found") if total_redactions == 0: msg = f"No instances of the provided texts found in the PDF" await ctx.warning(msg) return msg summary_parts = [f"Added {total_redactions} redaction(s) for {len(new_texts)} text(s):"] summary_parts.extend(text_summaries) if already_redacted: summary_parts.append(f"\nSkipped {len(already_redacted)} text(s) already redacted: {', '.join(repr(t) for t in already_redacted)}") summary_parts.append( "\nNote: Redactions are marked but not yet applied. " "Use save_redacted_pdf to apply and save the changes." ) summary = "\n".join(summary_parts) await ctx.info(f"Added {total_redactions} redaction annotations for {len(new_texts)} new text(s)") return summary except ToolError: raise except Exception as e: await ctx.error(f"Failed to redact text: {str(e)}") raise ToolError(f"Failed to redact text: {str(e)}")
  • Input schema definition for the redact_text tool using Pydantic's Annotated and Field for parameter descriptions, types, and defaults.
    pdf_path: Annotated[str, Field(description="Path to the loaded PDF file")], texts_to_redact: Annotated[list[str], Field(description="List of text strings to search for and redact")], fill_color: Annotated[tuple[float, float, float], Field( description="RGB color for redaction (values 0-1). Default is black (0,0,0)" )] = (0, 0, 0), ctx: Context = None
  • FastMCP decorator that registers the redact_text function as an MCP tool.
    @mcp.tool

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/marc-hanheide/redact_mcp'

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