Skip to main content
Glama
PSPDFKit

Nutrient Document Engine MCP Server

by PSPDFKit
rotatePages.ts8.23 kB
import { z } from 'zod'; import { handleApiError } from '../../utils/ErrorHandling.js'; import { DocumentEngineClient } from '../../api/Client.js'; import { BuildInstructions, RotateAction } from '../../api/DocumentEngineSchema.js'; import { DocumentFingerprint, DocumentFingerprintSchema, } from '../schemas/DocumentFingerprintSchema.js'; import { applyDocumentInstructions, getDocumentInfo } from '../../api/DocumentLayerAbstraction.js'; import { MCPToolOutput } from '../../mcpTools.js'; /** * Rotate Pages Tool * * Rotates a given set of pages in a document in 90 degree increments. * Supports rotating specific pages or page ranges. */ const rotationSchema = z .union([z.literal(90), z.literal(180), z.literal(270)]) .describe('Rotation angle in degrees (must be 90, 180, or 270)'); export const RotatePagesSchema = { document_fingerprint: DocumentFingerprintSchema, pages: z .array(z.number().int().min(0)) .min(1, 'At least one page is required') .describe('Array of page indices to rotate (0-based indexing)'), rotation: rotationSchema, }; const RotatePagesInputSchema = z.object(RotatePagesSchema); type RotatePagesInput = z.infer<typeof RotatePagesInputSchema>; type Rotation = z.infer<typeof rotationSchema>; /** * Validate page indices and convert to 1-based page numbers for the API */ function validateAndConvertPages(pages: number[], pageCount: number): number[] { const convertedPages: number[] = []; for (const pageIndex of pages) { // Validate page index if (pageIndex < 0 || pageIndex >= pageCount) { throw new Error( `Page index ${pageIndex} is out of bounds (document has ${pageCount} pages, valid indices are 0-${pageCount - 1})` ); } // Convert 0-based index to 1-based page number for the API convertedPages.push(pageIndex + 1); } return convertedPages; } /** * Rotate pages using document-apply-instructions API */ async function applyPageRotation( client: DocumentEngineClient, fingerprint: DocumentFingerprint, pageNumbers: number[], rotation: Rotation, pageCount: number ): Promise<{ documentId: string }> { try { const rotatePageAction: RotateAction = { type: 'rotate', rotateBy: rotation, }; // Create a set of page indices (0-based) that need to be rotated const pagesToRotate = new Set(pageNumbers.map(pageNum => pageNum - 1)); // Create parts array for build instructions in page order const parts = []; // Process all pages in sequential order to maintain page sequence for (let pageIndex = 0; pageIndex < pageCount; pageIndex++) { if (pagesToRotate.has(pageIndex)) { // This page needs to be rotated parts.push({ document: { id: '#self', }, pages: { start: pageIndex, end: pageIndex }, actions: [rotatePageAction], }); } else { // This page should not be rotated - include it as-is parts.push({ document: { id: '#self', }, pages: { start: pageIndex, end: pageIndex }, }); } } // Create build instructions with the parts const buildInstructions: BuildInstructions = { parts: parts, }; await applyDocumentInstructions(client, fingerprint, buildInstructions); return { documentId: fingerprint.document_id }; } catch (error: unknown) { throw handleApiError(error); } } /** * Format rotation direction for display */ function formatRotationDirection(rotation: number): string { switch (rotation) { case 90: return 'Clockwise (90°)'; case 180: return 'Upside down (180°)'; case 270: return 'Counter-clockwise (270°)'; default: return `${rotation}°`; } } /** * Rotate pages in a document */ export async function rotatePages( client: DocumentEngineClient, params: RotatePagesInput ): Promise<MCPToolOutput> { const startTime = Date.now(); try { // Validate input const validatedParams = RotatePagesInputSchema.parse(params); const { document_fingerprint, pages, rotation } = validatedParams; // Get document information const documentInfo = await getDocumentInfo(client, document_fingerprint); const pageCount = documentInfo.pageCount; // Validate page indices and convert to 1-based page numbers for the API const pageNumbers = validateAndConvertPages(pages, pageCount); // Rotate the pages const result = await applyPageRotation( client, document_fingerprint, pageNumbers, rotation, pageCount ); const processingTime = Date.now() - startTime; // Build the markdown response let markdown = `# Pages Rotated Successfully\n\n`; markdown += `✅ **Status:** Pages rotated \n`; markdown += `📄 **Document ID:** ${result.documentId} \n`; if (document_fingerprint.layer) { markdown += `🏷️ **Layer:** ${document_fingerprint.layer} \n`; } markdown += `📊 **Pages Rotated:** ${pageNumbers.length} page${pageNumbers.length === 1 ? '' : 's'} \n`; markdown += `🔄 **Rotation Applied:** ${rotation}° \n\n`; markdown += `---\n\n`; // Rotation details section markdown += `## Rotation Details\n`; markdown += `- **Rotation:** ${formatRotationDirection(rotation)}\n`; // Group consecutive pages for cleaner display const pageGroups: string[] = []; let currentGroup: number[] = []; // Convert 1-based page numbers back to 0-based indices for display const pageIndices = pageNumbers.map(pageNum => pageNum - 1); [...pageIndices] .sort((a, b) => a - b) .forEach((page, index, sortedPages) => { if (index === 0 || page !== sortedPages[index - 1] + 1) { if (currentGroup.length > 0) { pageGroups.push( currentGroup.length === 1 ? `Page ${currentGroup[0]}` : `Pages ${currentGroup[0]}-${currentGroup[currentGroup.length - 1]}` ); currentGroup = []; } currentGroup.push(page); } else { currentGroup.push(page); } if (index === sortedPages.length - 1 && currentGroup.length > 0) { pageGroups.push( currentGroup.length === 1 ? `Page ${currentGroup[0]}` : `Pages ${currentGroup[0]}-${currentGroup[currentGroup.length - 1]}` ); } }); markdown += `- **Detailed Page List:** ${pageGroups.join(', ')}\n\n`; markdown += `---\n\n`; // Operation summary section markdown += `## Operation Summary\n`; markdown += `- **Total Document Pages:** ${pageCount}\n`; markdown += `- **Pages Rotated:** ${pageNumbers.length}/${pageCount}\n`; markdown += `- **Processing Time:** ${(processingTime / 1000).toFixed(1)} seconds\n`; markdown += `- **Status:** Complete\n\n`; markdown += `---\n\n`; return { markdown }; } catch (error: unknown) { // Return error in markdown format let errorMarkdown = `# Error Rotating Pages\n\n`; errorMarkdown += `❌ **Status:** Failed \n`; errorMarkdown += `An error occurred while trying to rotate pages in the document: ${error instanceof Error ? error.message : 'Unknown error'}\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 += `**Pages:** ${params.pages ? JSON.stringify(params.pages) : 'None provided'} \n`; errorMarkdown += `**Rotation:** ${params.rotation}° \n\n`; errorMarkdown += `## Troubleshooting Tips\n`; errorMarkdown += `1. Verify the document ID is correct\n`; errorMarkdown += `2. Check that the page indices are valid and within document bounds (0-based indexing)\n`; errorMarkdown += `3. Ensure the rotation angle is one of the supported values (90, 180, 270)\n`; errorMarkdown += `4. Verify the document is not locked or protected\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