docxmcp
Version:
A Model Context Protocol (MCP) server for processing .docx files into markdown with image extraction
139 lines (119 loc) • 3.94 kB
JavaScript
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);
});