mirror of
https://github.com/samanhappy/mcphub.git
synced 2025-12-24 02:39:19 -05:00
Refactor: Clean up code formatting and improve readability across multiple files (#387)
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import dotenv from 'dotenv'
|
||||
import fs from 'fs'
|
||||
import { McpSettings, IUser } from '../types/index.js'
|
||||
import { getConfigFilePath } from '../utils/path.js'
|
||||
import { getPackageVersion } from '../utils/version.js'
|
||||
import { getDataService } from '../services/services.js'
|
||||
import { DataService } from '../services/dataService.js'
|
||||
import dotenv from 'dotenv';
|
||||
import fs from 'fs';
|
||||
import { McpSettings, IUser } from '../types/index.js';
|
||||
import { getConfigFilePath } from '../utils/path.js';
|
||||
import { getPackageVersion } from '../utils/version.js';
|
||||
import { getDataService } from '../services/services.js';
|
||||
import { DataService } from '../services/dataService.js';
|
||||
|
||||
dotenv.config()
|
||||
dotenv.config();
|
||||
|
||||
const defaultConfig = {
|
||||
port: process.env.PORT || 3000,
|
||||
@@ -15,74 +15,74 @@ const defaultConfig = {
|
||||
readonly: 'true' === process.env.READONLY || false,
|
||||
mcpHubName: 'mcphub',
|
||||
mcpHubVersion: getPackageVersion(),
|
||||
}
|
||||
};
|
||||
|
||||
const dataService: DataService = getDataService()
|
||||
const dataService: DataService = getDataService();
|
||||
|
||||
// Settings cache
|
||||
let settingsCache: McpSettings | null = null
|
||||
let settingsCache: McpSettings | null = null;
|
||||
|
||||
export const getSettingsPath = (): string => {
|
||||
return getConfigFilePath('mcp_settings.json', 'Settings')
|
||||
}
|
||||
return getConfigFilePath('mcp_settings.json', 'Settings');
|
||||
};
|
||||
|
||||
export const loadOriginalSettings = (): McpSettings => {
|
||||
// If cache exists, return cached data directly
|
||||
if (settingsCache) {
|
||||
return settingsCache
|
||||
return settingsCache;
|
||||
}
|
||||
|
||||
const settingsPath = getSettingsPath()
|
||||
const settingsPath = getSettingsPath();
|
||||
// check if file exists
|
||||
if (!fs.existsSync(settingsPath)) {
|
||||
console.warn(`Settings file not found at ${settingsPath}, using default settings.`)
|
||||
const defaultSettings = { mcpServers: {}, users: [] }
|
||||
console.warn(`Settings file not found at ${settingsPath}, using default settings.`);
|
||||
const defaultSettings = { mcpServers: {}, users: [] };
|
||||
// Cache default settings
|
||||
settingsCache = defaultSettings
|
||||
return defaultSettings
|
||||
settingsCache = defaultSettings;
|
||||
return defaultSettings;
|
||||
}
|
||||
|
||||
try {
|
||||
// Read and parse settings file
|
||||
const settingsData = fs.readFileSync(settingsPath, 'utf8')
|
||||
const settings = JSON.parse(settingsData)
|
||||
const settingsData = fs.readFileSync(settingsPath, 'utf8');
|
||||
const settings = JSON.parse(settingsData);
|
||||
|
||||
// Update cache
|
||||
settingsCache = settings
|
||||
settingsCache = settings;
|
||||
|
||||
console.log(`Loaded settings from ${settingsPath}`)
|
||||
return settings
|
||||
console.log(`Loaded settings from ${settingsPath}`);
|
||||
return settings;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to load settings from ${settingsPath}: ${error}`)
|
||||
throw new Error(`Failed to load settings from ${settingsPath}: ${error}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const loadSettings = (user?: IUser): McpSettings => {
|
||||
return dataService.filterSettings!(loadOriginalSettings(), user)
|
||||
}
|
||||
return dataService.filterSettings!(loadOriginalSettings(), user);
|
||||
};
|
||||
|
||||
export const saveSettings = (settings: McpSettings, user?: IUser): boolean => {
|
||||
const settingsPath = getSettingsPath()
|
||||
const settingsPath = getSettingsPath();
|
||||
try {
|
||||
const mergedSettings = dataService.mergeSettings!(loadOriginalSettings(), settings, user)
|
||||
fs.writeFileSync(settingsPath, JSON.stringify(mergedSettings, null, 2), 'utf8')
|
||||
const mergedSettings = dataService.mergeSettings!(loadOriginalSettings(), settings, user);
|
||||
fs.writeFileSync(settingsPath, JSON.stringify(mergedSettings, null, 2), 'utf8');
|
||||
|
||||
// Update cache after successful save
|
||||
settingsCache = mergedSettings
|
||||
settingsCache = mergedSettings;
|
||||
|
||||
return true
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`Failed to save settings to ${settingsPath}:`, error)
|
||||
return false
|
||||
console.error(`Failed to save settings to ${settingsPath}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear settings cache, force next loadSettings call to re-read from file
|
||||
*/
|
||||
export const clearSettingsCache = (): void => {
|
||||
settingsCache = null
|
||||
}
|
||||
settingsCache = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get current cache status (for debugging)
|
||||
@@ -90,71 +90,71 @@ export const clearSettingsCache = (): void => {
|
||||
export const getSettingsCacheInfo = (): { hasCache: boolean } => {
|
||||
return {
|
||||
hasCache: settingsCache !== null,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export function replaceEnvVars(input: Record<string, any>): Record<string, any>
|
||||
export function replaceEnvVars(input: string[] | undefined): string[]
|
||||
export function replaceEnvVars(input: string): string
|
||||
export function replaceEnvVars(input: Record<string, any>): Record<string, any>;
|
||||
export function replaceEnvVars(input: string[] | undefined): string[];
|
||||
export function replaceEnvVars(input: string): string;
|
||||
export function replaceEnvVars(
|
||||
input: Record<string, any> | string[] | string | undefined,
|
||||
): Record<string, any> | string[] | string {
|
||||
// Handle object input - recursively expand all nested values
|
||||
if (input && typeof input === 'object' && !Array.isArray(input)) {
|
||||
const res: Record<string, any> = {}
|
||||
const res: Record<string, any> = {};
|
||||
for (const [key, value] of Object.entries(input)) {
|
||||
if (typeof value === 'string') {
|
||||
res[key] = expandEnvVars(value)
|
||||
res[key] = expandEnvVars(value);
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
// Recursively handle nested objects and arrays
|
||||
res[key] = replaceEnvVars(value as any)
|
||||
res[key] = replaceEnvVars(value as any);
|
||||
} else {
|
||||
// Preserve non-string, non-object values (numbers, booleans, etc.)
|
||||
res[key] = value
|
||||
res[key] = value;
|
||||
}
|
||||
}
|
||||
return res
|
||||
return res;
|
||||
}
|
||||
|
||||
// Handle array input - recursively expand all elements
|
||||
if (Array.isArray(input)) {
|
||||
return input.map((item) => {
|
||||
if (typeof item === 'string') {
|
||||
return expandEnvVars(item)
|
||||
return expandEnvVars(item);
|
||||
} else if (typeof item === 'object' && item !== null) {
|
||||
return replaceEnvVars(item as any)
|
||||
return replaceEnvVars(item as any);
|
||||
}
|
||||
return item
|
||||
})
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
// Handle string input
|
||||
if (typeof input === 'string') {
|
||||
return expandEnvVars(input)
|
||||
return expandEnvVars(input);
|
||||
}
|
||||
|
||||
// Handle undefined/null array input
|
||||
if (input === undefined || input === null) {
|
||||
return []
|
||||
return [];
|
||||
}
|
||||
|
||||
return input
|
||||
return input;
|
||||
}
|
||||
|
||||
export const expandEnvVars = (value: string): string => {
|
||||
if (typeof value !== 'string') {
|
||||
return String(value)
|
||||
return String(value);
|
||||
}
|
||||
// Replace ${VAR} format
|
||||
let result = value.replace(/\$\{([^}]+)\}/g, (_, key) => process.env[key] || '')
|
||||
let result = value.replace(/\$\{([^}]+)\}/g, (_, key) => process.env[key] || '');
|
||||
// Also replace $VAR format (common on Unix-like systems)
|
||||
result = result.replace(/\$([A-Z_][A-Z0-9_]*)/g, (_, key) => process.env[key] || '')
|
||||
return result
|
||||
}
|
||||
result = result.replace(/\$([A-Z_][A-Z0-9_]*)/g, (_, key) => process.env[key] || '');
|
||||
return result;
|
||||
};
|
||||
|
||||
export default defaultConfig
|
||||
export default defaultConfig;
|
||||
|
||||
export function getNameSeparator(): string {
|
||||
const settings = loadSettings()
|
||||
return settings.systemConfig?.nameSeparator || '-'
|
||||
const settings = loadSettings();
|
||||
return settings.systemConfig?.nameSeparator || '-';
|
||||
}
|
||||
|
||||
@@ -69,10 +69,7 @@ export const login = async (req: Request, res: Response): Promise<void> => {
|
||||
// Check if user is admin with default password
|
||||
const version = getPackageVersion();
|
||||
const isUsingDefaultPassword =
|
||||
user.username === 'admin' &&
|
||||
user.isAdmin &&
|
||||
isDefaultPassword(password) &&
|
||||
version !== 'dev';
|
||||
user.username === 'admin' && user.isAdmin && isDefaultPassword(password) && version !== 'dev';
|
||||
|
||||
jwt.sign(payload, JWT_SECRET, { expiresIn: TOKEN_EXPIRY }, (err, token) => {
|
||||
if (err) throw err;
|
||||
|
||||
@@ -126,21 +126,19 @@ export const handleOAuthCallback = async (req: Request, res: Response) => {
|
||||
// Check for authorization errors
|
||||
if (error) {
|
||||
console.error(`OAuth authorization failed: ${error} - ${error_description || ''}`);
|
||||
return res
|
||||
.status(400)
|
||||
.send(
|
||||
generateHtmlResponse('error', t('oauthCallback.authorizationFailed'), '', [
|
||||
{ label: t('oauthCallback.authorizationFailedError'), value: String(error) },
|
||||
...(error_description
|
||||
? [
|
||||
{
|
||||
label: t('oauthCallback.authorizationFailedDetails'),
|
||||
value: String(error_description),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]),
|
||||
);
|
||||
return res.status(400).send(
|
||||
generateHtmlResponse('error', t('oauthCallback.authorizationFailed'), '', [
|
||||
{ label: t('oauthCallback.authorizationFailedError'), value: String(error) },
|
||||
...(error_description
|
||||
? [
|
||||
{
|
||||
label: t('oauthCallback.authorizationFailedDetails'),
|
||||
value: String(error_description),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
// Validate required parameters
|
||||
|
||||
@@ -529,7 +529,7 @@ export const updateSystemConfig = (req: Request, res: Response): void => {
|
||||
typeof mcpRouter.referer !== 'string' &&
|
||||
typeof mcpRouter.title !== 'string' &&
|
||||
typeof mcpRouter.baseUrl !== 'string')) &&
|
||||
(typeof nameSeparator !== 'string')
|
||||
typeof nameSeparator !== 'string'
|
||||
) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
|
||||
@@ -28,7 +28,7 @@ function getCurrentFileDir(): string {
|
||||
if (process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined) {
|
||||
return process.cwd();
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
return getCurrentModuleDir();
|
||||
} catch {
|
||||
|
||||
@@ -333,7 +333,7 @@ export class MCPHubOAuthProvider implements OAuthClientProvider {
|
||||
|
||||
const updatedConfig = await persistTokens(this.serverName, {
|
||||
accessToken: tokens.access_token,
|
||||
refreshToken: refreshTokenProvided ? tokens.refresh_token ?? null : undefined,
|
||||
refreshToken: refreshTokenProvided ? (tokens.refresh_token ?? null) : undefined,
|
||||
clearPendingAuthorization: hadPending,
|
||||
});
|
||||
|
||||
@@ -421,7 +421,9 @@ export class MCPHubOAuthProvider implements OAuthClientProvider {
|
||||
async saveCodeVerifier(verifier: string): Promise<void> {
|
||||
this._codeVerifier = verifier;
|
||||
try {
|
||||
const updatedConfig = await updatePendingAuthorization(this.serverName, { codeVerifier: verifier });
|
||||
const updatedConfig = await updatePendingAuthorization(this.serverName, {
|
||||
codeVerifier: verifier,
|
||||
});
|
||||
if (updatedConfig) {
|
||||
this.serverConfig = updatedConfig;
|
||||
}
|
||||
@@ -490,7 +492,10 @@ export class MCPHubOAuthProvider implements OAuthClientProvider {
|
||||
if (scope === 'client' || scope === 'all') {
|
||||
const supportsDynamicClient = currentConfig.oauth.dynamicRegistration?.enabled === true;
|
||||
|
||||
if (supportsDynamicClient && (currentConfig.oauth.clientId || currentConfig.oauth.clientSecret)) {
|
||||
if (
|
||||
supportsDynamicClient &&
|
||||
(currentConfig.oauth.clientId || currentConfig.oauth.clientSecret)
|
||||
) {
|
||||
removeRegisteredClient(this.serverName);
|
||||
const updated = await clearOAuthData(this.serverName, 'client');
|
||||
assignUpdatedConfig(updated);
|
||||
|
||||
@@ -380,10 +380,10 @@ export const initializeClientsFromSettings = async (
|
||||
try {
|
||||
for (const conf of allServers) {
|
||||
const { name } = conf;
|
||||
|
||||
|
||||
// Expand environment variables in all configuration values
|
||||
const expandedConf = replaceEnvVars(conf as any) as ServerConfigWithName;
|
||||
|
||||
|
||||
// Skip disabled servers
|
||||
if (expandedConf.enabled === false) {
|
||||
console.log(`Skipping disabled server: ${name}`);
|
||||
|
||||
@@ -18,18 +18,18 @@ function initializePackageRoot(): void {
|
||||
if (process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// Try to get the current module's directory
|
||||
const currentModuleDir = getCurrentModuleDir();
|
||||
|
||||
|
||||
// This file is in src/utils/path.ts (or dist/utils/path.js when compiled)
|
||||
// So package.json should be 2 levels up
|
||||
const possibleRoots = [
|
||||
path.resolve(currentModuleDir, '..', '..'), // dist -> package root
|
||||
path.resolve(currentModuleDir, '..'), // dist/utils -> dist -> package root
|
||||
];
|
||||
|
||||
|
||||
for (const root of possibleRoots) {
|
||||
const packageJsonPath = path.join(root, 'package.json');
|
||||
if (fs.existsSync(packageJsonPath)) {
|
||||
@@ -66,10 +66,10 @@ export const findPackageRoot = (startPath?: string): string | null => {
|
||||
}
|
||||
|
||||
const debug = process.env.DEBUG === 'true';
|
||||
|
||||
|
||||
// Possible locations for package.json relative to the search path
|
||||
const possibleRoots: string[] = [];
|
||||
|
||||
|
||||
if (startPath) {
|
||||
// When start path is provided (from fileURLToPath(import.meta.url))
|
||||
possibleRoots.push(
|
||||
@@ -78,25 +78,30 @@ export const findPackageRoot = (startPath?: string): string | null => {
|
||||
// When in dist/ (compiled code) - go up 1 level
|
||||
path.resolve(startPath, '..'),
|
||||
// Direct parent directories
|
||||
path.resolve(startPath)
|
||||
path.resolve(startPath),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Try to use require.resolve to find the module location (works in CommonJS and ESM with createRequire)
|
||||
try {
|
||||
// In ESM, we can use import.meta.resolve, but it's async in some versions
|
||||
// So we'll try to find the module by checking the node_modules structure
|
||||
|
||||
|
||||
// Check if this file is in a node_modules installation
|
||||
const currentFile = new Error().stack?.split('\n')[2]?.match(/\((.+?):\d+:\d+\)$/)?.[1];
|
||||
if (currentFile) {
|
||||
const nodeModulesIndex = currentFile.indexOf('node_modules');
|
||||
if (nodeModulesIndex !== -1) {
|
||||
// Extract the package path from node_modules
|
||||
const afterNodeModules = currentFile.substring(nodeModulesIndex + 'node_modules'.length + 1);
|
||||
const afterNodeModules = currentFile.substring(
|
||||
nodeModulesIndex + 'node_modules'.length + 1,
|
||||
);
|
||||
const packageNameEnd = afterNodeModules.indexOf(path.sep);
|
||||
if (packageNameEnd !== -1) {
|
||||
const packagePath = currentFile.substring(0, nodeModulesIndex + 'node_modules'.length + 1 + packageNameEnd);
|
||||
const packagePath = currentFile.substring(
|
||||
0,
|
||||
nodeModulesIndex + 'node_modules'.length + 1 + packageNameEnd,
|
||||
);
|
||||
possibleRoots.push(packagePath);
|
||||
}
|
||||
}
|
||||
@@ -108,18 +113,15 @@ export const findPackageRoot = (startPath?: string): string | null => {
|
||||
// Check module.filename location (works in Node.js when available)
|
||||
if (typeof __filename !== 'undefined') {
|
||||
const moduleDir = path.dirname(__filename);
|
||||
possibleRoots.push(
|
||||
path.resolve(moduleDir, '..', '..'),
|
||||
path.resolve(moduleDir, '..')
|
||||
);
|
||||
possibleRoots.push(path.resolve(moduleDir, '..', '..'), path.resolve(moduleDir, '..'));
|
||||
}
|
||||
|
||||
|
||||
// Check common installation locations
|
||||
possibleRoots.push(
|
||||
// Current working directory (for development/tests)
|
||||
process.cwd(),
|
||||
// Parent of cwd
|
||||
path.resolve(process.cwd(), '..')
|
||||
path.resolve(process.cwd(), '..'),
|
||||
);
|
||||
|
||||
if (debug) {
|
||||
@@ -157,12 +159,12 @@ export const findPackageRoot = (startPath?: string): string | null => {
|
||||
if (debug) {
|
||||
console.warn('DEBUG: Could not find package root directory');
|
||||
}
|
||||
|
||||
|
||||
// Cache null result as well to avoid repeated searches
|
||||
if (!startPath) {
|
||||
cachedPackageRoot = null;
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
@@ -11,13 +11,13 @@ export const getPackageVersion = (searchPath?: string): string => {
|
||||
try {
|
||||
// Use provided path or fallback to current working directory
|
||||
const startPath = searchPath || process.cwd();
|
||||
|
||||
|
||||
const packageRoot = findPackageRoot(startPath);
|
||||
if (!packageRoot) {
|
||||
console.warn('Could not find package root, using default version');
|
||||
return 'dev';
|
||||
}
|
||||
|
||||
|
||||
const packageJsonPath = path.join(packageRoot, 'package.json');
|
||||
const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8');
|
||||
const packageJson = JSON.parse(packageJsonContent);
|
||||
|
||||
Reference in New Issue
Block a user