uns-mcp-server
Version:
Pure JavaScript MCP server for Unstructured.io - No Python required!
282 lines (240 loc) ⢠7.72 kB
JavaScript
/**
* Unstructured.io MCP Server - NPX Executable
* Provides document processing capabilities via MCP protocol
*/
const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs');
const which = require('which');
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
// Simple console colors without external dependencies
const chalk = {
blue: (text) => `\x1b[34m${text}\x1b[0m`,
green: (text) => `\x1b[32m${text}\x1b[0m`,
yellow: (text) => `\x1b[33m${text}\x1b[0m`,
red: (text) => `\x1b[31m${text}\x1b[0m`,
gray: (text) => `\x1b[90m${text}\x1b[0m`
};
// Parse command line arguments
const argv = yargs(hideBin(process.argv))
.option('mode', {
alias: 'm',
description: 'Server mode (stdio or sse)',
type: 'string',
default: 'stdio'
})
.option('port', {
alias: 'p',
description: 'Port for SSE mode',
type: 'number',
default: 8080
})
.option('api-key', {
alias: 'k',
description: 'Unstructured API key',
type: 'string'
})
.option('config', {
alias: 'c',
description: 'Path to config file',
type: 'string'
})
.option('debug', {
alias: 'd',
description: 'Enable debug mode',
type: 'boolean',
default: false
})
.help()
.alias('help', 'h')
.argv;
class UnstructuredMCPServer {
constructor(options) {
this.options = options;
this.pythonCommand = null;
this.serverProcess = null;
}
async findPython() {
const pythonCommands = ['python3', 'python', 'python3.12', 'python3.11'];
for (const cmd of pythonCommands) {
try {
const pythonPath = await which(cmd);
// Verify it's Python 3.8+
const version = await this.getPythonVersion(pythonPath);
if (version && this.isPythonVersionValid(version)) {
return pythonPath;
}
} catch (e) {
// Continue to next option
}
}
throw new Error('Python 3.8+ not found. Please install Python.');
}
getPythonVersion(pythonPath) {
return new Promise((resolve, reject) => {
const proc = spawn(pythonPath, ['--version']);
let version = '';
proc.stdout.on('data', (data) => {
version += data.toString();
});
proc.stderr.on('data', (data) => {
version += data.toString();
});
proc.on('close', (code) => {
if (code === 0) {
const match = version.match(/Python (\d+)\.(\d+)\.(\d+)/);
if (match) {
resolve({
major: parseInt(match[1]),
minor: parseInt(match[2]),
patch: parseInt(match[3])
});
} else {
reject(new Error('Could not parse Python version'));
}
} else {
reject(new Error('Failed to get Python version'));
}
});
});
}
isPythonVersionValid(version) {
return version.major >= 3 && version.minor >= 8;
}
async checkUnsMcpInstalled() {
return new Promise((resolve) => {
const proc = spawn(this.pythonCommand, ['-c', 'import uns_mcp']);
proc.on('close', (code) => {
resolve(code === 0);
});
});
}
async installUnsMcp() {
console.log(chalk.yellow('Installing uns_mcp Python package...'));
// Try different installation methods
const installCommands = [
['-m', 'pip', 'install', '--user', 'uns_mcp'],
['-m', 'pip', 'install', '--user', '--break-system-packages', 'uns_mcp'],
['-m', 'pip', 'install', 'uns_mcp']
];
for (const args of installCommands) {
try {
const result = await new Promise((resolve, reject) => {
const proc = spawn(this.pythonCommand, args, {
stdio: 'pipe'
});
let stderr = '';
proc.stderr.on('data', (data) => {
stderr += data.toString();
});
proc.on('close', (code) => {
if (code === 0) {
resolve(true);
} else {
reject(new Error(stderr));
}
});
});
if (result) {
console.log(chalk.green('ā uns_mcp installed successfully'));
return;
}
} catch (e) {
// Try next method
continue;
}
}
throw new Error('Failed to install uns_mcp. Please install manually: pip install uns_mcp');
}
setupEnvironment() {
// Set up environment variables
const env = { ...process.env };
// Load from .env file if it exists
const envPath = path.join(process.cwd(), '.env');
if (fs.existsSync(envPath)) {
require('dotenv').config({ path: envPath });
}
// Override with command line options
if (this.options.apiKey) {
env.UNSTRUCTURED_API_KEY = this.options.apiKey;
}
if (this.options.debug) {
env.DEBUG_API_REQUESTS = 'true';
env.LOG_LEVEL = 'DEBUG';
}
// Check for required API key
if (!env.UNSTRUCTURED_API_KEY) {
console.warn(chalk.yellow('ā Warning: UNSTRUCTURED_API_KEY not set'));
console.log(chalk.gray('Get your API key at: https://unstructured.io'));
}
return env;
}
async start() {
try {
console.log(chalk.blue('š Starting Unstructured MCP Server...'));
// Find Python
this.pythonCommand = await this.findPython();
console.log(chalk.green(`ā Found Python: ${this.pythonCommand}`));
// Check if uns_mcp is installed
const isInstalled = await this.checkUnsMcpInstalled();
if (!isInstalled) {
await this.installUnsMcp();
}
// Set up environment
const env = this.setupEnvironment();
// Prepare command arguments
const args = ['-m', 'uns_mcp'];
if (this.options.mode === 'sse') {
args.push('--port', this.options.port.toString());
console.log(chalk.blue(`š” Server will run in SSE mode on port ${this.options.port}`));
} else {
// STDIO is the default mode
console.log(chalk.blue('š” Server will run in STDIO mode'));
}
// Start the Python MCP server
this.serverProcess = spawn(this.pythonCommand, args, {
env,
stdio: this.options.mode === 'stdio' ? 'inherit' : ['ignore', 'inherit', 'inherit']
});
this.serverProcess.on('error', (err) => {
console.error(chalk.red('ā Server error:'), err);
});
this.serverProcess.on('close', (code) => {
if (code !== 0) {
console.log(chalk.yellow(`Server exited with code ${code}`));
} else {
console.log(chalk.green('ā Server stopped gracefully'));
}
});
// Handle graceful shutdown
process.on('SIGINT', () => this.stop());
process.on('SIGTERM', () => this.stop());
if (this.options.mode === 'sse') {
console.log(chalk.green(`ā Server running at http://localhost:${this.options.port}/sse`));
} else {
console.log(chalk.green('ā Server running in STDIO mode'));
}
} catch (error) {
console.error(chalk.red('ā Failed to start server:'), error.message);
process.exit(1);
}
}
stop() {
console.log(chalk.yellow('\nā¹ Stopping server...'));
if (this.serverProcess) {
this.serverProcess.kill('SIGTERM');
}
process.exit(0);
}
}
// Main execution
async function main() {
const server = new UnstructuredMCPServer(argv);
await server.start();
}
main().catch(error => {
console.error(chalk.red('Fatal error:'), error);
process.exit(1);
});