server-universal.jsā¢35.5 kB
#!/usr/bin/env node
// Universal MCP Server - Handles both stdio and HTTP transport
import { createServer } from 'http';
import { URL } from 'url';
import { readFile, stat } from 'fs/promises';
import { join, extname } from 'path';
console.error('š Universal MCP Server starting...');
// Initialize database with optimization support (skip warming to prevent table errors)
const requireWarming = false; // Disable warming to prevent table errors
// AUTO-START TENANT BACKEND SERVICE
let tenantBackendService = null;
const TENANT_SERVICE_PORT = process.env.TENANT_SERVICE_PORT || 3100;
// Auto-start tenant backend service
try {
console.error('š Auto-starting Tenant Backend Service...');
const { spawn } = await import('child_process');
tenantBackendService = spawn('node', ['tenant-backend-service.js'], {
cwd: process.cwd(),
stdio: ['pipe', 'pipe', 'pipe'],
env: {
...process.env,
TENANT_SERVICE_PORT: TENANT_SERVICE_PORT
},
detached: false
});
tenantBackendService.stdout.on('data', (data) => {
console.error(`š§ Tenant Service: ${data.toString().trim()}`);
});
tenantBackendService.stderr.on('data', (data) => {
console.error(`ā Tenant Service Error: ${data.toString().trim()}`);
});
tenantBackendService.on('exit', (code) => {
console.error(`š„ Tenant Service exited with code: ${code}`);
// Auto-restart tenant service
setTimeout(() => {
console.error('š Auto-restarting Tenant Backend Service...');
// Restart logic would go here
}, 5000);
});
console.error('ā
Tenant Backend Service auto-started on port', TENANT_SERVICE_PORT);
} catch (error) {
console.error('ā ļø Failed to auto-start Tenant Backend Service:', error.message);
}
// Import SimpleEGWDatabase dynamically to handle missing database gracefully
let database = null;
try {
const { SimpleEGWDatabase } = await import('./database-utils.js');
database = new SimpleEGWDatabase(process.env.EGW_DATABASE_PATH || './data/egw-writings.db', requireWarming);
console.error('ā
Database initialized successfully');
} catch (error) {
console.error('ā ļø Database initialization failed, continuing without database:', error.message);
// Create a dummy database object that returns appropriate error messages
database = {
search: async () => { throw new Error('Database not available'); },
getBook: async () => { throw new Error('Database not available'); },
getParagraphs: async () => { throw new Error('Database not available'); },
getBooks: async () => { throw new Error('Database not available'); },
getStats: async () => { throw new Error('Database not available'); },
findEGWQuotes: async () => { throw new Error('Database not available'); },
launchLocalSetup: async () => { throw new Error('Database not available'); }
};
}
// AUTO-HEARTBEAT: Start heartbeat if enabled
if (process.env.EGW_AUTO_HEARTBEAT === 'true') {
console.error('š Auto-heartbeat enabled - starting heartbeat...');
(async () => {
try {
const { AutoHeartbeatStarter } = await import('./auto-heartbeat-starter.cjs');
const heartbeatResult = await AutoHeartbeatStarter.validateWithAutoStart('universal-server');
if (heartbeatResult.success) {
console.error('ā
Auto-heartbeat started successfully');
} else {
console.error('ā ļø Auto-heartbeat failed:', heartbeatResult.message);
}
} catch (error) {
console.error('ā Auto-heartbeat error:', error.message);
}
})();
}
// Admin configuration for unrestricted operations
const ADMIN_CONFIG = {
password: process.env.ADMIN_PASSWORD || 'admin18401844', // Default password, change via ENV
requirePassword: process.env.REQUIRE_ADMIN_PASSWORD !== 'false', // Default to true
logAttempts: true,
maxAttempts: 3,
lockoutDuration: 5 * 60 * 1000 // 5 minutes
};
// Track failed attempts for IP-based lockout
const failedAttempts = new Map();
// Function to verify admin password
function verifyAdminPassword(password, clientIP) {
if (!ADMIN_CONFIG.requirePassword) {
return true; // No password required if disabled
}
const attempts = failedAttempts.get(clientIP) || { count: 0, lastAttempt: 0 };
const now = Date.now();
// Check if locked out
if (attempts.count >= ADMIN_CONFIG.maxAttempts &&
now - attempts.lastAttempt < ADMIN_CONFIG.lockoutDuration) {
const lockoutRemaining = Math.ceil((ADMIN_CONFIG.lockoutDuration - (now - attempts.lastAttempt)) / 1000 / 60);
throw new Error(`Too many failed attempts. Account locked for ${lockoutRemaining} minutes.`);
}
// Check password
const isValid = password === ADMIN_CONFIG.password;
if (!isValid) {
attempts.count++;
attempts.lastAttempt = now;
failedAttempts.set(clientIP, attempts);
if (ADMIN_CONFIG.logAttempts) {
console.error(`ā Failed admin attempt from ${clientIP}: ${attempts.count}/${ADMIN_CONFIG.maxAttempts}`);
}
return false;
}
// Successful attempt - reset counter
failedAttempts.delete(clientIP);
if (ADMIN_CONFIG.logAttempts) {
console.error(`ā
Successful admin access from ${clientIP}`);
}
return true;
}
// Define tools with proper schemas
const tools = [
{
name: 'search_local',
description: 'Search local EGW writings database',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query'
},
limit: {
type: 'number',
description: 'Maximum results (default: 20)',
default: 20
},
},
required: ['query'],
},
},
{
name: 'get_local_book',
description: 'Get information about a specific book',
inputSchema: {
type: 'object',
properties: {
bookId: {
type: 'number',
description: 'Book ID'
},
},
required: ['bookId'],
},
},
{
name: 'get_local_content',
description: 'Get content from a specific book',
inputSchema: {
type: 'object',
properties: {
bookId: {
type: 'number',
description: 'Book ID'
},
limit: {
type: 'number',
description: 'Maximum paragraphs (default: 50)',
default: 50
},
offset: {
type: 'number',
description: 'Offset for pagination (default: 0)',
default: 0
},
},
required: ['bookId'],
},
},
{
name: 'list_local_books',
description: 'List all available books',
inputSchema: {
type: 'object',
properties: {
limit: {
type: 'number',
description: 'Maximum books (default: 50)',
default: 50
},
},
},
},
{
name: 'get_database_stats',
description: 'Get database statistics',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'find_egw_quotes',
description: 'Find specific EGW quotes containing a search term with proper filtering for genuine EGW content',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search term to find in EGW quotes'
},
numQuotes: {
type: 'number',
description: 'Number of quotes to return (default: 3)',
default: 3
},
},
required: ['query'],
},
},
{
name: 'launch_local_chat_ai',
description: 'Launch local EGW Writings MCP Server setup with chat interface',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'bash',
description: 'Execute bash commands with automatic GitHub PAT integration for git operations within specified workspace',
inputSchema: {
type: 'object',
properties: {
command: {
type: 'string',
description: 'Bash command to execute (git commands automatically use stored PAT)'
},
workspace: {
type: 'string',
description: 'Workspace directory to execute command in (for multi-tenant isolation)',
default: null
},
sessionId: {
type: 'string',
description: 'Session ID for logging and security (for multi-tenant)',
default: null
}
},
required: ['command'],
},
},
{
name: 'admin_local_server',
description: 'Execute system-level commands for file manipulation, git operations, and server management (ADMIN PASSWORD REQUIRED)',
inputSchema: {
type: 'object',
properties: {
command: {
type: 'string',
description: 'System-level bash command to execute'
},
adminPassword: {
type: 'string',
description: 'Admin password for system-level access'
}
},
required: ['command'],
},
},
];
// Build capabilities object
const capabilities = {};
tools.forEach(tool => {
capabilities[tool.name] = {
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema
};
});
// Handle different MCP methods
async function handleRequest(request) {
const { method, params, id } = request;
console.error(`š„ Request: ${method} (id: ${id})`);
try {
switch (method) {
case 'initialize':
return handleInitialize(id);
case 'tools/list':
return handleToolsList(id);
case 'tools/call':
return await handleToolCall(params, id);
case 'notifications/initialized':
case 'notifications/cancelled':
// These are notifications, no response needed
return null;
default:
return {
jsonrpc: '2.0',
id,
error: {
code: -32601,
message: `Method not found: ${method}`,
},
};
}
} catch (error) {
console.error('ā Request handling error:', error.message);
return {
jsonrpc: '2.0',
id,
error: {
code: -32603,
message: `Internal error: ${error.message}`,
},
};
}
}
function handleInitialize(id) {
const response = {
jsonrpc: '2.0',
id,
result: {
protocolVersion: '2025-06-18',
capabilities: {
tools: capabilities,
resources: {},
},
serverInfo: {
name: 'egw-research-server',
version: '1.0.0',
},
},
};
console.error('š¤ Sending initialize response');
return response;
}
function handleToolsList(id) {
const response = {
jsonrpc: '2.0',
id,
result: {
tools: tools,
},
};
console.error('š¤ Sending tools/list response');
return response;
}
async function handleToolCall(params, id) {
const { name, arguments: args } = params;
console.error(`š ļø Tool call: ${name}`, args);
try {
let response;
switch (name) {
case 'search_local': {
const { query, limit = 20 } = args;
try {
const results = await database.search(query, limit);
response = {
jsonrpc: '2.0',
id,
result: {
content: [
{
type: 'text',
text: JSON.stringify({
results: results,
total: results.length,
query: query,
message: results.length > 0 ?
`Found ${results.length} results for "${query}"` :
`No results found for "${query}"`
}, null, 2),
},
],
},
};
} catch (error) {
response = {
jsonrpc: '2.0',
id,
error: {
code: -32603,
message: `Search failed: ${error.message}`,
},
};
}
break;
}
case 'get_local_book': {
const { bookId } = args;
try {
const book = await database.getBook(bookId);
if (book) {
response = {
jsonrpc: '2.0',
id,
result: {
content: [
{
type: 'text',
text: JSON.stringify(book, null, 2),
},
],
},
};
} else {
response = {
jsonrpc: '2.0',
id,
error: {
code: -32602,
message: `Book with ID ${bookId} not found`,
},
};
}
} catch (error) {
response = {
jsonrpc: '2.0',
id,
error: {
code: -32603,
message: `Failed to get book: ${error.message}`,
},
};
}
break;
}
case 'get_local_content': {
const { bookId, limit = 50, offset = 0 } = args;
try {
const paragraphs = await database.getParagraphs(bookId, limit, offset);
response = {
jsonrpc: '2.0',
id,
result: {
content: [
{
type: 'text',
text: JSON.stringify({
bookId,
paragraphs: paragraphs,
total: paragraphs.length,
limit,
offset
}, null, 2),
},
],
},
};
} catch (error) {
response = {
jsonrpc: '2.0',
id,
error: {
code: -32603,
message: `Failed to get content: ${error.message}`,
},
};
}
break;
}
case 'list_local_books': {
const { limit = 50 } = args;
try {
const books = await database.getBooks(limit);
response = {
jsonrpc: '2.0',
id,
result: {
content: [
{
type: 'text',
text: JSON.stringify(books, null, 2),
},
],
},
};
} catch (error) {
response = {
jsonrpc: '2.0',
id,
error: {
code: -32603,
message: `Failed to list books: ${error.message}`,
},
};
}
break;
}
case 'get_database_stats': {
try {
const stats = await database.getStats();
response = {
jsonrpc: '2.0',
id,
result: {
content: [
{
type: 'text',
text: JSON.stringify(stats, null, 2),
},
],
},
};
} catch (error) {
response = {
jsonrpc: '2.0',
id,
error: {
code: -32603,
message: `Failed to get stats: ${error.message}`,
},
};
}
break;
}
case 'find_egw_quotes': {
const { query, numQuotes = 3 } = args;
try {
const result = await database.findEGWQuotes(query, numQuotes);
response = {
jsonrpc: '2.0',
id,
result: {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
},
};
} catch (error) {
response = {
jsonrpc: '2.0',
id,
error: {
code: -32603,
message: `Failed to find EGW quotes: ${error.message}`,
},
};
}
break;
}
case 'launch_local_chat_ai': {
try {
const result = await database.launchLocalSetup();
response = {
jsonrpc: '2.0',
id,
result: {
content: [
{
type: 'text',
text: JSON.stringify({
success: true,
message: 'EGW Writings MCP Server and Chat CLI launched successfully',
details: result
}, null, 2),
},
],
},
};
} catch (error) {
response = {
jsonrpc: '2.0',
id,
error: {
code: -32603,
message: `Failed to launch local chat AI: ${error.message}`,
},
};
}
break;
}
case 'run_bash_command': {
// Alias for bash tool - map cwd parameter to workspace
const { command, cwd } = args;
return await handleToolCall({
name: 'bash',
arguments: {
command,
workspace: cwd,
sessionId: null
}
}, id);
}
case 'bash': {
const { command, workspace, sessionId } = args;
try {
let result;
// Use persistent tenant backend if sessionId is provided
if (sessionId && tenantBackendService) {
console.error(`š Using persistent tenant backend for session: ${sessionId.substring(0, 8)}...`);
// First, ensure tenant backend is started
try {
const startResponse = await fetch(`http://localhost:${TENANT_SERVICE_PORT}/start-tenant`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId, workspace })
});
if (!startResponse.ok) {
throw new Error('Failed to start tenant backend');
}
const startResult = await startResponse.json();
console.error(`ā
Tenant backend ready: ${startResult.message}`);
} catch (error) {
// Tenant might already be running, continue with command execution
console.error(`š Tenant backend check: ${error.message}`);
}
// Execute command in persistent tenant backend
const bashResponse = await fetch(`http://localhost:${TENANT_SERVICE_PORT}/tenant-bash`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId, command })
});
if (!bashResponse.ok) {
throw new Error('Tenant backend command execution failed');
}
const bashResult = await bashResponse.json();
result = {
success: bashResult.success,
stdout: bashResult.stdout,
stderr: bashResult.stderr,
exit_code: bashResult.exit_code || 0,
workspace: bashResult.workspace,
isolation: 'persistent_tenant_backend'
};
} else {
// Fallback to direct execution if no tenant backend
console.error(`ā ļø No tenant backend - using direct execution`);
const { exec } = await import('child_process');
// Determine execution directory with isolation
let executionDir = process.cwd(); // Default to server directory
// If workspace is provided, use it for multi-tenant isolation
if (workspace) {
const { mkdir } = await import('fs/promises');
try {
// Create workspace directory if it doesn't exist
await mkdir(workspace, { recursive: true });
executionDir = workspace;
console.error(`šļø Executing in tenant workspace: ${workspace}`);
} catch (error) {
console.error(`ā Failed to create workspace ${workspace}: ${error.message}`);
executionDir = workspace; // Try anyway
console.error(`šļø Executing in existing tenant workspace: ${workspace}`);
}
}
// Enhanced git command handling with PAT support
let enhancedCommand = command;
const githubPAT = process.env.GITHUB_PAT;
const hasPAT = !!githubPAT;
// Automatically configure git for GitHub operations if PAT is available
if (hasPAT && command.startsWith('git')) {
// For git clone, embed PAT in URL
if (command.includes('git clone https://github.com/')) {
enhancedCommand = command.replace(
'https://github.com/',
`https://${githubPAT}@github.com/`
);
console.error('š Auto-configured git clone with PAT [HIDDEN]');
}
// For git push, configure remote URL with PAT
else if (command.includes('git push')) {
// First configure the remote URL with PAT, then push
enhancedCommand = `
git remote set-url origin https://${githubPAT}@github.com/$(git config --get remote.origin.url | sed 's/.*github\\.com\\///')
git push origin
`;
console.error('š Auto-configured git push with PAT [HIDDEN]');
}
// For other git operations that might need authentication
else if (command.includes('git pull') || command.includes('git fetch')) {
enhancedCommand = command.replace(
'origin',
`https://${githubPAT}@github.com/$(git config --get remote.origin.url | sed 's/.*github\\.com\\///')`
);
console.error('š Auto-configured git pull/fetch with PAT [HIDDEN]');
}
}
result = await new Promise((resolve, reject) => {
// Use cross-platform shell detection
const shell = process.platform === 'win32' ? 'cmd.exe' : '/bin/bash';
console.error(`š§ Using shell: ${shell} on platform: ${process.platform}`);
exec(enhancedCommand, {
cwd: executionDir, // CRITICAL: Execute in specified workspace for isolation
timeout: 30000,
maxBuffer: 1024 * 1024 * 10, // 10MB buffer
env: {
...process.env,
GIT_TERMINAL_PROMPT: '0', // Prevent interactive prompts
GIT_ASKPASS: 'echo', // No password prompts
PWD: executionDir, // Set working directory
GIT_WORK_TREE: executionDir, // Git working tree
GIT_DIR: workspace ? `${workspace}/.git` : undefined // Git directory if workspace provided
},
shell: shell // Use cross-platform compatible shell
}, (error, stdout, stderr) => {
if (error) {
resolve({
success: false,
error: error.message,
stdout: stdout || '',
stderr: stderr || '',
exit_code: error.code || -1,
workspace: workspace || executionDir,
isolation: workspace ? 'tenant_isolated' : 'server_shared'
});
} else {
resolve({
success: true,
stdout: stdout || '',
stderr: stderr || '',
exit_code: 0,
workspace: workspace || executionDir,
isolation: workspace ? 'tenant_isolated' : 'server_shared'
});
}
});
});
}
const githubPAT = process.env.GITHUB_PAT;
const hasPAT = !!githubPAT && command.startsWith('git');
response = {
jsonrpc: '2.0',
id,
result: {
content: [
{
type: 'text',
text: JSON.stringify({
success: result.success,
command: command,
stdout: result.stdout,
stderr: result.stderr,
exit_code: result.exit_code,
message: result.success ?
`Command executed successfully in ${result.isolation}` :
`Command failed with exit code ${result.exit_code}`,
pat_used: hasPAT ? 'Auto-configured with stored PAT' : undefined,
workspace: result.workspace,
session_id: sessionId || 'unspecified',
isolation: result.isolation
}, null, 2),
},
],
},
};
} catch (error) {
response = {
jsonrpc: '2.0',
id,
error: {
code: -32603,
message: `Failed to execute bash command: ${error.message}`,
},
};
}
break;
}
case 'admin_local_server': {
const { command, adminPassword } = args;
// Get client IP for rate limiting and lockout
const getClientIP = () => {
// In HTTP mode, get from request
// In stdio mode, use a generic identifier
return 'stdio-client';
};
try {
// Verify admin password
const clientIP = getClientIP();
if (!verifyAdminPassword(adminPassword, clientIP)) {
response = {
jsonrpc: '2.0',
id,
error: {
code: -32602,
message: 'Admin password required for system-level access. Provide adminPassword parameter.',
},
};
break;
}
const { exec } = await import('child_process');
const result = await new Promise((resolve, reject) => {
exec(command, {
timeout: 30000,
maxBuffer: 1024 * 1024 * 10 // 10MB buffer
}, (error, stdout, stderr) => {
if (error) {
resolve({
success: false,
error: error.message,
stdout: stdout || '',
stderr: stderr || '',
exit_code: error.code || -1
});
} else {
resolve({
success: true,
stdout: stdout || '',
stderr: stderr || '',
exit_code: 0
});
}
});
});
response = {
jsonrpc: '2.0',
id,
result: {
content: [
{
type: 'text',
text: JSON.stringify({
success: result.success,
command: command,
stdout: result.stdout,
stderr: result.stderr,
exit_code: result.exit_code,
message: result.success ?
`System command executed successfully` :
`System command failed with exit code ${result.exit_code}`
}, null, 2),
},
],
},
};
} catch (error) {
response = {
jsonrpc: '2.0',
id,
error: {
code: -32603,
message: `Failed to execute system command: ${error.message}`,
},
};
}
break;
}
default:
response = {
jsonrpc: '2.0',
id,
error: {
code: -32601,
message: `Unknown tool: ${name}`,
},
};
}
console.error('š¤ Sending tool call response');
return response;
} catch (error) {
console.error(`ā Tool execution error (${name}):`, error.message);
return {
jsonrpc: '2.0',
id,
error: {
code: -32603,
message: `Tool execution error: ${error.message}`,
},
};
}
}
// ===== HTML FILE SERVING =====
async function serveHtmlFile(req, res, filePath) {
try {
// Resolve file path (security: prevent directory traversal)
const resolvedPath = join(process.cwd(), filePath);
// Check if file exists
const stats = await stat(resolvedPath);
if (!stats.isFile()) {
res.writeHead(404);
res.end('File not found');
return;
}
// Determine content type based on file extension
const ext = extname(resolvedPath).toLowerCase();
const contentTypes = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml'
};
const contentType = contentTypes[ext] || 'text/plain';
// Read and serve file
const content = await readFile(resolvedPath);
res.setHeader('Content-Type', contentType);
res.writeHead(200);
res.end(content);
console.error(`š Served HTML file: ${filePath}`);
} catch (error) {
console.error('ā Error serving HTML file:', error.message);
res.writeHead(500);
res.end('Internal server error');
}
}
// ===== STDIO HANDLER =====
function setupStdioHandler() {
console.error('š” Setting up stdio handler...');
process.stdin.setEncoding('utf8');
process.stdout.setEncoding('utf8');
let buffer = '';
process.stdin.on('data', async (chunk) => {
buffer += chunk;
const messages = buffer.split('\n');
buffer = messages.pop() || '';
for (const message of messages) {
if (message.trim()) {
try {
const request = JSON.parse(message);
const response = await handleRequest(request);
if (response) {
process.stdout.write(JSON.stringify(response) + '\n');
}
} catch (error) {
console.error('ā Stdio error:', error.message);
}
}
}
});
}
// ===== HTTP HANDLER =====
function setupHttpHandler() {
console.error('š Setting up HTTP handler...');
const server = createServer(async (req, res) => {
// Set CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Max-Age', '86400');
// Handle OPTIONS requests for CORS preflight
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
// Parse URL and handle different routes
const url = new URL(req.url || '/', `http://${req.headers.host}`);
// Handle MCP JSON-RPC requests
if (req.method === 'POST' && url.pathname === '/mcp') {
try {
// Read request body
let body = '';
for await (const chunk of req) {
body += chunk;
}
console.error('š„ HTTP Request body:', body);
// Parse JSON-RPC request
const request = JSON.parse(body);
// Handle request
const response = await handleRequest(request);
if (response) {
// Send JSON-RPC response
res.setHeader('Content-Type', 'application/json');
res.writeHead(200);
res.end(JSON.stringify(response));
console.error('š¤ HTTP Response sent');
} else {
// No response needed (for notifications)
res.writeHead(204);
res.end();
}
} catch (error) {
console.error('ā HTTP Server error:', error.message);
// Send error response
const errorResponse = {
jsonrpc: '2.0',
id: null,
error: {
code: -32700,
message: 'Parse error'
}
};
res.setHeader('Content-Type', 'application/json');
res.writeHead(400);
res.end(JSON.stringify(errorResponse));
}
return;
} else if (req.method === 'GET' && (url.pathname.endsWith('.html') || url.pathname.endsWith('.css') || url.pathname.endsWith('.js'))) {
// Serve static files (HTML, CSS, JS)
await serveHtmlFile(req, res, url.pathname);
return;
} else if (req.method === 'GET' && url.pathname === '/') {
// Serve default index page or redirect to html_wrapper_solution
try {
// Try to serve streamlit wrapper as default
await serveHtmlFile(req, res, 'html_wrapper_solution/streamlit_wrapper.html');
return;
} catch (error) {
// Fallback to simple HTML page
res.setHeader('Content-Type', 'text/html');
res.writeHead(200);
res.end(`
<!DOCTYPE html>
<html>
<head><title>EGW MCP Server</title></head>
<body>
<h1>EGW Writings MCP Server</h1>
<p>HTML Wrapper Solution is available at: <a href="/html_wrapper_solution/streamlit_wrapper.html">Streamlit Wrapper</a></p>
<p>Universal Wrapper: <a href="/html_wrapper_solution/universal_python_web_wrapper.html">Universal Wrapper</a></p>
<p>MCP Endpoint: <a href="/mcp">/mcp</a></p>
</body>
</html>
`);
return;
}
} else {
// 404 for other routes
res.writeHead(404);
res.end(JSON.stringify({ error: 'Not found' }));
return;
}
});
// Start HTTP server on port 3000 (Smithery expects this port)
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.error(`ā
HTTP server ready - listening on port ${PORT}`);
console.error(`š” HTTP Endpoint: http://localhost:${PORT}/mcp`);
});
return server;
}
// ===== MAIN ENTRY POINT =====
// Check if we're running in stdio mode (no TTY) or HTTP mode
if (process.stdin.isTTY) {
// We have a TTY, so we're probably running interactively
// Start HTTP server for testing
console.error('š Running in HTTP mode (TTY detected)');
setupHttpHandler();
} else {
// No TTY, so we're probably running in stdio mode
// Setup stdio handler for Smithery
console.error('š§ Running in stdio mode (no TTY)');
setupStdioHandler();
// Also start HTTP server on same port for Smithery
const httpServer = setupHttpHandler();
// Handle graceful shutdown
process.on('SIGINT', () => {
console.error('š Received SIGINT - shutting down');
httpServer?.close();
process.exit(0);
});
process.on('SIGTERM', () => {
console.error('š Received SIGTERM - shutting down');
httpServer?.close();
process.exit(0);
});
}
console.error('ā
Universal MCP Server ready - supports both stdio and HTTP');