File size: 6,421 Bytes
eb846d0 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
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';
// Get the directory name in ESM
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 {
// Initialize default admin user if no users exist
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(() => {
// Find and serve frontend
this.findAndServeFrontend();
});
} catch (error) {
console.error('Error initializing server:', error);
throw error;
}
}
private findAndServeFrontend(): void {
// Find frontend path
this.frontendPath = this.findFrontendDistPath();
if (this.frontendPath) {
console.log(`Serving frontend from: ${this.frontendPath}`);
// Serve static files with base path
this.app.use(this.basePath, express.static(this.frontendPath));
// Add the wildcard route for SPA with base path
if (fs.existsSync(path.join(this.frontendPath, 'index.html'))) {
this.app.get(`${this.basePath}/*`, (_req, res) => {
res.sendFile(path.join(this.frontendPath!, 'index.html'));
});
// Also handle root redirect if base path is set
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;
}
// Helper method to find frontend dist path in different environments
private findFrontendDistPath(): string | null {
// Debug flag for detailed logging
const debug = process.env.DEBUG === 'true';
if (debug) {
console.log('DEBUG: Current directory:', process.cwd());
console.log('DEBUG: Script directory:', __dirname);
}
// First, find the package root directory
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;
}
// Check for frontend dist in the standard location
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;
}
// Helper method to find the package root (where package.json is located)
private findPackageRoot(): string | null {
const debug = process.env.DEBUG === 'true';
// Possible locations for package.json
const possibleRoots = [
// Standard npm package location
path.resolve(__dirname, '..', '..'),
// Current working directory
process.cwd(),
// When running from dist directory
path.resolve(__dirname, '..'),
// When installed via npx
path.resolve(__dirname, '..', '..', '..'),
];
// Special handling for npx
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);
}
// Continue to the next potential root
}
}
}
return null;
}
}
export default AppServer;
|