#
tokens: 20106/50000 34/34 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .github
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   └── config.yml
│   ├── pull_request_template.md
│   └── workflows
│       └── ci.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .python-version
├── .yamllint.yaml
├── CHEATSHEET.md
├── CONTRIBUTING.md
├── demo
│   ├── convert-contents-tool.png
│   ├── v1-demo-markdown-text-and-html.png
│   └── v1-demo-md-to-html-attached.png
├── Dockerfile
├── examples
│   └── defaults
│       ├── academic-pdf.yaml
│       └── basic-html.yaml
├── LICENSE
├── pyproject.toml
├── README.md
├── smithery.yaml
├── src
│   └── mcp_pandoc
│       ├── __init__.py
│       └── server.py
├── testing
│   ├── defaults
│   │   ├── academic-pdf.yaml
│   │   ├── basic-html.yaml
│   │   └── pdf_defaults.yaml
│   ├── input
│   │   ├── test.html
│   │   ├── test.md
│   │   └── test.txt
│   ├── output
│   │   ├── test_output.docx
│   │   ├── test_output.html
│   │   └── test_output.pdf
│   └── test_defaults.yaml
├── tests
│   ├── fixtures
│   │   ├── test.docx
│   │   ├── test.epub
│   │   ├── test.html
│   │   ├── test.ipynb
│   │   ├── test.md
│   │   ├── test.odt
│   │   ├── test.rst
│   │   ├── test.tex
│   │   └── test.txt
│   ├── output
│   │   ├── test.docx
│   │   ├── test.epub
│   │   ├── test.html
│   │   ├── test.ipynb
│   │   ├── test.md
│   │   ├── test.odt
│   │   ├── test.pdf
│   │   ├── test.rst
│   │   ├── test.tex
│   │   └── test.txt
│   ├── test_advanced_features.py
│   └── test_conversions.py
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------

```
3.11

```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info

# Virtual environments
.venv
COMMIT_EDITMSG

# AI assistant context - private development tool
CLAUDE.md

```

--------------------------------------------------------------------------------
/.yamllint.yaml:
--------------------------------------------------------------------------------

```yaml
---
extends: default

ignore: |
  .venv/
  tests/
  examples/
  testing/
  smithery.yaml
  .github/ISSUE_TEMPLATE/

rules:
  line-length:
    max: 120
    level: warning
  trailing-spaces: disable
  indentation:
    spaces: 2
    indent-sequences: false
  document-start: disable

```

--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------

```yaml
---
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
  rev: v0.4.8
  hooks:
  - id: ruff
- repo: https://github.com/adrienverge/yamllint
  rev: v1.35.1
  hooks:
  - id: yamllint
- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: v4.4.0
  hooks:
  - id: trailing-whitespace
  - id: end-of-file-fixer
  - id: check-yaml
  - id: check-json

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
[![Downloads](https://img.shields.io/pypi/dm/mcp-pandoc.svg)](https://pypi.python.org/pypi/mcp-pandoc)
[![CI](https://github.com/vivekVells/mcp-pandoc/actions/workflows/ci.yml/badge.svg)](https://github.com/vivekVells/mcp-pandoc/actions/workflows/ci.yml)
<br />

![image](https://github.com/user-attachments/assets/10f18317-58e7-430e-9aec-b706b60fe2c6)

<!-- [![Downloads](https://static.pepy.tech/badge/mcp-pandoc/month)](https://pepy.tech/project/mcp-pandoc) -->
<!-- ![PyPI - Downloads](https://img.shields.io/pypi/dm/mcp-pandoc?style=social) -->

<!--
[![Downloads](https://img.shields.io/pypi/dm/mcp-pandoc.svg)](https://pypi.python.org/pypi/mcp-pandoc)
[![CI](https://github.com/vivekVells/mcp-pandoc/actions/workflows/ci.yml/badge.svg)](https://github.com/vivekVells/mcp-pandoc/actions/workflows/ci.yml)
<a href="https://smithery.ai/server/mcp-pandoc"><img alt="Smithery Badge" src="https://smithery.ai/badge/mcp-pandoc"></a> <a href="https://glama.ai/mcp/servers/xyzzgaj9bk"><img width="380" height="200" src="https://glama.ai/mcp/servers/xyzzgaj9bk/badge" /></a> 
-->
[![MseeP.ai Security Assessment Badge](https://mseep.net/pr/vivekvells-mcp-pandoc-badge.png)](https://mseep.ai/app/vivekvells-mcp-pandoc)
<a href="https://glama.ai/mcp/servers/xyzzgaj9bk"><img width="380" height="200" src="https://glama.ai/mcp/servers/xyzzgaj9bk/badge" />

# mcp-pandoc: A Document Conversion MCP Server

> Officially included in the [Model Context Protocol servers](https://github.com/modelcontextprotocol/servers/blob/main/README.md) open-source project. 🎉

## Overview

A Model Context Protocol server for document format conversion using [pandoc](https://pandoc.org/index.html). This server provides tools to transform content between different document formats while preserving formatting and structure.

Please note that mcp-pandoc is currently in early development. PDF support is under development, and the functionality and available tools are subject to change and expansion as we continue to improve the server.

Credit: This project uses the [Pandoc Python package](https://pypi.org/project/pandoc/) for document conversion, forming the foundation for this project.

## 📋 Quick Reference

**New to mcp-pandoc?** Check out **[📖 CHEATSHEET.md](CHEATSHEET.md)** for

- ⚡ Copy-paste examples for all formats
- 🔄 Bidirectional conversion matrix
- 🎯 Common workflows and pro tips
- 🌟 Reference document styling guide

_Perfect for quick lookups and getting started fast!_

## Demo

[![mcp-pandoc - v1: Seamless Document Format Conversion for Claude using MCP server](https://img.youtube.com/vi/vN3VOb0rygM/maxresdefault.jpg)](https://youtu.be/vN3VOb0rygM)

> 🎥 [Watch on YouTube](https://youtu.be/vN3VOb0rygM)

<details>
<summary>Screenshots</summary>

<img width="2407" alt="Screenshot 2024-12-26 at 3 33 54 PM" src="https://github.com/user-attachments/assets/ce3f5396-252a-4bba-84aa-65b2a06b859e" />
<img width="2052" alt="Screenshot 2024-12-26 at 3 38 24 PM" src="https://github.com/user-attachments/assets/8c525ad1-b184-41ca-b068-7dd34b60b85d" />
<img width="1498" alt="Screenshot 2024-12-26 at 3 40 51 PM" src="https://github.com/user-attachments/assets/a1e0682d-fe44-40b6-9988-bf805627beeb" />
<img width="760" alt="Screenshot 2024-12-26 at 3 41 20 PM" src="https://github.com/user-attachments/assets/1d7f5998-6d7f-48fa-adcf-fc37d0521213" />
<img width="1493" alt="Screenshot 2024-12-26 at 3 50 27 PM" src="https://github.com/user-attachments/assets/97992c5d-8efc-40af-a4c3-94c51c392534" />
</details>

More to come...

## Tools

1. `convert-contents`
   - Transforms content between supported formats
   - Inputs:
     - `contents` (string): Source content to convert (required if input_file not provided)
     - `input_file` (string): Complete path to input file (required if contents not provided)
     - `input_format` (string): Source format of the content (defaults to markdown)
     - `output_format` (string): Target format (defaults to markdown)
     - `output_file` (string): Complete path for output file (required for pdf, docx, rst, latex, epub formats)
     - `reference_doc` (string): Path to a reference document to use for styling (supported for docx output format)
     - `defaults_file` (string): Path to a Pandoc defaults file (YAML) containing conversion options
     - `filters` (array): List of Pandoc filter paths to apply during conversion
   - Supported input/output formats:
     - markdown
     - html
     - pdf
     - docx
     - rst
     - latex
     - epub
     - txt
     - ipynb
     - odt
   - Note: For advanced formats (pdf, docx, rst, latex, epub), an output_file path is required

### 🔧 Advanced Features

#### Defaults Files (YAML Configuration)

Use defaults files to create reusable conversion templates with consistent formatting:

```yaml
# academic-paper.yaml
from: markdown
to: pdf
number-sections: true
toc: true
metadata:
  title: "Academic Paper"
  author: "Research Team"
```

Example usage: `"Convert paper.md to PDF using defaults academic-paper.yaml and save as paper.pdf"`

#### Pandoc Filters

Apply custom filters for enhanced processing:

Example usage: `"Convert docs.md to HTML with filters ['/path/to/mermaid-filter.py'] and save as docs.html"`

> 💡 **For comprehensive examples and workflows**, see **[CHEATSHEET.md](CHEATSHEET.md)**

## 📊 Supported Formats & Conversions

### Bidirectional Conversion Matrix

| From\To      | MD  | HTML | TXT | DOCX | PDF | RST | LaTeX | EPUB | IPYNB | ODT |
| ------------ | --- | ---- | --- | ---- | --- | --- | ----- | ---- | ----- | --- |
| **Markdown** | ✅  | ✅   | ✅  | ✅   | ✅  | ✅  | ✅    | ✅   | ✅    | ✅  |
| **HTML**     | ✅  | ✅   | ✅  | ✅   | ✅  | ✅  | ✅    | ✅   | ✅    | ✅  |
| **TXT**      | ✅  | ✅   | ✅  | ✅   | ✅  | ✅  | ✅    | ✅   | ✅    | ✅  |
| **DOCX**     | ✅  | ✅   | ✅  | ✅   | ✅  | ✅  | ✅    | ✅   | ✅    | ✅  |
| **RST**      | ✅  | ✅   | ✅  | ✅   | ✅  | ✅  | ✅    | ✅   | ✅    | ✅  |
| **LaTeX**    | ✅  | ✅   | ✅  | ✅   | ✅  | ✅  | ✅    | ✅   | ✅    | ✅  |
| **EPUB**     | ✅  | ✅   | ✅  | ✅   | ✅  | ✅  | ✅    | ✅   | ✅    | ✅  |
| **IPYNB**    | ✅  | ✅   | ✅  | ✅   | ✅  | ✅  | ✅    | ✅   | ✅    | ✅  |
| **ODT**      | ✅  | ✅   | ✅  | ✅   | ✅  | ✅  | ✅    | ✅   | ✅    | ✅  |

### A Note on PDF Support

This tool uses `pandoc` for conversions, which allows for generating PDF files from the formats listed above. However, converting _from_ a PDF to other formats is not supported. Therefore, PDF should be considered an **output-only** format.

### Format Categories

| Category     | Formats                     | Requirements                    |
| ------------ | --------------------------- | ------------------------------- |
| **Basic**    | MD, HTML, TXT, IPYNB, ODT   | None                            |
| **Advanced** | DOCX, PDF, RST, LaTeX, EPUB | Must specify `output_file` path |
| **Styled**   | DOCX with reference doc     | Custom template support ⭐      |

### Requirements by Format

- **PDF (.pdf)** - requires TeX Live installation
- **DOCX (.docx)** - supports custom styling via reference documents
- **All others** - no additional requirements

Note: For advanced formats:

1. Complete file paths with filename and extension are required
2. **PDF conversion requires TeX Live installation** (see Critical Requirements section -> For macOS: `brew install texlive`)
3. When no output path is specified:
   - Basic formats: Displays converted content in the chat
   - Advanced formats: May save in system temp directory (/tmp/ on Unix systems)

## Usage & configuration

**NOTE: Ensure to complete installing required packages mentioned below under "Critical Requirements".**

To use the published one

```bash
{
  "mcpServers": {
    "mcp-pandoc": {
      "command": "uvx",
      "args": ["mcp-pandoc"]
    }
  }
}
```

**💡 Quick Start**: See **[CHEATSHEET.md](CHEATSHEET.md)** for copy-paste examples and common workflows.

### ⚠️ Important Notes

#### Critical Requirements

1. **Pandoc Installation**

- **Required**: Install `pandoc` - the core document conversion engine
- Installation:

  ```bash
  # macOS
  brew install pandoc

  # Ubuntu/Debian
  sudo apt-get install pandoc

  # Windows
  # Download installer from: https://pandoc.org/installing.html
  ```

- **Verify**: `pandoc --version`

2. **UV package installation**

- **Required**: Install `uv` package (includes `uvx` command)
- Installation:

  ```bash
  # macOS
  brew install uv

  # Windows/Linux
  pip install uv
  ```

- **Verify**: `uvx --version`

3. **PDF Conversion Prerequisites:** Only needed if you need to convert & save pdf

- TeX Live must be installed before attempting PDF conversion
- Installation commands:

  ```bash
  # Ubuntu/Debian
  sudo apt-get install texlive-xetex

  # macOS
  brew install texlive

  # Windows
  # Install MiKTeX or TeX Live from:
  # https://miktex.org/ or https://tug.org/texlive/
  ```

4. **File Path Requirements**

- When saving or converting files, you MUST provide complete file paths including filename and extension
- The tool does not automatically generate filenames or extensions

#### Examples

✅ Correct Usage:

```bash
# Converting content to PDF
"Convert this text to PDF and save as /path/to/document.pdf"

# Converting between file formats
"Convert /path/to/input.md to PDF and save as /path/to/output.pdf"

# Converting to DOCX with a reference document template
"Convert input.md to DOCX using template.docx as reference and save as output.docx"

# Step-by-step reference document workflow
"First create a reference document: pandoc -o custom-reference.docx --print-default-data-file reference.docx" or if you already have one, use that
"Then convert with custom styling: Convert this text to DOCX using /path/to/custom-reference.docx as reference and save as /path/to/styled-output.docx"
```

❌ Incorrect Usage:

```bash
# Missing filename and extension
"Save this as PDF in /documents/"

# Missing complete path
"Convert this to PDF"

# Missing extension
"Save as /documents/story"
```

#### Common Issues and Solutions

1. **PDF Conversion Fails**

   - Error: "xelatex not found"
   - Solution: Install TeX Live first (see installation commands above)

2. **File Conversion Fails**

   - Error: "Invalid file path"
   - Solution: Provide complete path including filename and extension
   - Example: `/path/to/document.pdf` instead of just `/path/to/`

3. **Format Conversion Fails**

   - Error: "Unsupported format"
   - Solution: Use only supported formats:
     - Basic: txt, html, markdown
     - Advanced: pdf, docx, rst, latex, epub

4. **Reference Document Issues**
   - Error: "Reference document not found"
   - Solution: Ensure the reference document path exists and is accessible
   - Note: Reference documents only work with DOCX output format
   - How to create: `pandoc -o reference.docx --print-default-data-file reference.docx`

## Quickstart

<!-- Uncomment after smithery fix
### Install

#### Option 1: Installing manually via claude_desktop_config.json config file
-->

### Installing manually via claude_desktop_config.json config file

- On MacOS: `open ~/Library/Application\ Support/Claude/claude_desktop_config.json`
- On Windows: `%APPDATA%/Claude/claude_desktop_config.json`

a) Only for local development & contribution to this repo

<details>
  <summary>Development/Unpublished Servers Configuration</summary>

ℹ️ Replace <DIRECTORY> with your locally cloned project path

```bash
"mcpServers": {
  "mcp-pandoc": {
    "command": "uv",
    "args": [
      "--directory",
      "<DIRECTORY>/mcp-pandoc",
      "run",
      "mcp-pandoc"
    ]
  }
}
```

</details>

b) Published Servers Configuration - Consumers should use this config

```bash
"mcpServers": {
  "mcp-pandoc": {
    "command": "uvx",
    "args": [
      "mcp-pandoc"
    ]
  }
}
```

<!-- Uncomment after smithery cli fix
#### Option 2: To install Published Servers Configuration automatically via Smithery

Run the following bash command to install **published** [mcp-pandoc pypi](https://pypi.org/project/mcp-pandoc) for Claude Desktop automatically via [Smithery](https://smithery.ai/server/mcp-pandoc):

```bash
npx -y @smithery/cli install mcp-pandoc --client claude
```
-->

- If you face any issue, use the "Published Servers Configuration" above directly instead of this cli.

**Note**: To use locally configured mcp-pandoc, follow "Development/Unpublished Servers Configuration" step above.

## Development

### Testing

To run the comprehensive test suite and validate all supported bidirectional conversions, use the following command:

```bash
uv run pytest tests/test_conversions.py
```

This ensures backward compatibility and verifies the tool's core functionality.

### Building and Publishing

To prepare the package for distribution:

1. Sync dependencies and update lockfile:

```bash
uv sync
```

2. Build package distributions:

```bash
uv build
```

This will create source and wheel distributions in the `dist/` directory.

3. Publish to PyPI:

```bash
uv publish
```

Note: You'll need to set PyPI credentials via environment variables or command flags:

- Token: `--token` or `UV_PUBLISH_TOKEN`
- Or username/password: `--username`/`UV_PUBLISH_USERNAME` and `--password`/`UV_PUBLISH_PASSWORD`

### Debugging

Since MCP servers run over stdio, debugging can be challenging. For the best debugging
experience, we strongly recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector).

You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) with this command:

```bash
npx @modelcontextprotocol/inspector uv --directory /Users/vivekvells/Desktop/code/ai/mcp-pandoc run mcp-pandoc
```

Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging.

---

## Contributing

We welcome contributions to enhance mcp-pandoc! Here's how you can get involved:

1. **Report Issues**: Found a bug or have a feature request? Open an issue on our [GitHub Issues](https://github.com/vivekVells/mcp-pandoc/issues) page.
2. **Submit Pull Requests**: Improve the codebase or add features by creating a pull request.

---

```

--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
# Contributing to mcp-pandoc

Thank you for your interest in contributing! Choose your path below:

## 🚀 Quick Start (Simple Changes)

**Fixing docs, typos, or small bugs?**

1. **Fork & clone:** `git clone your-fork-url`
2. **Make your change:** Edit the files you need
3. **Test:** `uv run pytest tests/test_conversions.py`
4. **Submit PR:** Include screenshots showing it works

That's it! The PR template will guide you through the rest.

**Need to add features or understand the codebase?** Expand the sections below.

---

<details>
<summary>📦 Full Development Setup (expand for new features)</summary>

## Prerequisites

### Required Dependencies
```bash
# Core dependencies (required for all development)
# macOS
brew install pandoc uv

# Ubuntu/Debian
sudo apt-get install pandoc
pip install uv

# Windows
# Download pandoc from: https://pandoc.org/installing.html
pip install uv
```

### Optional: PDF Support
If working with PDF conversion features:

```bash
# macOS
brew install texlive

# Ubuntu/Debian
sudo apt-get install texlive-xetex

# Windows
# Install MiKTeX or TeX Live from:
# https://miktex.org/ or https://tug.org/texlive/
```

## Development Setup

1. **Clone and setup:**
   ```bash
   git clone https://github.com/vivekVells/mcp-pandoc.git
   cd mcp-pandoc
   uv sync
   ```

2. **Test everything works:**
   ```bash
   uv run pytest tests/test_conversions.py
   uv run mcp-pandoc
   ```

</details>

<details>
<summary>🏗️ Understanding the Codebase (expand to learn architecture)</summary>

## Project Structure

```
/mcp-pandoc/
├── src/mcp_pandoc/
│   ├── __init__.py              # Entry point
│   └── server.py                # Main MCP server implementation
├── tests/
│   ├── fixtures/                # Test input files for all formats
│   ├── output/                  # Test output directory
│   └── test_conversions.py      # Comprehensive format testing
├── README.md                    # User documentation
├── CHEATSHEET.md               # Quick reference guide
└── pyproject.toml              # Python project configuration
```

## Core Architecture
- **MCP Server**: Implements Model Context Protocol for document conversion
- **Primary Tool**: `convert-contents` handles all format conversions
- **Supported Formats**: 10 formats with bidirectional conversion support
- **Format Categories**:
  - **Basic**: md, html, txt, ipynb, odt (can display converted content)
  - **Advanced**: pdf, docx, rst, latex, epub (require output file paths)

## Key Files
- `src/mcp_pandoc/server.py`: Core server implementation with tool definitions
- `tests/test_conversions.py`: Parametrized testing for all format combinations
- `pyproject.toml`: Dependencies and build configuration

</details>

<details>
<summary>⚙️ Development Guidelines (expand for code standards)</summary>

## Code Quality Standards

### Linting (Required)
**Run before every commit - automated checks prevent regressions:**

```bash
# Python code quality (catches syntax errors like false vs False)
uv run ruff check .

# YAML file validation (CI configs, etc.)
uv run yamllint .
```

**When to run:**
- **Before committing**: Pre-commit hooks run these automatically
- **After changes**: Verify your code meets standards
- **CI will fail** if linting doesn't pass

**What it catches:**
- Syntax errors that broke production (PR #31: `false` vs `False`)
- Code style inconsistencies
- YAML formatting issues in configs

### Code Standards

1. **Follow Existing Patterns**:
   - Study `src/mcp_pandoc/server.py` for coding style
   - Use async/await patterns for MCP operations
   - Implement comprehensive error handling

2. **Type Hints**: All functions should include proper type annotations

3. **Error Handling**: Provide clear, actionable error messages
   ```python
   # Good
   raise ValueError(f"Output file path is required for {output_format} format")

   # Bad
   raise ValueError("Invalid format")
   ```

4. **JSON Schema Validation**: New parameters must include proper schema definitions

## Testing Requirements

1. **Run Tests**: Always run the full test suite before submitting changes
   ```bash
   uv run pytest tests/test_conversions.py
   ```

2. **Add Tests**: New functionality must include corresponding tests

3. **Test Coverage**: The project uses parametrized testing to verify all format combinations work correctly

4. **Manual Testing**: Test with MCP Inspector if making server changes:
   ```bash
   npx @modelcontextprotocol/inspector uv --directory $(pwd) run mcp-pandoc
   ```

## Documentation Requirements

1. **Update README.md**: Document new features with clear examples
2. **Update CHEATSHEET.md**: Add quick reference examples for new functionality
3. **Update Tool Descriptions**: Modify docstrings in `server.py` for parameter changes
4. **Version Documentation**: Note any breaking changes or new requirements

</details>

<details>
<summary>🔄 Format Support (expand if adding new formats)</summary>

## Current Support Matrix
- **Bidirectional**: md ↔ html ↔ txt ↔ docx ↔ rst ↔ latex ↔ epub ↔ ipynb ↔ odt
- **Output Only**: PDF (can convert TO PDF, but not FROM PDF)
- **Special Features**: DOCX reference document styling

## Adding New Formats
1. Update `SUPPORTED_FORMATS` in `server.py`
2. Add to JSON Schema enum validation
3. Create test fixtures in `tests/fixtures/`
4. Update documentation and conversion matrix
5. Test all bidirectional conversions

</details>

## Getting Help

- **Issues**: Open an issue on GitHub for bugs or feature requests
- **Discussions**: Use GitHub Discussions for questions about usage or development
- **Testing**: Use MCP Inspector for debugging server interactions

## Code of Conduct

This project follows standard open source community guidelines:
- Be respectful and inclusive
- Focus on constructive feedback
- Help newcomers learn and contribute
- Maintain a professional and welcoming environment

---

Thank you for contributing to mcp-pandoc! Your efforts help make document conversion more accessible for everyone.

```

--------------------------------------------------------------------------------
/tests/output/test.txt:
--------------------------------------------------------------------------------

```
Test

```

--------------------------------------------------------------------------------
/tests/output/test.md:
--------------------------------------------------------------------------------

```markdown
# []{#anchor}Test

```

--------------------------------------------------------------------------------
/tests/output/test.html:
--------------------------------------------------------------------------------

```html
<h1 id="test"><span id="anchor"></span>Test</h1>

```

--------------------------------------------------------------------------------
/tests/fixtures/test.md:
--------------------------------------------------------------------------------

```markdown
# Test Document

This is a test document for pandoc conversion from md.

```

--------------------------------------------------------------------------------
/tests/fixtures/test.txt:
--------------------------------------------------------------------------------

```
# Test Document

This is a test document for pandoc conversion from txt.

```

--------------------------------------------------------------------------------
/tests/fixtures/test.html:
--------------------------------------------------------------------------------

```html
# Test Document

This is a test document for pandoc conversion from html.

```

--------------------------------------------------------------------------------
/testing/output/test_output.html:
--------------------------------------------------------------------------------

```html
<h1 id="test-document">Test Document</h1>
<p>This is a test document for pandoc conversion from md.</p>

```

--------------------------------------------------------------------------------
/testing/test_defaults.yaml:
--------------------------------------------------------------------------------

```yaml
from: markdown
to: html
standalone: true
metadata:
  title: "MCP-Pandoc Defaults Test"
  author: "Test Suite"
  date: "2025"
variables:
  fontsize: "12pt"
  geometry: "margin=1in"

```

--------------------------------------------------------------------------------
/testing/defaults/pdf_defaults.yaml:
--------------------------------------------------------------------------------

```yaml
from: markdown
to: pdf
pdf-engine: xelatex
geometry:
  - margin=1in
fontsize: 12pt
number-sections: true
metadata:
  title: "PDF with Defaults Test"
  author: "MCP-Pandoc Test Suite"
  date: "January 2025"

```

--------------------------------------------------------------------------------
/examples/defaults/basic-html.yaml:
--------------------------------------------------------------------------------

```yaml
# Basic HTML conversion defaults
from: markdown
to: html
standalone: true
self-contained: true
css:
  - https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css
metadata:
  title: "Test Document"
  author: "MCP-Pandoc Tester"

```

--------------------------------------------------------------------------------
/testing/defaults/basic-html.yaml:
--------------------------------------------------------------------------------

```yaml
# Basic HTML conversion defaults
from: markdown
to: html
standalone: true
self-contained: true
css:
  - https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css
metadata:
  title: "Test Document"
  author: "MCP-Pandoc Tester"

```

--------------------------------------------------------------------------------
/src/mcp_pandoc/__init__.py:
--------------------------------------------------------------------------------

```python
"""mcp_pandoc package initialization."""
import asyncio

from . import server


def main():
    """Run the mcp-pandoc server."""
    asyncio.run(server.main())

# Optionally expose other important items at package level
__all__ = ['main', 'server']

```

--------------------------------------------------------------------------------
/testing/input/test.txt:
--------------------------------------------------------------------------------

```
Plain text document for testing
=============================

This is a simple plain text document that will be used to test
text format conversions in mcp-pandoc.

Features to test:
- Simple paragraphs
- Line breaks
- Basic formatting

End of test document.

```

--------------------------------------------------------------------------------
/examples/defaults/academic-pdf.yaml:
--------------------------------------------------------------------------------

```yaml
# Academic PDF defaults
from: markdown
to: pdf
pdf-engine: xelatex
number-sections: true
toc: true
toc-depth: 3
geometry:
  - margin=1in
fontsize: 12pt
metadata:
  title: "Academic Paper"
  author: "Research Team"
  date: "2025"
variables:
  documentclass: article
  fontfamily: times

```

--------------------------------------------------------------------------------
/testing/defaults/academic-pdf.yaml:
--------------------------------------------------------------------------------

```yaml
# Academic PDF defaults
from: markdown
to: pdf
pdf-engine: xelatex
number-sections: true
toc: true
toc-depth: 3
geometry:
  - margin=1in
fontsize: 12pt
metadata:
  title: "Academic Paper"
  author: "Research Team"
  date: "2025"
variables:
  documentclass: article
  fontfamily: times

```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------

```yaml
blank_issues_enabled: false
contact_links:
  - name: Feature Request or Discussion
    url: https://github.com/vivekVells/mcp-pandoc/discussions
    about: For feature requests or general discussions, please use GitHub Discussions
  - name: Documentation
    url: https://github.com/vivekVells/mcp-pandoc/blob/main/README.md
    about: Check the README for setup and usage instructions

```

--------------------------------------------------------------------------------
/testing/input/test.html:
--------------------------------------------------------------------------------

```html
<!DOCTYPE html>
<html>
<head>
    <title>Test HTML Document</title>
</head>
<body>
    <h1>HTML Test Document</h1>
    <p>This is a test HTML document with <strong>bold</strong> and <em>italic</em> text.</p>
    <ul>
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
    </ul>
    <blockquote>
        <p>This is a blockquote for testing HTML to other format conversions.</p>
    </blockquote>
</body>
</html>

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
# Start with a Python base image
FROM python:3.11-slim-bookworm

# Set the working directory
WORKDIR /app

# Install system dependencies
RUN apt-get update && \
    apt-get install -y texlive-xetex pandoc && \
    rm -rf /var/lib/apt/lists/*

# Copy the application files
COPY . .

# Install the Python dependencies
RUN pip install --no-cache-dir .

# Set the environment variables
ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy

# Run the MCP server
ENTRYPOINT ["uv", "run", "mcp-pandoc"]

```

--------------------------------------------------------------------------------
/testing/input/test.md:
--------------------------------------------------------------------------------

```markdown
# MCP-Pandoc Test Document

## Introduction

This is a comprehensive test document for **mcp-pandoc** functionality testing.

## Features

1. **Bold text** and *italic text*
2. `Code snippets` and blocks
3. [Links](https://example.com)
4. Lists and tables

### Code Block

```python
def hello_world():
    print("Hello from mcp-pandoc!")
    return True
```

### Table

| Feature | Status | Notes |
|---------|--------|-------|
| Markdown | ✅ | Working |
| HTML | ✅ | Working |
| PDF | ⚠️ | Requires TeX |

## Conclusion

This document tests various markdown features for conversion validation.

> **Note**: This is a blockquote to test formatting preservation.

```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
name: mcp-pandoc
version: "1.0.0"

# specify how Smithery launches the MCP server
startCommand:
  type: stdio
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    properties: {}
  # using uv run mcp-pandoc as your startup command
  commandFunction: |-
    () => ({ command: "uv", args: ["run", "mcp-pandoc"] })

# dependencies to be installed by Smithery before running your tool
dependencies:
  system:   # for system-level OS packages
    - pandoc
    - texlive-xetex      # full TeX Live for PDF support
  python:   # pip-installable Python packages
    - uv           # the UV runner package
    - mcp-pandoc   # ensure your own package is installed in the environment

```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
[project]
name = "mcp-pandoc"
version = "0.8.1"
description = "MCP to interface with pandoc to convert files to different formats with enhanced features like Mermaid diagram conversion and defaults file support."
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
 "mcp>=1.2.1",
 "pyyaml>=6.0.2",
 "pandoc>=2.4",
 "pypandoc>=1.14",
 "pandocfilters>=1.5.0",
 "panflute>=2.3.1",
]
[[project.authors]]
name = "Vivek Vellaiyappan Surulimuthu"
email = "[email protected]"

[build-system]
requires = [ "hatchling",]
build-backend = "hatchling.build"

[dependency-groups]
dev = [
    "pytest-asyncio>=1.0.0",
    "pytest>=8.4.1",
    "ruff>=0.12.11",
    "jsonschema>=4.25.1",
    "yamllint>=1.37.1",
    "pre-commit>=4.3.0",
]

[project.scripts]
mcp-pandoc = "mcp_pandoc:main"
[tool.ruff]
line-length = 120
exclude = ["tests/*"]

[tool.ruff.lint]
select = ["E", "F", "I", "B", "UP", "D", "N", "S"]
ignore = []

[tool.yamllint]
config-file = ".yamllint.yaml"

```

--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------

```yaml
---
name: Build & Test

"on":
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: "3.11"

    - name: Install uv
      run: pip install uv

    - name: Install Pandoc and TeX Live
      run: sudo apt-get update && sudo apt-get install -y pandoc texlive-xetex

    - name: Sync dependencies (all groups)
      run: uv sync --all-groups

    - name: Run ruff (lint)
      run: uv run ruff check .

    - name: Run yamllint
      run: uv run yamllint .

    - name: Run pytest
      run: uv run pytest

    - name: Build project
      run: uv build

    - name: Test server imports
      run: uv run python -c "import mcp_pandoc; print('✅ Server imports correctly')"
      # run: |
      #   # MCP servers are stdio-based and block waiting for JSON-RPC input
      #   # A successful timeout means the server started and was running correctly
      #   timeout 3 uv run mcp-pandoc >/dev/null 2>&1
      #   if [ $? -eq 124 ]; then  # 124 is timeout's exit code when process is killed
      #       echo "✅ MCP server starts and runs correctly"
      #   else
      #       echo "❌ MCP server failed to start or crashed"
      #       exit 1
      #   fi

```

--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------

```markdown
## Summary

Brief description of what this PR changes and why.

## Essential Checklist

- [ ] Tests pass locally (`uv run pytest tests/test_conversions.py`)
- [ ] Code follows existing patterns in `src/mcp_pandoc/server.py`
- [ ] Documentation updated (if needed)
- [ ] Screenshots included below for visual verification

## Screenshots (Required)

Please include screenshots to help reviewers verify your changes:

**For new features or format support:**
- [ ] Before/after conversion examples showing the new functionality
- [ ] Sample input and output files

**For bug fixes:**
- [ ] Screenshots showing the error before the fix
- [ ] Screenshots showing the fix working correctly

**For all changes:**
- [ ] Proof that existing functionality still works (test a few conversions)

<!-- Upload screenshots here or link to them -->

## Additional Context

<details>
<summary>🔄 Format Support Changes (expand if adding/modifying formats)</summary>

- [ ] New format added to `SUPPORTED_FORMATS` in server.py
- [ ] Bidirectional conversion testing included
- [ ] Test fixtures added to `tests/fixtures/`
- [ ] Conversion matrix in README.md updated
- [ ] CHEATSHEET.md examples added

</details>

<details>
<summary>⚠️ Breaking Changes (expand if applicable)</summary>

- [ ] Breaking changes clearly documented
- [ ] Migration guide provided (if needed)
- [ ] Version bump considerations noted

</details>

---

Any additional notes for reviewers.

```

--------------------------------------------------------------------------------
/tests/test_conversions.py:
--------------------------------------------------------------------------------

```python
import pypandoc
import os
import pytest

# Define paths
FIXTURE_DIR = os.path.join(os.path.dirname(__file__), 'fixtures')
OUTPUT_DIR = os.path.join(os.path.dirname(__file__), 'output')

# Ensure output directory exists
if not os.path.exists(OUTPUT_DIR):
    os.makedirs(OUTPUT_DIR)

# All supported formats
FORMATS = ['md', 'html', 'txt', 'rst', 'tex', 'docx', 'pdf', 'epub', 'ipynb', 'odt']

# Create a dummy fixture file for each format
for format in FORMATS:
    if format not in ['pdf', 'docx', 'epub', 'ipynb', 'odt']:
        with open(os.path.join(FIXTURE_DIR, f'test.{format}'), 'w') as f:
            f.write(f'# Test Document\n\nThis is a test document for pandoc conversion from {format}.\n')

# Create valid docx, epub, ipynb, and odt fixtures
pypandoc.convert_text('# Test', 'docx', format='md', outputfile=os.path.join(FIXTURE_DIR, 'test.docx'))
pypandoc.convert_text('# Test', 'epub', format='md', outputfile=os.path.join(FIXTURE_DIR, 'test.epub'))
pypandoc.convert_text('# Test', 'ipynb', format='md', outputfile=os.path.join(FIXTURE_DIR, 'test.ipynb'))
pypandoc.convert_text('# Test', 'odt', format='md', outputfile=os.path.join(FIXTURE_DIR, 'test.odt'))

@pytest.mark.parametrize("from_format", FORMATS)
@pytest.mark.parametrize("to_format", FORMATS)
def test_bidirectional_conversions(from_format, to_format):
    """Tests all bidirectional conversions between supported formats."""
    if from_format == to_format:
        pytest.skip("Skipping conversion from a format to itself.")

    # PDF is a special case, we can only convert *to* it, not *from* it with pandoc easily
    if from_format == 'pdf':
        pytest.skip("Skipping conversion from PDF as it is not reliably supported by pandoc.")

    # For this test, we will only test converting *to* pdf from markdown
    if to_format == 'pdf' and from_format != 'md':
        pytest.skip("Skipping conversion to PDF from formats other than markdown for this test.")

    input_file = os.path.join(FIXTURE_DIR, f'test.{from_format}')
    output_file = os.path.join(OUTPUT_DIR, f'test.{to_format}')

    # pypandoc uses 'plain' for txt and 'latex' for tex
    pandoctor_from_format = from_format
    if from_format == 'txt':
        pandoctor_from_format = 'markdown' # Treat txt as markdown
    elif from_format == 'tex':
        pandoctor_from_format = 'latex'

    pandoctor_to_format = to_format
    if to_format == 'txt':
        pandoctor_to_format = 'plain'

    try:
        pypandoc.convert_file(input_file, pandoctor_to_format, format=pandoctor_from_format, outputfile=output_file)
        assert os.path.exists(output_file)
    except Exception as e:
        pytest.fail(f"Conversion from {from_format} to {to_format} failed with error: {e}")
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------

```yaml
name: Bug Report
description: Report a problem with mcp-pandoc
title: "[Bug] "
labels: ["bug"]
body:
  - type: markdown
    attributes:
      value: |
        Thank you for reporting a bug! Please provide the following information to help us resolve the issue quickly.

  - type: textarea
    id: use-case
    attributes:
      label: What were you trying to accomplish?
      description: Describe the use case or task you were working on when the issue occurred.
      placeholder: "I was trying to convert a Markdown file to PDF using Claude Desktop..."
    validations:
      required: true

  - type: textarea
    id: expected-behavior
    attributes:
      label: Expected Behavior
      description: What did you expect to happen?
      placeholder: "The conversion should complete successfully and create a PDF file..."
    validations:
      required: true

  - type: textarea
    id: actual-behavior
    attributes:
      label: Actual Behavior
      description: What actually happened? Include any error messages.
      placeholder: "Got an error: Invalid request error..."
    validations:
      required: true

  - type: textarea
    id: environment
    attributes:
      label: Environment Information
      description: Run these commands and paste the output
      value: |
        **Operating System:**
        ```
        # Run this command:
        # macOS/Linux: uname -a
        # Windows: systeminfo | findstr /C:"OS"
        ```

        **Python Version:**
        ```
        # Run: python --version
        ```

        **mcp-pandoc Version:**
        ```
        # Run: python -c "import mcp_pandoc; print(mcp_pandoc.__version__)"
        # Or: pip show mcp-pandoc | grep Version
        ```

        **MCP Client:**
        (e.g., Claude Desktop, Cursor IDE, Kiro, n8n, etc.)
    validations:
      required: true

  - type: textarea
    id: reproduction-steps
    attributes:
      label: Steps to Reproduce
      description: Provide minimal steps to reproduce the issue
      placeholder: |
        1. Configure mcp-pandoc in Claude Desktop
        2. Try to convert markdown to PDF
        3. See error...
    validations:
      required: false

  - type: textarea
    id: screenshots-logs
    attributes:
      label: Screenshots or Logs
      description: If applicable, add screenshots or paste relevant logs
      placeholder: Paste error logs or attach screenshots here
    validations:
      required: false

  - type: checkboxes
    id: checklist
    attributes:
      label: Pre-submission Checklist
      description: Please check these items before submitting
      options:
        - label: I have searched existing issues to avoid duplicates
          required: true
        - label: I have provided environment information requested above
          required: true
```

--------------------------------------------------------------------------------
/tests/test_advanced_features.py:
--------------------------------------------------------------------------------

```python
"""Test suite for advanced mcp-pandoc features

This file tests enhanced functionality beyond basic conversions:
1. Defaults file support (YAML configuration files) - Added in PR #24
2. Enhanced filter support with path resolution - Added in PR #24
3. Future advanced features will be added here

Focuses on testing advanced feature functionality and integration.
"""
import os
import sys
import tempfile

import pytest
import yaml


class TestDefaultsFileSupport:
    """Test the defaults file functionality added in PR #24"""

    def setup_method(self):
        """Setup test fixtures"""
        self.temp_dir = tempfile.mkdtemp()

    def teardown_method(self):
        """Cleanup test fixtures"""
        import shutil
        if os.path.exists(self.temp_dir):
            shutil.rmtree(self.temp_dir)

    def test_valid_defaults_file_creation(self):
        """Test creating and parsing a valid defaults file"""
        defaults_content = {
            'from': 'markdown',
            'to': 'html',
            'standalone': True,
            'css': ['style.css'],
            'variables': {
                'title': 'Test Document',
                'author': 'Test User'
            }
        }

        defaults_path = os.path.join(self.temp_dir, "test_defaults.yaml")
        with open(defaults_path, 'w') as f:
            yaml.dump(defaults_content, f)

        # Verify file exists and can be parsed
        assert os.path.exists(defaults_path)

        with open(defaults_path) as f:
            loaded_content = yaml.safe_load(f)

        assert loaded_content == defaults_content
        assert loaded_content['from'] == 'markdown'
        assert loaded_content['to'] == 'html'
        assert loaded_content['variables']['title'] == 'Test Document'

    def test_malformed_yaml_detection(self):
        """Test that malformed YAML files raise appropriate errors"""
        malformed_path = os.path.join(self.temp_dir, "malformed.yaml")
        with open(malformed_path, 'w') as f:
            f.write("invalid: yaml: content: [unclosed")

        # Should raise YAML error when trying to parse
        with pytest.raises(yaml.YAMLError):
            with open(malformed_path) as f:
                yaml.safe_load(f)

    def test_security_safe_yaml_loading(self):
        """Test that YAML loading is secure (uses safe_load)"""
        # Create a YAML file with potentially dangerous content
        dangerous_yaml = """
!!python/object/apply:os.system
- "echo 'dangerous code'"
"""
        dangerous_path = os.path.join(self.temp_dir, "dangerous.yaml")
        with open(dangerous_path, 'w') as f:
            f.write(dangerous_yaml)

        # Verify that safe_load doesn't execute dangerous content
        with open(dangerous_path) as f:
            try:
                result = yaml.safe_load(f)
                # If it loads, it should be safe (no code execution)
                assert result is None or isinstance(result, (dict, list, str, int, float))
            except yaml.YAMLError:
                # This is acceptable - safe_load rejecting dangerous content
                pass

    def test_empty_and_null_yaml_handling(self):
        """Test handling of edge cases in YAML files"""
        # Test empty file
        empty_path = os.path.join(self.temp_dir, "empty.yaml")
        with open(empty_path, 'w') as f:
            f.write("")

        with open(empty_path) as f:
            result = yaml.safe_load(f)
        assert result is None

        # Test file with only null
        null_path = os.path.join(self.temp_dir, "null.yaml")
        with open(null_path, 'w') as f:
            yaml.dump(None, f)

        with open(null_path) as f:
            result = yaml.safe_load(f)
        assert result is None


class TestFilterSupport:
    """Test the filter functionality added in PR #24"""

    def setup_method(self):
        """Setup test fixtures"""
        self.temp_dir = tempfile.mkdtemp()

    def teardown_method(self):
        """Cleanup test fixtures"""
        import shutil
        if os.path.exists(self.temp_dir):
            shutil.rmtree(self.temp_dir)

    def test_filter_file_creation_and_permissions(self):
        """Test creating filter files with proper permissions"""
        filter_content = '''#!/usr/bin/env python3
"""
Simple test Pandoc filter
"""
import sys
import json

def main():
    # Read JSON from stdin (Pandoc AST)
    doc = json.load(sys.stdin)
    # Echo it back (no transformation for test)
    json.dump(doc, sys.stdout)

if __name__ == "__main__":
    main()
'''
        filter_path = os.path.join(self.temp_dir, "test_filter.py")
        with open(filter_path, 'w') as f:
            f.write(filter_content)

        # Test permission handling
        os.chmod(filter_path, 0o644)  # Start without execute permission
        assert os.path.exists(filter_path)
        assert not os.access(filter_path, os.X_OK)

        # Test making executable
        os.chmod(filter_path, 0o755)
        assert os.access(filter_path, os.X_OK)

    def test_filter_path_resolution_scenarios(self):
        """Test various filter path resolution scenarios"""
        # Test absolute path
        abs_filter = os.path.join(self.temp_dir, "absolute_filter.py")
        with open(abs_filter, 'w') as f:
            f.write("#!/usr/bin/env python3\n# Absolute path filter")
        os.chmod(abs_filter, 0o755)

        assert os.path.isabs(abs_filter)
        assert os.path.exists(abs_filter)
        assert os.access(abs_filter, os.X_OK)

        # Test relative path resolution
        current_dir = os.getcwd()
        try:
            os.chdir(self.temp_dir)
            rel_filter = "relative_filter.py"
            with open(rel_filter, 'w') as f:
                f.write("#!/usr/bin/env python3\n# Relative path filter")
            os.chmod(rel_filter, 0o755)

            assert os.path.exists(rel_filter)
            assert os.path.exists(os.path.abspath(rel_filter))
        finally:
            os.chdir(current_dir)

    def test_multiple_filter_organization(self):
        """Test organizing multiple filters"""
        # Create filters subdirectory (common pattern)
        filters_dir = os.path.join(self.temp_dir, "filters")
        os.makedirs(filters_dir)

        # Create multiple filters
        filter_names = ["mermaid_filter.py", "citation_filter.py", "custom_filter.py"]
        filter_paths = []

        for name in filter_names:
            filter_path = os.path.join(filters_dir, name)
            with open(filter_path, 'w') as f:
                f.write(f"#!/usr/bin/env python3\n# {name} implementation")
            os.chmod(filter_path, 0o755)
            filter_paths.append(filter_path)

        # Verify all filters exist and are executable
        for path in filter_paths:
            assert os.path.exists(path)
            assert os.access(path, os.X_OK)


class TestNewDependencies:
    """Test that the new dependencies added in PR #24 work correctly"""

    def test_yaml_dependency(self):
        """Test pyyaml dependency functionality"""
        import yaml

        # Test basic functionality
        assert hasattr(yaml, 'safe_load')
        assert hasattr(yaml, 'dump')
        assert hasattr(yaml, 'YAMLError')

        # Test actual usage
        test_data = {'key': 'value', 'number': 42, 'list': [1, 2, 3]}
        yaml_string = yaml.dump(test_data)
        loaded_data = yaml.safe_load(yaml_string)
        assert loaded_data == test_data

    def test_pandocfilters_dependency(self):
        """Test pandocfilters dependency functionality"""
        import pandocfilters

        # Test basic functionality
        assert hasattr(pandocfilters, 'walk')
        assert hasattr(pandocfilters, 'toJSONFilter')

        # Test that we can import common filter functions
        from pandocfilters import Para, Str

        # Test basic filter element creation
        text_element = Str("test")
        para_element = Para([text_element])

        assert text_element['t'] == 'Str'
        assert text_element['c'] == 'test'
        assert para_element['t'] == 'Para'

    def test_panflute_dependency(self):
        """Test panflute dependency functionality"""
        import panflute

        # Test basic functionality
        assert hasattr(panflute, 'run_filter')
        assert hasattr(panflute, 'Doc')
        assert hasattr(panflute, 'Para')

        # Test basic element creation
        doc = panflute.Doc()
        para = panflute.Para()

        assert isinstance(doc, panflute.Doc)
        assert isinstance(para, panflute.Para)


class TestBackwardsCompatibility:
    """Test that PR #24 maintains backwards compatibility"""

    def test_existing_parameters_still_work(self):
        """Test that all existing parameters are still supported"""
        # Test old-style arguments still work
        old_style_args = {
            "contents": "# Test Document",
            "output_format": "html",
            "input_format": "markdown",
            "output_file": "/tmp/test.html",
            "reference_doc": "/path/to/reference.docx"
        }

        # These should all be valid parameter names
        required_params = {"contents", "output_format", "input_format"}
        optional_params = {"output_file", "reference_doc"}

        assert required_params.issubset(set(old_style_args.keys()))
        assert optional_params.issubset(set(old_style_args.keys()))

    def test_new_parameters_are_optional(self):
        """Test that new parameters are optional and don't break existing usage"""
        # Existing usage should work without new parameters
        minimal_args = {
            "contents": "# Test",
            "output_format": "html"
        }

        # New parameters should be additive
        enhanced_args = {
            **minimal_args,
            "defaults_file": "/path/to/defaults.yaml",
            "filters": ["/path/to/filter.py"]
        }

        # Both should be valid argument structures
        assert "contents" in minimal_args
        assert "output_format" in minimal_args
        assert "defaults_file" in enhanced_args
        assert "filters" in enhanced_args
        assert isinstance(enhanced_args["filters"], list)


class TestVersionUpdate:
    """Test that version information is properly updated"""

    def test_version_in_pyproject_toml(self):
        """Test that pyproject.toml has the correct version"""
        pyproject_path = os.path.join(os.path.dirname(__file__), '..', 'pyproject.toml')

        with open(pyproject_path) as f:
            content = f.read()

        # Version should be updated correctly following semantic versioning
        assert 'version = "0.8.1"' in content

        # New dependencies should be present
        assert 'pyyaml' in content
        assert 'pandocfilters' in content
        assert 'panflute' in content

    def test_server_module_imports(self):
        """Test that the server module has proper imports for new features"""
        # Add the src directory to path for import
        src_path = os.path.join(os.path.dirname(__file__), '..', 'src')
        if src_path not in sys.path:
            sys.path.insert(0, src_path)

        # Import the server module
        from mcp_pandoc import server

        # Verify core function exists
        assert hasattr(server, 'handle_call_tool')

        # Verify we can import the new dependencies at module level
        import pandocfilters
        import panflute
        import yaml

        # All should import successfully
        assert yaml
        assert pandocfilters
        assert panflute

```

--------------------------------------------------------------------------------
/CHEATSHEET.md:
--------------------------------------------------------------------------------

```markdown
# mcp-pandoc Quick Reference Cheatsheet

_Last Updated: June 27, 2025_

## 🚀 Prerequisites (One-Time Setup)

| Component               | macOS                  | Ubuntu/Debian                        | Windows                                                               |
| ----------------------- | ---------------------- | ------------------------------------ | --------------------------------------------------------------------- |
| **Pandoc**              | `brew install pandoc`  | `sudo apt-get install pandoc`        | [Download installer](https://pandoc.org/installing.html)              |
| **UV**                  | `brew install uv`      | `pip install uv`                     | `pip install uv`                                                      |
| **TeX Live** (PDF only) | `brew install texlive` | `sudo apt-get install texlive-xetex` | [MiKTeX](https://miktex.org/) or [TeX Live](https://tug.org/texlive/) |

## 📊 Supported Formats & Conversions

### Bidirectional Conversion Matrix

| From\To      | MD  | HTML | TXT | DOCX | PDF | RST | LaTeX | EPUB | IPYNB | ODT |
| ------------ | --- | ---- | --- | ---- | --- | --- | ----- | ---- | ----- | --- |
| **Markdown** | ✅  | ✅   | ✅  | ✅   | ✅  | ✅  | ✅    | ✅   | ✅    | ✅  |
| **HTML**     | ✅  | ✅   | ✅  | ✅   | ✅  | ✅  | ✅    | ✅   | ✅    | ✅  |
| **TXT**      | ✅  | ✅   | ✅  | ✅   | ✅  | ✅  | ✅    | ✅   | ✅    | ✅  |
| **DOCX**     | ✅  | ✅   | ✅  | ✅   | ✅  | ✅  | ✅    | ✅   | ✅    | ✅  |
| **RST**      | ✅  | ✅   | ✅  | ✅   | ✅  | ✅  | ✅    | ✅   | ✅    | ✅  |
| **LaTeX**    | ✅  | ✅   | ✅  | ✅   | ✅  | ✅  | ✅    | ✅   | ✅    | ✅  |
| **EPUB**     | ✅  | ✅   | ✅  | ✅   | ✅  | ✅  | ✅    | ✅   | ✅    | ✅  |
| **IPYNB**    | ✅  | ✅   | ✅  | ✅   | ✅  | ✅  | ✅    | ✅   | ✅    | ✅  |
| **ODT**      | ✅  | ✅   | ✅  | ✅   | ✅  | ✅  | ✅    | ✅   | ✅    | ✅  |

### A Note on PDF Support

This tool uses `pandoc` for conversions, which allows for generating PDF files from the formats listed above. However, converting _from_ a PDF to other formats is not supported. Therefore, PDF should be considered an **output-only** format.

### Format Categories

| Category     | Formats                     | Requirements                    |
| ------------ | --------------------------- | ------------------------------- |
| **Basic**    | MD, HTML, TXT, IPYNB, ODT   | None                            |
| **Advanced** | DOCX, PDF, RST, LaTeX, EPUB | Must specify `output_file` path |
| **Styled**   | DOCX with reference doc     | Custom template support         |

## ⚡ Quick Examples

### Simple Text-to-Format Conversions

```bash
# Markdown to HTML (displayed)
"Convert this to HTML: # Hello World"

# Markdown to DOCX (saved)
"Convert this to DOCX and save as /tmp/doc.docx: # My Document"

# Markdown to PDF (saved)
"Convert this to PDF and save as /tmp/doc.pdf: # My Document"
```

### File-to-File Conversions

```bash
# DOCX to PDF
"Convert /path/input.docx to PDF and save as /path/output.pdf"

# Markdown to DOCX
"Convert /path/input.md to DOCX and save as /path/output.docx"

# HTML to Markdown
"Convert /path/input.html to Markdown and save as /path/output.md"

# IPYNB to HTML
"Convert /path/input.ipynb to HTML and save as /path/output.html"

# ODT to Markdown
"Convert /path/input.odt to Markdown and save as /path/output.md"
```

### Reference Document Styling

```bash
# Step 1: Create reference document
pandoc -o /tmp/reference.docx --print-default-data-file reference.docx

# Step 2: Use reference for styled conversion
"Convert this to DOCX using /tmp/reference.docx as reference and save as /tmp/styled.docx:
# Professional Report
This will be styled according to the reference document."
```

### Consistent Configuration with Defaults Files

```bash
# Create a defaults file for academic papers
"Create defaults file /tmp/academic.yaml with: 
from: markdown
to: pdf
number-sections: true
toc: true
bibliography: references.bib"

# Use defaults for conversion
"Convert paper.md to PDF using defaults /tmp/academic.yaml and save as paper.pdf"

# Web publishing defaults
"Convert blog.md to HTML using defaults /tmp/web.yaml and save as blog.html"
```

### Enhanced Processing with Filters

```bash
# Single filter for Mermaid diagrams
"Convert docs.md to HTML with filters ['/path/to/mermaid-filter.py'] and save as docs.html"

# Multiple filters for academic workflow
"Convert thesis.md to PDF with filters ['/filters/citations.py', '/filters/crossref.py'] and save as thesis.pdf"

# Combine defaults and filters
"Convert paper.md to HTML using defaults /tmp/academic.yaml with filters ['/filters/mermaid.py'] and save as paper.html"
```

## 🔄 Common Workflows

### Publishing Pipeline

| Step | Command                                                  | Output            |
| ---- | -------------------------------------------------------- | ----------------- |
| 1    | `"Convert manuscript.md to DOCX and save as draft.docx"` | Draft for review  |
| 2    | `"Convert draft.docx to PDF and save as final.pdf"`      | Publication ready |

### Documentation Workflow

| Step | Command                                                   | Purpose           |
| ---- | --------------------------------------------------------- | ----------------- |
| 1    | `"Convert README.md to HTML and save as docs/index.html"` | Web documentation |
| 2    | `"Convert README.md to PDF and save as docs/manual.pdf"`  | Printable manual  |

### Professional Reports

| Step | Command                                                                                | Result             |
| ---- | -------------------------------------------------------------------------------------- | ------------------ |
| 1    | Create template: `pandoc -o template.docx --print-default-data-file reference.docx`    | Custom styling     |
| 2    | `"Convert report.md to DOCX using template.docx as reference and save as report.docx"` | Branded document   |
| 3    | `"Convert report.docx to PDF and save as report.pdf"`                                  | Final distribution |

### Academic Documents with Filters

| Step | Command                                                                                         | Purpose               |
| ---- | ----------------------------------------------------------------------------------------------- | --------------------- |
| 1    | Create filter: `echo '#!/usr/bin/env python3' > /tmp/citation_filter.py`                       | Custom citations      |
| 2    | `"Convert paper.md to PDF with filters ['/tmp/citation_filter.py'] and save as paper.pdf"`     | Processed citations   |
| 3    | `"Convert paper.md to DOCX with filters ['/tmp/mermaid_filter.py'] and save as paper.docx"`    | Diagram processing    |

### Streamlined Workflow with Defaults Files

| Step | Command                                                                                    | Benefit              |
| ---- | ------------------------------------------------------------------------------------------ | -------------------- |
| 1    | Create defaults: `echo 'to: html\nstandalone: true\ncss: [style.css]' > /tmp/web.yaml`    | Reusable config      |
| 2    | `"Convert docs.md to HTML using defaults /tmp/web.yaml and save as docs.html"`            | Consistent styling   |
| 3    | `"Convert blog.md to HTML using defaults /tmp/web.yaml and save as blog.html"`            | Same config applied  |

## 💡 Pro Tips

### File Paths

| ✅ Correct               | ❌ Incorrect           |
| ------------------------ | ---------------------- |
| `/tmp/document.pdf`      | `/tmp/document`        |
| `C:\Documents\file.docx` | `C:\Documents\`        |
| `./output/report.html`   | `just convert to HTML` |

### Format-Specific Notes

| Format         | Requirements           | Notes                     |
| -------------- | ---------------------- | ------------------------- |
| **PDF**        | TeX Live installed     | Uses XeLaTeX engine       |
| **DOCX**       | Optional reference doc | Supports custom styling   |
| **EPUB**       | Output file required   | Good for e-books          |
| **LaTeX**      | Output file required   | Academic documents        |
| **Defaults**   | YAML format           | Reusable configurations   |
| **Filters**    | Executable scripts    | Custom content processing |

### Reference Documents

| Use Case               | Command                                                       |
| ---------------------- | ------------------------------------------------------------- |
| **Create default**     | `pandoc -o ref.docx --print-default-data-file reference.docx` |
| **Corporate branding** | Customize ref.docx in Word/LibreOffice → Save                 |
| **Apply styling**      | Add `reference_doc: "/path/to/ref.docx"` parameter            |

### Defaults Files

| Use Case                  | Example YAML Content                                          |
| ------------------------- | ------------------------------------------------------------- |
| **Academic paper**        | `from: markdown\nto: pdf\nnumber-sections: true\ntoc: true`  |
| **Web publishing**        | `to: html\nstandalone: true\ncss: [style.css]\nself-contained: true` |
| **E-book creation**       | `to: epub\nmetadata:\n  title: "My Book"\n  author: "Author Name"` |

### Pandoc Filters

| Filter Type           | Purpose                      | Example Usage                               |
| --------------------- | ---------------------------- | ------------------------------------------- |
| **Mermaid diagrams**  | Convert code blocks to SVG   | `filters: ['/path/to/mermaid-filter.py']`   |
| **Citation processing** | Format academic citations   | `filters: ['/path/to/pandoc-citeproc']`     |
| **Custom formatting** | Transform specific elements  | `filters: ['/filters/custom.py']`           |

### Error Troubleshooting

| Error                                   | Solution                                    |
| --------------------------------------- | ------------------------------------------- |
| "xelatex not found"                     | Install TeX Live                            |
| "Reference document not found"          | Check file path exists                      |
| "output_file path is required"          | Add complete file path for advanced formats |
| "only supported for docx output format" | Reference docs only work with DOCX          |
| "Defaults file not found"              | Verify YAML file path and accessibility     |
| "Filter not executable"                | Check filter permissions: `chmod +x filter.py` |
| "Invalid YAML in defaults file"        | Validate YAML syntax and structure          |

## 🎯 Parameter Quick Reference

| Parameter       | Type   | Required | Description                   | Example                     |
| --------------- | ------ | -------- | ----------------------------- | --------------------------- |
| `contents`      | string | ✅\*     | Text to convert               | `"# Hello World"`           |
| `input_file`    | string | ✅\*     | File to convert               | `"/path/input.md"`          |
| `output_format` | string | ✅       | Target format                 | `"docx"`, `"pdf"`, `"html"` |
| `output_file`   | string | ⚠️\*\*   | Save location                 | `"/path/output.docx"`       |
| `input_format`  | string | ❌       | Source format (auto-detected) | `"markdown"`                |
| `reference_doc` | string | ❌       | DOCX template                 | `"/path/template.docx"`     |
| `defaults_file` | string | ❌       | Pandoc defaults YAML config   | `"/path/defaults.yaml"`     |
| `filters`       | array  | ❌       | Pandoc filters list           | `["/path/filter.py"]`       |

\*Either `contents` OR `input_file` required  
\*\*Required for: PDF, DOCX, RST, LaTeX, EPUB

---

_Quick reference for mcp-pandoc document conversion capabilities_

```

--------------------------------------------------------------------------------
/src/mcp_pandoc/server.py:
--------------------------------------------------------------------------------

```python
"""mcp-pandoc server module."""
import os

import mcp.server.stdio
import mcp.types as types
import pypandoc
import yaml
from mcp.server import NotificationOptions, Server
from mcp.server.models import InitializationOptions

server = Server("mcp-pandoc")


@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
    """List available tools.

    Each tool specifies its arguments using JSON Schema validation.
    """
    return [
        types.Tool(
            name="convert-contents",
            description=(
                "Converts content between different formats. Transforms input content from any supported format "
                "into the specified output format.\n\n"
                "🚨 CRITICAL REQUIREMENTS - PLEASE READ:\n"
                "1. PDF Conversion:\n"
                "   * You MUST install TeX Live BEFORE attempting PDF conversion:\n"
                "   * Ubuntu/Debian: `sudo apt-get install texlive-xetex`\n"
                "   * macOS: `brew install texlive`\n"
                "   * Windows: Install MiKTeX or TeX Live from https://miktex.org/ or https://tug.org/texlive/\n"
                "   * PDF conversion will FAIL without this installation\n\n"
                "2. File Paths - EXPLICIT REQUIREMENTS:\n"
                "   * When asked to save or convert to a file, you MUST provide:\n"
                "     - Complete directory path\n"
                "     - Filename\n"
                "     - File extension\n"
                "   * Example request: 'Write a story and save as PDF'\n"
                "   * You MUST specify: '/path/to/story.pdf' or 'C:\\Documents\\story.pdf'\n"
                "   * The tool will NOT automatically generate filenames or extensions\n\n"
                "3. File Location After Conversion:\n"
                "   * After successful conversion, the tool will display the exact path where the file is saved\n"
                "   * Look for message: 'Content successfully converted and saved to: [file_path]'\n"
                "   * You can find your converted file at the specified location\n"
                "   * If no path is specified, files may be saved in system temp directory (/tmp/ on Unix systems)\n"
                "   * For better control, always provide explicit output file paths\n\n"
                "Supported formats:"
                "- Basic: txt, html, markdown, ipynb, odt"
                "- Advanced (REQUIRE complete file paths): pdf, docx, rst, latex, epub"
                "✅ CORRECT Usage Examples:\n"
                "1. 'Convert this text to HTML' (basic conversion)\n"
                "   - Tool will show converted content\n\n"
                "2. 'Save this text as PDF at /documents/story.pdf'\n"
                "   - Correct: specifies path + filename + extension\n"
                "   - Tool will show: 'Content successfully converted and saved to: /documents/story.pdf'\n\n"
                "❌ INCORRECT Usage Examples:\n"
                "1. 'Save this as PDF in /documents/'\n"
                "   - Missing filename and extension\n"
                "2. 'Convert to PDF'\n"
                "   - Missing complete file path\n\n"
                "When requesting conversion, ALWAYS specify:\n"
                "1. The content or input file\n"
                "2. The desired output format\n"
                "3. For advanced formats: complete output path + filename + extension\n"
                "Example: 'Convert this markdown to PDF and save as /path/to/output.pdf'\n\n"
                "🎨 DOCX STYLING (NEW FEATURE):\n"
                "4. Custom DOCX Styling with Reference Documents:\n"
                "   * Use reference_doc parameter to apply professional styling to DOCX output\n"
                "   * Create custom templates with your branding, fonts, and formatting\n"
                "   * Perfect for corporate reports, academic papers, and professional documents\n"
                "   * Example: 'Convert this report to DOCX using /templates/corporate-style.docx as reference "
                "and save as /reports/Q4-report.docx'\n\n"
                "🎯 PANDOC FILTERS (NEW FEATURE):\n"
                "5. Pandoc Filter Support:\n"
                "   * Use filters parameter to apply custom Pandoc filters during conversion\n"
                "   * Filters are Python scripts that modify document content during processing\n"
                "   * Perfect for Mermaid diagram conversion, custom styling, and content transformation\n"
                "   * Example: 'Convert this markdown with mermaid diagrams to DOCX using "
                "filters=[\"./filters/mermaid-to-png-vibrant.py\"] and save as /reports/diagram-report.docx'\n\n"
                "📋 Creating Reference Documents:\n"
                "   * Generate template: pandoc -o template.docx --print-default-data-file reference.docx\n"
                "   * Customize in Word/LibreOffice: fonts, colors, headers, margins\n"
                "   * Use for consistent branding across all documents\n\n"
                "📋 Filter Requirements:\n"
                "   * Filters must be executable Python scripts\n"
                "   * Use absolute paths or paths relative to current working directory\n"
                "   * Filters are applied in the order specified\n"
                "   * Common filters: mermaid conversion, color processing, table formatting\n\n"
                "📄 Defaults File Support (NEW FEATURE):\n"
                "7. Pandoc Defaults File Support:\n"
                "   * Use defaults_file parameter to specify a YAML configuration file\n"
                "   * Similar to using pandoc -d option in the command line\n"
                "   * Allows setting multiple options in a single file\n"
                "   * Options in the defaults file can include filters, reference-doc, and other Pandoc options\n"
                "   * Example: 'Convert this markdown to DOCX using defaults_file=\"/path/to/defaults.yaml\" "
                "and save as /reports/report.docx'\n\n"
                "Note: After conversion, always check the success message for the exact file location."
            ),
            inputSchema={
                "type": "object",
                "properties": {
                    "contents": {
                        "type": "string",
                        "description": "The content to be converted (required if input_file not provided)"
                    },
                    "input_file": {
                        "type": "string",
                        "description": (
                            "Complete path to input file including filename and extension "
                            "(e.g., '/path/to/input.md')"
                        )
                    },
                    "input_format": {
                        "type": "string",
                        "description": "Source format of the content (defaults to markdown)",
                        "default": "markdown",
                        "enum": ["markdown", "html", "pdf", "docx", "rst", "latex", "epub", "txt", "ipynb", "odt"]
                    },
                    "output_format": {
                        "type": "string",
                        "description": "Desired output format (defaults to markdown)",
                        "default": "markdown",
                        "enum": ["markdown", "html", "pdf", "docx", "rst", "latex", "epub", "txt", "ipynb", "odt"]
                    },
                    "output_file": {
                        "type": "string",
                        "description": (
                            "Complete path where to save the output including filename and extension "
                            "(required for pdf, docx, rst, latex, epub formats)"
                        )
                    },
                    "reference_doc": {
                        "type": "string",
                        "description": (
                            "Path to a reference document to use for styling "
                            "(supported for docx output format)"
                        )
                    },
                    "filters": {
                        "type": "array",
                        "items": {"type": "string"},
                        "description": (
                            "List of Pandoc filter paths to apply during conversion. "
                            "Filters are applied in the order specified."
                        )
                    },
                    "defaults_file": {
                        "type": "string",
                        "description": (
                            "Path to a Pandoc defaults file (YAML) containing conversion options. "
                            "Similar to using pandoc -d option."
                        )
                    }
                },
                "additionalProperties": False
            },
        )
    ]

@server.call_tool()
async def handle_call_tool(
    name: str, arguments: dict | None
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
    """Handle tool execution requests.

    Tools can modify server state and notify clients of changes.
    """
    if name not in ["convert-contents"]:
        raise ValueError(f"Unknown tool: {name}")

    print(arguments)

    if not arguments:
        raise ValueError("Missing arguments")

    # Extract all possible arguments
    contents = arguments.get("contents")
    input_file = arguments.get("input_file")
    output_file = arguments.get("output_file")
    output_format = arguments.get("output_format", "markdown").lower()
    input_format = arguments.get("input_format", "markdown").lower()
    reference_doc = arguments.get("reference_doc")
    filters = arguments.get("filters", [])
    defaults_file = arguments.get("defaults_file")

    # Validate input parameters
    if not contents and not input_file:
        raise ValueError("Either 'contents' or 'input_file' must be provided")

    # Validate reference_doc if provided
    if reference_doc:
        if output_format != "docx":
            raise ValueError("reference_doc parameter is only supported for docx output format")
        if not os.path.exists(reference_doc):
            raise ValueError(f"Reference document not found: {reference_doc}")

    # Validate defaults_file if provided
    if defaults_file:
        if not os.path.exists(defaults_file):
            raise ValueError(f"Defaults file not found: {defaults_file}")

        # Check if it's a valid YAML file and readable
        try:
            with open(defaults_file) as f:
                yaml_content = yaml.safe_load(f)

            # Validate the YAML structure
            if not isinstance(yaml_content, dict):
                raise ValueError(f"Invalid defaults file format: {defaults_file} - must be a YAML dictionary")

            # Check if the defaults file specifies an output format that conflicts with the requested format
            if 'to' in yaml_content and yaml_content['to'] != output_format:
                print(
                    f"Warning: Defaults file specifies output format '{yaml_content['to']}' "
                    f"but requested format is '{output_format}'. Using requested format."
                )

        except yaml.YAMLError as e:
            raise ValueError(f"Error parsing defaults file {defaults_file}: {str(e)}") from e
        except PermissionError as e:
            raise ValueError(f"Permission denied when reading defaults file: {defaults_file}") from e
        except Exception as e:
            raise ValueError(f"Error reading defaults file {defaults_file}: {str(e)}") from e

    # Define supported formats
    supported_formats = {'html', 'markdown', 'pdf', 'docx', 'rst', 'latex', 'epub', 'txt', 'ipynb', 'odt'}
    if output_format not in supported_formats:
        raise ValueError(
            f"Unsupported output format: '{output_format}'. Supported formats are: {', '.join(supported_formats)}"
        )

    # Validate output_file requirement for advanced formats
    advanced_formats = {'pdf', 'docx', 'rst', 'latex', 'epub'}
    if output_format in advanced_formats and not output_file:
        raise ValueError(f"output_file path is required for {output_format} format")

    # Validate filters if provided
    if filters:
        if not isinstance(filters, list):
            raise ValueError("filters parameter must be an array of strings")

        for filter_path in filters:
            if not isinstance(filter_path, str):
                raise ValueError("Each filter must be a string path")

    def resolve_filter_path(filter_path, defaults_file=None):
        """Resolve a filter path by trying multiple possible locations.

        Args:
        ----
            filter_path: The original filter path (absolute or relative)
            defaults_file: Optional path to the defaults file for context

        Returns:
        -------
            Resolved absolute path to the filter if found, or None if not found

        """
        # If it's already an absolute path, just use it
        if os.path.isabs(filter_path):
            paths = [filter_path]
        else:
            # Try multiple locations for relative paths
            paths = [
                # 1. Relative to current working directory
                os.path.abspath(filter_path),

                # 2. Relative to the defaults file directory (if provided)
                os.path.join(os.path.dirname(os.path.abspath(defaults_file)), filter_path) if defaults_file else None,

                # 3. Relative to the .pandoc/filters directory
                os.path.join(os.path.expanduser("~"), ".pandoc", "filters", os.path.basename(filter_path))
            ]
            # Remove None entries
            paths = [p for p in paths if p]

        # Try each path
        for path in paths:
            if os.path.exists(path):
                # Check if executable and try to make it executable if not
                if not os.access(path, os.X_OK):
                    try:
                        os.chmod(path, os.stat(path).st_mode | 0o111)
                        print(f"Made filter executable: {path}")
                    except Exception as e:
                        print(f"Warning: Could not make filter executable: {path} - {str(e)}")
                        continue

                print(f"Using filter: {path}")
                return path

        return None

    def validate_filters(filters, defaults_file=None):
        """Validate filter paths and ensure they exist and are executable."""
        validated_filters = []

        for filter_path in filters:
            resolved_path = resolve_filter_path(filter_path, defaults_file)
            if resolved_path:
                validated_filters.append(resolved_path)
            else:
                raise ValueError(f"Filter not found in any of the searched locations: {filter_path}")

        return validated_filters

    def format_result_info(filters=None, defaults_file=None, validated_filters=None):
        """Format filter and defaults file information for result messages."""
        filter_info = ""
        defaults_info = ""

        if filters and validated_filters:
            filter_names = [os.path.basename(f) for f in validated_filters]
            filter_info = f" with filters: {', '.join(filter_names)}"

        if defaults_file:
            defaults_basename = os.path.basename(defaults_file)
            defaults_info = f" using defaults file: {defaults_basename}"

        return filter_info, defaults_info

    try:
        # Prepare conversion arguments
        extra_args = []

        # Add defaults file if provided - ensure it's properly formatted for Pandoc
        if defaults_file:
            # Make sure the path is absolute
            defaults_file_abs = os.path.abspath(defaults_file)
            extra_args.extend(["--defaults", defaults_file_abs])

        # Set environment variables for filters
        env = os.environ.copy()
        if output_file:
            # Set PANDOC_OUTPUT_DIR to the directory of the output file
            output_dir = os.path.dirname(os.path.abspath(output_file))
            env["PANDOC_OUTPUT_DIR"] = output_dir
        else:
            output_dir = None

        # Validate filters once and reuse the result
        validated_filters = validate_filters(filters, defaults_file) if filters else []

        # Handle filter arguments
        for filter_path in validated_filters:
            extra_args.extend(["--filter", filter_path])

        # Handle PDF-specific conversion if needed
        if output_format == "pdf":
            extra_args.extend([
                "--pdf-engine=xelatex",
                "-V", "geometry:margin=1in"
            ])

        # Handle reference doc for docx format
        if reference_doc and output_format == "docx":
            extra_args.extend([
                "--reference-doc", reference_doc
            ])

        # No special processing needed for content

        # Convert content using pypandoc
        if input_file:
            if not os.path.exists(input_file):
                raise ValueError(f"Input file not found: {input_file}")


            if output_file:
                # Convert file to file
                converted_output = pypandoc.convert_file(
                    input_file,
                    output_format,
                    outputfile=output_file,
                    extra_args=extra_args
                )

                # Create result message with filter and defaults information
                filter_info, defaults_info = format_result_info(filters, defaults_file, validated_filters)
                result_message = f"File successfully converted{filter_info}{defaults_info} and saved to: {output_file}"
            else:
                # Convert file to string
                converted_output = pypandoc.convert_file(
                    input_file,
                    output_format,
                    extra_args=extra_args
                )
        else:
            # No special processing needed for content

            if output_file:
                # Convert content to file
                pypandoc.convert_text(
                    contents,
                    output_format,
                    format=input_format,
                    outputfile=output_file,
                    extra_args=extra_args
                )

                # Create result message with filter and defaults information
                filter_info, defaults_info = format_result_info(filters, defaults_file, validated_filters)
                result_message = (
                    f"Content successfully converted{filter_info}{defaults_info} and saved to: {output_file}"
                )
            else:
                # Convert content to string
                converted_output = pypandoc.convert_text(
                    contents,
                    output_format,
                    format=input_format,
                    extra_args=extra_args
                )

        if output_file:
            notify_with_result = result_message
        else:
            if not converted_output:
                raise ValueError("Conversion resulted in empty output")

            # Add filter and defaults information to the notification
            filter_info, defaults_info = format_result_info(filters, defaults_file, validated_filters)
            # Adjust format for inline display
            if filter_info:
                filter_info = f" (with filters: {', '.join([os.path.basename(f) for f in validated_filters])})"
            if defaults_info:
                defaults_info = f" (using defaults file: {os.path.basename(defaults_file)})"

            notify_with_result = (
                f'Following are the converted contents in {output_format} format{filter_info}{defaults_info}.\n'
                f'Ask user if they expect to save this file. If so, provide the output_file parameter with '
                f'complete path.\n'
                f'Converted Contents:\n\n{converted_output}'
            )

        return [
            types.TextContent(
                type="text",
                text=notify_with_result
            )
        ]

    except Exception as e:
        # Handle Pandoc conversion errors
        error_prefix = "Error converting"
        error_details = str(e)

        if "Filter not found" in error_details or "Filter is not executable" in error_details:
            error_prefix = "Filter error during conversion"
        elif "defaults" in error_details and defaults_file:
            error_prefix = "Defaults file error during conversion"
            # Add more context about the defaults file
            error_details += f" (defaults file: {defaults_file})"
        elif "pandoc" in error_details.lower() and "not found" in error_details.lower():
            error_prefix = "Pandoc executable not found"
            error_details = "Please ensure Pandoc is installed and available in your PATH"

        error_msg = (
            f"{error_prefix} {'file' if input_file else 'contents'} from {input_format} to "
            f"{output_format}: {error_details}"
        )
        raise ValueError(error_msg) from e

async def main():
    """Run the mcp-pandoc server using stdin/stdout streams."""
    async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            InitializationOptions(
                server_name="mcp-pandoc",
                server_version="0.8.1",  # Universal MCP compatibility & SDK upgrade
                capabilities=server.get_capabilities(
                    notification_options=NotificationOptions(),
                    experimental_capabilities={},
                ),
            ),
        )

```