- Professional header with badges - Complete feature overview - Multiple installation methods (dotnet, Docker, Claude Desktop) - Configuration reference - Full API documentation for all 43+ MCP tools - Project structure and tech stack - Contributing guidelines - Troubleshooting section 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
📄 PaperlessMCP
Model Context Protocol Server for Paperless-ngx
Seamlessly integrate your Paperless-ngx document management system with Claude via the Model Context Protocol
Features • Installation • Configuration • Usage • API Reference • Contributing
🎯 Overview
PaperlessMCP is a powerful Model Context Protocol (MCP) server that bridges Paperless-ngx with AI assistants like Claude. It provides comprehensive document management capabilities through a modern, type-safe .NET implementation with built-in retry logic and error handling.
What is MCP?
The Model Context Protocol enables AI models to securely interact with external data sources and tools. PaperlessMCP implements this protocol to give Claude direct access to your document management system.
✨ Features
📚 Document Operations
- Search & Discovery - Full-text search with advanced filtering (tags, dates, correspondents, types)
- Document Management - Create, read, update, delete documents with metadata
- Bulk Operations - Process multiple documents at once (add/remove tags, set properties, reprocess)
- File Uploads - Support for base64 content or local file paths with automatic retries
- Download URLs - Get preview, thumbnail, and original file URLs
🏷️ Metadata Management
- Tags - Create, organize, and manage document tags with auto-tagging rules
- Correspondents - Track document sources and senders
- Document Types - Classify documents with custom types
- Storage Paths - Organize files with template-based storage paths
- Custom Fields - Define and assign custom metadata fields (string, date, boolean, monetary, etc.)
🔧 Developer Features
- Dual Transport - Supports both stdio (Claude Desktop) and HTTP transports
- Pagination - Efficient handling of large datasets with configurable page sizes
- Dry Run Mode - Preview destructive operations before execution
- Retry Logic - Built-in exponential backoff for transient failures
- Health Checks - Verify connectivity and discover API capabilities
- Comprehensive Tests - Full test suite with 100% coverage
🚀 Installation
Prerequisites
- .NET 10 SDK (or later)
- A running Paperless-ngx instance
- Paperless-ngx API token (How to get one)
Method 1: Clone and Build
# Clone the repository
git clone https://github.com/barryw/PaperlessMCP.git
cd PaperlessMCP
# Build the project
dotnet build
# Run tests (optional)
dotnet test
Method 2: Docker
# Build the Docker image
docker build -t paperless-mcp ./PaperlessMCP
# Run with environment variables
docker run -d \
-e PAPERLESS_BASE_URL=https://your-paperless-instance.com \
-e PAPERLESS_API_TOKEN=your-token-here \
-p 5000:5000 \
paperless-mcp
Method 3: Claude Desktop Integration
-
Build the project:
cd PaperlessMCP dotnet build -c Release -
Add to your Claude Desktop configuration (
claude_desktop_config.json):macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json{ "mcpServers": { "paperless": { "command": "dotnet", "args": [ "run", "--project", "/absolute/path/to/PaperlessMCP/PaperlessMCP", "--", "--stdio" ], "env": { "PAPERLESS_BASE_URL": "https://your-paperless-instance.com", "PAPERLESS_API_TOKEN": "your-token-here" } } } } -
Restart Claude Desktop
⚙️ Configuration
Environment Variables
PaperlessMCP supports multiple configuration methods. Environment variables take precedence over appsettings.json.
| Variable | Aliases | Required | Default | Description |
|---|---|---|---|---|
PAPERLESS_BASE_URL |
PAPERLESS_URL |
✅ Yes | - | Base URL of your Paperless-ngx instance |
PAPERLESS_API_TOKEN |
PAPERLESS_TOKEN |
✅ Yes | - | API authentication token |
MAX_PAGE_SIZE |
- | ❌ No | 100 |
Maximum items per page for paginated requests |
MCP_PORT |
- | ❌ No | 5000 |
HTTP server port (HTTP mode only) |
appsettings.json
Alternatively, configure via appsettings.json:
{
"Paperless": {
"BaseUrl": "https://your-paperless-instance.com",
"ApiToken": "your-token-here",
"MaxPageSize": 100
},
"Mcp": {
"Port": 5000
},
"Logging": {
"LogLevel": {
"Default": "Information",
"ModelContextProtocol": "Debug"
}
}
}
Transport Modes
- stdio mode (for Claude Desktop):
dotnet run -- --stdio - HTTP mode (for remote access):
dotnet run(default)
💻 Usage
With Claude Desktop
Once configured, simply ask Claude to interact with your documents:
"Search for all invoices from 2024"
"Upload this PDF to Paperless and tag it as 'Receipt'"
"Show me documents with the tag 'Important' that have no correspondent"
"Create a new tag called 'Urgent' with a red color"
HTTP Endpoint
When running in HTTP mode, the MCP endpoint is available at:
http://localhost:5000/mcp
Running the Server
# Stdio mode (Claude Desktop)
dotnet run --project PaperlessMCP/PaperlessMCP -- --stdio
# HTTP mode (remote access)
dotnet run --project PaperlessMCP/PaperlessMCP
# Docker
docker run -e PAPERLESS_BASE_URL=... -e PAPERLESS_API_TOKEN=... -p 5000:5000 paperless-mcp
📖 API Reference
Health & Capabilities
paperless.ping
Verify connectivity and authentication with Paperless-ngx.
Returns: Connection status and server version
paperless.capabilities
Return supported API endpoints and detected Paperless-ngx version information.
Returns: Available endpoints, bulk operations, and server capabilities
Document Operations
paperless.documents.search
Search for documents with full-text search and filters.
Parameters:
query(string, optional) - Full-text search querytags(string, optional) - Filter by tag IDs (comma-separated)tagsExclude(string, optional) - Exclude tag IDs (comma-separated)correspondent(int, optional) - Filter by correspondent IDdocumentType(int, optional) - Filter by document type IDstoragePath(int, optional) - Filter by storage path IDcreatedAfter(string, optional) - Filter by creation date (YYYY-MM-DD)createdBefore(string, optional) - Filter by creation date (YYYY-MM-DD)addedAfter(string, optional) - Filter by added date (YYYY-MM-DD)addedBefore(string, optional) - Filter by added date (YYYY-MM-DD)archiveSerialNumber(int, optional) - Filter by archive serial numberpage(int, default: 1) - Page numberpageSize(int, default: 25, max: 100) - Results per pageordering(string, optional) - Sort field (e.g., 'created', '-created', 'title')includeContent(bool, default: false) - Include document content in resultscontentMaxLength(int, default: 500) - Max content length whenincludeContent=true
Returns: Paginated list of document summaries
paperless.documents.get
Get a document by its ID.
Parameters:
id(int, required) - Document ID
Returns: Complete document details including content
paperless.documents.download
Get download URLs for a document's original file, preview, and thumbnail.
Parameters:
id(int, required) - Document ID
Returns: Object with download URLs (original_url, preview_url, thumbnail_url)
paperless.documents.preview
Get the preview URL for a document.
Parameters:
id(int, required) - Document ID
Returns: Preview URL
paperless.documents.thumbnail
Get the thumbnail URL for a document.
Parameters:
id(int, required) - Document ID
Returns: Thumbnail URL
paperless.documents.upload
Upload a new document to Paperless-ngx via base64-encoded content.
Parameters:
fileContent(string, required) - Base64-encoded file contentfileName(string, required) - Original filename with extensiontitle(string, optional) - Document titlecorrespondent(int, optional) - Correspondent IDdocumentType(int, optional) - Document type IDstoragePath(int, optional) - Storage path IDtags(string, optional) - Tag IDs (comma-separated)archiveSerialNumber(int, optional) - Archive serial numbercreated(string, optional) - Created date (YYYY-MM-DD)
Returns: Task ID and upload status
Note: For large files, use paperless.documents.upload_from_path instead.
paperless.documents.upload_from_path
Upload a document from a local file path. More reliable for large files.
Parameters:
filePath(string, required) - Absolute path to the filetitle(string, optional) - Document title (defaults to filename)correspondent(int, optional) - Correspondent IDdocumentType(int, optional) - Document type IDstoragePath(int, optional) - Storage path IDtags(string, optional) - Tag IDs (comma-separated)archiveSerialNumber(int, optional) - Archive serial numbercreated(string, optional) - Created date (YYYY-MM-DD)
Returns: Task ID, upload status, and file information
Features: Supports ~/ expansion, automatic retries, file validation
paperless.documents.update
Update document metadata.
Parameters:
id(int, required) - Document IDtitle(string, optional) - New titlecorrespondent(int, optional) - Correspondent ID (use -1 to clear)documentType(int, optional) - Document type ID (use -1 to clear)storagePath(int, optional) - Storage path ID (use -1 to clear)tags(string, optional) - Tag IDs to set (comma-separated)archiveSerialNumber(int, optional) - Archive serial numbercreated(string, optional) - Created date (YYYY-MM-DD)
Returns: Updated document
paperless.documents.delete
Delete a document. Requires explicit confirmation.
Parameters:
id(int, required) - Document IDconfirm(bool, default: false) - Must be true to confirm deletion
Returns: Deletion status or dry-run preview
Safety: Without confirm=true, returns a dry-run preview showing what would be deleted.
paperless.documents.bulk_update
Perform bulk operations on multiple documents.
Parameters:
documentIds(string, required) - Document IDs (comma-separated)operation(string, required) - Operation:add_tag,remove_tag,set_correspondent,set_document_type,set_storage_path,delete,reprocessvalue(int, optional) - Parameter value (e.g., tag ID, correspondent ID)dryRun(bool, default: true) - Preview changes without applyingconfirm(bool, default: false) - Must be true to execute
Returns: Affected document IDs and operation status
Safety: Defaults to dry-run mode to prevent accidental changes.
paperless.documents.reprocess
Reprocess a document's OCR and content extraction.
Parameters:
id(int, required) - Document IDconfirm(bool, default: false) - Must be true to confirm reprocessing
Returns: Processing status
Tag Operations
paperless.tags.list
List all tags with pagination.
Parameters:
page(int, default: 1) - Page numberpageSize(int, default: 25, max: 100) - Results per pageordering(string, optional) - Sort field (e.g., 'name', '-document_count')
Returns: Paginated list of tags
paperless.tags.get
Get a tag by its ID.
Parameters:
id(int, required) - Tag ID
Returns: Tag details
paperless.tags.create
Create a new tag.
Parameters:
name(string, required) - Tag namecolor(string, optional) - Hex color (e.g., '#ff0000')match(string, optional) - Match pattern for auto-taggingmatchingAlgorithm(int, optional) - Matching algorithm (0=None, 1=Any, 2=All, 3=Literal, 4=Regex, 5=Fuzzy, 6=Auto)isInboxTag(bool, optional) - Mark as inbox tag
Returns: Created tag
paperless.tags.update
Update an existing tag.
Parameters:
id(int, required) - Tag IDname(string, optional) - New namecolor(string, optional) - Hex colormatch(string, optional) - Match patternmatchingAlgorithm(int, optional) - Matching algorithmisInboxTag(bool, optional) - Inbox tag status
Returns: Updated tag
paperless.tags.delete
Delete a tag. Requires explicit confirmation.
Parameters:
id(int, required) - Tag IDconfirm(bool, default: false) - Must be true to confirm deletion
Returns: Deletion status or dry-run preview
paperless.tags.bulk_delete
Delete multiple tags.
Parameters:
tagIds(string, required) - Tag IDs (comma-separated)dryRun(bool, default: true) - Preview changes without applyingconfirm(bool, default: false) - Must be true to execute
Returns: Affected tag IDs and operation status
Correspondent Operations
paperless.correspondents.list
List all correspondents with pagination.
Parameters:
page(int, default: 1) - Page numberpageSize(int, default: 25, max: 100) - Results per pageordering(string, optional) - Sort field (e.g., 'name', '-document_count', 'last_correspondence')
Returns: Paginated list of correspondents
paperless.correspondents.get
Get a correspondent by its ID.
Parameters:
id(int, required) - Correspondent ID
Returns: Correspondent details
paperless.correspondents.create
Create a new correspondent.
Parameters:
name(string, required) - Correspondent namematch(string, optional) - Match pattern for auto-assignmentmatchingAlgorithm(int, optional) - Matching algorithm (0=None, 1=Any, 2=All, 3=Literal, 4=Regex, 5=Fuzzy, 6=Auto)
Returns: Created correspondent
paperless.correspondents.update
Update an existing correspondent.
Parameters:
id(int, required) - Correspondent IDname(string, optional) - New namematch(string, optional) - Match patternmatchingAlgorithm(int, optional) - Matching algorithm
Returns: Updated correspondent
paperless.correspondents.delete
Delete a correspondent. Requires explicit confirmation.
Parameters:
id(int, required) - Correspondent IDconfirm(bool, default: false) - Must be true to confirm deletion
Returns: Deletion status or dry-run preview
paperless.correspondents.bulk_delete
Delete multiple correspondents.
Parameters:
correspondentIds(string, required) - Correspondent IDs (comma-separated)dryRun(bool, default: true) - Preview changes without applyingconfirm(bool, default: false) - Must be true to execute
Returns: Affected correspondent IDs and operation status
Document Type Operations
paperless.document_types.list
List all document types with pagination.
Parameters:
page(int, default: 1) - Page numberpageSize(int, default: 25, max: 100) - Results per pageordering(string, optional) - Sort field (e.g., 'name', '-document_count')
Returns: Paginated list of document types
paperless.document_types.get
Get a document type by its ID.
Parameters:
id(int, required) - Document type ID
Returns: Document type details
paperless.document_types.create
Create a new document type.
Parameters:
name(string, required) - Document type namematch(string, optional) - Match pattern for auto-assignmentmatchingAlgorithm(int, optional) - Matching algorithm (0=None, 1=Any, 2=All, 3=Literal, 4=Regex, 5=Fuzzy, 6=Auto)
Returns: Created document type
paperless.document_types.update
Update an existing document type.
Parameters:
id(int, required) - Document type IDname(string, optional) - New namematch(string, optional) - Match patternmatchingAlgorithm(int, optional) - Matching algorithm
Returns: Updated document type
paperless.document_types.delete
Delete a document type. Requires explicit confirmation.
Parameters:
id(int, required) - Document type IDconfirm(bool, default: false) - Must be true to confirm deletion
Returns: Deletion status or dry-run preview
paperless.document_types.bulk_delete
Delete multiple document types.
Parameters:
documentTypeIds(string, required) - Document type IDs (comma-separated)dryRun(bool, default: true) - Preview changes without applyingconfirm(bool, default: false) - Must be true to execute
Returns: Affected document type IDs and operation status
Storage Path Operations
paperless.storage_paths.list
List all storage paths with pagination.
Parameters:
page(int, default: 1) - Page numberpageSize(int, default: 25, max: 100) - Results per pageordering(string, optional) - Sort field (e.g., 'name', '-document_count')
Returns: Paginated list of storage paths
paperless.storage_paths.get
Get a storage path by its ID.
Parameters:
id(int, required) - Storage path ID
Returns: Storage path details
paperless.storage_paths.create
Create a new storage path.
Parameters:
name(string, required) - Storage path namepath(string, required) - Path template (e.g.,{correspondent}/{document_type})match(string, optional) - Match pattern for auto-assignmentmatchingAlgorithm(int, optional) - Matching algorithm (0=None, 1=Any, 2=All, 3=Literal, 4=Regex, 5=Fuzzy, 6=Auto)
Returns: Created storage path
paperless.storage_paths.update
Update an existing storage path.
Parameters:
id(int, required) - Storage path IDname(string, optional) - New namepath(string, optional) - Path templatematch(string, optional) - Match patternmatchingAlgorithm(int, optional) - Matching algorithm
Returns: Updated storage path
paperless.storage_paths.delete
Delete a storage path. Requires explicit confirmation.
Parameters:
id(int, required) - Storage path IDconfirm(bool, default: false) - Must be true to confirm deletion
Returns: Deletion status or dry-run preview
paperless.storage_paths.bulk_delete
Delete multiple storage paths.
Parameters:
storagePathIds(string, required) - Storage path IDs (comma-separated)dryRun(bool, default: true) - Preview changes without applyingconfirm(bool, default: false) - Must be true to execute
Returns: Affected storage path IDs and operation status
Custom Field Operations
paperless.custom_fields.list
List all custom field definitions with pagination.
Parameters:
page(int, default: 1) - Page numberpageSize(int, default: 25, max: 100) - Results per page
Returns: Paginated list of custom field definitions
paperless.custom_fields.get
Get a custom field definition by its ID.
Parameters:
id(int, required) - Custom field ID
Returns: Custom field details
paperless.custom_fields.create
Create a new custom field definition.
Parameters:
name(string, required) - Custom field namedataType(string, required) - Data type:string,url,date,boolean,integer,float,monetary,documentlink,selectselectOptions(string, optional) - Select options (comma-separated, for 'select' type only)defaultCurrency(string, optional) - Default currency (for 'monetary' type only)
Returns: Created custom field
paperless.custom_fields.update
Update an existing custom field definition.
Parameters:
id(int, required) - Custom field IDname(string, optional) - New nameselectOptions(string, optional) - Select options (comma-separated)defaultCurrency(string, optional) - Default currency
Returns: Updated custom field
paperless.custom_fields.delete
Delete a custom field definition. Requires explicit confirmation.
Parameters:
id(int, required) - Custom field IDconfirm(bool, default: false) - Must be true to confirm deletion
Returns: Deletion status or dry-run preview
paperless.custom_fields.assign
Assign a custom field value to a document.
Parameters:
documentId(int, required) - Document IDfieldId(int, required) - Custom field IDvalue(string, required) - Value to assign (string, number, boolean, or date depending on field type)
Returns: Assignment status and assigned value
🛠️ Development
Building from Source
# Clone the repository
git clone https://github.com/barryw/PaperlessMCP.git
cd PaperlessMCP
# Restore dependencies
dotnet restore
# Build
dotnet build
# Run tests
dotnet test
# Build for release
dotnet build -c Release
Running Tests
# Run all tests
dotnet test
# Run tests with coverage
dotnet test /p:CollectCoverage=true
# Run specific test file
dotnet test --filter "FullyQualifiedName~DocumentToolsTests"
Project Structure
PaperlessMCP/
├── PaperlessMCP/ # Main application
│ ├── Client/ # Paperless API client
│ │ ├── PaperlessClient.cs # Main API client implementation
│ │ └── PaperlessAuthHandler.cs # Authentication handler
│ ├── Configuration/ # Configuration options
│ │ └── PaperlessOptions.cs # Connection configuration
│ ├── Models/ # Data models
│ │ ├── Common/ # Shared models
│ │ ├── Documents/ # Document models
│ │ ├── Tags/ # Tag models
│ │ ├── Correspondents/ # Correspondent models
│ │ ├── DocumentTypes/ # Document type models
│ │ ├── StoragePaths/ # Storage path models
│ │ └── CustomFields/ # Custom field models
│ ├── Tools/ # MCP tool implementations
│ │ ├── HealthTools.cs # Health checks
│ │ ├── DocumentTools.cs # Document operations
│ │ ├── TagTools.cs # Tag operations
│ │ ├── CorrespondentTools.cs # Correspondent operations
│ │ ├── DocumentTypeTools.cs # Document type operations
│ │ ├── StoragePathTools.cs # Storage path operations
│ │ └── CustomFieldTools.cs # Custom field operations
│ ├── Program.cs # Application entry point
│ ├── Dockerfile # Docker configuration
│ └── appsettings.json # Default configuration
├── PaperlessMCP.Tests/ # Test project
│ ├── Client/ # Client tests
│ ├── Tools/ # Tool tests
│ └── Fixtures/ # Test fixtures
└── PaperlessMCP.sln # Solution file
Technology Stack
- .NET 10 - Modern, cross-platform framework
- ModelContextProtocol 0.2.0-preview.1 - MCP server implementation
- Polly 8.5.2 - Resilience and transient fault handling
- xUnit - Testing framework
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
Guidelines
- Fork the repository and create your branch from
main - Write tests for any new functionality
- Ensure all tests pass with
dotnet test - Follow the existing code style and conventions
- Update documentation for any API changes
- Submit a pull request with a clear description of changes
Development Setup
# Fork and clone your fork
git clone https://github.com/YOUR-USERNAME/PaperlessMCP.git
cd PaperlessMCP
# Create a feature branch
git checkout -b feature/your-feature-name
# Make changes and test
dotnet test
# Commit and push
git add .
git commit -m "Add your feature"
git push origin feature/your-feature-name
🐛 Troubleshooting
Common Issues
Connection Errors
Problem: "Failed to connect to Paperless instance"
Solution:
- Verify
PAPERLESS_BASE_URLis correct and accessible - Ensure your Paperless-ngx instance is running
- Check network connectivity and firewall rules
- Verify API endpoint:
curl https://your-instance/api/
Authentication Errors
Problem: "401 Unauthorized"
Solution:
- Verify your
PAPERLESS_API_TOKENis correct - Generate a new token in Paperless-ngx: Settings → API → Tokens
- Ensure the token has necessary permissions
Upload Failures
Problem: Document uploads timeout or fail
Solution:
- For large files, use
paperless.documents.upload_from_pathinstead of base64 upload - Check file size limits in Paperless-ngx configuration
- Verify disk space on Paperless-ngx server
- Check Paperless-ngx logs for processing errors
Claude Desktop Integration
Problem: Server doesn't appear in Claude Desktop
Solution:
- Verify JSON syntax in
claude_desktop_config.json - Use absolute paths (no relative paths or
~) - Restart Claude Desktop completely
- Check Claude Desktop logs for errors
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments
- Paperless-ngx - The excellent document management system
- Model Context Protocol - For the MCP specification
- Anthropic - For Claude and the MCP implementation
📞 Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Paperless-ngx Docs: docs.paperless-ngx.com
- MCP Docs: modelcontextprotocol.io
Made with ❤️ for the Paperless-ngx and MCP communities