UNPKG

docxmcp

Version:

A Model Context Protocol (MCP) server for processing .docx files into markdown with image extraction

139 lines (119 loc) 3.94 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { PROJECT_DIR } from './constants.js'; import { processDocx, cleanupImages } from './docx-processor.js'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; import { promises as fs } from 'fs'; import { readFileSync } from 'fs'; import { log } from './logger.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Read package.json for dynamic version const packageJson = JSON.parse(readFileSync(join(dirname(__dirname), 'package.json'), 'utf8')); // Create an MCP server const server = new Server({ name: "docxmcp", version: packageJson.version, description: "MCP server for processing .docx files into markdown with image extraction" }, { capabilities: { tools: {} } }); // Document processing schema const docxToolSchema = { type: 'object', properties: { filePath: { type: 'string', description: 'Path to the .docx file to process' } }, required: ['filePath'] }; // Register handlers server.setRequestHandler(ListToolsRequestSchema, async () => { log.debug('Received ListToolsRequest'); return { tools: [{ name: 'process_docx', title: 'Process DOCX File', description: 'Processes a .docx file and returns its contents as markdown with image references', inputSchema: docxToolSchema }] }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; log.debug(`Received CallToolRequest for tool: ${name}`, args); switch (name) { case 'process_docx': const { filePath } = args; // Verify file exists and has .docx extension if (!filePath.toLowerCase().endsWith('.docx')) { throw new Error('File must be a .docx document'); } try { await fs.access(filePath); } catch { throw new Error('File does not exist or is not accessible'); } log.info(`Processing .docx file: ${filePath}`); const result = await processDocx(filePath); // Build response content array with text and images const content = []; // Add the markdown content as text content.push({ type: 'text', text: result.markdown }); // Add images as proper image content items if (result.images && result.images.length > 0) { log.debug(`Including ${result.images.length} images in response`); for (const image of result.images) { content.push({ type: 'image', data: image.base64Data, mimeType: image.contentType }); } } // Clean up temporary image files immediately since we're using base64 if (result.images && result.images.length > 0) { cleanupImages(result.images).catch(err => log.error('Failed to cleanup images:', err)); } return { content: content, messages: result.messages || [] }; default: throw new Error(`Unknown tool: ${name}`); } }); // Start the server async function main() { try { const transport = new StdioServerTransport(); await server.connect(transport); log.info("DocxMCP Server running on stdio"); log.debug(`Project directory: ${PROJECT_DIR}`); log.debug(`Current directory: ${process.cwd()}`); } catch (error) { log.error("Failed to start server:", error); process.exit(1); } } // Handle graceful shutdown process.on('SIGINT', () => { log.info('Shutting down server...'); process.exit(0); }); main().catch((error) => { log.error("Fatal error in main():", error); process.exit(1); });