# 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
[](https://pypi.python.org/pypi/mcp-pandoc)
[](https://github.com/vivekVells/mcp-pandoc/actions/workflows/ci.yml)
<br />

<!-- [](https://pepy.tech/project/mcp-pandoc) -->
<!--  -->
<!--
[](https://pypi.python.org/pypi/mcp-pandoc)
[](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>
-->
[](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
[](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={},
),
),
)
```