mirror of
https://github.com/samanhappy/mcphub.git
synced 2025-12-24 02:39:19 -05:00
Compare commits
6 Commits
v0.10.3
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
017e405c41 | ||
|
|
8da0323326 | ||
|
|
71e217fcc2 | ||
|
|
fa133e21b0 | ||
|
|
602b5cb80e | ||
|
|
e63f045819 |
175
docs/BASE_PATH_CONFIGURATION.md
Normal file
175
docs/BASE_PATH_CONFIGURATION.md
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
# BASE_PATH Configuration Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
MCPHub supports running under a custom base path (e.g., `/mcphub/`) for scenarios where you need to deploy the application under a subdirectory or behind a reverse proxy.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Setting BASE_PATH
|
||||||
|
|
||||||
|
Add the `BASE_PATH` environment variable to your `.env` file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PORT=3000
|
||||||
|
NODE_ENV=development
|
||||||
|
BASE_PATH=/mcphub/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Trailing slashes in BASE_PATH are automatically normalized (removed). Both `/mcphub/` and `/mcphub` will work and be normalized to `/mcphub`.
|
||||||
|
|
||||||
|
### In Production (Docker)
|
||||||
|
|
||||||
|
Set the environment variable when running the container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -e BASE_PATH=/mcphub/ -p 3000:3000 mcphub
|
||||||
|
```
|
||||||
|
|
||||||
|
### Behind a Reverse Proxy (nginx)
|
||||||
|
|
||||||
|
Example nginx configuration:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location /mcphub/ {
|
||||||
|
proxy_pass http://localhost:3000/mcphub/;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### Backend Routes
|
||||||
|
|
||||||
|
All backend routes are automatically prefixed with BASE_PATH:
|
||||||
|
|
||||||
|
- **Without BASE_PATH:**
|
||||||
|
- Config: `http://localhost:3000/config`
|
||||||
|
- Auth: `http://localhost:3000/api/auth/login`
|
||||||
|
- Health: `http://localhost:3000/health`
|
||||||
|
|
||||||
|
- **With BASE_PATH="/mcphub":**
|
||||||
|
- Config: `http://localhost:3000/mcphub/config`
|
||||||
|
- Auth: `http://localhost:3000/mcphub/api/auth/login`
|
||||||
|
- Health: `http://localhost:3000/health` (global, no prefix)
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
The frontend automatically detects the BASE_PATH at runtime by calling the `/config` endpoint. All API calls are automatically prefixed.
|
||||||
|
|
||||||
|
### Development Mode
|
||||||
|
|
||||||
|
The Vite dev server proxy is automatically configured to support BASE_PATH:
|
||||||
|
|
||||||
|
1. Set `BASE_PATH` in your `.env` file
|
||||||
|
2. Start the dev server: `pnpm dev`
|
||||||
|
3. Access the application through Vite: `http://localhost:5173`
|
||||||
|
4. All API calls are proxied correctly with the BASE_PATH prefix
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
You can test the BASE_PATH configuration with curl:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Set BASE_PATH=/mcphub/ in .env file
|
||||||
|
|
||||||
|
# Test config endpoint
|
||||||
|
curl http://localhost:3000/mcphub/config
|
||||||
|
|
||||||
|
# Test login
|
||||||
|
curl -X POST http://localhost:3000/mcphub/api/auth/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"admin","password":"admin123"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Issue: Login fails with BASE_PATH set
|
||||||
|
|
||||||
|
**Solution:** Make sure you're using version 0.10.4 or later, which includes the fix for BASE_PATH in development mode.
|
||||||
|
|
||||||
|
### Issue: 404 errors on API endpoints
|
||||||
|
|
||||||
|
**Symptoms:**
|
||||||
|
- Login returns 404
|
||||||
|
- Config endpoint returns 404
|
||||||
|
- API calls fail with 404
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
1. Verify BASE_PATH is set correctly in `.env` file
|
||||||
|
2. Restart the backend server to pick up the new configuration
|
||||||
|
3. Check that you're accessing the correct URL with the BASE_PATH prefix
|
||||||
|
|
||||||
|
### Issue: Vite proxy not working
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
1. Ensure you have the latest version of `frontend/vite.config.ts`
|
||||||
|
2. Restart the frontend dev server
|
||||||
|
3. Verify the BASE_PATH is being loaded from the `.env` file in the project root
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### Backend (src/config/index.ts)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const normalizeBasePath = (path: string): string => {
|
||||||
|
if (!path) return '';
|
||||||
|
return path.replace(/\/+$/, '');
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultConfig = {
|
||||||
|
basePath: normalizeBasePath(process.env.BASE_PATH || ''),
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend (frontend/vite.config.ts)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
const env = loadEnv(mode, path.resolve(__dirname, '..'), '');
|
||||||
|
let basePath = env.BASE_PATH || '';
|
||||||
|
basePath = basePath.replace(/\/+$/, '');
|
||||||
|
|
||||||
|
const proxyConfig: Record<string, any> = {};
|
||||||
|
const pathsToProxy = ['/api', '/config', '/public-config', '/health', '/oauth'];
|
||||||
|
|
||||||
|
pathsToProxy.forEach((path) => {
|
||||||
|
const proxyPath = basePath + path;
|
||||||
|
proxyConfig[proxyPath] = {
|
||||||
|
target: 'http://localhost:3000',
|
||||||
|
changeOrigin: true,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
server: {
|
||||||
|
proxy: proxyConfig,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend Runtime (frontend/src/utils/runtime.ts)
|
||||||
|
|
||||||
|
The frontend loads the BASE_PATH at runtime from the `/config` endpoint:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const loadRuntimeConfig = async (): Promise<RuntimeConfig> => {
|
||||||
|
// Tries different possible config paths
|
||||||
|
const response = await fetch('/config');
|
||||||
|
const data = await response.json();
|
||||||
|
return data.data; // Contains basePath, version, name
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Related Files
|
||||||
|
|
||||||
|
- `src/config/index.ts` - Backend BASE_PATH normalization
|
||||||
|
- `frontend/vite.config.ts` - Vite proxy configuration
|
||||||
|
- `frontend/src/utils/runtime.ts` - Frontend runtime config loading
|
||||||
|
- `tests/integration/base-path-routes.test.ts` - Integration tests
|
||||||
@@ -78,7 +78,7 @@ git clone https://github.com/YOUR_USERNAME/mcphub.git
|
|||||||
cd mcphub
|
cd mcphub
|
||||||
|
|
||||||
# 2. Add upstream remote
|
# 2. Add upstream remote
|
||||||
git remote add upstream https://github.com/mcphub/mcphub.git
|
git remote add upstream https://github.com/samanhappy/mcphub.git
|
||||||
|
|
||||||
# 3. Install dependencies
|
# 3. Install dependencies
|
||||||
pnpm install
|
pnpm install
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ docker --version
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 克隆主仓库
|
# 克隆主仓库
|
||||||
git clone https://github.com/mcphub/mcphub.git
|
git clone https://github.com/samanhappy/mcphub.git
|
||||||
cd mcphub
|
cd mcphub
|
||||||
|
|
||||||
# 或者克隆您的 fork
|
# 或者克隆您的 fork
|
||||||
|
|||||||
@@ -388,7 +388,7 @@ CMD ["node", "dist/index.js"]
|
|||||||
````md
|
````md
|
||||||
```bash
|
```bash
|
||||||
# 克隆 MCPHub 仓库
|
# 克隆 MCPHub 仓库
|
||||||
git clone https://github.com/mcphub/mcphub.git
|
git clone https://github.com/samanhappy/mcphub.git
|
||||||
cd mcphub
|
cd mcphub
|
||||||
|
|
||||||
# 安装依赖
|
# 安装依赖
|
||||||
@@ -413,7 +413,7 @@ npm start
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 克隆 MCPHub 仓库
|
# 克隆 MCPHub 仓库
|
||||||
git clone https://github.com/mcphub/mcphub.git
|
git clone https://github.com/samanhappy/mcphub.git
|
||||||
cd mcphub
|
cd mcphub
|
||||||
|
|
||||||
# 安装依赖
|
# 安装依赖
|
||||||
@@ -441,7 +441,7 @@ npm start
|
|||||||
```powershell
|
```powershell
|
||||||
# Windows PowerShell 安装步骤
|
# Windows PowerShell 安装步骤
|
||||||
# 克隆仓库
|
# 克隆仓库
|
||||||
git clone https://github.com/mcphub/mcphub.git
|
git clone https://github.com/samanhappy/mcphub.git
|
||||||
Set-Location mcphub
|
Set-Location mcphub
|
||||||
|
|
||||||
# 安装 Node.js 依赖
|
# 安装 Node.js 依赖
|
||||||
@@ -458,7 +458,7 @@ npm run dev
|
|||||||
```powershell
|
```powershell
|
||||||
# Windows PowerShell 安装步骤
|
# Windows PowerShell 安装步骤
|
||||||
# 克隆仓库
|
# 克隆仓库
|
||||||
git clone https://github.com/mcphub/mcphub.git
|
git clone https://github.com/samanhappy/mcphub.git
|
||||||
Set-Location mcphub
|
Set-Location mcphub
|
||||||
|
|
||||||
# 安装 Node.js 依赖
|
# 安装 Node.js 依赖
|
||||||
|
|||||||
@@ -331,7 +331,7 @@ MCPHub 文档支持以下图标库的图标:
|
|||||||
"pages": [
|
"pages": [
|
||||||
{
|
{
|
||||||
"name": "GitHub 仓库",
|
"name": "GitHub 仓库",
|
||||||
"url": "https://github.com/mcphub/mcphub",
|
"url": "https://github.com/samanhappy/mcphub",
|
||||||
"icon": "github"
|
"icon": "github"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -382,7 +382,6 @@ zh/
|
|||||||
"pages": [
|
"pages": [
|
||||||
"zh/concepts/introduction",
|
"zh/concepts/introduction",
|
||||||
"zh/concepts/architecture",
|
"zh/concepts/architecture",
|
||||||
"zh/concepts/mcp-protocol",
|
|
||||||
"zh/concepts/routing"
|
"zh/concepts/routing"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ MCPHub 是一个现代化的 Model Context Protocol (MCP) 服务器管理平台
|
|||||||
了解 MCPHub 的核心概念,为深入使用做好准备。
|
了解 MCPHub 的核心概念,为深入使用做好准备。
|
||||||
|
|
||||||
<CardGroup cols={2}>
|
<CardGroup cols={2}>
|
||||||
<Card title="MCP 协议介绍" icon="network-wired" href="/zh/concepts/mcp-protocol">
|
|
||||||
深入了解 Model Context Protocol 的工作原理和最佳实践
|
|
||||||
</Card>
|
|
||||||
<Card title="智能路由机制" icon="route" href="/zh/features/smart-routing">
|
<Card title="智能路由机制" icon="route" href="/zh/features/smart-routing">
|
||||||
学习 MCPHub 的智能路由算法和配置策略
|
学习 MCPHub 的智能路由算法和配置策略
|
||||||
</Card>
|
</Card>
|
||||||
@@ -57,12 +54,6 @@ MCPHub 支持多种部署方式,满足不同规模和场景的需求。
|
|||||||
<Card title="Docker 部署" icon="docker" href="/zh/configuration/docker-setup">
|
<Card title="Docker 部署" icon="docker" href="/zh/configuration/docker-setup">
|
||||||
使用 Docker 容器快速部署,支持单机和集群模式
|
使用 Docker 容器快速部署,支持单机和集群模式
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="云服务部署" icon="cloud" href="/zh/deployment/cloud">
|
|
||||||
在 AWS、GCP、Azure 等云平台上部署 MCPHub
|
|
||||||
</Card>
|
|
||||||
<Card title="Kubernetes" icon="dharmachakra" href="/zh/deployment/kubernetes">
|
|
||||||
在 Kubernetes 集群中部署高可用的 MCPHub 服务
|
|
||||||
</Card>
|
|
||||||
</CardGroup>
|
</CardGroup>
|
||||||
|
|
||||||
## API 和集成
|
## API 和集成
|
||||||
@@ -73,9 +64,6 @@ MCPHub 提供完整的 RESTful API 和多语言 SDK,方便与现有系统集
|
|||||||
<Card title="API 参考文档" icon="code" href="/zh/api-reference/introduction">
|
<Card title="API 参考文档" icon="code" href="/zh/api-reference/introduction">
|
||||||
完整的 API 接口文档,包含详细的请求示例和响应格式
|
完整的 API 接口文档,包含详细的请求示例和响应格式
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="SDK 和工具" icon="toolbox" href="/zh/sdk">
|
|
||||||
官方 SDK 和命令行工具,加速开发集成
|
|
||||||
</Card>
|
|
||||||
</CardGroup>
|
</CardGroup>
|
||||||
|
|
||||||
## 社区和支持
|
## 社区和支持
|
||||||
@@ -83,7 +71,7 @@ MCPHub 提供完整的 RESTful API 和多语言 SDK,方便与现有系统集
|
|||||||
加入 MCPHub 社区,获取帮助和分享经验。
|
加入 MCPHub 社区,获取帮助和分享经验。
|
||||||
|
|
||||||
<CardGroup cols={2}>
|
<CardGroup cols={2}>
|
||||||
<Card title="GitHub 仓库" icon="github" href="https://github.com/mcphub/mcphub">
|
<Card title="GitHub 仓库" icon="github" href="https://github.com/samanhappy/mcphub">
|
||||||
查看源代码、提交问题和贡献代码
|
查看源代码、提交问题和贡献代码
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="Discord 社区" icon="discord" href="https://discord.gg/mcphub">
|
<Card title="Discord 社区" icon="discord" href="https://discord.gg/mcphub">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig, loadEnv } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import tailwindcss from '@tailwindcss/vite';
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
@@ -8,45 +8,48 @@ import { readFileSync } from 'fs';
|
|||||||
// Get package.json version
|
// Get package.json version
|
||||||
const packageJson = JSON.parse(readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8'));
|
const packageJson = JSON.parse(readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8'));
|
||||||
|
|
||||||
// For runtime configuration, we'll always use relative paths
|
|
||||||
// BASE_PATH will be determined at runtime
|
|
||||||
const basePath = '';
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig(({ mode }) => {
|
||||||
base: './', // Always use relative paths for runtime configuration
|
// Load env file from parent directory (project root)
|
||||||
plugins: [react(), tailwindcss()],
|
const env = loadEnv(mode, path.resolve(__dirname, '..'), '');
|
||||||
resolve: {
|
|
||||||
alias: {
|
// Get BASE_PATH from environment, default to empty string
|
||||||
'@': path.resolve(__dirname, './src'),
|
// Normalize by removing trailing slashes to avoid double slashes
|
||||||
},
|
let basePath = env.BASE_PATH || '';
|
||||||
},
|
basePath = basePath.replace(/\/+$/, '');
|
||||||
define: {
|
|
||||||
// Make package version available as global variable
|
// Create proxy configuration dynamically based on BASE_PATH
|
||||||
// BASE_PATH will be loaded at runtime
|
const proxyConfig: Record<string, any> = {};
|
||||||
'import.meta.env.PACKAGE_VERSION': JSON.stringify(packageJson.version),
|
|
||||||
},
|
// List of paths that need to be proxied
|
||||||
build: {
|
const pathsToProxy = ['/api', '/config', '/public-config', '/health', '/oauth'];
|
||||||
sourcemap: true, // Enable source maps for production build
|
|
||||||
},
|
pathsToProxy.forEach((path) => {
|
||||||
server: {
|
const proxyPath = basePath + path;
|
||||||
proxy: {
|
proxyConfig[proxyPath] = {
|
||||||
[`${basePath}/api`]: {
|
target: 'http://localhost:3000',
|
||||||
target: 'http://localhost:3000',
|
changeOrigin: true,
|
||||||
changeOrigin: true,
|
};
|
||||||
},
|
});
|
||||||
[`${basePath}/auth`]: {
|
|
||||||
target: 'http://localhost:3000',
|
return {
|
||||||
changeOrigin: true,
|
base: './', // Always use relative paths for runtime configuration
|
||||||
},
|
plugins: [react(), tailwindcss()],
|
||||||
[`${basePath}/config`]: {
|
resolve: {
|
||||||
target: 'http://localhost:3000',
|
alias: {
|
||||||
changeOrigin: true,
|
'@': path.resolve(__dirname, './src'),
|
||||||
},
|
|
||||||
[`${basePath}/public-config`]: {
|
|
||||||
target: 'http://localhost:3000',
|
|
||||||
changeOrigin: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
define: {
|
||||||
|
// Make package version available as global variable
|
||||||
|
// BASE_PATH will be loaded at runtime
|
||||||
|
'import.meta.env.PACKAGE_VERSION': JSON.stringify(packageJson.version),
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
sourcemap: true, // Enable source maps for production build
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
proxy: proxyConfig,
|
||||||
|
},
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,10 +8,19 @@ import { DataService } from '../services/dataService.js';
|
|||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize the base path by removing trailing slashes
|
||||||
|
*/
|
||||||
|
const normalizeBasePath = (path: string): string => {
|
||||||
|
if (!path) return '';
|
||||||
|
// Remove trailing slashes
|
||||||
|
return path.replace(/\/+$/, '');
|
||||||
|
};
|
||||||
|
|
||||||
const defaultConfig = {
|
const defaultConfig = {
|
||||||
port: process.env.PORT || 3000,
|
port: process.env.PORT || 3000,
|
||||||
initTimeout: process.env.INIT_TIMEOUT || 300000,
|
initTimeout: process.env.INIT_TIMEOUT || 300000,
|
||||||
basePath: process.env.BASE_PATH || '',
|
basePath: normalizeBasePath(process.env.BASE_PATH || ''),
|
||||||
readonly: 'true' === process.env.READONLY || false,
|
readonly: 'true' === process.env.READONLY || false,
|
||||||
mcpHubName: 'mcphub',
|
mcpHubName: 'mcphub',
|
||||||
mcpHubVersion: getPackageVersion(),
|
mcpHubVersion: getPackageVersion(),
|
||||||
|
|||||||
130
tests/integration/base-path-routes.test.ts
Normal file
130
tests/integration/base-path-routes.test.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
|
||||||
|
import request from 'supertest';
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
|
jest.mock('../../src/utils/i18n.js', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
initI18n: jest.fn().mockResolvedValue(undefined),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../src/models/User.js', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
initializeDefaultUser: jest.fn().mockResolvedValue(undefined),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../src/services/oauthService.js', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
initOAuthProvider: jest.fn(),
|
||||||
|
getOAuthRouter: jest.fn(() => null),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../src/services/mcpService.js', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
initUpstreamServers: jest.fn().mockResolvedValue(undefined),
|
||||||
|
connected: jest.fn().mockReturnValue(true),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../src/middlewares/userContext.js', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
userContextMiddleware: jest.fn((_req, _res, next) => next()),
|
||||||
|
sseUserContextMiddleware: jest.fn((_req, _res, next) => next()),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('AppServer with BASE_PATH configuration', () => {
|
||||||
|
// Save original BASE_PATH
|
||||||
|
const originalBasePath = process.env.BASE_PATH;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
// Clear module cache to allow fresh imports with different config
|
||||||
|
jest.resetModules();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Restore original BASE_PATH or remove it
|
||||||
|
if (originalBasePath !== undefined) {
|
||||||
|
process.env.BASE_PATH = originalBasePath;
|
||||||
|
} else {
|
||||||
|
delete process.env.BASE_PATH;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const flushPromises = async () => {
|
||||||
|
await new Promise((resolve) => setImmediate(resolve));
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should serve auth routes with BASE_PATH=/mcphub/', async () => {
|
||||||
|
// Set environment variable for BASE_PATH (with trailing slash)
|
||||||
|
process.env.BASE_PATH = '/mcphub/';
|
||||||
|
|
||||||
|
// Dynamically import after setting env var
|
||||||
|
const { AppServer } = await import('../../src/server.js');
|
||||||
|
const config = await import('../../src/config/index.js');
|
||||||
|
|
||||||
|
// Verify config loaded the BASE_PATH and normalized it (removed trailing slash)
|
||||||
|
expect(config.default.basePath).toBe('/mcphub');
|
||||||
|
|
||||||
|
const appServer = new AppServer();
|
||||||
|
await appServer.initialize();
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
const app = appServer.getApp();
|
||||||
|
|
||||||
|
// Test that /mcphub/config endpoint exists
|
||||||
|
const configResponse = await request(app).get('/mcphub/config');
|
||||||
|
expect(configResponse.status).not.toBe(404);
|
||||||
|
|
||||||
|
// Test that /mcphub/public-config endpoint exists
|
||||||
|
const publicConfigResponse = await request(app).get('/mcphub/public-config');
|
||||||
|
expect(publicConfigResponse.status).not.toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should serve auth routes without BASE_PATH (default)', async () => {
|
||||||
|
// Ensure BASE_PATH is not set
|
||||||
|
delete process.env.BASE_PATH;
|
||||||
|
|
||||||
|
// Dynamically import after clearing env var
|
||||||
|
jest.resetModules();
|
||||||
|
const { AppServer } = await import('../../src/server.js');
|
||||||
|
const config = await import('../../src/config/index.js');
|
||||||
|
|
||||||
|
// Verify config has empty BASE_PATH
|
||||||
|
expect(config.default.basePath).toBe('');
|
||||||
|
|
||||||
|
const appServer = new AppServer();
|
||||||
|
await appServer.initialize();
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
const app = appServer.getApp();
|
||||||
|
|
||||||
|
// Test that /config endpoint exists (without base path)
|
||||||
|
const configResponse = await request(app).get('/config');
|
||||||
|
expect(configResponse.status).not.toBe(404);
|
||||||
|
|
||||||
|
// Test that /public-config endpoint exists
|
||||||
|
const publicConfigResponse = await request(app).get('/public-config');
|
||||||
|
expect(publicConfigResponse.status).not.toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should serve global endpoints without BASE_PATH prefix', async () => {
|
||||||
|
process.env.BASE_PATH = '/test-base/';
|
||||||
|
|
||||||
|
jest.resetModules();
|
||||||
|
const { AppServer } = await import('../../src/server.js');
|
||||||
|
|
||||||
|
const appServer = new AppServer();
|
||||||
|
await appServer.initialize();
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
const app = appServer.getApp();
|
||||||
|
|
||||||
|
// Test that /health endpoint is accessible globally (no BASE_PATH prefix)
|
||||||
|
// The /health endpoint is intentionally mounted without BASE_PATH
|
||||||
|
const healthResponse = await request(app).get('/health');
|
||||||
|
expect(healthResponse.status).not.toBe(404);
|
||||||
|
|
||||||
|
// Also verify that BASE_PATH prefixed routes exist
|
||||||
|
const configResponse = await request(app).get('/test-base/config');
|
||||||
|
expect(configResponse.status).not.toBe(404);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user