@etm-professional-control/winccoa-mcp-server
Version:
MCP Server for WinCC OA with field-specific configurations
317 lines • 12.2 kB
JavaScript
/*******************************************************/
/* */
/* This file was initially creates by Martin Kumhera */
/* and extended by AI with CNS (UNS) functions! */
/* */
/*******************************************************/
// Try to load dotenv if available BEFORE importing config
try {
const dotenv = await import('dotenv');
const path = await import('path');
const { fileURLToPath } = await import('url');
const fs = await import('fs');
// Get the directory of the current script
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Check if .env file exists
const envPath = path.join(__dirname, '.env');
if (fs.existsSync(envPath)) {
// Load .env from the script directory
const result = dotenv.config({ path: envPath });
console.log(`✓ .env file found and loaded from: ${envPath}`);
// Debug: Check if dotenv actually loaded the variables
if (result.error) {
console.log(`✗ Error loading .env file: ${result.error}`);
}
else {
console.log(`✓ dotenv.config() successful`);
console.log(`✓ MCP_API_TOKEN after loading: ${process.env.MCP_API_TOKEN ? 'SET' : 'NOT SET'}`);
if (process.env.MCP_API_TOKEN) {
console.log(`✓ MCP_API_TOKEN value: ${process.env.MCP_API_TOKEN.substring(0, 8)}...`);
}
}
}
else {
console.log(`✗ .env file not found at: ${envPath}`);
}
}
catch (error) {
// dotenv not available, continue without it
console.log('dotenv not available, using environment variables directly');
}
// Import modules dynamically after dotenv is loaded
console.log('🔄 Starting module imports...');
let StreamableHTTPServerTransport;
let express;
let initializeServer;
let serverConfig;
let loadSSLConfig;
let validateConfig;
let https;
let cors;
let rateLimit;
try {
console.log('🔄 Importing StreamableHTTPServerTransport...');
({ StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js"));
console.log('✅ StreamableHTTPServerTransport imported');
console.log('🔄 Importing express...');
express = (await import('express')).default;
console.log('✅ Express imported');
console.log('🔄 Importing server.js...');
({ initializeServer } = await import('./server.js'));
console.log('✅ server.js imported');
console.log('🔄 Importing server.config.js...');
({ serverConfig, loadSSLConfig, validateConfig } = await import('./config/server.config.js'));
console.log('✅ server.config.js imported');
console.log('🔍 serverConfig.http.auth.token:', serverConfig.http.auth.token ? 'SET' : 'NOT SET');
console.log('🔄 Importing https...');
https = await import('https');
console.log('✅ https imported');
const { createRequire } = await import('module');
const require = createRequire(import.meta.url);
cors = require('cors');
rateLimit = require('express-rate-limit');
console.log('✅ All modules imported successfully');
}
catch (importError) {
console.error('❌ Error importing modules:', importError);
process.exit(1);
}
let server;
// ==================== EXPRESS SERVER SETUP ====================
console.log('🔄 Setting up Express server...');
const app = express();
app.use(express.json());
console.log('✅ Express JSON middleware enabled');
// Apply CORS if enabled
if (serverConfig.http.cors.enabled) {
console.log('🔄 Setting up CORS middleware...');
app.use(cors({
origin: serverConfig.http.cors.origins,
credentials: serverConfig.http.cors.credentials
}));
console.log('✅ CORS middleware enabled for origins:', serverConfig.http.cors.origins);
}
else {
console.log('ℹ️ CORS disabled');
}
// Apply rate limiting if enabled
if (serverConfig.security.rateLimit.enabled) {
console.log('🔄 Setting up rate limiting middleware...');
const limiter = rateLimit({
windowMs: serverConfig.security.rateLimit.windowMs,
max: serverConfig.security.rateLimit.max,
message: 'Too many requests from this IP, please try again later.'
});
app.use('/mcp', limiter);
console.log('✅ Rate limiting enabled:', serverConfig.security.rateLimit.max, 'requests per', serverConfig.security.rateLimit.windowMs, 'ms');
}
else {
console.log('ℹ️ Rate limiting disabled');
}
// IP filtering middleware
if (serverConfig.security.ipFilter.enabled) {
console.log('🔄 Setting up IP filtering middleware...');
app.use((req, res, next) => {
const clientIp = req.ip || req.connection.remoteAddress;
console.log('🔍 IP filter check for:', clientIp);
// Check whitelist
if (serverConfig.security.ipFilter.whitelist.length > 0) {
if (!serverConfig.security.ipFilter.whitelist.includes(clientIp)) {
console.log('❌ IP not whitelisted:', clientIp);
res.status(403).json({
jsonrpc: '2.0',
error: {
code: -32003,
message: 'Forbidden: IP not whitelisted'
},
id: null
});
return;
}
console.log('✅ IP whitelisted:', clientIp);
}
// Check blacklist
if (serverConfig.security.ipFilter.blacklist.includes(clientIp)) {
console.log('❌ IP blacklisted:', clientIp);
res.status(403).json({
jsonrpc: '2.0',
error: {
code: -32003,
message: 'Forbidden: IP blacklisted'
},
id: null
});
return;
}
next();
});
console.log('✅ IP filtering enabled. Whitelist:', serverConfig.security.ipFilter.whitelist, 'Blacklist:', serverConfig.security.ipFilter.blacklist);
}
else {
console.log('ℹ️ IP filtering disabled');
}
// Authentication middleware
function authenticate(req, res, next) {
console.log('🔍 Authentication check started');
console.log('🔍 Auth enabled:', serverConfig.http.auth.enabled);
console.log('🔍 Auth type:', serverConfig.http.auth.type);
if (!serverConfig.http.auth.enabled) {
console.log('ℹ️ Authentication disabled, skipping');
return next();
}
let token;
if (serverConfig.http.auth.type === 'bearer') {
const authHeader = req.headers['authorization'];
console.log('🔍 Authorization header:', authHeader ? `${authHeader.substring(0, 20)}...` : 'NOT SET');
if (authHeader && authHeader.startsWith('Bearer ')) {
token = authHeader.replace('Bearer ', '');
console.log('🔍 Bearer token extracted:', token ? `${token.substring(0, 8)}...` : 'NOT FOUND');
}
}
else if (serverConfig.http.auth.type === 'api-key') {
token = req.headers['x-api-key'] || req.query.apiKey;
console.log('🔍 API key token:', token ? `${token.substring(0, 8)}...` : 'NOT FOUND');
}
// Fallback to body token for backward compatibility
token = token || req.body?.token;
console.log('🔍 Final token (after fallback):', token ? `${token.substring(0, 8)}...` : 'NOT FOUND');
console.log('🔍 Expected token:', serverConfig.http.auth.token ? `${serverConfig.http.auth.token.substring(0, 8)}...` : 'NOT SET');
if (token !== serverConfig.http.auth.token) {
console.log('❌ Authentication failed: token mismatch');
res.status(401).json({
jsonrpc: '2.0',
error: {
code: -32001,
message: 'Unauthorized: Invalid or missing token'
},
id: null
});
return;
}
console.log('✅ Authentication successful');
next();
}
app.post('/mcp', authenticate, async (req, res) => {
console.log('📨 Received POST MCP request');
console.log('🔍 Request body size:', JSON.stringify(req.body).length, 'bytes');
console.log('🔍 Request headers:', Object.keys(req.headers));
try {
console.log('🔄 Creating StreamableHTTPServerTransport...');
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined
});
res.on('close', () => {
console.log('📪 Request closed');
transport.close();
server.close();
});
console.log('🔄 Connecting server to transport...');
await server.connect(transport);
console.log('✅ Server connected to transport');
console.log('🔄 Handling request...');
await transport.handleRequest(req, res, req.body);
console.log('✅ Request handled successfully');
}
catch (error) {
console.error('❌ Error handling MCP request:', error);
if (error instanceof Error) {
console.error('❌ Error stack:', error.stack);
}
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error'
},
id: null
});
}
}
});
app.get('/mcp', async (req, res) => {
console.log('📨 Received GET MCP request (not allowed)');
res
.writeHead(405)
.end(JSON.stringify({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Method not allowed.'
},
id: null
}));
});
app.delete('/mcp', async (req, res) => {
console.log('📨 Received DELETE MCP request (not allowed)');
res
.writeHead(405)
.end(JSON.stringify({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Method not allowed.'
},
id: null
}));
});
// Health check endpoint
app.get('/health', (req, res) => {
res.json({
status: 'ok',
service: 'WinCC OA MCP Server',
version: '3.0.0',
timestamp: new Date().toISOString()
});
});
// Initialize and start the server
async function start() {
// Validate configuration
const configErrors = validateConfig();
if (configErrors.length > 0) {
console.error('Configuration errors:');
configErrors.forEach(error => console.error(` - ${error}`));
process.exit(1);
}
server = await initializeServer();
const { host, port } = serverConfig.http;
// Create HTTP or HTTPS server
let httpServer;
if (serverConfig.http.ssl.enabled) {
const sslConfig = loadSSLConfig();
if (!sslConfig) {
console.error('SSL is enabled but certificates could not be loaded');
process.exit(1);
}
httpServer = https.createServer(sslConfig, app);
}
else {
httpServer = app;
}
httpServer.listen(port, host, () => {
const protocol = serverConfig.http.ssl.enabled ? 'https' : 'http';
console.log(`MCP Extended WinCC OA Server with CNS/UNS`);
console.log(`Server listening on ${protocol}://${host}:${port}`);
console.log(`Health check: ${protocol}://${host}:${port}/health`);
if (serverConfig.http.auth.enabled) {
console.log(`Authentication: ${serverConfig.http.auth.type}`);
if (serverConfig.http.auth.token) {
console.log(`API Token: ${serverConfig.http.auth.token.substring(0, 8)}... (first 8 chars shown)`);
}
}
else {
console.log('⚠️ WARNING: Authentication is disabled!');
}
if (serverConfig.http.cors.enabled) {
console.log('CORS enabled for:', serverConfig.http.cors.origins);
}
});
}
start().catch((error) => {
console.error("Failed to start server:", error);
process.exit(1);
});
export {};
//# sourceMappingURL=index_http.js.map