Major architectural improvements and bug fixes in the v2.0.x series:
## v2.0.5 - Page Range Parsing (Current Release)
- Fix page range parsing bug affecting 6 mixins (e.g., "93-95" or "11-30")
- Create shared parse_pages_parameter() utility function
- Support mixed formats: "1,3-5,7,10-15"
- Update: pdf_utilities, content_analysis, image_processing, misc_tools, table_extraction, text_extraction
## v2.0.4 - Chunk Hint Fix
- Fix next_chunk_hint to show correct page ranges
- Dynamic calculation based on actual pages being extracted
- Example: "30-50" now correctly shows "40-49" for next chunk
## v2.0.3 - Initial Range Support
- Add page range support to text extraction ("11-30")
- Fix _parse_pages_parameter to handle ranges with Python's range()
- Convert 1-based user input to 0-based internal indexing
## v2.0.2 - Lazy Import Fix
- Fix ModuleNotFoundError for reportlab on startup
- Implement lazy imports for optional dependencies
- Graceful degradation with helpful error messages
## v2.0.1 - Dependency Restructuring
- Move reportlab to optional [forms] extra
- Document installation: uvx --with mcp-pdf[forms] mcp-pdf
## v2.0.0 - Official FastMCP Pattern Migration
- Migrate to official fastmcp.contrib.mcp_mixin pattern
- Create 12 specialized mixins with 42 tools total
- Architecture: mixins_official/ using MCPMixin base class
- Backwards compatibility: server_legacy.py preserved
Technical Improvements:
- Centralized utility functions (DRY principle)
- Consistent behavior across all PDF tools
- Better error messages with actionable instructions
- Library-specific adapters for table extraction
Files Changed:
- New: src/mcp_pdf/mixins_official/utils.py (shared utilities)
- Updated: 6 mixins with improved page parsing
- Version: pyproject.toml, server.py → 2.0.5
PyPI: https://pypi.org/project/mcp-pdf/2.0.5/
10 KiB
10 KiB
MCPMixin Architecture Guide
Overview
This document explains how to refactor large FastMCP servers using the MCPMixin pattern for better organization, maintainability, and modularity.
Current vs MCPMixin Architecture
Current Monolithic Structure
server.py (6500+ lines)
├── 24+ tools with @mcp.tool() decorators
├── Security utilities scattered throughout
├── PDF processing helpers mixed in
└── Single main() function
Problems:
- Single file responsibility overload
- Difficult to test individual components
- Hard to add new tool categories
- Security logic scattered throughout
- No clear separation of concerns
MCPMixin Modular Structure
mcp_pdf/
├── server.py (main entry point, ~100 lines)
├── security.py (centralized security utilities)
├── mixins/
│ ├── __init__.py
│ ├── base.py (MCPMixin base class)
│ ├── text_extraction.py (extract_text, ocr_pdf, is_scanned_pdf)
│ ├── table_extraction.py (extract_tables with fallbacks)
│ ├── document_analysis.py (metadata, structure, health)
│ ├── image_processing.py (extract_images, pdf_to_markdown)
│ ├── form_management.py (create/fill/extract forms)
│ ├── document_assembly.py (merge, split, reorder)
│ └── annotations.py (sticky notes, highlights, multimedia)
└── tests/
├── test_mixin_architecture.py
├── test_text_extraction.py
├── test_table_extraction.py
└── ... (individual mixin tests)
Key Benefits of MCPMixin Architecture
1. Modular Design
- Each mixin handles one functional domain
- Clear separation of concerns
- Easy to understand and maintain individual components
2. Auto-Registration
- Tools automatically discovered and registered
- Consistent naming and description patterns
- No manual tool registration needed
3. Testability
- Each mixin can be tested independently
- Mock dependencies easily
- Focused unit tests per domain
4. Scalability
- Add new tool categories by creating new mixins
- Compose servers with different mixin combinations
- Progressive disclosure of capabilities
5. Security Centralization
- Shared security utilities in single module
- Consistent validation across all tools
- Centralized error handling and sanitization
6. Configuration Management
- Centralized configuration in server class
- Mixin-specific configuration passed during initialization
- Environment variable management in one place
MCPMixin Base Class Features
Auto-Registration
class TextExtractionMixin(MCPMixin):
@mcp_tool(name="extract_text", description="Extract text from PDF")
async def extract_text(self, pdf_path: str) -> Dict[str, Any]:
# Implementation automatically registered as MCP tool
pass
Permission System
def get_required_permissions(self) -> List[str]:
return ["read_files", "ocr_processing"]
Component Discovery
def get_registered_components(self) -> Dict[str, Any]:
return {
"mixin": "TextExtraction",
"tools": ["extract_text", "ocr_pdf", "is_scanned_pdf"],
"resources": [],
"prompts": [],
"permissions_required": ["read_files", "ocr_processing"]
}
Implementation Examples
Text Extraction Mixin
from .base import MCPMixin, mcp_tool
from ..security import validate_pdf_path, sanitize_error_message
class TextExtractionMixin(MCPMixin):
def get_mixin_name(self) -> str:
return "TextExtraction"
def get_required_permissions(self) -> List[str]:
return ["read_files", "ocr_processing"]
@mcp_tool(name="extract_text", description="Extract text with intelligent method selection")
async def extract_text(self, pdf_path: str, method: str = "auto") -> Dict[str, Any]:
try:
validated_path = await validate_pdf_path(pdf_path)
# Implementation here...
return {"success": True, "text": extracted_text}
except Exception as e:
return {"success": False, "error": sanitize_error_message(str(e))}
Server Composition
class PDFToolsServer:
def __init__(self):
self.mcp = FastMCP("pdf-tools")
self.mixins = []
# Initialize mixins
mixin_classes = [
TextExtractionMixin,
TableExtractionMixin,
DocumentAnalysisMixin,
# ... other mixins
]
for mixin_class in mixin_classes:
mixin = mixin_class(self.mcp, **self.config)
self.mixins.append(mixin)
Migration Strategy
Phase 1: Setup Infrastructure
- Create
mixins/directory structure - Implement
MCPMixinbase class - Extract security utilities to
security.py - Set up testing framework
Phase 2: Extract First Mixin
- Start with
TextExtractionMixin - Move text extraction tools from server.py
- Update imports and dependencies
- Test thoroughly
Phase 3: Iterative Migration
- Extract one mixin at a time
- Test each migration independently
- Update server.py to use new mixins
- Maintain backward compatibility
Phase 4: Cleanup and Optimization
- Remove original server.py code
- Optimize mixin interactions
- Add advanced features (progressive disclosure, etc.)
- Final testing and documentation
Testing Strategy
Unit Testing Per Mixin
class TestTextExtractionMixin:
def setup_method(self):
self.mcp = FastMCP("test")
self.mixin = TextExtractionMixin(self.mcp)
@pytest.mark.asyncio
async def test_extract_text_validation(self):
result = await self.mixin.extract_text("")
assert not result["success"]
Integration Testing
class TestMixinComposition:
def test_no_tool_name_conflicts(self):
# Ensure no tools have conflicting names
pass
def test_comprehensive_coverage(self):
# Ensure all original tools are covered
pass
Auto-Discovery Testing
def test_mixin_auto_registration(self):
mixin = TextExtractionMixin(mcp)
components = mixin.get_registered_components()
assert "extract_text" in components["tools"]
Advanced Patterns
Progressive Tool Disclosure
class SecureTextExtractionMixin(TextExtractionMixin):
def __init__(self, mcp_server, permissions=None, **kwargs):
self.user_permissions = permissions or []
super().__init__(mcp_server, **kwargs)
def _should_auto_register_tool(self, name: str, method: Callable) -> bool:
# Only register tools user has permission for
required_perms = self._get_tool_permissions(name)
return all(perm in self.user_permissions for perm in required_perms)
Dynamic Tool Visibility
@mcp_tool(name="advanced_ocr", description="Advanced OCR with ML")
async def advanced_ocr(self, pdf_path: str) -> Dict[str, Any]:
if not self._check_premium_features():
return {"error": "Premium feature not available"}
# Implementation...
Bulk Operations
class BulkProcessingMixin(MCPMixin):
@mcp_tool(name="bulk_extract_text", description="Process multiple PDFs")
async def bulk_extract_text(self, pdf_paths: List[str]) -> Dict[str, Any]:
# Leverage other mixins for bulk operations
pass
Performance Considerations
Lazy Loading
- Mixins only initialize when first used
- Heavy dependencies loaded on-demand
- Configurable mixin selection
Memory Management
- Clear separation prevents memory leaks
- Each mixin manages its own resources
- Proper cleanup in error cases
Startup Time
- Fast initialization with auto-registration
- Parallel mixin initialization possible
- Tool registration is cached
Security Enhancements
Centralized Validation
# security.py
async def validate_pdf_path(pdf_path: str) -> Path:
# Single source of truth for PDF validation
pass
def sanitize_error_message(error_msg: str) -> str:
# Consistent error sanitization
pass
Permission-Based Access
class SecureMixin(MCPMixin):
def get_required_permissions(self) -> List[str]:
return ["read_files", "specific_operation"]
def _check_permissions(self, required: List[str]) -> bool:
return all(perm in self.user_permissions for perm in required)
Deployment Configurations
Development Server
# All mixins enabled, debug logging
server = PDFToolsServer(
mixins="all",
debug=True,
security_mode="relaxed"
)
Production Server
# Selected mixins, strict security
server = PDFToolsServer(
mixins=["TextExtraction", "TableExtraction"],
security_mode="strict",
rate_limiting=True
)
Specialized Deployment
# OCR-only server
server = PDFToolsServer(
mixins=["TextExtraction"],
tools=["ocr_pdf", "is_scanned_pdf"],
gpu_acceleration=True
)
Comparison with Current Approach
| Aspect | Current FastMCP | MCPMixin Pattern |
|---|---|---|
| Organization | Single 6500+ line file | Modular mixins (~200-500 lines each) |
| Testability | Hard to test individual tools | Easy isolated testing |
| Maintainability | Difficult to navigate/modify | Clear separation of concerns |
| Extensibility | Add to monolithic file | Create new mixin |
| Security | Scattered validation | Centralized security utilities |
| Performance | All tools loaded always | Lazy loading possible |
| Reusability | Monolithic server only | Mixins reusable across projects |
| Debugging | Hard to isolate issues | Clear component boundaries |
Conclusion
The MCPMixin pattern transforms large, monolithic FastMCP servers into maintainable, testable, and scalable architectures. While it requires initial refactoring effort, the long-term benefits in maintainability, testability, and extensibility make it worthwhile for any server with 10+ tools.
The pattern is particularly valuable for:
- Complex servers with multiple tool categories
- Team development where different developers work on different domains
- Production deployments requiring security and reliability
- Long-term maintenance and feature evolution
For your MCP PDF server with 24+ tools, the MCPMixin pattern would provide significant improvements in code organization, testing capabilities, and future extensibility.