UNPKG

@mcp-apps/pdf-tools-mcp-server

Version:

MCP server for interacting with PDF documents

370 lines 16.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.PDFService = void 0; exports.loadPdfWithPdfjs = loadPdfWithPdfjs; exports.extractTextFromPages = extractTextFromPages; // PDF Service for handling PDF document operations const fs = __importStar(require("fs")); const path = __importStar(require("path")); const url = __importStar(require("url")); const pdfjsLib = __importStar(require("pdfjs-dist/legacy/build/pdf.mjs")); const workerPath = require.resolve('pdfjs-dist/legacy/build/pdf.worker.mjs'); pdfjsLib.GlobalWorkerOptions.workerSrc = url.pathToFileURL(workerPath).toString(); /** * Converts a file URL to a local file path * @param fileUrl URL with file:// protocol * @returns Local file path */ function fileUrlToPath(fileUrl) { if (fileUrl.startsWith('file://')) { // Convert file URL to local path return url.fileURLToPath(fileUrl); } // If it's already a path, return as is return fileUrl; } /** * Loads a PDF document using pdf.js library * @param filePath Path to the PDF file * @returns Promise resolving to the loaded PDF document */ async function loadPdfWithPdfjs(filePath) { // Load PDF document const documentInitParameters = { url: filePath, enableXfa: true, // Enable XFA support }; return await pdfjsLib.getDocument(documentInitParameters).promise; } /** * Extracts text from the specified pages of a PDF document * @param pdfDocument The PDF document loaded with pdf.js * @param pageNumbers Array of page numbers to extract text from (1-based indexing) * @returns Promise resolving to an array of objects with page number and extracted text */ async function extractTextFromPages(pdfDocument, pageNumbers) { // Get total page count const numPages = pdfDocument.numPages; // Determine which pages to extract text from const pagesToExtract = pageNumbers || Array.from({ length: numPages }, (_, i) => i + 1); // Extract text from each page const result = []; for (const pageNum of pagesToExtract) { if (pageNum > numPages) { console.warn(`Skipping page ${pageNum} as it exceeds the document length of ${numPages} pages`); continue; } const page = await pdfDocument.getPage(pageNum); const textContent = await page.getTextContent(); let textItems = textContent.items.map((item) => item.str).join(' '); // Extract XFA text content if available try { const xfa = await page.getXfa(); if (xfa && typeof xfa === 'object') { // Extract XFA text content const xfaTextContent = []; const flattenXfaFields = (field) => { if (!field) return; // For direct value or text content let textContent = ''; if (typeof field === 'string') { textContent = field; } else if (field.value) { textContent = field.value; } else if (field.attributes) { textContent = field.attributes.title || field.attributes.value || field.attributes.textContent || ''; } if (field.name === 'textarea' || field.name === 'div') { textContent += '\n'; } if (textContent && textContent.trim()) { xfaTextContent.push(textContent.trim()); } // Process children if available if (field.children && Array.isArray(field.children)) { for (const child of field.children) { flattenXfaFields(child); } } }; // Process the root XFA object and its children flattenXfaFields(xfa); if (xfaTextContent.length > 0) { // Append XFA content to the text content textItems += ' ' + xfaTextContent.join(' '); } } } catch (xfaError) { console.warn(`Error extracting XFA content from page ${pageNum}:`, xfaError); // Continue with the regular content extraction } result.push({ page: pageNum, text: textItems }); } return result; } class PDFService { static async extractText(filePath, pageNumbers) { // Convert file URL to local path if needed const localFilePath = fileUrlToPath(filePath); // Check if file exists if (!fs.existsSync(localFilePath)) { throw new Error(`PDF file not found: ${localFilePath}`); } try { // Load PDF document with XFA support enabled const documentInitParameters = { url: localFilePath, enableXfa: true, // Enable XFA support }; const loadingTask = pdfjsLib.getDocument(documentInitParameters); const pdfDocument = await loadingTask.promise; // Get total page count const numPages = pdfDocument.numPages; // Determine which pages to extract text from const pagesToExtract = pageNumbers || Array.from({ length: numPages }, (_, i) => i + 1); // Extract text from each page const result = []; for (const pageNum of pagesToExtract) { if (pageNum > numPages) { console.warn(`Skipping page ${pageNum} as it exceeds the document length of ${numPages} pages`); continue; } const page = await pdfDocument.getPage(pageNum); const textContent = await page.getTextContent(); let textItems = textContent.items.map((item) => item.str).join(' '); // Extract XFA text content if available try { const xfa = await page.getXfa(); if (xfa && typeof xfa === 'object') { // Extract XFA text content const xfaTextContent = []; const flattenXfaFields = (field) => { if (!field) return; // For direct value or text content let textContent = ''; if (typeof field === 'string') { textContent = field; } else if (field.value) { textContent = field.value; } else if (field.attributes) { textContent = field.attributes.title || field.attributes.value || field.attributes.textContent || ''; } if (field.name === 'textarea' || field.name === 'div') { textContent += '\n'; } if (textContent && textContent.trim()) { xfaTextContent.push(textContent.trim()); } // Process children if available if (field.children && Array.isArray(field.children)) { for (const child of field.children) { flattenXfaFields(child); } } }; // Process the root XFA object and its children flattenXfaFields(xfa); if (xfaTextContent.length > 0) { // Append XFA content to the text content textItems += ' ' + xfaTextContent.join(' '); } } } catch (xfaError) { console.warn(`Error extracting XFA content from page ${pageNum}:`, xfaError); // Continue with the regular content extraction } result.push({ page: pageNum, text: textItems }); } return result; } catch (error) { console.error("Error extracting text from PDF:", error); throw error; } } /** * Extracts tables from a PDF document * @param filePath Path to the PDF file * @param pageNumbers Optional specific pages to extract from * @returns Array of extracted tables */ static async extractTables(filePath, pageNumbers) { // Convert file URL to local path if needed const localFilePath = fileUrlToPath(filePath); // Check if file exists if (!fs.existsSync(localFilePath)) { throw new Error(`PDF file not found: ${localFilePath}`); } // This is a placeholder implementation // In a real implementation, we would use a PDF processing library return [ { rows: [ ['Header 1', 'Header 2', 'Header 3'], ['Value 1', 'Value 2', 'Value 3'], ['Value 4', 'Value 5', 'Value 6'] ], pageNumber: 1 } ]; } /** * Gets metadata from a PDF document * @param filePath Path to the PDF file * @returns The document metadata */ static async getMetadata(filePath) { // Convert file URL to local path if needed const localFilePath = fileUrlToPath(filePath); // Check if file exists if (!fs.existsSync(localFilePath)) { throw new Error(`PDF file not found: ${localFilePath}`); } try { // Get file stats const stats = fs.statSync(localFilePath); const fileSize = (stats.size / (1024 * 1024)).toFixed(2) + ' MB'; const fileName = path.basename(localFilePath); // Load PDF document const data = new Uint8Array(fs.readFileSync(localFilePath)); const loadingTask = pdfjsLib.getDocument({ data }); const pdfDocument = await loadingTask.promise; // Get metadata const metadata = await pdfDocument.getMetadata(); const info = metadata.info || {}; return { fileName, fileSize, pageCount: pdfDocument.numPages, author: info.Author || 'Unknown', creationDate: info.CreationDate || new Date().toISOString(), modificationDate: info.ModDate || new Date().toISOString(), keywords: info.Keywords ? info.Keywords.split(',').map((k) => k.trim()) : [] }; } catch (error) { console.error("Error getting PDF metadata:", error); throw error; } } /** * Analyzes a PDF document * @param filePath Path to the PDF file * @param analysisType Type of analysis to perform * @returns Analysis results */ static async analyzeDocument(filePath, analysisType) { // Convert file URL to local path if needed const localFilePath = fileUrlToPath(filePath); // Check if file exists if (!fs.existsSync(localFilePath)) { throw new Error(`PDF file not found: ${localFilePath}`); } try { // Load PDF document const data = new Uint8Array(fs.readFileSync(localFilePath)); const loadingTask = pdfjsLib.getDocument({ data }); const pdfDocument = await loadingTask.promise; // Perform requested analysis switch (analysisType) { case 'structure': { // Analyze document structure const numPages = pdfDocument.numPages; const outlinePromise = pdfDocument.getOutline?.() || Promise.resolve([]); const outline = await outlinePromise; // Check if document has images by looking at first page const page = await pdfDocument.getPage(1); const operatorList = await page.getOperatorList(); const hasImages = operatorList.fnArray.some((fn) => fn === pdfjsLib.OPS.paintImageXObject); // Analyze text layout to detect tables (simplified) const textContent = await page.getTextContent(); const hasTables = textContent.items.length > 10; // Very simplified check // Extract section titles from outline or first-level headings const sections = outline.length > 0 ? outline.map((item) => item.title) : ['Document Content']; // Fallback if no outline return { pageCount: numPages, hasImages, hasTables, sections }; } case 'content': // For a real implementation, we would use NLP to extract key phrases return { summary: `This document contains ${pdfDocument.numPages} pages of content.`, keywords: ['pdf', 'document', 'content'], entities: [] }; case 'images': // For a real implementation, we would extract and analyze all images return { imageCount: 0, // This would require a full page-by-page scan imageTypes: [], imageCaption: '' }; case 'classification': // For a real implementation, we would use ML to classify document type return { documentType: 'Unknown', confidenceScore: 0.5, topCategories: ['Document'] }; default: throw new Error(`Unknown analysis type: ${analysisType}`); } } catch (error) { console.error(`Error analyzing document (${analysisType}):`, error); throw error; } } } exports.PDFService = PDFService; //# sourceMappingURL=pdfService.js.map