#!/usr/bin/env node
/**
* Nutrient DWS API MCP Server
*
* This server provides a Model Context Protocol (MCP) interface to the Nutrient DWS Processor API.
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { AiRedactArgsSchema, BuildAPIArgsSchema, DirectoryTreeArgsSchema, SignAPIArgsSchema } from './schemas.js'
import { performBuildCall } from './dws/build.js'
import { performSignCall } from './dws/sign.js'
import { performAiRedactCall } from './dws/ai-redact.js'
import { performDirectoryTreeCall } from './fs/directoryTree.js'
import { setSandboxDirectory } from './fs/sandbox.js'
import { createErrorResponse } from './responses.js'
import { getVersion } from './version.js'
import { parseSandboxPath } from './utils/sandbox.js'
const server = new McpServer(
{
name: 'nutrient-dws-mcp-server',
version: getVersion(),
},
{
capabilities: {
tools: {},
logging: {},
},
},
)
function addToolsToServer(server: McpServer, sandboxEnabled: boolean = false) {
server.tool(
'document_processor',
`Processes documents using Nutrient DWS Processor API. Reads from and writes to file system or sandbox (if enabled).
Features:
• Import XFDF annotations
• Flatten annotations
• OCR processing
• Page rotation
• Watermarking (text/image)
• Redaction creation and application
Output formats: PDF, PDF/A, images (PNG, JPEG, WebP), JSON extraction, Office (DOCX, XLSX, PPTX)`,
BuildAPIArgsSchema.shape,
async ({ instructions, outputPath }) => {
try {
return performBuildCall(instructions, outputPath)
} catch (error) {
return createErrorResponse(`Error: ${error instanceof Error ? error.message : String(error)}`)
}
},
)
server.tool(
'document_signer',
`Digitally signs PDF files using Nutrient DWS Sign API. Reads from and writes to file system or sandbox (if enabled).
Signature types:
• CMS/PKCS#7 (standard digital signatures)
• CAdES (advanced electronic signatures)
Appearance options:
• Visible or invisible signatures
• Multiple display modes (signature only, description only, or both)
• Customizable elements (signer name, reason, location, date)
• Support for watermarks and custom graphics
Positioning:
• Place on specific page coordinates
• Use existing signature form fields`,
SignAPIArgsSchema.shape,
async ({ filePath, signatureOptions, watermarkImagePath, graphicImagePath, outputPath }) => {
try {
return performSignCall(filePath, outputPath, signatureOptions, watermarkImagePath, graphicImagePath)
} catch (error) {
return createErrorResponse(`Error: ${error instanceof Error ? error.message : String(error)}`)
}
},
)
server.tool(
'ai_redactor',
`AI-powered document redaction using Nutrient DWS AI Redaction API. Reads from and writes to file system or sandbox (if enabled).
Automatically detects and permanently removes sensitive information from documents using AI analysis.
Detected content types include:
• Personally identifiable information (names, addresses, phone numbers)
• Financial data (credit card numbers, bank accounts, SSNs)
• Email addresses and URLs
• Protected health information (PHI)
• Any custom criteria you specify
By default (when neither stage nor apply is set), redactions are detected and immediately applied. Set stage to true to detect and stage redactions without applying them. Set apply to true to apply previously staged redactions.`,
AiRedactArgsSchema.shape,
async ({ filePath, criteria, outputPath, stage, apply }) => {
try {
return performAiRedactCall(filePath, criteria, outputPath, stage, apply)
} catch (error) {
return createErrorResponse(`Error: ${error instanceof Error ? error.message : String(error)}`)
}
},
)
if (sandboxEnabled) {
server.tool(
'sandbox_file_tree',
'Returns the file tree of the sandbox directory. It will recurse into subdirectories and return a list of files and directories.',
{},
async () => performDirectoryTreeCall('.'),
)
} else {
server.tool(
'directory_tree',
'Returns the directory tree of a given path. All paths are resolved relative to root directory.',
DirectoryTreeArgsSchema.shape,
async ({ path }) => performDirectoryTreeCall(path),
)
}
}
async function parseCommandLineArgs() {
const args = process.argv.slice(2)
try {
const sandboxDir = parseSandboxPath(args, process.env.SANDBOX_PATH) || null
return { sandboxDir }
} catch (error) {
await server.server.sendLoggingMessage({
level: 'error',
data: `Error: ${error instanceof Error ? error.message : String(error)}`,
})
process.exit(1)
}
}
export async function runServer() {
const { sandboxDir } = await parseCommandLineArgs()
if (sandboxDir) {
try {
await setSandboxDirectory(sandboxDir)
} catch (error) {
console.error(`Error setting sandbox directory: ${error instanceof Error ? error.message : String(error)}`)
process.exit(1)
}
} else {
console.warn(
'Info: No sandbox directory specified. File operations will not be restricted.\n' +
'Sandboxed mode is recommended - To enable sandboxed mode and restrict file operations, set SANDBOX_PATH environment variable',
)
}
addToolsToServer(server, sandboxDir !== null)
const transport = new StdioServerTransport()
await server.connect(transport)
return server
}
runServer()
.then(async (server) => {
server.server.getClientCapabilities()
await server.server.sendLoggingMessage({
level: 'info',
data: `Nutrient DWS MCP Server ${getVersion()} running.`,
})
})
.catch((error) => {
console.error('Fatal error running server:', error)
process.exit(1)
})
process.stdin.on('close', async () => {
await server.server.sendLoggingMessage({
level: 'info',
data: `Nutrient DWS MCP Server ${getVersion()} closed.`,
})
await server.close()
})