Rename package to mcwaddams
Some checks are pending
Test Dashboard / test-and-dashboard (push) Waiting to run

Named for Milton Waddams, who was relocated to the basement with
boxes of legacy documents. He handles the .doc and .xls files from
1997 that nobody else wants to touch.

- Rename package from mcp-office-tools to mcwaddams
- Update author to Ryan Malloy
- Update all imports and references
- Add Office Space themed README narrative
- All 53 tests passing
This commit is contained in:
Ryan Malloy 2026-01-11 11:35:35 -07:00
parent 6fb76d8760
commit 31948d6ffc
39 changed files with 167 additions and 307 deletions

View File

@ -1,10 +1,10 @@
# CLAUDE.md # CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with the MCP Office Tools codebase. This file provides guidance to Claude Code (claude.ai/code) when working with the mcwaddams codebase.
## Project Overview ## Project Overview
MCP Office Tools is a FastMCP server that provides comprehensive Microsoft Office document processing capabilities including text extraction, image extraction, metadata extraction, and format detection. The server supports Word (.docx, .doc), Excel (.xlsx, .xls), PowerPoint (.pptx, .ppt), and CSV files with intelligent method selection and automatic fallbacks. mcwaddams is a FastMCP server that provides comprehensive Microsoft Office document processing capabilities including text extraction, image extraction, metadata extraction, and format detection. The server supports Word (.docx, .doc), Excel (.xlsx, .xls), PowerPoint (.pptx, .ppt), and CSV files with intelligent method selection and automatic fallbacks.
## Development Commands ## Development Commands
@ -23,7 +23,7 @@ uv sync --dev
uv run pytest uv run pytest
# Run with coverage # Run with coverage
uv run pytest --cov=mcp_office_tools uv run pytest --cov=mcwaddams
# Run specific test file # Run specific test file
uv run pytest tests/test_server.py uv run pytest tests/test_server.py
@ -47,10 +47,10 @@ uv run mypy src/
### Running the Server ### Running the Server
```bash ```bash
# Run MCP server directly # Run MCP server directly
uv run mcp-office-tools uv run mcwaddams
# Run with Python module # Run with Python module
uv run python -m mcp_office_tools.server uv run python -m mcwaddams.server
# Test with sample documents # Test with sample documents
uv run python examples/test_office_tools.py /path/to/test.docx uv run python examples/test_office_tools.py /path/to/test.docx
@ -69,8 +69,8 @@ uv publish
### Core Components ### Core Components
- **`src/mcp_office_tools/server.py`**: Main server implementation with all Office processing tools - **`src/mcwaddams/server.py`**: Main server implementation with all Office processing tools
- **`src/mcp_office_tools/utils/`**: Utility modules for validation, caching, and file detection - **`src/mcwaddams/utils/`**: Utility modules for validation, caching, and file detection
- **FastMCP Framework**: Uses FastMCP for MCP protocol implementation - **FastMCP Framework**: Uses FastMCP for MCP protocol implementation
- **Multi-library approach**: Integrates python-docx, openpyxl, python-pptx, pandas, and legacy format handlers - **Multi-library approach**: Integrates python-docx, openpyxl, python-pptx, pandas, and legacy format handlers
@ -165,8 +165,8 @@ Tools are registered using FastMCP decorators and follow MCP protocol standards
## Project Structure ## Project Structure
``` ```
mcp-office-tools/ mcwaddams/
├── src/mcp_office_tools/ ├── src/mcwaddams/
│ ├── __init__.py # Package initialization │ ├── __init__.py # Package initialization
│ ├── server.py # Main FastMCP server with tools │ ├── server.py # Main FastMCP server with tools
│ ├── utils/ # Utility modules │ ├── utils/ # Utility modules
@ -218,7 +218,7 @@ The project uses pytest with:
## Relationship to MCP PDF Tools ## Relationship to MCP PDF Tools
MCP Office Tools is designed as a companion to MCP PDF Tools: mcwaddams is designed as a companion to MCP PDF Tools:
- Consistent API design patterns - Consistent API design patterns
- Similar caching and URL handling - Similar caching and URL handling
- Parallel tool organization - Parallel tool organization

View File

@ -151,10 +151,10 @@ except OfficeFileError as e:
### **Server Status: OPERATIONAL ✅** ### **Server Status: OPERATIONAL ✅**
```bash ```bash
$ uv run mcp-office-tools --version $ uv run mcwaddams --version
MCP Office Tools v0.1.0 MCP Office Tools v0.1.0
$ uv run mcp-office-tools $ uv run mcwaddams
[Server starts successfully with FastMCP banner] [Server starts successfully with FastMCP banner]
``` ```
@ -178,8 +178,8 @@ $ uv run mcp-office-tools
```json ```json
{ {
"mcpServers": { "mcpServers": {
"mcp-office-tools": { "mcwaddams": {
"command": "mcp-office-tools" "command": "mcwaddams"
} }
} }
} }

View File

@ -97,13 +97,13 @@ quick-test:
# Coverage report # Coverage report
coverage: coverage:
@echo "📊 Generating coverage report..." @echo "📊 Generating coverage report..."
@uv run pytest --cov=mcp_office_tools --cov-report=html --cov-report=term @uv run pytest --cov=mcwaddams --cov-report=html --cov-report=term
@echo "✅ Coverage report generated at htmlcov/index.html" @echo "✅ Coverage report generated at htmlcov/index.html"
# Run server in development mode # Run server in development mode
dev: dev:
@echo "🚀 Starting MCP Office Tools server..." @echo "🚀 Starting MCP Office Tools server..."
@uv run mcp-office-tools @uv run mcwaddams
# Build distribution packages # Build distribution packages
build: build:
@ -116,7 +116,7 @@ info:
@echo "MCP Office Tools - Project Information" @echo "MCP Office Tools - Project Information"
@echo "=======================================" @echo "======================================="
@echo "" @echo ""
@echo "Project: mcp-office-tools" @echo "Project: mcwaddams"
@echo "Version: $(shell grep '^version' pyproject.toml | cut -d'"' -f2)" @echo "Version: $(shell grep '^version' pyproject.toml | cut -d'"' -f2)"
@echo "Python: $(shell python --version)" @echo "Python: $(shell python --version)"
@echo "UV: $(shell uv --version 2>/dev/null || echo 'not installed')" @echo "UV: $(shell uv --version 2>/dev/null || echo 'not installed')"

213
README.md
View File

