mirror of
https://github.com/samanhappy/mcphub.git
synced 2025-12-24 02:39:19 -05:00
feat: enhance configuration file handling and dynamic frontend path resolution (#40)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
96
bin/cli.js
Executable file
96
bin/cli.js
Executable file
@@ -0,0 +1,96 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { execSync } from 'child_process';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
// Enable debug logging if needed
|
||||||
|
// process.env.DEBUG = 'true';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
// Start with more debug information
|
||||||
|
console.log('📋 MCPHub CLI');
|
||||||
|
console.log(`📁 CLI script location: ${__dirname}`);
|
||||||
|
|
||||||
|
// The npm package directory structure when installed is:
|
||||||
|
// node_modules/@samanhappy/mcphub/
|
||||||
|
// - dist/
|
||||||
|
// - bin/
|
||||||
|
// - frontend/dist/
|
||||||
|
|
||||||
|
// Get the package root - this is where package.json is located
|
||||||
|
function findPackageRoot() {
|
||||||
|
const isDebug = process.env.DEBUG === 'true';
|
||||||
|
|
||||||
|
// Possible locations for package.json
|
||||||
|
const possibleRoots = [
|
||||||
|
// Standard npm package location
|
||||||
|
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 (isDebug) {
|
||||||
|
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 (isDebug) {
|
||||||
|
console.log(`DEBUG: Found package.json at ${packageJsonPath}`);
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Continue to the next potential root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('⚠️ Could not find package.json, using default path');
|
||||||
|
return path.resolve(__dirname, '..');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locate and check the frontend distribution
|
||||||
|
function checkFrontend(packageRoot) {
|
||||||
|
const isDebug = process.env.DEBUG === 'true';
|
||||||
|
const frontendDistPath = path.join(packageRoot, 'frontend', 'dist');
|
||||||
|
|
||||||
|
if (isDebug) {
|
||||||
|
console.log(`DEBUG: Checking frontend at: ${frontendDistPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(frontendDistPath) && fs.existsSync(path.join(frontendDistPath, 'index.html'))) {
|
||||||
|
console.log('✅ Frontend distribution found');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ Frontend distribution not found at', frontendDistPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectRoot = findPackageRoot();
|
||||||
|
console.log(`📦 Using package root: ${projectRoot}`);
|
||||||
|
|
||||||
|
// Check if frontend exists
|
||||||
|
checkFrontend(projectRoot);
|
||||||
|
|
||||||
|
// Start the server
|
||||||
|
console.log('🚀 Starting MCPHub server...');
|
||||||
|
import(path.join(projectRoot, 'dist', 'index.js')).catch(err => {
|
||||||
|
console.error('Failed to start MCPHub:', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
87
package.json
87
package.json
@@ -1,11 +1,24 @@
|
|||||||
{
|
{
|
||||||
"name": "mcphub",
|
"name": "@samanhappy/mcphub",
|
||||||
"version": "0.0.1",
|
"version": "0.0.27",
|
||||||
"description": "A hub server for mcp servers",
|
"description": "A hub server for mcp servers",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"mcphub": "bin/cli.js"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist",
|
||||||
|
"bin",
|
||||||
|
"mcp_settings.json",
|
||||||
|
"servers.json",
|
||||||
|
"frontend/dist",
|
||||||
|
"README.md",
|
||||||
|
"LICENSE"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "pnpm backend:build && pnpm frontend:build",
|
||||||
|
"backend:build": "tsc",
|
||||||
"start": "node dist/index.js",
|
"start": "node dist/index.js",
|
||||||
"backend:dev": "tsx watch src/index.ts",
|
"backend:dev": "tsx watch src/index.ts",
|
||||||
"lint": "eslint . --ext .ts",
|
"lint": "eslint . --ext .ts",
|
||||||
@@ -14,36 +27,55 @@
|
|||||||
"frontend:dev": "cd frontend && vite",
|
"frontend:dev": "cd frontend && vite",
|
||||||
"frontend:build": "cd frontend && vite build",
|
"frontend:build": "cd frontend && vite build",
|
||||||
"frontend:preview": "cd frontend && vite preview",
|
"frontend:preview": "cd frontend && vite preview",
|
||||||
"dev": "concurrently \"pnpm backend:dev\" \"pnpm frontend:dev\""
|
"dev": "concurrently \"pnpm backend:dev\" \"pnpm frontend:dev\"",
|
||||||
|
"prepublishOnly": "npm run build && node scripts/verify-dist.js"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"typescript",
|
"typescript",
|
||||||
"server"
|
"server",
|
||||||
|
"mcp",
|
||||||
|
"model context protocol"
|
||||||
],
|
],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.10.2",
|
"@modelcontextprotocol/sdk": "^1.10.2",
|
||||||
"@radix-ui/react-accordion": "^1.2.3",
|
|
||||||
"@radix-ui/react-slot": "^1.1.2",
|
|
||||||
"@shadcn/ui": "^0.0.4",
|
|
||||||
"@tailwindcss/vite": "^4.1.3",
|
|
||||||
"@types/react": "^19.0.12",
|
|
||||||
"@types/react-dom": "^19.0.4",
|
|
||||||
"@types/uuid": "^10.0.0",
|
|
||||||
"autoprefixer": "^10.4.21",
|
|
||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.2",
|
||||||
"class-variance-authority": "^0.7.1",
|
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-validator": "^7.2.1",
|
"express-validator": "^7.2.1",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"uuid": "^11.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@radix-ui/react-accordion": "^1.2.3",
|
||||||
|
"@radix-ui/react-slot": "^1.1.2",
|
||||||
|
"@shadcn/ui": "^0.0.4",
|
||||||
|
"@tailwindcss/postcss": "^4.1.3",
|
||||||
|
"@tailwindcss/vite": "^4.1.3",
|
||||||
|
"@types/bcryptjs": "^3.0.0",
|
||||||
|
"@types/express": "^4.17.21",
|
||||||
|
"@types/jest": "^29.5.5",
|
||||||
|
"@types/jsonwebtoken": "^9.0.9",
|
||||||
|
"@types/node": "^20.8.2",
|
||||||
|
"@types/react": "^19.0.12",
|
||||||
|
"@types/react-dom": "^19.0.4",
|
||||||
|
"@types/uuid": "^10.0.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
||||||
|
"@typescript-eslint/parser": "^6.7.4",
|
||||||
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
|
"autoprefixer": "^10.4.21",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"concurrently": "^8.2.2",
|
||||||
|
"eslint": "^8.50.0",
|
||||||
"i18next": "^24.2.3",
|
"i18next": "^24.2.3",
|
||||||
"i18next-browser-languagedetector": "^8.0.4",
|
"i18next-browser-languagedetector": "^8.0.4",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jest": "^29.7.0",
|
||||||
"lucide-react": "^0.486.0",
|
"lucide-react": "^0.486.0",
|
||||||
"next": "^15.2.4",
|
"next": "^15.2.4",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
|
"prettier": "^3.0.3",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-i18next": "^15.4.1",
|
"react-i18next": "^15.4.1",
|
||||||
@@ -51,27 +83,14 @@
|
|||||||
"tailwind-merge": "^3.1.0",
|
"tailwind-merge": "^3.1.0",
|
||||||
"tailwind-scrollbar-hide": "^2.0.0",
|
"tailwind-scrollbar-hide": "^2.0.0",
|
||||||
"tailwindcss": "^4.0.17",
|
"tailwindcss": "^4.0.17",
|
||||||
"uuid": "^11.1.0",
|
|
||||||
"zod": "^3.24.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@tailwindcss/postcss": "^4.1.3",
|
|
||||||
"@types/bcryptjs": "^3.0.0",
|
|
||||||
"@types/express": "^4.17.21",
|
|
||||||
"@types/jest": "^29.5.5",
|
|
||||||
"@types/jsonwebtoken": "^9.0.9",
|
|
||||||
"@types/node": "^20.8.2",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
|
||||||
"@typescript-eslint/parser": "^6.7.4",
|
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
|
||||||
"concurrently": "^8.2.2",
|
|
||||||
"eslint": "^8.50.0",
|
|
||||||
"jest": "^29.7.0",
|
|
||||||
"prettier": "^3.0.3",
|
|
||||||
"ts-jest": "^29.1.1",
|
"ts-jest": "^29.1.1",
|
||||||
"ts-node-dev": "^2.0.0",
|
"ts-node-dev": "^2.0.0",
|
||||||
"tsx": "^4.7.0",
|
"tsx": "^4.7.0",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^5.4.18"
|
"vite": "^5.4.18",
|
||||||
|
"zod": "^3.24.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
44
scripts/verify-dist.js
Executable file
44
scripts/verify-dist.js
Executable file
@@ -0,0 +1,44 @@
|
|||||||
|
// scripts/verify-dist.js
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const projectRoot = path.resolve(__dirname, '..');
|
||||||
|
|
||||||
|
// Check if frontend dist exists
|
||||||
|
const frontendDistPath = path.join(projectRoot, 'frontend', 'dist');
|
||||||
|
const frontendIndexPath = path.join(frontendDistPath, 'index.html');
|
||||||
|
|
||||||
|
if (!fs.existsSync(frontendDistPath)) {
|
||||||
|
console.error('❌ Error: frontend/dist directory does not exist!');
|
||||||
|
console.error('Run "npm run frontend:build" to generate the frontend dist files.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(frontendIndexPath)) {
|
||||||
|
console.error('❌ Error: frontend/dist/index.html does not exist!');
|
||||||
|
console.error('Frontend build may be incomplete. Run "npm run frontend:build" again.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if backend dist exists
|
||||||
|
const backendDistPath = path.join(projectRoot, 'dist');
|
||||||
|
const serverJsPath = path.join(backendDistPath, 'server.js');
|
||||||
|
|
||||||
|
if (!fs.existsSync(backendDistPath)) {
|
||||||
|
console.error('❌ Error: dist directory does not exist!');
|
||||||
|
console.error('Run "npm run backend:build" to generate the backend dist files.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(serverJsPath)) {
|
||||||
|
console.error('❌ Error: dist/server.js does not exist!');
|
||||||
|
console.error('Backend build may be incomplete. Run "npm run backend:build" again.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// All checks passed
|
||||||
|
console.log('✅ Verification passed! Frontend and backend dist files are present.');
|
||||||
|
console.log('📦 Package is ready for publishing.');
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import path from 'path';
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { McpSettings } from '../types/index.js';
|
import { McpSettings } from '../types/index.js';
|
||||||
|
import { getConfigFilePath } from '../utils/path.js';
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ const defaultConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getSettingsPath = (): string => {
|
export const getSettingsPath = (): string => {
|
||||||
return path.resolve(process.cwd(), 'mcp_settings.json');
|
return getConfigFilePath('mcp_settings.json', 'Settings');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loadSettings = (): McpSettings => {
|
export const loadSettings = (): McpSettings => {
|
||||||
|
|||||||
@@ -1,8 +1,36 @@
|
|||||||
import express, { Request, Response, NextFunction } from 'express';
|
import express, { Request, Response, NextFunction } from 'express';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { dirname } from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
import { auth } from './auth.js';
|
import { auth } from './auth.js';
|
||||||
import { initializeDefaultUser } from '../models/User.js';
|
import { initializeDefaultUser } from '../models/User.js';
|
||||||
|
|
||||||
|
// Create __dirname equivalent for ES modules
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
// Try to find the correct frontend file path
|
||||||
|
const findFrontendPath = (): string => {
|
||||||
|
// First try development environment path
|
||||||
|
const devPath = path.join(dirname(__dirname), 'frontend', 'dist', 'index.html');
|
||||||
|
if (fs.existsSync(devPath)) {
|
||||||
|
return path.join(dirname(__dirname), 'frontend', 'dist');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try npm/npx installed path (remove /dist directory)
|
||||||
|
const npmPath = path.join(dirname(dirname(__dirname)), 'frontend', 'dist', 'index.html');
|
||||||
|
if (fs.existsSync(npmPath)) {
|
||||||
|
return path.join(dirname(dirname(__dirname)), 'frontend', 'dist');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If none of the above paths exist, return the most reasonable default path and log a warning
|
||||||
|
console.warn('Warning: Could not locate frontend files. Using default path.');
|
||||||
|
return path.join(dirname(__dirname), 'frontend', 'dist');
|
||||||
|
};
|
||||||
|
|
||||||
|
const frontendPath = findFrontendPath();
|
||||||
|
|
||||||
export const errorHandler = (
|
export const errorHandler = (
|
||||||
err: Error,
|
err: Error,
|
||||||
_req: Request,
|
_req: Request,
|
||||||
@@ -17,7 +45,8 @@ export const errorHandler = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const initMiddlewares = (app: express.Application): void => {
|
export const initMiddlewares = (app: express.Application): void => {
|
||||||
app.use(express.static('frontend/dist'));
|
// Serve static files from the dynamically determined frontend path
|
||||||
|
app.use(express.static(frontendPath));
|
||||||
|
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
if (req.path !== '/sse' && req.path !== '/messages') {
|
if (req.path !== '/sse' && req.path !== '/messages') {
|
||||||
@@ -36,7 +65,8 @@ export const initMiddlewares = (app: express.Application): void => {
|
|||||||
app.use('/api', auth);
|
app.use('/api', auth);
|
||||||
|
|
||||||
app.get('/', (_req: Request, res: Response) => {
|
app.get('/', (_req: Request, res: Response) => {
|
||||||
res.sendFile(path.join(process.cwd(), 'frontend', 'dist', 'index.html'));
|
// Serve the frontend application
|
||||||
|
res.sendFile(path.join(frontendPath, 'index.html'));
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(errorHandler);
|
app.use(errorHandler);
|
||||||
|
|||||||
132
src/server.ts
132
src/server.ts
@@ -1,6 +1,8 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import config from './config/index.js';
|
import config from './config/index.js';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import fs from 'fs';
|
||||||
import { initMcpServer } from './services/mcpService.js';
|
import { initMcpServer } from './services/mcpService.js';
|
||||||
import { initMiddlewares } from './middlewares/index.js';
|
import { initMiddlewares } from './middlewares/index.js';
|
||||||
import { initRoutes } from './routes/index.js';
|
import { initRoutes } from './routes/index.js';
|
||||||
@@ -13,9 +15,14 @@ import {
|
|||||||
import { migrateUserData } from './utils/migration.js';
|
import { migrateUserData } from './utils/migration.js';
|
||||||
import { initializeDefaultUser } from './models/User.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 {
|
export class AppServer {
|
||||||
private app: express.Application;
|
private app: express.Application;
|
||||||
private port: number | string;
|
private port: number | string;
|
||||||
|
private frontendPath: string | null = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.app = express();
|
this.app = express();
|
||||||
@@ -48,9 +55,8 @@ export class AppServer {
|
|||||||
throw error;
|
throw error;
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.app.get('*', (_req, res) => {
|
// Find and serve frontend
|
||||||
res.sendFile(path.join(process.cwd(), 'frontend', 'dist', 'index.html'));
|
this.findAndServeFrontend();
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error initializing server:', error);
|
console.error('Error initializing server:', error);
|
||||||
@@ -58,15 +64,135 @@ export class AppServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private findAndServeFrontend(): void {
|
||||||
|
// Find frontend path
|
||||||
|
this.frontendPath = this.findFrontendDistPath();
|
||||||
|
|
||||||
|
if (this.frontendPath) {
|
||||||
|
console.log(`Serving frontend from: ${this.frontendPath}`);
|
||||||
|
this.app.use(express.static(this.frontendPath));
|
||||||
|
|
||||||
|
// Add the wildcard route for SPA
|
||||||
|
if (fs.existsSync(path.join(this.frontendPath, 'index.html'))) {
|
||||||
|
this.app.get('*', (_req, res) => {
|
||||||
|
res.sendFile(path.join(this.frontendPath!, 'index.html'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('Frontend dist directory not found. Server will run without frontend.');
|
||||||
|
this.app.get('/', (_req, res) => {
|
||||||
|
res
|
||||||
|
.status(404)
|
||||||
|
.send('Frontend not found. MCPHub API is running, but the UI is not available.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
start(): void {
|
start(): void {
|
||||||
this.app.listen(this.port, () => {
|
this.app.listen(this.port, () => {
|
||||||
console.log(`Server is running on port ${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 {
|
getApp(): express.Application {
|
||||||
return this.app;
|
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;
|
export default AppServer;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
|
||||||
import { MarketServer } from '../types/index.js';
|
import { MarketServer } from '../types/index.js';
|
||||||
|
import { getConfigFilePath } from '../utils/path.js';
|
||||||
|
|
||||||
// Get path to the servers.json file
|
// Get path to the servers.json file
|
||||||
export const getServersJsonPath = (): string => {
|
export const getServersJsonPath = (): string => {
|
||||||
return path.resolve(process.cwd(), 'servers.json');
|
return getConfigFilePath('servers.json', 'Servers');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load all market servers from servers.json
|
// Load all market servers from servers.json
|
||||||
|
|||||||
42
src/utils/path.ts
Normal file
42
src/utils/path.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { dirname } from 'path';
|
||||||
|
|
||||||
|
// Get current file's directory
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
// Project root directory should be the parent directory of src
|
||||||
|
const rootDir = dirname(dirname(__dirname));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the path to a configuration file by checking multiple potential locations.
|
||||||
|
* @param filename The name of the file to locate (e.g., 'servers.json', 'mcp_settings.json')
|
||||||
|
* @param description Brief description of the file for logging purposes
|
||||||
|
* @returns The path to the file
|
||||||
|
*/
|
||||||
|
export const getConfigFilePath = (filename: string, description = 'Configuration'): string => {
|
||||||
|
// Try to find the correct path to the file
|
||||||
|
const potentialPaths = [
|
||||||
|
// Prioritize process.cwd() as the first location to check
|
||||||
|
path.resolve(process.cwd(), filename),
|
||||||
|
// Use path relative to the root directory
|
||||||
|
path.join(rootDir, filename),
|
||||||
|
// If installed with npx, may need to look one level up
|
||||||
|
path.join(dirname(rootDir), filename)
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const filePath of potentialPaths) {
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all paths do not exist, use default path
|
||||||
|
// Using the default path is acceptable because it ensures the application can proceed
|
||||||
|
// even if the configuration file is missing. This fallback is particularly useful in
|
||||||
|
// development environments or when the file is optional.
|
||||||
|
const defaultPath = path.resolve(process.cwd(), filename);
|
||||||
|
console.debug(`${description} file not found at any expected location, using default path: ${defaultPath}`);
|
||||||
|
return defaultPath;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user