|
import express from 'express'; |
|
import config from './config/index.js'; |
|
import path from 'path'; |
|
import { fileURLToPath } from 'url'; |
|
import fs from 'fs'; |
|
import { initUpstreamServers } from './services/mcpService.js'; |
|
import { initMiddlewares } from './middlewares/index.js'; |
|
import { initRoutes } from './routes/index.js'; |
|
import { |
|
handleSseConnection, |
|
handleSseMessage, |
|
handleMcpPostRequest, |
|
handleMcpOtherRequest, |
|
} from './services/sseService.js'; |
|
import { initializeDefaultUser } from './models/User.js'; |
|
|
|
|
|
const __filename = fileURLToPath(import.meta.url); |
|
const __dirname = path.dirname(__filename); |
|
|
|
export class AppServer { |
|
private app: express.Application; |
|
private port: number | string; |
|
private frontendPath: string | null = null; |
|
private basePath: string; |
|
|
|
constructor() { |
|
this.app = express(); |
|
this.port = config.port; |
|
this.basePath = config.basePath; |
|
} |
|
|
|
async initialize(): Promise<void> { |
|
try { |
|
|
|
await initializeDefaultUser(); |
|
|
|
initMiddlewares(this.app); |
|
initRoutes(this.app); |
|
console.log('Server initialized successfully'); |
|
|
|
initUpstreamServers() |
|
.then(() => { |
|
console.log('MCP server initialized successfully'); |
|
this.app.get(`${this.basePath}/sse/:group?`, (req, res) => handleSseConnection(req, res)); |
|
this.app.post(`${this.basePath}/messages`, handleSseMessage); |
|
this.app.post(`${this.basePath}/mcp/:group?`, handleMcpPostRequest); |
|
this.app.get(`${this.basePath}/mcp/:group?`, handleMcpOtherRequest); |
|
this.app.delete(`${this.basePath}/mcp/:group?`, handleMcpOtherRequest); |
|
}) |
|
.catch((error) => { |
|
console.error('Error initializing MCP server:', error); |
|
throw error; |
|
}) |
|
.finally(() => { |
|
|
|
this.findAndServeFrontend(); |
|
}); |
|
} catch (error) { |
|
console.error('Error initializing server:', error); |
|
throw error; |
|
} |
|
} |
|
|
|
private findAndServeFrontend(): void { |
|
|
|
this.frontendPath = this.findFrontendDistPath(); |
|
|
|
if (this.frontendPath) { |
|
console.log(`Serving frontend from: ${this.frontendPath}`); |
|
|
|
this.app.use(this.basePath, express.static(this.frontendPath)); |
|
|
|
|
|
if (fs.existsSync(path.join(this.frontendPath, 'index.html'))) { |
|
this.app.get(`${this.basePath}/*`, (_req, res) => { |
|
res.sendFile(path.join(this.frontendPath!, 'index.html')); |
|
}); |
|
|
|
|
|
if (this.basePath) { |
|
this.app.get('/', (_req, res) => { |
|
res.redirect(this.basePath); |
|
}); |
|
} |
|
} |
|
} else { |
|
console.warn('Frontend dist directory not found. Server will run without frontend.'); |
|
const rootPath = this.basePath || '/'; |
|
this.app.get(rootPath, (_req, res) => { |
|
res |
|
.status(404) |
|
.send('Frontend not found. MCPHub API is running, but the UI is not available.'); |
|
}); |
|
} |
|
} |
|
|
|
start(): void { |
|
this.app.listen(this.port, () => { |
|
console.log(`Server is running on port ${this.port}`); |
|
if (this.frontendPath) { |
|
console.log(`Open http://localhost:${this.port} in your browser to access MCPHub UI`); |
|
} else { |
|
console.log( |
|
`MCPHub API is running on http://localhost:${this.port}, but the UI is not available`, |
|
); |
|
} |
|
}); |
|
} |
|
|
|
getApp(): express.Application { |
|
return this.app; |
|
} |
|
|
|
|
|
private findFrontendDistPath(): string | null { |
|
|
|
const debug = process.env.DEBUG === 'true'; |
|
|
|
if (debug) { |
|
console.log('DEBUG: Current directory:', process.cwd()); |
|
console.log('DEBUG: Script directory:', __dirname); |
|
} |
|
|
|
|
|
const packageRoot = this.findPackageRoot(); |
|
|
|
if (debug) { |
|
console.log('DEBUG: Using package root:', packageRoot); |
|
} |
|
|
|
if (!packageRoot) { |
|
console.warn('Could not determine package root directory'); |
|
return null; |
|
} |
|
|
|
|
|
const frontendDistPath = path.join(packageRoot, 'frontend', 'dist'); |
|
|
|
if (debug) { |
|
console.log(`DEBUG: Checking frontend at: ${frontendDistPath}`); |
|
} |
|
|
|
if ( |
|
fs.existsSync(frontendDistPath) && |
|
fs.existsSync(path.join(frontendDistPath, 'index.html')) |
|
) { |
|
return frontendDistPath; |
|
} |
|
|
|
console.warn('Frontend distribution not found at', frontendDistPath); |
|
return null; |
|
} |
|
|
|
|
|
private findPackageRoot(): string | null { |
|
const debug = process.env.DEBUG === 'true'; |
|
|
|
|
|
const possibleRoots = [ |
|
|
|
path.resolve(__dirname, '..', '..'), |
|
|
|
process.cwd(), |
|
|
|
path.resolve(__dirname, '..'), |
|
|
|
path.resolve(__dirname, '..', '..', '..'), |
|
]; |
|
|
|
|
|
if (process.argv[1] && process.argv[1].includes('_npx')) { |
|
const npxDir = path.dirname(process.argv[1]); |
|
possibleRoots.unshift(path.resolve(npxDir, '..')); |
|
} |
|
|
|
if (debug) { |
|
console.log('DEBUG: Checking for package.json in:', possibleRoots); |
|
} |
|
|
|
for (const root of possibleRoots) { |
|
const packageJsonPath = path.join(root, 'package.json'); |
|
if (fs.existsSync(packageJsonPath)) { |
|
try { |
|
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); |
|
if (pkg.name === 'mcphub' || pkg.name === '@samanhappy/mcphub') { |
|
if (debug) { |
|
console.log(`DEBUG: Found package.json at ${packageJsonPath}`); |
|
} |
|
return root; |
|
} |
|
} catch (e) { |
|
if (debug) { |
|
console.error(`DEBUG: Failed to parse package.json at ${packageJsonPath}:`, e); |
|
} |
|
|
|
} |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
} |
|
|
|
export default AppServer; |
|
|