@ -1,15 +1,15 @@
<div align="center"> <div align="center">
# 📊 MCP Office Tools # 📎 mcwaddams
**MCP server for extracting text, tables, images, and data from Microsoft Office files** **MCP server for Microsoft Office document processing**
[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg?style=flat-square)](https://www.python.org/downloads/) [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg?style=flat-square)](https://www.python.org/downloads/)
[![FastMCP](https://img.shields.io/badge/FastMCP-0.5+-green.svg?style=flat-square)](https://gofastmcp.com) [![FastMCP](https://img.shields.io/badge/FastMCP-0.5+-green.svg?style=flat-square)](https://gofastmcp.com)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
[![MCP Protocol](https://img.shields.io/badge/MCP-Protocol-purple?style=flat-square)](https://modelcontextprotocol.io) [![MCP Protocol](https://img.shields.io/badge/MCP-Protocol-purple?style=flat-square)](https://modelcontextprotocol.io)
*Word, Excel, PowerPoint, CSV — all the formats your AI agent needs to read but can't* *"I was told there would be document extraction."*
[Installation](#-installation) • [Tools](#-available-tools) • [Examples](#-usage-examples) • [Testing](#-testing) [Installation](#-installation) • [Tools](#-available-tools) • [Examples](#-usage-examples) • [Testing](#-testing)
@ -17,14 +17,22 @@
--- ---
## The Backstory
Milton Waddams was relocated to the basement. They took his stapler. But down there, surrounded by boxes of `.doc` files from 1997 and `.xls` spreadsheets that predate Unicode, he became something else entirely: a document processing expert.
This MCP server channels that energy. It handles the legacy formats nobody else wants to touch. It extracts text from files that should have been migrated to Google Docs a decade ago. It reads the TPS reports.
---
## ✨ Features ## ✨ Features
- **Universal extraction** — Pull text, images, and metadata from any Office format - **Universal extraction** — Pull text, images, and metadata from any Office format
- **Format-specific tools** — Deep analysis for Word (tables, structure), Excel (formulas, charts), PowerPoint - **Format-specific tools** — Deep analysis for Word (tables, structure), Excel (formulas, charts), PowerPoint
- **Automatic pagination** — Large documents get chunked so they don't blow up your context window - **Automatic pagination** — Large documents get chunked so they don't blow up your context window
- **Fallback processing** — When one library chokes on a weird file, we try another. No silent failures. - **Fallback processing** — When one library chokes on a weird file, we try another
- **URL support** — Pass a URL instead of a file path; we'll download and cache it - **URL support** — Pass a URL instead of a file path; we'll download and cache it
- **Legacy formats** — Yes, even those .doc and .xls files from 2003 still work - **Legacy formats** — Yes, even those `.doc` and `.xls` files from the basement
--- ---
@ -32,11 +40,11 @@
```bash ```bash
# Quick install with uvx (recommended) # Quick install with uvx (recommended)
uvx mcp-office-tools uvx mcwaddams
# Or install with uv/pip # Or install with uv/pip
uv add mcp-office-tools uv add mcwaddams
pip install mcp-office-tools pip install mcwaddams
``` ```
### Claude Desktop Configuration ### Claude Desktop Configuration
@ -46,9 +54,9 @@ Add to your `claude_desktop_config.json`:
```json ```json
{ {
"mcpServers": { "mcpServers": {
"office-tools": { "mcwaddams": {
"command": "uvx", "command": "uvx",
"args": ["mcp-office-tools"] "args": ["mcwaddams"]
} }
} }
} }
@ -57,7 +65,7 @@ Add to your `claude_desktop_config.json`:
### Claude Code Configuration ### Claude Code Configuration
```bash ```bash
claude mcp add office-tools "uvx mcp-office-tools" claude mcp add mcwaddams "uvx mcwaddams"
``` ```
--- ---
@ -183,7 +191,7 @@ Use `text_patterns_only=True` to skip heading style detection for documents with
## 🎯 MCP Prompts ## 🎯 MCP Prompts
Pre-built workflows that chain multiple tools together. Use these as starting points: Pre-built workflows that chain multiple tools together:
| Prompt | Level | Description | | Prompt | Level | Description |
|--------|-------|-------------| |--------|-------|-------------|
@ -245,99 +253,7 @@ result = await analyze_excel_data(
check_data_quality=True check_data_quality=True
) )
# Returns per-column analysis # Returns per-column analysis with quality issues
# {
# "analysis": {
# "Sheet1": {
# "dimensions": {"rows": 1000, "columns": 12},
# "column_info": {
# "Revenue": {
# "data_type": "float64",
# "null_percentage": 2.3,
# "statistics": {"mean": 45000, "median": 42000, ...},
# "quality_issues": ["5 potential outliers"]
# }
# },
# "data_quality": {
# "completeness_percentage": 97.8,
# "duplicate_rows": 12
# }
# }
# }
# }
```
### Extract Excel Formulas
```python
result = await extract_excel_formulas(
file_path="financial-model.xlsx",
analyze_dependencies=True
)
# Returns formula details with dependency mapping
# {
# "formulas": {
# "Sheet1": [
# {
# "cell": "D2",
# "formula": "=B2*C2",
# "value": 1500.00,
# "dependencies": ["B2", "C2"]
# }
# ]
# }
# }
```
### Generate Chart Data
```python
result = await create_excel_chart_data(
file_path="quarterly-revenue.xlsx",
chart_type="line",
output_format="chartjs"
)
# Returns ready-to-use Chart.js configuration
# {
# "chartjs": {
# "type": "line",
# "data": {
# "labels": ["Q1", "Q2", "Q3", "Q4"],
# "datasets": [{"label": "Revenue", "data": [100, 120, 115, 140]}]
# }
# }
# }
```
### Extract Word Tables
```python
result = await extract_word_tables(
file_path="contract.docx",
output_format="markdown"
)
# Returns tables with optional format conversion
# {
# "tables": [
# {
# "table_index": 0,
# "dimensions": {"rows": 5, "columns": 3},
# "converted_output": "| Name | Role | Department |\n|---|---|---|\n..."
# }
# ]
# }
```
### Process Documents from URLs
```python
# Documents are downloaded and cached automatically
result = await extract_text("https://example.com/report.docx")
# Cache expires after 1 hour by default
``` ```
### Index Document for On-Demand Resource Fetching ### Index Document for On-Demand Resource Fetching
@ -351,8 +267,7 @@ result = await index_document("novel.docx")
# "doc_id": "56036b0f171a", # "doc_id": "56036b0f171a",
# "resources": { # "resources": {
# "chapter": [ # "chapter": [
# {"id": "1", "title": "Chapter 1: The Beginning", "uri": "chapter://56036b0f171a/1"}, # {"id": "1", "title": "Chapter 1", "uri": "chapter://56036b0f171a/1"},
# {"id": "2", "title": "Chapter 2: Rising Action", "uri": "chapter://56036b0f171a/2"},
# ... # ...
# ], # ],
# "image": [ # "image": [
@ -362,63 +277,47 @@ result = await index_document("novel.docx")
# } # }
# } # }
# Now fetch specific content via MCP resources: # Fetch specific content via MCP resources:
# - chapter://56036b0f171a/1 → Chapter 1 as markdown # - chapter://56036b0f171a/1 → Chapter 1 as markdown
# - chapter://56036b0f171a/1.txt → Chapter 1 as plain text # - chapter://56036b0f171a/1.txt → Chapter 1 as plain text
# - chapters://56036b0f171a/1-3 → Chapters 1-3 combined # - chapters://56036b0f171a/1-3 → Chapters 1-3 combined
# - image://56036b0f171a/0 → First embedded image
# Works with Excel and PowerPoint too:
await index_document("data.xlsx")
# → sheet://abc123/Revenue, sheet://abc123/Expenses, ...
await index_document("presentation.pptx")
# → slide://def456/1, slide://def456/2, ...
``` ```
--- ---
## 🧪 Testing ## 🧪 Testing
We built a visual test dashboard because staring at pytest output gets old. Run `make test` and you get an HTML report with pass/fail stats, detailed I/O for each test, and expandable tracebacks when things break.
```bash ```bash
# Run tests and generate the dashboard # Run tests and generate the dashboard
make test make test
# Just pytest, no dashboard # Just pytest
make test-pytest make test-pytest
# Open existing dashboard # Open dashboard
make view-dashboard make view-dashboard
``` ```
The dashboard has an MS Office-inspired theme (Word blue, Excel green, PowerPoint orange) and groups tests by category so you can see what's working at a glance.
--- ---
## 🏗 Architecture ## 🏗 Architecture
The mixin pattern keeps things modular — universal tools work on everything, format-specific tools go deeper. When the primary library can't handle something (corrupted files, weird formatting), we fall back to alternatives. The mixin pattern keeps things modular — universal tools work on everything, format-specific tools go deeper.
``` ```
mcp-office-tools/ mcwaddams/
├── src/mcp_office_tools/ ├── src/mcwaddams/
│ ├── server.py # FastMCP server + resource templates │ ├── server.py # FastMCP server + resource templates
│ ├── resources.py # Resource store for on-demand content │ ├── resources.py # Resource store for on-demand content
│ ├── mixins/ │ ├── mixins/
│ │ ├── universal.py # Format-agnostic tools (incl. index_document) │ │ ├── universal.py # Format-agnostic tools
│ │ ├── word.py # Word-specific tools │ │ ├── word.py # Word-specific tools
│ │ ├── excel.py # Excel-specific tools │ │ ├── excel.py # Excel-specific tools
│ │ └── powerpoint.py # PowerPoint tools (WIP) │ │ └── powerpoint.py # PowerPoint tools
│ ├── utils/ │ ├── utils/ # Validation, caching, detection
│ │ ├── validation.py # File validation
│ │ ├── file_detection.py # Format detection
│ │ ├── caching.py # URL caching
│ │ └── decorators.py # Error handling, defaults
│ └── pagination.py # Large document pagination │ └── pagination.py # Large document pagination
├── tests/ # pytest test suite ├── tests/
└── reports/ # Test dashboard output └── reports/
``` ```
### Processing Libraries ### Processing Libraries
@ -436,57 +335,17 @@ mcp-office-tools/
## 🔧 Development ## 🔧 Development
```bash ```bash
# Clone and install git clone https://github.com/ryanmalloy/mcwaddams.git
git clone https://github.com/yourusername/mcp-office-tools.git cd mcwaddams
cd mcp-office-tools
uv sync --dev uv sync --dev
# Run tests
uv run pytest uv run pytest
# Format and lint
uv run black src/ tests/ uv run black src/ tests/
uv run ruff check src/ tests/ uv run ruff check src/ tests/
# Type check
uv run mypy src/
``` ```
--- ---
## 📦 Dependencies
**Core:**
- `fastmcp` - MCP server framework
- `python-docx` - Word document processing
- `openpyxl` - Excel spreadsheet processing
- `python-pptx` - PowerPoint processing
- `pandas` - Data analysis and CSV handling
- `mammoth` - Word to HTML/Markdown conversion
- `olefile` - Legacy OLE format support
- `xlrd` - Legacy Excel support
- `pillow` - Image processing
- `aiohttp` / `aiofiles` - Async HTTP and file I/O
**Optional:**
- `python-magic` - Enhanced MIME type detection
- `msoffcrypto-tool` - Encrypted file detection
---
## 🤝 Related Projects
- **[MCP PDF Tools](https://github.com/yourusername/mcp-pdf-tools)** - Companion server for PDF processing
- **[FastMCP](https://gofastmcp.com)** - The framework powering this server
## 📝 Behind the Scenes
This README was rewritten during a human-AI collaboration session. The process raised questions about discernment, voice, and what makes documentation actually land:
- **[AI Isn't New. Your Discernment Is What Matters.](https://ryanmalloy.com/blog/ai-discernment)** — Ryan's take on 40 years of writing code and why discernment matters more than the tools
---
## 📜 License ## 📜 License
MIT License - see [LICENSE](LICENSE) for details. MIT License - see [LICENSE](LICENSE) for details.
@ -495,6 +354,8 @@ MIT License - see [LICENSE](LICENSE) for details.
<div align="center"> <div align="center">
*Named for Milton Waddams, who was relocated to the basement with the legacy documents.*
**Built with [FastMCP](https://gofastmcp.com) and the [Model Context Protocol](https://modelcontextprotocol.io)** **Built with [FastMCP](https://gofastmcp.com) and the [Model Context Protocol](https://modelcontextprotocol.io)**
</div> </div>

View File

@ -59,7 +59,7 @@ async def test_tool_functionality():
mixin = UniversalMixin(app) mixin = UniversalMixin(app)
# Mock dependencies # Mock dependencies
with patch('mcp_office_tools.utils.validation.validate_office_file'): with patch('mcwaddams.utils.validation.validate_office_file'):
# Test tool directly through mixin # Test tool directly through mixin
result = await mixin.extract_text("/test.csv") result = await mixin.extract_text("/test.csv")
assert "text" in result assert "text" in result
@ -185,13 +185,13 @@ uv run pytest
uv run pytest tests/test_universal_mixin.py -v uv run pytest tests/test_universal_mixin.py -v
# With coverage # With coverage
uv run pytest --cov=mcp_office_tools uv run pytest --cov=mcwaddams
``` ```
### Continuous Integration ### Continuous Integration
```bash ```bash
# All tests with coverage reporting # All tests with coverage reporting
uv run pytest --cov=mcp_office_tools --cov-report=xml --cov-report=html uv run pytest --cov=mcwaddams --cov-report=xml --cov-report=html
``` ```
## Key Testing Fixtures ## Key Testing Fixtures

View File

@ -17,7 +17,7 @@ This document captures critical bugs discovered and fixed while processing compl
## 1. FastMCP Banner Corruption ## 1. FastMCP Banner Corruption
**File:** `src/mcp_office_tools/server.py` **File:** `src/mcwaddams/server.py`
**Symptom:** MCP connection fails with `Invalid JSON: EOF while parsing` **Symptom:** MCP connection fails with `Invalid JSON: EOF while parsing`
@ -35,7 +35,7 @@ def main():
## 2. Page Range Cap Bug ## 2. Page Range Cap Bug
**File:** `src/mcp_office_tools/utils/word_processing.py` **File:** `src/mcwaddams/utils/word_processing.py`
**Symptom:** Requesting pages 1-5 returns truncated content, but pages 195-200 returns everything. **Symptom:** Requesting pages 1-5 returns truncated content, but pages 195-200 returns everything.
@ -57,7 +57,7 @@ max_chars = num_pages_requested * 50000
## 3. Heading Scan Limit Bug ## 3. Heading Scan Limit Bug
**File:** `src/mcp_office_tools/utils/word_processing.py` **File:** `src/mcwaddams/utils/word_processing.py`
**Symptom:** `_get_available_headings()` returns empty list for documents with chapters beyond the first few pages. **Symptom:** `_get_available_headings()` returns empty list for documents with chapters beyond the first few pages.
@ -81,7 +81,7 @@ for element in doc.element.body: # Scan ALL elements
## 4. Short-Text Fallback Logic Bug ## 4. Short-Text Fallback Logic Bug
**File:** `src/mcp_office_tools/utils/word_processing.py` **File:** `src/mcwaddams/utils/word_processing.py`
**Symptom:** Chapter search fails even when chapter text exists and is under 100 characters. **Symptom:** Chapter search fails even when chapter text exists and is under 100 characters.
@ -115,7 +115,7 @@ if is_heading_style or len(text_content.strip()) < 100:
## 5. Critical xpath API Mismatch (ROOT CAUSE) ## 5. Critical xpath API Mismatch (ROOT CAUSE)
**File:** `src/mcp_office_tools/utils/word_processing.py` **File:** `src/mcwaddams/utils/word_processing.py`
**Symptom:** Chapter search always returns "not found" even for chapters that clearly exist. **Symptom:** Chapter search always returns "not found" even for chapters that clearly exist.
@ -152,7 +152,7 @@ if pPr is not None:
## 6. Image Mode Default ## 6. Image Mode Default
**File:** `src/mcp_office_tools/mixins/word.py` **File:** `src/mcwaddams/mixins/word.py`
**Symptom:** Responses exceed token limits when documents contain images. **Symptom:** Responses exceed token limits when documents contain images.

View File

@ -10,7 +10,7 @@ from pathlib import Path
# Add the package to Python path for local testing # Add the package to Python path for local testing
sys.path.insert(0, str(Path(__file__).parent.parent / "src")) sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
from mcp_office_tools.server import ( from mcwaddams.server import (
extract_text, extract_text,
extract_images, extract_images,
extract_metadata, extract_metadata,

View File

@ -29,15 +29,15 @@ def test_import():
print("🔍 Testing package import...") print("🔍 Testing package import...")
try: try:
import mcp_office_tools import mcwaddams
print(f"✅ Package imported successfully - Version: {mcp_office_tools.__version__}") print(f"✅ Package imported successfully - Version: {mcwaddams.__version__}")
# Test server import # Test server import
from mcp_office_tools.server import app from mcwaddams.server import app
print("✅ Server module imported successfully") print("✅ Server module imported successfully")
# Test utils import # Test utils import
from mcp_office_tools.utils import OfficeFileError, get_supported_extensions from mcwaddams.utils import OfficeFileError, get_supported_extensions
print("✅ Utils module imported successfully") print("✅ Utils module imported successfully")
# Test supported extensions # Test supported extensions
@ -58,7 +58,7 @@ async def test_utils():
print("\n🔧 Testing utility functions...") print("\n🔧 Testing utility functions...")
try: try:
from mcp_office_tools.utils import ( from mcwaddams.utils import (
detect_file_format, detect_file_format,
validate_office_path, validate_office_path,
OfficeFileError OfficeFileError
@ -103,7 +103,7 @@ def test_server_structure():
print("\n🖥️ Testing server structure...") print("\n🖥️ Testing server structure...")
try: try:
from mcp_office_tools.server import app from mcwaddams.server import app
# Check that app has tools # Check that app has tools
if hasattr(app, '_tools'): if hasattr(app, '_tools'):
@ -134,7 +134,7 @@ async def test_caching():
print("\n📦 Testing caching functionality...") print("\n📦 Testing caching functionality...")
try: try:
from mcp_office_tools.utils.caching import OfficeFileCache, get_cache from mcwaddams.utils.caching import OfficeFileCache, get_cache
# Test cache creation # Test cache creation
cache = get_cache() cache = get_cache()
@ -145,7 +145,7 @@ async def test_caching():
print(f"✅ Cache stats: {stats['total_files']} files, {stats['total_size_mb']} MB") print(f"✅ Cache stats: {stats['total_files']} files, {stats['total_size_mb']} MB")
# Test URL validation # Test URL validation
from mcp_office_tools.utils.validation import is_url from mcwaddams.utils.validation import is_url
assert is_url("https://example.com/file.docx") assert is_url("https://example.com/file.docx")
assert not is_url("/local/path/file.docx") assert not is_url("/local/path/file.docx")
@ -243,7 +243,7 @@ async def main():
print("🎉 Installation verified successfully!") print("🎉 Installation verified successfully!")
print("✅ MCP Office Tools is ready to use.") print("✅ MCP Office Tools is ready to use.")
print("\n🚀 Next steps:") print("\n🚀 Next steps:")
print(" 1. Run the MCP server: uv run mcp-office-tools") print(" 1. Run the MCP server: uv run mcwaddams")
print(" 2. Add to Claude Desktop config") print(" 2. Add to Claude Desktop config")
print(" 3. Test with Office documents") print(" 3. Test with Office documents")
return 0 return 0

View File

@ -1,12 +1,12 @@
[project] [project]
name = "mcp-office-tools" name = "mcwaddams"
version = "0.1.0" version = "0.1.0"
description = "MCP server for comprehensive Microsoft Office document processing" description = "MCP server for Microsoft Office document processing. Named for Milton Waddams, who was relocated to the basement with boxes of legacy documents."
authors = [{name = "MCP Office Tools", email = "contact@mcpofficetools.dev"}] authors = [{name = "Ryan Malloy", email = "ryan@supported.systems"}]
readme = "README.md" readme = "README.md"
license = {text = "MIT"} license = {text = "MIT"}
requires-python = ">=3.11" requires-python = ">=3.11"
keywords = ["mcp", "office", "docx", "xlsx", "pptx", "word", "excel", "powerpoint", "document", "processing"] keywords = ["mcp", "office", "docx", "xlsx", "pptx", "word", "excel", "powerpoint", "document", "processing", "milton", "legacy"]
classifiers = [ classifiers = [
"Development Status :: 4 - Beta", "Development Status :: 4 - Beta",
"Intended Audience :: Developers", "Intended Audience :: Developers",
@ -64,20 +64,19 @@ enhanced = [
] ]
[project.urls] [project.urls]
Homepage = "https://github.com/mcp-office-tools/mcp-office-tools" Homepage = "https://github.com/ryanmalloy/mcwaddams"
Documentation = "https://mcp-office-tools.readthedocs.io" Repository = "https://github.com/ryanmalloy/mcwaddams"
Repository = "https://github.com/mcp-office-tools/mcp-office-tools" Issues = "https://github.com/ryanmalloy/mcwaddams/issues"
Issues = "https://github.com/mcp-office-tools/mcp-office-tools/issues"
[project.scripts] [project.scripts]
mcp-office-tools = "mcp_office_tools.server:main" mcwaddams = "mcwaddams.server:main"
[build-system] [build-system]
requires = ["hatchling"] requires = ["hatchling"]
build-backend = "hatchling.build" build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel] [tool.hatch.build.targets.wheel]
packages = ["src/mcp_office_tools"] packages = ["src/mcwaddams"]
[tool.hatch.build.targets.sdist] [tool.hatch.build.targets.sdist]
include = [ include = [
@ -145,7 +144,7 @@ minversion = "7.0"
addopts = [ addopts = [
"--strict-markers", "--strict-markers",
"--strict-config", "--strict-config",
"--cov=mcp_office_tools", "--cov=mcwaddams",
"--cov-report=term-missing", "--cov-report=term-missing",
"--cov-report=html", "--cov-report=html",
"--cov-report=xml", "--cov-report=xml",
@ -161,7 +160,7 @@ markers = [
] ]
[tool.coverage.run] [tool.coverage.run]
source = ["src/mcp_office_tools"] source = ["src/mcwaddams"]
omit = [ omit = [
"*/tests/*", "*/tests/*",
"*/test_*", "*/test_*",

View File

@ -1,9 +1,9 @@
{ {
"metadata": { "metadata": {
"start_time": "2026-01-11T09:40:29.164041", "start_time": "2026-01-11T11:35:12.792077",
"pytest_version": "9.0.2", "pytest_version": "9.0.2",
"end_time": "2026-01-11T09:40:30.048909", "end_time": "2026-01-11T11:35:14.191660",
"duration": 0.8848621845245361, "duration": 1.399580955505371,
"exit_status": 0 "exit_status": 0
}, },
"summary": { "summary": {

View File

@ -39,7 +39,7 @@ async def test_mcp_server():
try: try:
# Import the server components # Import the server components
from mcp_office_tools.mixins import UniversalMixin from mcwaddams.mixins import UniversalMixin
# Test the Universal Mixin directly # Test the Universal Mixin directly
universal = UniversalMixin() universal = UniversalMixin()

View File

@ -12,9 +12,9 @@ def test_pagination():
try: try:
# Import the server components # Import the server components
from mcp_office_tools.server import app from mcwaddams.server import app
from mcp_office_tools.mixins.word import WordMixin from mcwaddams.mixins.word import WordMixin
from mcp_office_tools.pagination import DocumentPaginationManager, paginate_document_conversion from mcwaddams.pagination import DocumentPaginationManager, paginate_document_conversion
print("✅ Successfully imported all pagination components:") print("✅ Successfully imported all pagination components:")
print(" • DocumentPaginationManager") print(" • DocumentPaginationManager")

View File

@ -14,7 +14,7 @@ from typing import Dict, Any
from fastmcp import FastMCP from fastmcp import FastMCP
# FastMCP testing utilities are created manually # FastMCP testing utilities are created manually
from mcp_office_tools.mixins import UniversalMixin, WordMixin, ExcelMixin, PowerPointMixin from mcwaddams.mixins import UniversalMixin, WordMixin, ExcelMixin, PowerPointMixin
@pytest.fixture @pytest.fixture
@ -211,23 +211,23 @@ class MockValidationContext:
self.patches = [] self.patches = []
def __enter__(self): def __enter__(self):
import mcp_office_tools.utils.validation import mcwaddams.utils.validation
import mcp_office_tools.utils.file_detection import mcwaddams.utils.file_detection
from unittest.mock import patch from unittest.mock import patch
if self.resolve_path: if self.resolve_path:
p1 = patch('mcp_office_tools.utils.validation.resolve_office_file_path', p1 = patch('mcwaddams.utils.validation.resolve_office_file_path',
return_value=self.resolve_path) return_value=self.resolve_path)
self.patches.append(p1) self.patches.append(p1)
p1.start() p1.start()
p2 = patch('mcp_office_tools.utils.validation.validate_office_file', p2 = patch('mcwaddams.utils.validation.validate_office_file',
return_value=self.validation_result) return_value=self.validation_result)
self.patches.append(p2) self.patches.append(p2)
p2.start() p2.start()
p3 = patch('mcp_office_tools.utils.file_detection.detect_format', p3 = patch('mcwaddams.utils.file_detection.detect_format',
return_value=self.format_detection) return_value=self.format_detection)
self.patches.append(p3) self.patches.append(p3)
p3.start() p3.start()

View File

@ -20,8 +20,8 @@ from typing import Dict, Any
from fastmcp import FastMCP from fastmcp import FastMCP
# FastMCP testing - using direct tool access # FastMCP testing - using direct tool access
from mcp_office_tools.mixins import UniversalMixin, WordMixin, ExcelMixin, PowerPointMixin from mcwaddams.mixins import UniversalMixin, WordMixin, ExcelMixin, PowerPointMixin
from mcp_office_tools.utils import OfficeFileError from mcwaddams.utils import OfficeFileError
class TestMixinArchitecture: class TestMixinArchitecture:
@ -131,9 +131,9 @@ class TestUniversalMixinUnit:
await universal_mixin.extract_text("/nonexistent/file.docx") await universal_mixin.extract_text("/nonexistent/file.docx")
@pytest.mark.asyncio @pytest.mark.asyncio
@patch('mcp_office_tools.mixins.universal.validate_office_file') @patch('mcwaddams.mixins.universal.validate_office_file')
@patch('mcp_office_tools.mixins.universal.detect_format') @patch('mcwaddams.mixins.universal.detect_format')
@patch('mcp_office_tools.mixins.universal.resolve_office_file_path') @patch('mcwaddams.mixins.universal.resolve_office_file_path')
async def test_extract_text_csv_success(self, mock_resolve, mock_detect, mock_validate, universal_mixin, mock_csv_file): async def test_extract_text_csv_success(self, mock_resolve, mock_detect, mock_validate, universal_mixin, mock_csv_file):
"""Test successful CSV text extraction with proper mocking.""" """Test successful CSV text extraction with proper mocking."""
# Setup mocks # Setup mocks
@ -200,9 +200,9 @@ class TestWordMixinUnit:
await word_mixin.convert_to_markdown("/nonexistent/file.docx") await word_mixin.convert_to_markdown("/nonexistent/file.docx")
@pytest.mark.asyncio @pytest.mark.asyncio
@patch('mcp_office_tools.mixins.word.validate_office_file') @patch('mcwaddams.mixins.word.validate_office_file')
@patch('mcp_office_tools.mixins.word.detect_format') @patch('mcwaddams.mixins.word.detect_format')
@patch('mcp_office_tools.mixins.word.resolve_office_file_path') @patch('mcwaddams.mixins.word.resolve_office_file_path')
async def test_convert_to_markdown_non_word_document(self, mock_resolve, mock_detect, mock_validate, word_mixin): async def test_convert_to_markdown_non_word_document(self, mock_resolve, mock_detect, mock_validate, word_mixin):
"""Test that non-Word documents are rejected for markdown conversion.""" """Test that non-Word documents are rejected for markdown conversion."""
# Setup mocks for a non-Word document # Setup mocks for a non-Word document
@ -287,9 +287,9 @@ class TestMockingStrategies:
} }
@pytest.mark.asyncio @pytest.mark.asyncio
@patch('mcp_office_tools.mixins.universal.resolve_office_file_path') @patch('mcwaddams.mixins.universal.resolve_office_file_path')
@patch('mcp_office_tools.mixins.universal.validate_office_file') @patch('mcwaddams.mixins.universal.validate_office_file')
@patch('mcp_office_tools.mixins.universal.detect_format') @patch('mcwaddams.mixins.universal.detect_format')
async def test_comprehensive_mocking_pattern(self, mock_detect, mock_validate, mock_resolve, mock_office_file): async def test_comprehensive_mocking_pattern(self, mock_detect, mock_validate, mock_resolve, mock_office_file):
"""Demonstrate comprehensive mocking pattern for tool testing.""" """Demonstrate comprehensive mocking pattern for tool testing."""
app = FastMCP("Test App") app = FastMCP("Test App")
@ -347,8 +347,8 @@ class TestFileOperationMocking:
universal.register_all(app) universal.register_all(app)
# Mock only the validation/detection layers # Mock only the validation/detection layers
with patch('mcp_office_tools.utils.validation.validate_office_file') as mock_validate: with patch('mcwaddams.utils.validation.validate_office_file') as mock_validate:
with patch('mcp_office_tools.utils.file_detection.detect_format') as mock_detect: with patch('mcwaddams.utils.file_detection.detect_format') as mock_detect:
mock_validate.return_value = {"is_valid": True, "errors": []} mock_validate.return_value = {"is_valid": True, "errors": []}
mock_detect.return_value = { mock_detect.return_value = {
"category": "data", "category": "data",
@ -375,9 +375,9 @@ class TestAsyncPatterns:
universal.register_all(app) universal.register_all(app)
# Mock all async dependencies # Mock all async dependencies
with patch('mcp_office_tools.mixins.universal.resolve_office_file_path') as mock_resolve: with patch('mcwaddams.mixins.universal.resolve_office_file_path') as mock_resolve:
with patch('mcp_office_tools.mixins.universal.validate_office_file') as mock_validate: with patch('mcwaddams.mixins.universal.validate_office_file') as mock_validate:
with patch('mcp_office_tools.mixins.universal.detect_format') as mock_detect: with patch('mcwaddams.mixins.universal.detect_format') as mock_detect:
# Make mocks properly async # Make mocks properly async
mock_resolve.return_value = "/test.csv" mock_resolve.return_value = "/test.csv"
mock_validate.return_value = {"is_valid": True, "errors": []} mock_validate.return_value = {"is_valid": True, "errors": []}

View File

@ -8,8 +8,8 @@ from unittest.mock import patch, MagicMock
# FastMCP testing - using direct tool access # FastMCP testing - using direct tool access
from mcp_office_tools.server import app from mcwaddams.server import app
from mcp_office_tools.utils import OfficeFileError from mcwaddams.utils import OfficeFileError
class TestServerInitialization: class TestServerInitialization:
@ -54,7 +54,7 @@ class TestServerInitialization:
def test_mixin_composition_works(self): def test_mixin_composition_works(self):
"""Test that mixin composition created the expected server structure.""" """Test that mixin composition created the expected server structure."""
# Import the server module to ensure all mixins are initialized # Import the server module to ensure all mixins are initialized
import mcp_office_tools.server as server_module import mcwaddams.server as server_module
# Verify the mixins were created # Verify the mixins were created
assert hasattr(server_module, 'universal_mixin') assert hasattr(server_module, 'universal_mixin')
@ -63,7 +63,7 @@ class TestServerInitialization:
assert hasattr(server_module, 'powerpoint_mixin') assert hasattr(server_module, 'powerpoint_mixin')
# Verify mixin instances are correct types # Verify mixin instances are correct types
from mcp_office_tools.mixins import UniversalMixin, WordMixin, ExcelMixin, PowerPointMixin from mcwaddams.mixins import UniversalMixin, WordMixin, ExcelMixin, PowerPointMixin
assert isinstance(server_module.universal_mixin, UniversalMixin) assert isinstance(server_module.universal_mixin, UniversalMixin)
assert isinstance(server_module.word_mixin, WordMixin) assert isinstance(server_module.word_mixin, WordMixin)
assert isinstance(server_module.excel_mixin, ExcelMixin) assert isinstance(server_module.excel_mixin, ExcelMixin)

View File

@ -16,8 +16,8 @@ from pathlib import Path
from fastmcp import FastMCP from fastmcp import FastMCP
# FastMCP testing - using direct tool access # FastMCP testing - using direct tool access
from mcp_office_tools.mixins.universal import UniversalMixin from mcwaddams.mixins.universal import UniversalMixin
from mcp_office_tools.utils import OfficeFileError from mcwaddams.utils import OfficeFileError
class TestUniversalMixinRegistration: class TestUniversalMixinRegistration:
@ -69,9 +69,9 @@ class TestExtractText:
await mixin.extract_text("/nonexistent/file.docx") await mixin.extract_text("/nonexistent/file.docx")
@pytest.mark.asyncio @pytest.mark.asyncio
@patch('mcp_office_tools.mixins.universal.resolve_office_file_path') @patch('mcwaddams.mixins.universal.resolve_office_file_path')
@patch('mcp_office_tools.mixins.universal.validate_office_file') @patch('mcwaddams.mixins.universal.validate_office_file')
@patch('mcp_office_tools.mixins.universal.detect_format') @patch('mcwaddams.mixins.universal.detect_format')
async def test_extract_text_validation_failure(self, mock_detect, mock_validate, mock_resolve, mixin): async def test_extract_text_validation_failure(self, mock_detect, mock_validate, mock_resolve, mixin):
"""Test extract_text with validation failure.""" """Test extract_text with validation failure."""
mock_resolve.return_value = "/test.docx" mock_resolve.return_value = "/test.docx"
@ -84,9 +84,9 @@ class TestExtractText:
await mixin.extract_text("/test.docx") await mixin.extract_text("/test.docx")
@pytest.mark.asyncio @pytest.mark.asyncio
@patch('mcp_office_tools.mixins.universal.resolve_office_file_path') @patch('mcwaddams.mixins.universal.resolve_office_file_path')
@patch('mcp_office_tools.mixins.universal.validate_office_file') @patch('mcwaddams.mixins.universal.validate_office_file')
@patch('mcp_office_tools.mixins.universal.detect_format') @patch('mcwaddams.mixins.universal.detect_format')
async def test_extract_text_csv_success(self, mock_detect, mock_validate, mock_resolve, mixin): async def test_extract_text_csv_success(self, mock_detect, mock_validate, mock_resolve, mixin):
"""Test successful CSV text extraction.""" """Test successful CSV text extraction."""
# Setup mocks # Setup mocks
@ -126,9 +126,9 @@ class TestExtractText:
async def test_extract_text_parameter_handling(self, mixin): async def test_extract_text_parameter_handling(self, mixin):
"""Test extract_text parameter validation and handling.""" """Test extract_text parameter validation and handling."""
# Mock all dependencies for parameter testing # Mock all dependencies for parameter testing
with patch('mcp_office_tools.mixins.universal.resolve_office_file_path') as mock_resolve: with patch('mcwaddams.mixins.universal.resolve_office_file_path') as mock_resolve:
with patch('mcp_office_tools.mixins.universal.validate_office_file') as mock_validate: with patch('mcwaddams.mixins.universal.validate_office_file') as mock_validate:
with patch('mcp_office_tools.mixins.universal.detect_format') as mock_detect: with patch('mcwaddams.mixins.universal.detect_format') as mock_detect:
mock_resolve.return_value = "/test.docx" mock_resolve.return_value = "/test.docx"
mock_validate.return_value = {"is_valid": True, "errors": []} mock_validate.return_value = {"is_valid": True, "errors": []}
mock_detect.return_value = {"category": "word", "extension": ".docx", "format_name": "Word"} mock_detect.return_value = {"category": "word", "extension": ".docx", "format_name": "Word"}
@ -174,9 +174,9 @@ class TestExtractImages:
await mixin.extract_images("/nonexistent/file.docx") await mixin.extract_images("/nonexistent/file.docx")
@pytest.mark.asyncio @pytest.mark.asyncio
@patch('mcp_office_tools.mixins.universal.resolve_office_file_path') @patch('mcwaddams.mixins.universal.resolve_office_file_path')
@patch('mcp_office_tools.mixins.universal.validate_office_file') @patch('mcwaddams.mixins.universal.validate_office_file')
@patch('mcp_office_tools.mixins.universal.detect_format') @patch('mcwaddams.mixins.universal.detect_format')
async def test_extract_images_unsupported_format(self, mock_detect, mock_validate, mock_resolve, mixin): async def test_extract_images_unsupported_format(self, mock_detect, mock_validate, mock_resolve, mixin):
"""Test extract_images with unsupported format (CSV) returns empty list.""" """Test extract_images with unsupported format (CSV) returns empty list."""
mock_resolve.return_value = "/test.csv" mock_resolve.return_value = "/test.csv"
@ -263,9 +263,9 @@ class TestDocumentHealth:
return mixin return mixin
@pytest.mark.asyncio @pytest.mark.asyncio
@patch('mcp_office_tools.mixins.universal.resolve_office_file_path') @patch('mcwaddams.mixins.universal.resolve_office_file_path')
@patch('mcp_office_tools.mixins.universal.validate_office_file') @patch('mcwaddams.mixins.universal.validate_office_file')
@patch('mcp_office_tools.mixins.universal.detect_format') @patch('mcwaddams.mixins.universal.detect_format')
async def test_analyze_document_health_success(self, mock_detect, mock_validate, mock_resolve, mixin): async def test_analyze_document_health_success(self, mock_detect, mock_validate, mock_resolve, mixin):
"""Test successful document health analysis.""" """Test successful document health analysis."""
mock_resolve.return_value = "/test.docx" mock_resolve.return_value = "/test.docx"
@ -343,9 +343,9 @@ class TestMockingPatterns:
async def test_comprehensive_mocking_pattern(self, mixin): async def test_comprehensive_mocking_pattern(self, mixin):
"""Demonstrate comprehensive mocking for complex tool testing.""" """Demonstrate comprehensive mocking for complex tool testing."""
# Mock all external dependencies # Mock all external dependencies
with patch('mcp_office_tools.mixins.universal.resolve_office_file_path') as mock_resolve: with patch('mcwaddams.mixins.universal.resolve_office_file_path') as mock_resolve:
with patch('mcp_office_tools.mixins.universal.validate_office_file') as mock_validate: with patch('mcwaddams.mixins.universal.validate_office_file') as mock_validate:
with patch('mcp_office_tools.mixins.universal.detect_format') as mock_detect: with patch('mcwaddams.mixins.universal.detect_format') as mock_detect:
# Setup realistic mock responses # Setup realistic mock responses
mock_resolve.return_value = "/realistic/path/document.docx" mock_resolve.return_value = "/realistic/path/document.docx"

View File

@ -14,8 +14,8 @@ from pathlib import Path
from fastmcp import FastMCP from fastmcp import FastMCP
# FastMCP testing - using direct tool access # FastMCP testing - using direct tool access
from mcp_office_tools.mixins.word import WordMixin from mcwaddams.mixins.word import WordMixin
from mcp_office_tools.utils import OfficeFileError from mcwaddams.utils import OfficeFileError
class TestWordMixinRegistration: class TestWordMixinRegistration:
@ -58,9 +58,9 @@ class TestConvertToMarkdown:
await mixin.convert_to_markdown("/nonexistent/file.docx") await mixin.convert_to_markdown("/nonexistent/file.docx")
@pytest.mark.asyncio @pytest.mark.asyncio
@patch('mcp_office_tools.mixins.word.resolve_office_file_path') @patch('mcwaddams.mixins.word.resolve_office_file_path')
@patch('mcp_office_tools.mixins.word.validate_office_file') @patch('mcwaddams.mixins.word.validate_office_file')
@patch('mcp_office_tools.mixins.word.detect_format') @patch('mcwaddams.mixins.word.detect_format')
async def test_convert_to_markdown_validation_failure(self, mock_detect, mock_validate, mock_resolve, mixin): async def test_convert_to_markdown_validation_failure(self, mock_detect, mock_validate, mock_resolve, mixin):
"""Test convert_to_markdown with validation failure.""" """Test convert_to_markdown with validation failure."""
mock_resolve.return_value = "/test.docx" mock_resolve.return_value = "/test.docx"
@ -73,9 +73,9 @@ class TestConvertToMarkdown:
await mixin.convert_to_markdown("/test.docx") await mixin.convert_to_markdown("/test.docx")
@pytest.mark.asyncio @pytest.mark.asyncio
@patch('mcp_office_tools.mixins.word.resolve_office_file_path') @patch('mcwaddams.mixins.word.resolve_office_file_path')
@patch('mcp_office_tools.mixins.word.validate_office_file') @patch('mcwaddams.mixins.word.validate_office_file')
@patch('mcp_office_tools.mixins.word.detect_format') @patch('mcwaddams.mixins.word.detect_format')
async def test_convert_to_markdown_non_word_document(self, mock_detect, mock_validate, mock_resolve, mixin): async def test_convert_to_markdown_non_word_document(self, mock_detect, mock_validate, mock_resolve, mixin):
"""Test that non-Word documents are rejected.""" """Test that non-Word documents are rejected."""
mock_resolve.return_value = "/test.xlsx" mock_resolve.return_value = "/test.xlsx"
@ -90,9 +90,9 @@ class TestConvertToMarkdown:
await mixin.convert_to_markdown("/test.xlsx") await mixin.convert_to_markdown("/test.xlsx")
@pytest.mark.asyncio @pytest.mark.asyncio
@patch('mcp_office_tools.mixins.word.resolve_office_file_path') @patch('mcwaddams.mixins.word.resolve_office_file_path')
@patch('mcp_office_tools.mixins.word.validate_office_file') @patch('mcwaddams.mixins.word.validate_office_file')
@patch('mcp_office_tools.mixins.word.detect_format') @patch('mcwaddams.mixins.word.detect_format')
async def test_convert_to_markdown_docx_success(self, mock_detect, mock_validate, mock_resolve, mixin): async def test_convert_to_markdown_docx_success(self, mock_detect, mock_validate, mock_resolve, mixin):
"""Test successful DOCX to markdown conversion.""" """Test successful DOCX to markdown conversion."""
# Setup mocks # Setup mocks
@ -141,9 +141,9 @@ class TestConvertToMarkdown:
async def test_convert_to_markdown_parameter_handling(self, mixin): async def test_convert_to_markdown_parameter_handling(self, mixin):
"""Test convert_to_markdown parameter validation and handling.""" """Test convert_to_markdown parameter validation and handling."""
# Mock all dependencies for parameter testing # Mock all dependencies for parameter testing
with patch('mcp_office_tools.mixins.word.resolve_office_file_path') as mock_resolve: with patch('mcwaddams.mixins.word.resolve_office_file_path') as mock_resolve:
with patch('mcp_office_tools.mixins.word.validate_office_file') as mock_validate: with patch('mcwaddams.mixins.word.validate_office_file') as mock_validate:
with patch('mcp_office_tools.mixins.word.detect_format') as mock_detect: with patch('mcwaddams.mixins.word.detect_format') as mock_detect:
mock_resolve.return_value = "/test.docx" mock_resolve.return_value = "/test.docx"
mock_validate.return_value = {"is_valid": True, "errors": []} mock_validate.return_value = {"is_valid": True, "errors": []}
mock_detect.return_value = {"category": "word", "extension": ".docx", "format_name": "Word"} mock_detect.return_value = {"category": "word", "extension": ".docx", "format_name": "Word"}
@ -185,9 +185,9 @@ class TestConvertToMarkdown:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_convert_to_markdown_bookmark_priority(self, mixin): async def test_convert_to_markdown_bookmark_priority(self, mixin):
"""Test that bookmark extraction takes priority over page ranges.""" """Test that bookmark extraction takes priority over page ranges."""
with patch('mcp_office_tools.mixins.word.resolve_office_file_path') as mock_resolve: with patch('mcwaddams.mixins.word.resolve_office_file_path') as mock_resolve:
with patch('mcp_office_tools.mixins.word.validate_office_file') as mock_validate: with patch('mcwaddams.mixins.word.validate_office_file') as mock_validate:
with patch('mcp_office_tools.mixins.word.detect_format') as mock_detect: with patch('mcwaddams.mixins.word.detect_format') as mock_detect:
mock_resolve.return_value = "/test.docx" mock_resolve.return_value = "/test.docx"
mock_validate.return_value = {"is_valid": True, "errors": []} mock_validate.return_value = {"is_valid": True, "errors": []}
mock_detect.return_value = {"category": "word", "extension": ".docx", "format_name": "Word"} mock_detect.return_value = {"category": "word", "extension": ".docx", "format_name": "Word"}
@ -225,9 +225,9 @@ class TestConvertToMarkdown:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_convert_to_markdown_summary_mode(self, mixin): async def test_convert_to_markdown_summary_mode(self, mixin):
"""Test summary_only mode functionality.""" """Test summary_only mode functionality."""
with patch('mcp_office_tools.mixins.word.resolve_office_file_path') as mock_resolve: with patch('mcwaddams.mixins.word.resolve_office_file_path') as mock_resolve:
with patch('mcp_office_tools.mixins.word.validate_office_file') as mock_validate: with patch('mcwaddams.mixins.word.validate_office_file') as mock_validate:
with patch('mcp_office_tools.mixins.word.detect_format') as mock_detect: with patch('mcwaddams.mixins.word.detect_format') as mock_detect:
mock_resolve.return_value = "/test.docx" mock_resolve.return_value = "/test.docx"
mock_validate.return_value = {"is_valid": True, "errors": []} mock_validate.return_value = {"is_valid": True, "errors": []}
mock_detect.return_value = {"category": "word", "extension": ".docx", "format_name": "Word"} mock_detect.return_value = {"category": "word", "extension": ".docx", "format_name": "Word"}
@ -373,9 +373,9 @@ class TestLegacyWordSupport:
return mixin return mixin
@pytest.mark.asyncio @pytest.mark.asyncio
@patch('mcp_office_tools.mixins.word.resolve_office_file_path') @patch('mcwaddams.mixins.word.resolve_office_file_path')
@patch('mcp_office_tools.mixins.word.validate_office_file') @patch('mcwaddams.mixins.word.validate_office_file')
@patch('mcp_office_tools.mixins.word.detect_format') @patch('mcwaddams.mixins.word.detect_format')
async def test_convert_legacy_doc_to_markdown(self, mock_detect, mock_validate, mock_resolve, mixin): async def test_convert_legacy_doc_to_markdown(self, mock_detect, mock_validate, mock_resolve, mixin):
"""Test conversion of legacy .doc files.""" """Test conversion of legacy .doc files."""
mock_resolve.return_value = "/test.doc" mock_resolve.return_value = "/test.doc"
@ -425,9 +425,9 @@ class TestPageRangeFiltering:
return mixin return mixin
@pytest.mark.asyncio @pytest.mark.asyncio
@patch('mcp_office_tools.mixins.word.resolve_office_file_path') @patch('mcwaddams.mixins.word.resolve_office_file_path')
@patch('mcp_office_tools.mixins.word.validate_office_file') @patch('mcwaddams.mixins.word.validate_office_file')
@patch('mcp_office_tools.mixins.word.detect_format') @patch('mcwaddams.mixins.word.detect_format')
async def test_page_range_filters_different_content(self, mock_detect, mock_validate, mock_resolve, mixin): async def test_page_range_filters_different_content(self, mock_detect, mock_validate, mock_resolve, mixin):
"""Test that different page_range values return different content. """Test that different page_range values return different content.

View File

@ -17,8 +17,8 @@ warnings.filterwarnings("ignore", category=FutureWarning)
# Add src to path # Add src to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
from mcp_office_tools.mixins.excel import ExcelMixin from mcwaddams.mixins.excel import ExcelMixin
from mcp_office_tools.mixins.word import WordMixin from mcwaddams.mixins.word import WordMixin
# Test files - real files from user's system # Test files - real files from user's system

2
uv.lock generated
View File

@ -1537,7 +1537,7 @@ wheels = [
] ]
[[package]] [[package]]
name = "mcp-office-tools" name = "mcwaddams"
version = "0.1.0" version = "0.1.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# Quick script to open the test dashboard in browser # Quick script to open the test dashboard in browser
DASHBOARD_PATH="/home/rpm/claude/mcp-office-tools/reports/test_dashboard.html" DASHBOARD_PATH="/home/rpm/claude/mcwaddams/reports/test_dashboard.html"
echo "📊 Opening MCP Office Tools Test Dashboard..." echo "📊 Opening MCP Office Tools Test Dashboard..."
echo "Dashboard: $DASHBOARD_PATH" echo "Dashboard: $DASHBOARD_PATH"