UNPKG

textin-mcp-ts

Version:

TextIn MCP service in TypeScript

257 lines (256 loc) 12.2 kB
#!/usr/bin/env node "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const fastmcp_1 = require("fastmcp"); const fs_1 = require("fs"); const fs_2 = require("fs"); const path_1 = require("path"); const axios_1 = __importDefault(require("axios")); const zod_1 = require("zod"); // Zod schemas for parameter validation const ConvertFileOptionsSchema = zod_1.z.object({ pdfPwd: zod_1.z.string().optional(), charDetails: zod_1.z.boolean().optional(), pageDetails: zod_1.z.boolean().optional(), catalogDetails: zod_1.z.boolean().optional(), dpi: zod_1.z.number().optional(), pageStart: zod_1.z.number().optional(), pageCount: zod_1.z.number().optional(), getImage: zod_1.z.enum(['none', 'url', 'base64', 'both']).optional(), getTable: zod_1.z.boolean().optional(), getFormula: zod_1.z.boolean().optional() }); const EnhanceImageOptionsSchema = zod_1.z.object({ enhanceMode: zod_1.z.number().optional(), cropImage: zod_1.z.number().optional(), onlyPosition: zod_1.z.number().optional(), dewarpImage: zod_1.z.number().optional(), deblurImage: zod_1.z.number().optional() }); // Create an MCP server const mcp = new fastmcp_1.FastMCP({ name: "TextIn MCP Server", version: "1.0.0", }); // Supported file formats const supportedFormats = new Set([ 'png', 'jpg', 'jpeg', 'pdf', 'bmp', 'tiff', 'webp', 'doc', 'docx', 'html', 'mhtml' ]); // Helper function to validate file format async function validateFile(filePath, formats) { const ext = (0, path_1.extname)(filePath).toLowerCase().slice(1); if (!formats.has(ext)) { throw new Error(`Unsupported file format. Supported formats are: ${Array.from(formats).join(', ')}`); } try { await fs_2.promises.access(filePath); } catch { throw new Error(`Input file not found: ${filePath}`); } } // Helper function to get API credentials function getApiCredentials() { const appId = process.env.TEXTIN_APP_ID; const secretCode = process.env.TEXTIN_SECRET_CODE; if (!appId || !secretCode) { throw new Error('Missing API credentials. Please set TEXTIN_APP_ID and TEXTIN_SECRET_CODE environment variables.'); } return { appId, secretCode }; } // Convert file to markdown mcp.addTool({ name: "convertFileToMarkdown", description: "Convert various file formats to Markdown format, write to a Markdown file and return its content", parameters: zod_1.z.object({ inputPath: zod_1.z.string().describe('Path to the input file (supported formats: png, jpg, jpeg, pdf, bmp, tiff, webp, doc, docx, html, mhtml)'), outputPath: zod_1.z.string().optional().describe('Optional path to save the markdown file. If not provided, will use same name as input with .md extension'), options: zod_1.z.object({ pdfPwd: zod_1.z.string().optional().describe('Optional password for encrypted PDF'), charDetails: zod_1.z.boolean().optional().describe('Whether to return character position details'), pageDetails: zod_1.z.boolean().optional().describe('Whether to return detailed page parsing results'), catalogDetails: zod_1.z.boolean().optional().describe('Whether to return catalog details'), dpi: zod_1.z.number().optional().describe('PDF document coordinate reference (72, 144, or 216)'), pageStart: zod_1.z.number().optional().describe('Starting page number for PDF conversion'), pageCount: zod_1.z.number().optional().describe('Number of pages to convert for PDF'), getImage: zod_1.z.enum(['none', 'url', 'base64', 'both']).optional().describe('Image extraction mode (\'none\', \'url\', \'base64\', or \'both\')'), getTable: zod_1.z.boolean().optional().describe('Whether to extract tables'), getFormula: zod_1.z.boolean().optional().describe('Whether to extract formulas式') }).optional() }), execute: async ({ inputPath, outputPath, options = {} }) => { const validatedOptions = ConvertFileOptionsSchema.parse(options); await validateFile(inputPath, supportedFormats); const finalOutputPath = outputPath || inputPath.replace(/\.[^.]+$/, '.md'); const { appId, secretCode } = getApiCredentials(); const params = { get_image: validatedOptions.getImage || 'both', char_details: validatedOptions.charDetails ? 1 : 0, page_details: validatedOptions.pageDetails ?? 1, catalog_details: validatedOptions.catalogDetails ? 1 : 0, dpi: validatedOptions.dpi || 144, get_table: validatedOptions.getTable ?? 1, get_formula: validatedOptions.getFormula ?? 1, pdf_pwd: validatedOptions.pdfPwd, page_start: validatedOptions.pageStart, page_count: validatedOptions.pageCount }; try { const response = await axios_1.default.post('https://api.textin.com/ai/service/v1/pdf_to_markdown', (0, fs_1.createReadStream)(inputPath), { headers: { 'x-ti-app-id': appId, 'x-ti-secret-code': secretCode }, params }); if (response.data.code !== 200) { throw new Error(response.data.code); } const markdownContent = response.data.result?.markdown; if (!markdownContent) { throw new Error('No markdown content in response'); } await fs_2.promises.writeFile(finalOutputPath, markdownContent, 'utf-8'); return markdownContent; } catch (error) { throw new Error(`API request failed: ${error.message}`); } }, }); // Scan directory for supported files mcp.addTool({ name: "scanDirectory", description: "Scan directory for supported file formats", parameters: zod_1.z.object({ directoryPath: zod_1.z.string().describe('Path to the directory to scan') }), execute: async ({ directoryPath }) => { try { const files = await fs_2.promises.readdir(directoryPath, { withFileTypes: true }); const supportedFiles = []; for (const file of files) { if (file.isFile()) { const ext = (0, path_1.extname)(file.name).toLowerCase().slice(1); if (supportedFormats.has(ext)) { supportedFiles.push((0, path_1.join)(directoryPath, file.name)); } } } return supportedFiles.join('\n'); } catch (error) { throw new Error(`Failed to scan directory: ${error.message}`); } }, }); // Extract information from documents mcp.addTool({ name: "extractInfo", description: "Extract information from various document formats", parameters: zod_1.z.object({ inputPath: zod_1.z.string().describe('Path to the input file (supported formats: png, jpg, jpeg, doc, docx, pdf, ofd, xlsx, xls)'), key: zod_1.z.string().optional().describe('Optional comma-separated list of keys to extract single values'), tableHeader: zod_1.z.string().optional().describe('Optional comma-separated list of table headers to extract table data') }), execute: async ({ inputPath, key, tableHeader }) => { const supportedExtractFormats = new Set([ 'png', 'jpg', 'jpeg', 'doc', 'docx', 'pdf', 'ofd', 'xlsx', 'xls' ]); await validateFile(inputPath, supportedExtractFormats); const { appId, secretCode } = getApiCredentials(); try { const response = await axios_1.default.post('https://api.textin.com/ai/service/v1/entity_extraction', (0, fs_1.createReadStream)(inputPath), { headers: { 'x-ti-app-id': appId, 'x-ti-secret-code': secretCode }, params: { key, table_header: tableHeader } }); if (response.data.code !== 200) { throw new Error(response.data.code); } const transformedResult = response.data.result.detail_structure.map((item) => { return { fields: Object.fromEntries(Object.entries(item.fields).map(([key, value]) => [ key, value.map((fieldItem) => fieldItem.value) ])), stamps: item.stamps.map((stamp) => { const { position, ...rest } = stamp; return rest; }) }; }); return JSON.stringify(transformedResult); } catch (error) { throw new Error(`API request failed: ${error.message}`); } }, }); // Enhance image mcp.addTool({ name: "enhanceImage", description: "Enhance and process images with various options", parameters: zod_1.z.object({ inputPath: zod_1.z.string().describe('Path to the input image file or URL'), outputPath: zod_1.z.string().optional().describe('Optional path to save the enhanced image. If not provided, will use same name with \'_enhanced\' suffix'), options: zod_1.z.object({ enhanceMode: zod_1.z.number().optional().describe('Enhancement mode (-1: disabled, 1: brighten, 2: enhance and sharpen, 3: black&white, 4: grayscale, 5: remove shadow, 6: halftone)'), cropImage: zod_1.z.number().optional().describe('Whether to crop image (0: no, 1: yes)'), onlyPosition: zod_1.z.number().optional().describe('Whether to return only corner positions (0: return corners and image, 1: return only corners)'), dewarpImage: zod_1.z.number().optional().describe('Whether to dewarp image (0: no, 1: yes)'), deblurImage: zod_1.z.number().optional().describe('Whether to deblur image (0: no, 1: yes)') }).optional() }), execute: async ({ inputPath, outputPath, options = {} }) => { const validatedOptions = EnhanceImageOptionsSchema.parse(options); const { appId, secretCode } = getApiCredentials(); try { await fs_2.promises.access(inputPath); } catch { throw new Error(`Input file not found: ${inputPath}`); } const finalOutputPath = outputPath || inputPath.replace(/\.[^.]+$/, '_enhanced' + (0, path_1.extname)(inputPath)); try { const imageData = await fs_2.promises.readFile(inputPath); const response = await axios_1.default.post('https://api.textin.com/ai/service/v1/crop_enhance_image', imageData, { headers: { 'x-ti-app-id': appId, 'x-ti-secret-code': secretCode }, params: { enhance_mode: validatedOptions.enhanceMode ?? -1, crop_image: validatedOptions.cropImage ?? 1, only_position: validatedOptions.onlyPosition ?? 0, dewarp_image: validatedOptions.dewarpImage ?? 1, deblur_image: validatedOptions.deblurImage ?? 0 } }); if (response.data.code !== 200) { throw new Error(response.data); } const imageList = response.data.result?.image_list; if (!imageList?.length) { throw new Error('No enhanced image data received'); } const imageBase64 = imageList[0].image; const imageBuffer = Buffer.from(imageBase64.includes('base64,') ? imageBase64.split('base64,')[1] : imageBase64, 'base64'); await fs_2.promises.writeFile(finalOutputPath, imageBuffer); return finalOutputPath; } catch (error) { throw new Error(`Failed to enhance image: ${error.message}`); } }, }); // Start the server mcp.start({ transportType: 'stdio', });