Skip to main content
Glama
PSPDFKit

Nutrient Document Engine MCP Server

by PSPDFKit
addWatermark.ts7.61 kB
import { z } from 'zod'; import { handleApiError } from '../../utils/ErrorHandling.js'; import { DocumentEngineClient } from '../../api/Client.js'; import { BuildInstructions, ImageWatermarkAction, TextWatermarkAction, } from '../../api/DocumentEngineSchema.js'; import { DocumentFingerprint, DocumentFingerprintSchema, } from '../schemas/DocumentFingerprintSchema.js'; import { applyDocumentInstructions, getDocumentInfo } from '../../api/DocumentLayerAbstraction.js'; import { MCPToolOutput } from '../../mcpTools.js'; /** * Add Watermark Tool * * Adds a text or image watermark to all pages of a document using the Document Engine API. * Supports customization of opacity, font size, and rotation. */ export const AddWatermarkSchema = { document_fingerprint: DocumentFingerprintSchema, watermark_type: z.enum(['text', 'image']), content: z.string().min(1, 'Content is required (text content or image URL)'), opacity: z.number().min(0).max(1).optional().default(0.7), rotation: z .number() .optional() .default(0) .describe(' Rotation of the watermark in counterclockwise degrees.'), }; const AddWatermarkInputSchema = z.object(AddWatermarkSchema); type AddWatermarkInput = z.infer<typeof AddWatermarkInputSchema>; /** * Create text watermark action * * Note: Using string values for width/height instead of WatermarkDimension objects * because the actual API expects strings, not the complex dimension objects in the schema. * Position support is temporarily removed as it's not currently supported by the API. */ function createTextWatermarkAction( content: string, opacity: number, rotation: number = 0 ): TextWatermarkAction { // Using 'any' type to bypass TypeScript errors while schema is being updated // to match actual API expectations (strings vs WatermarkDimension objects) return { type: 'watermark', text: content, width: '100%', // String format required by API height: '100%', // String format required by API opacity, rotation, // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Watermark action object has flexible structure for API } as any; } /** * Create image watermark action * * Note: Using string values for width/height instead of WatermarkDimension objects * because the actual API expects strings, not the complex dimension objects in the schema. * Position support is temporarily removed as it's not currently supported by the API. */ function createImageWatermarkAction( imageUrl: string, opacity: number, rotation: number = 0 ): ImageWatermarkAction { // Using 'as any' to bypass TypeScript errors while schema is being updated // to match actual API expectations (strings vs WatermarkDimension objects) return { type: 'watermark', image: { url: imageUrl }, width: '25%', // String format required by API height: '15%', // String format required by API opacity, rotation, // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Watermark action object has flexible structure for API } as any; } /** * Apply watermark using layer-aware document instructions API */ async function applyWatermark( client: DocumentEngineClient, fingerprint: DocumentFingerprint, watermarkAction: TextWatermarkAction | ImageWatermarkAction ): Promise<DocumentFingerprint> { try { // Create build instructions to watermark the existing document const buildInstructions: BuildInstructions = { parts: [ { document: { id: '#self', }, }, ], actions: [watermarkAction], }; await applyDocumentInstructions(client, fingerprint, buildInstructions); return fingerprint; } catch (error: unknown) { throw handleApiError(error); } } /** * Add watermark to a document */ export async function addWatermark( client: DocumentEngineClient, params: AddWatermarkInput ): Promise<MCPToolOutput> { const startTime = Date.now(); try { // Validate input const validatedParams = AddWatermarkInputSchema.parse(params); const { document_fingerprint, watermark_type, content, opacity, rotation } = validatedParams; // Get original document information const documentInfo = await getDocumentInfo(client, document_fingerprint); const pageCount = documentInfo.pageCount; // Create watermark action based on type let watermarkAction: TextWatermarkAction | ImageWatermarkAction; if (watermark_type === 'text') { watermarkAction = createTextWatermarkAction(content, opacity, rotation); } else { // Validate URL format for image watermarks try { new URL(content); } catch { throw new Error('Invalid image URL provided'); } watermarkAction = createImageWatermarkAction(content, opacity, rotation); } // Apply watermark using layer-aware document instructions API const buildResult = await applyWatermark(client, document_fingerprint, watermarkAction); const processingTime = Date.now() - startTime; // Build the markdown response let markdown = `# Watermark Applied Successfully\n\n`; markdown += `✅ **Status:** Watermark added to all pages \n`; markdown += `📄 **Document ID:** ${buildResult.document_id} \n`; if (document_fingerprint.layer) { markdown += `🔀 **Layer:** ${document_fingerprint.layer} \n`; } markdown += `📊 **Pages Watermarked:** ${pageCount} \n\n`; markdown += `---\n\n`; // Watermark details section markdown += `## Watermark Details\n`; markdown += `- **Type:** ${watermark_type === 'text' ? 'Text watermark' : 'Image watermark'}\n`; markdown += `- **Content:** ${watermark_type === 'text' ? `"${content}"` : `Image from ${content}`}\n`; markdown += `- **Rotation:** ${rotation}°\n`; markdown += `- **Opacity:** ${Math.round(opacity * 100)}%\n`; markdown += `\n---\n\n`; // Application summary section markdown += `## Application Summary\n`; markdown += `- **Pages Processed:** ${pageCount}/${pageCount}\n`; markdown += `- **Processing Time:** ${(processingTime / 1000).toFixed(1)} seconds\n`; markdown += `- **Status:** Complete\n\n`; markdown += `---\n\n`; return { markdown }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); // Return error in markdown format let errorMarkdown = `# Error Adding Watermark\n\n`; errorMarkdown += `An error occurred while trying to add watermark to the document: ${errorMessage}\n\n`; errorMarkdown += `**Document ID:** ${params.document_fingerprint?.document_id || 'Unknown'} \n`; if (params.document_fingerprint?.layer) { errorMarkdown += `**Layer:** ${params.document_fingerprint.layer} \n`; } errorMarkdown += `**Watermark Type:** ${params.watermark_type} \n`; errorMarkdown += `**Content:** ${params.content} \n`; errorMarkdown += `**Rotation:** ${params.rotation || 0}° \n\n`; errorMarkdown += `## Troubleshooting Tips\n`; errorMarkdown += `1. Verify the document ID is correct\n`; errorMarkdown += `2. For image watermarks, ensure the URL is accessible\n`; errorMarkdown += `3. Check that opacity is between 0 and 1\n`; errorMarkdown += `4. Try using a different position if the watermark overlaps content\n`; errorMarkdown += `5. Adjust the rotation angle if the watermark is not oriented correctly\n\n`; errorMarkdown += `Please check your parameters and try again.`; return { markdown: errorMarkdown }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/PSPDFKit/nutrient-document-engine-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server