A Model Context Protocol (MCP) server that provides secure integration with Stirling PDF, enabling AI assistants to perform comprehensive PDF manipulation operations.
Built with TypeScript for type safety and modern development practices.
v1.1.0 - December 2025
- ✅ Fixed multipart/form-data handling - Switched from native
fetchtoaxiosfor reliable file uploads from Docker containers - ✅ Fixed watermark functionality - Resolved "colorString is null" error by ensuring
customColorparameter is properly sent - ✅ Improved error handling - Now displays actual error messages from Stirling PDF API
- ✅ Verified Docker MCP Gateway compatibility - Fully tested with Docker Desktop MCP Toolkit
This MCP server provides a secure interface for AI assistants to interact with a self-hosted Stirling PDF instance, enabling powerful PDF manipulation capabilities directly from Claude Desktop or other MCP-compatible clients.
merge_pdfs- Merge multiple PDF files into a single documentsplit_pdf- Split a PDF into multiple files at specified page numberscompress_pdf- Compress PDF files to reduce size with configurable optimization levelsconvert_pdf_to_images- Convert PDF pages to image files (PNG, JPG, GIF)rotate_pdf- Rotate pages in a PDF documentadd_watermark- Add text watermarks to PDF documentsremove_pages- Remove specified pages from a PDFextract_images- Extract all images from a PDF documentconvert_images_to_pdf- Convert one or more images to a PDF documentocr_pdf- Perform OCR on a PDF to make it searchable
- Node.js 20 or higher
- Docker Desktop with MCP Toolkit enabled
- Docker MCP CLI plugin (
docker mcpcommand) - Stirling PDF instance - A running Stirling PDF server (self-hosted or accessible)
- Download from: https://github.com/Stirling-Tools/Stirling-PDF
- Quick start with Docker:
docker run -d -p 8080:8080 frooodle/s-pdf:latest
You need a running Stirling PDF instance to use this MCP server. Options:
-
Docker (Recommended):
docker run -d \ -p 8080:8080 \ -v ./configs:/configs \ -v ./logs:/logs \ -e DOCKER_ENABLE_SECURITY=false \ --name stirling-pdf \ frooodle/s-pdf:latest
-
Docker Compose: See the official documentation
-
Self-hosted: Follow the installation guide
# Project files are already in the repository
cd mcp-server-stirling-pdfnpm installKey Dependencies:
@modelcontextprotocol/sdk- MCP protocol implementationaxios- HTTP client for reliable multipart/form-data uploadsform-data- Multipart form data librarytypescript- Type-safe development
npm run builddocker build -t stirling-pdf-mcp-server .# Set your Stirling PDF instance URL
docker mcp secret set STIRLING_PDF_URL="http://host.docker.internal:8080"
# If your Stirling PDF has authentication enabled, set the API key
docker mcp secret set STIRLING_PDF_API_KEY="your-api-key-here"
# Verify secrets
docker mcp secret listNote: Use http://host.docker.internal:8080 to connect to Stirling PDF running on your host machine.
# Create catalogs directory if it doesn't exist
mkdir -p ~/.docker/mcp/catalogs
# Create or edit custom.yaml
nano ~/.docker/mcp/catalogs/custom.yamlAdd this entry to custom.yaml:
version: 2
name: custom
displayName: Custom MCP Servers
registry:
stirling-pdf:
description: "MCP server for Stirling PDF - comprehensive PDF manipulation capabilities"
title: "Stirling PDF"
type: server
dateAdded: "2025-12-17T00:00:00Z"
image: stirling-pdf-mcp-server:latest
ref: ""
readme: ""
toolsUrl: ""
source: ""
upstream: ""
icon: ""
tools:
- name: merge_pdfs
- name: split_pdf
- name: compress_pdf
- name: convert_pdf_to_images
- name: rotate_pdf
- name: add_watermark
- name: remove_pages
- name: extract_images
- name: convert_images_to_pdf
- name: ocr_pdf
secrets:
- name: STIRLING_PDF_URL
env: STIRLING_PDF_URL
example: "http://host.docker.internal:8080"
- name: STIRLING_PDF_API_KEY
env: STIRLING_PDF_API_KEY
example: "your-api-key-here"
metadata:
category: productivity
tags:
- pdf
- documents
- conversion
- ocr
license: GPL-3.0
owner: local# Edit registry file
nano ~/.docker/mcp/registry.yamlAdd this entry under the existing registry: key:
registry:
# ... existing servers ...
stirling-pdf:
ref: ""IMPORTANT: The entry must be under the registry: key, not at the root level.
Find your Claude Desktop config file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
Edit the file and add your custom catalog to the args array:
{
"mcpServers": {
"mcp-toolkit-gateway": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-v", "/var/run/docker.sock:/var/run/docker.sock",
"-v", "/Users/your_username/.docker/mcp:/mcp",
"docker/mcp-gateway",
"--catalog=/mcp/catalogs/docker-mcp.yaml",
"--catalog=/mcp/catalogs/custom.yaml",
"--config=/mcp/config.yaml",
"--registry=/mcp/registry.yaml",
"--tools-config=/mcp/tools.yaml",
"--transport=stdio"
]
}
}
}Replace /Users/your_username with your actual home directory path:
- macOS:
/Users/your_username - Windows:
C:\\Users\\your_username(use double backslashes) - Linux:
/home/your_username
- Quit Claude Desktop completely
- Start Claude Desktop again
- Your Stirling PDF tools should now appear!
# Verify it appears in the list
docker mcp server list | grep stirling
# Test the server manually (optional)
docker run --rm -i \
-e STIRLING_PDF_URL=http://host.docker.internal:8080 \
-e STIRLING_PDF_API_KEY=your-api-key \
stirling-pdf-mcp-server:latest \
node /app/dist/index.js <<'EOF'
{"jsonrpc":"2.0","id":1,"method":"tools/list"}
EOF
# You should see a JSON response listing all 10 tools
# Check logs if needed
docker ps
docker logs <container_name>Testing in Claude Desktop: After restarting Claude, ask:
- "What PDF tools do you have available?"
- "Add a watermark saying 'TEST' to this PDF" (upload a PDF file)
# Install dependencies
npm install
# Run in development mode with auto-reload
npm run dev
# Type check
npm run typecheck
# Build
npm run build
# Run production build
npm start# Set environment variables for testing
export STIRLING_PDF_URL="http://localhost:8080"
export STIRLING_PDF_API_KEY="test-key"
# Run directly
npm startIn Claude Desktop, you can ask:
- "Merge these two PDF files into one"
- "Combine all the PDF documents I just sent you"
- "Split this PDF at pages 3 and 7"
- "Break this PDF into separate files at page 5"
- "Compress this PDF to make it smaller"
- "Reduce the file size of this PDF with high compression"
- "Convert this PDF to PNG images at 300 DPI"
- "Turn these images into a single PDF"
- "Rotate all pages in this PDF 90 degrees"
- "Rotate pages 2, 3, and 4 by 180 degrees"
- "Add a 'DRAFT' watermark to this PDF"
- "Add 'CONFIDENTIAL' as a watermark with 30% opacity"
- "Remove pages 1, 3, and 5 from this PDF"
- "Extract all images from this PDF"
- "Make this scanned PDF searchable using OCR"
- "Perform OCR on this PDF in English and Spanish"
Claude Desktop → MCP Gateway → Stirling PDF MCP Server → Stirling PDF Instance
↓
Docker Desktop Secrets
(URL, API Key)
- Language: TypeScript (strict mode)
- HTTP Client: Axios (for reliable multipart/form-data handling)
- MCP SDK: @modelcontextprotocol/sdk
- Transport: stdio (standard input/output)
- Form Data: form-data library for multipart uploads
- Runtime: Node.js 20+
All PDF and image files are passed as base64 data URLs. Claude Desktop handles this automatically when you upload files.
Example format:
data:application/pdf;base64,JVBERi0xLjQKJeLjz9MKM...
- Type Safety: Catch errors at compile time
- Better IDE Support: Enhanced autocomplete and refactoring
- Modern JavaScript: Use latest ECMAScript features
- Maintainability: Self-documenting code with types
- API Type Definitions: Strongly typed Stirling PDF API interactions
- Define the tool function in
src/index.ts:
async function myNewTool(param: string): Promise<string> {
try {
validateRequired(param, "param");
const formData = new FormData();
const buffer = base64ToBuffer(param); // Convert base64 data URL to Buffer
formData.append("fileInput", buffer, "input.pdf");
// Add additional parameters as needed
formData.append("paramName", "paramValue");
const resultBuffer = await callStirlingAPI("/api/v1/endpoint", formData);
const resultBase64 = bufferToBase64DataUrl(resultBuffer);
return `✅ Success message\n\n📄 Result:\n${resultBase64}`;
} catch (error) {
logger.error("Error:", error);
return formatError(error);
}
}Note: The callStirlingAPI function uses axios for reliable multipart/form-data handling. All file uploads must use Buffer objects, not the raw FormData from the browser.
- Add tool definition to
TOOLSarray:
{
name: "my_new_tool",
description: "What it does",
inputSchema: {
type: "object",
properties: {
param: { type: "string", description: "Description" }
},
required: ["param"]
}
}- Add case to tool handler:
case "my_new_tool": {
const param = (args?.param as string) || "";
return {
content: [{ type: "text", text: await myNewTool(param) }]
};
}- Rebuild and redeploy:
npm run build
docker build -t stirling-pdf-mcp-server .- Verify Docker image built successfully:
docker images | grep stirling-pdf - Check catalog file syntax:
cat ~/.docker/mcp/catalogs/custom.yaml - Ensure Claude Desktop config includes custom catalog
- Restart Claude Desktop completely
- Verify Stirling PDF is running:
curl http://localhost:8080 - Check if using correct URL in secrets:
docker mcp secret list - Use
host.docker.internalinstead oflocalhostfor host-based Stirling PDF - Check firewall settings
- Verify secrets are set:
docker mcp secret list - Check if Stirling PDF has security enabled
- Verify API key is correct in Stirling PDF settings
- Test API key with curl:
curl -H "X-API-KEY: your-key" http://localhost:8080/api/v1/info/status
- Check TypeScript version compatibility:
npm run typecheck - Ensure all dependencies are installed:
npm install - Clear node_modules and reinstall:
rm -rf node_modules && npm install
- Check file size limits in Stirling PDF configuration
- Verify PDF files are valid and not corrupted
- Check Stirling PDF logs:
docker logs stirling-pdf - Ensure timeout is sufficient for large files (default: 2 minutes)
If you see Cannot invoke "String.startsWith(String)" because "colorString" is null:
- This error occurs when the
customColorparameter is missing or malformed - The MCP server automatically includes
customColor=#000000(black watermark) - This was fixed in the latest version by using axios instead of fetch
- If you modified the code, ensure
customColorparameter is included in watermark requests
- Verify you're using axios: The native Node.js
fetchAPI has issues with multipart/form-data from Docker containers - Check parameter format: Stirling PDF is sensitive to parameter formats
- Review Stirling PDF logs:
docker logs stirling-pdf --tail 50shows the actual Java errors - Test with curl: Verify the API works outside the MCP server:
curl -X POST http://localhost:8080/api/v1/security/add-watermark \ -H "X-API-KEY: your-key" \ -F "fileInput=@test.pdf" \ -F "watermarkType=text" \ -F "watermarkText=TEST" \ -F "customColor=#000000"
- All secrets stored in Docker Desktop secrets (never hardcoded)
- API key transmitted securely via X-API-KEY header
- Running as non-root user in Docker container
- Sensitive data never logged
- Input validation on all parameters
- Timeout protection on external calls (2 minute timeout)
- Stirling PDF runs locally - no data sent to external services
For complete Stirling PDF API documentation, visit:
- Your instance:
http://your-instance:port/swagger-ui/index.html - Official docs: https://docs.stirlingpdf.com/API/
- Stirling PDF API Documentation
- Stirling PDF GitHub Repository
- Stirling PDF Getting Started Guide
- API Reference on DeepWiki
GPL-3.0
This project follows the GPL-3.0 license. All modifications must comply with GPL-3.0 requirements.
Empowering AI assistants with comprehensive PDF manipulation capabilities.