Compare commits

...

25 Commits

Author SHA1 Message Date
samanhappy
1bd4fd6d9c feat: Add OpenAPI support with comprehensive configuration options and client integration (#184)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-16 17:50:51 +08:00
Imgodmaoyouknow
4b3bb26301 fix: 修复从市场安装服务器时,选择SSE和Streamable HTTP不生效的问题 (#180) 2025-06-13 22:50:36 +08:00
samanhappy
40af398f68 chore: update @modelcontextprotocol/sdk to version 1.12.1 (#176) 2025-06-10 16:50:28 +08:00
samanhappy
4726f00a22 feat: add request options configuration to server form (#171) 2025-06-10 13:51:01 +08:00
samanhappy
77f64b7b98 feat: enhance DynamicForm to support array and object (#173)
Co-authored-by: samanhappy@qq.com <my6051199>
2025-06-10 13:50:14 +08:00
samanhappy
d9cbc5381a feat: implement keep-alive functionality for SSE connections (#166)
Co-authored-by: samanhappy@qq.com <my6051199>
2025-06-07 20:41:51 +08:00
samanhappy
56c6447469 feat: implement settings cache with load, save, clear, and status functions (#167) 2025-06-07 20:36:52 +08:00
samanhappy
f8149c4b0b fix: update SSE transport path to use basePath from config (#165)
Co-authored-by: samanhappy@qq.com <my6051199>
2025-06-07 20:35:20 +08:00
purefkh
e259f30539 fix: save user config when install mcp server from market (#168) 2025-06-07 20:34:51 +08:00
samanhappy
3a421bc476 fix: update tool name formatting from '/' to '-' in ToolCard and mcpService (#164)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-05 15:34:49 +08:00
samanhappy
503b60edb7 feat: add tool management features including toggle and description updates (#163)
Co-authored-by: samanhappy@qq.com <my6051199>
2025-06-04 16:03:45 +08:00
dependabot[bot]
4039a85ee1 chore(deps-dev): bump concurrently from 8.2.2 to 9.1.2 (#156)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-02 19:49:58 +08:00
dependabot[bot]
3a83b83a9e chore(deps-dev): bump @vitejs/plugin-react from 4.4.1 to 4.5.0 (#160)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-02 19:47:04 +08:00
samanhappy
c1621805de fix: expand environment variables in database and OpenAI configuration (#162) 2025-06-02 19:46:39 +08:00
samanhappy
ecf79eaef3 fix: update error response status for missing transport in SSE service (#161) 2025-06-02 10:56:23 +08:00
samanhappy
e2cb5fa467 fix: update favicon path from "/favicon.svg" to "/favicon.ico" in documentation (#155) 2025-06-01 08:27:05 +08:00
samanhappy
7193ca4cdc fix: update "Get Started" button link to point to the correct documentation URL (#154) 2025-06-01 00:23:18 +08:00
samanhappy
9675cd8533 generate documents using mintlify (#153) 2025-06-01 00:17:09 +08:00
samanhappy
65c95aaa0b feat: Implement tool management features including tool execution and result handling (#152) 2025-05-31 22:00:05 +08:00
samanhappy
d2bbadea83 Add HTTP headers support and batch update for routing configuration (#151) 2025-05-31 18:36:41 +08:00
samanhappy
394945bbc5 fix: enhance environment variable handling in replaceEnvVars and expandEnvVars functions (#149)
Co-authored-by: samanhappy@qq.com <my6051199>
2025-05-31 08:41:20 +08:00
samanhappy
bfc184afc6 fix: enhance database initialization by adding required PostgreSQL extensions and improving error handling (#144)
Co-authored-by: samanhappy@qq.com <my6051199>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-30 23:03:42 +08:00
Oven
1acacfab88 配置MCP Server时,env中填写的${xxx}值可以从环境变量中读取 (#135) 2025-05-30 23:02:34 +08:00
terrydash
63c55875b5 add env var for setting file (#147)
Co-authored-by: Xu, Guoxu | Terry | CNTD <guoxu.xu@rakuten.com>
2025-05-30 23:01:52 +08:00
samanhappy
80c6aae116 Add comprehensive documentation for MCPHub including deployment, configuration, and smart routing features (#142) 2025-05-29 15:44:39 +08:00
93 changed files with 23398 additions and 1390 deletions

View File

@@ -20,6 +20,6 @@
}
],
"@typescript-eslint/no-explicit-any": "off",
"no-undef": "off",
"no-undef": "off"
}
}

BIN
articals/assets/sr-conf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
articals/assets/sr-dc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
articals/assets/sr-time.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
articals/assets/sr-web.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

177
articals/smart-routing.md Normal file
View File

@@ -0,0 +1,177 @@
# 无限工具智能路由MCPHub 引领 AI 工具使用新范式
## 概述
在现代 AI 应用中,随着 MCP 服务器数量的快速增长和工具种类的不断丰富,如何从数百个可用工具中快速定位最适合当前任务的工具,成为开发者和 AI 助手面临的一项重要挑战。
传统做法要么将所有工具暴露给 AI 助手处理,导致 token 消耗巨大、响应延迟严重;要么依赖开发者手动分组配置,灵活性和智能性不足。
MCPHub 推出的智能路由功能,基于向量语义搜索技术,实现了自然语言驱动的工具发现与精准推荐。它让 AI 助手能够像人类专家一样,根据任务描述自动选择最优工具组合,大幅提升了系统效率和用户体验。
## 什么是智能路由
### 技术原理
智能路由是 MCPHub 的核心功能之一。它将每个 MCP 工具的名称和描述嵌入为高维语义向量。当用户发起自然语言任务请求时,系统会将该请求也转换为向量,通过计算相似度,快速返回最相关的工具列表。
这一过程摒弃了传统的关键词匹配,具备更强的语义理解能力,能够处理自然语言的模糊性和多样性。
### 核心组件
- **向量嵌入引擎**:支持如 `text-embedding-3-small``bge-m3` 等主流模型,将文本描述转为语义向量。
- **PostgreSQL + pgvector**:使用开源向量数据库方案,支持高效的向量索引和搜索。
- **两步工作流分离**
- `search_tools`:负责语义工具发现
- `call_tool`:执行实际工具调用逻辑
## 为什么需要智能路由
### 1. 减少认知负荷
- 当工具数量超过 100 个AI 模型难以处理所有工具上下文。
- 智能路由通过语义压缩,将候选工具缩小至 510 个,提高决策效率。
### 2. 显著降低 token 消耗
- 传统做法传入全量工具描述,可能消耗上千 token。
- 使用智能路由,通常可将 token 使用降低 7090%。
### 3. 提升调用准确率
- 理解任务语义:如“图片增强”→选择图像处理工具,而不是依赖命名关键词。
- 上下文感知:考虑输入/输出格式和工具组合能力,匹配更合理的执行链路。
## 智能路由配置指南
### 1. 启动支持 `pgvector` 的 PostgreSQL 数据库
```bash
docker run --name mcphub-postgres \
-e POSTGRES_DB=mcphub \
-e POSTGRES_USER=mcphub \
-e POSTGRES_PASSWORD=your_password \
-p 5432:5432 \
-d pgvector/pgvector:pg17
```
如已部署 PostgreSQL可直接创建数据库并启用 `pgvector` 扩展:
```sql
CREATE DATABASE mcphub;
CREATE EXTENSION vector;
```
### 2. 获取 embedding 模型的 API Key
前往 OpenAI 或其他提供商获取嵌入模型的 API Key。国内用户推荐使用硅基流动 `bge-m3` 免费模型,没有注册过的用户可以使用我的邀请链接:[https://cloud.siliconflow.cn/i/TQhVYBvA](https://cloud.siliconflow.cn/i/TQhVYBvA)。
### 3. 控制台配置
![配置](./assets/sr-conf.png)
在 MCPHub 控制台中,进入「智能路由」配置页面,填写以下信息:
- **数据库 URL**`postgresql://mcphub:your_password@localhost:5432/mcphub`
- **OpenAI API Key** :填写你获取的嵌入模型 API Key
- **OpenAI 基础 URL**`https://api.siliconflow.cn/v1`
- **嵌入模型**:推荐使用 `BAAI/bge-m3`
开启「启用智能路由」后系统会自动:
- 对所有工具生成向量嵌入
- 构建向量索引
- 自动监听新增工具,更新索引
## 工具定义
### search_tools - 工具搜索
```ts
{
"name": "search_tools",
"arguments": {
"query": "help me resize and convert images",
"limit": 10
}
}
```
### call_tool - 工具执行
```ts
{
"name": "call_tool",
"arguments": {
"toolName": "image_resizer",
"arguments": {
"input_path": "/path/to/image.png",
"width": 800,
"height": 600
}
}
}
```
## 演示
下面我将通过几个示例来展示如何使用智能路由。
首先,我们需要在 mcphub 添加几个不同类型的 MCP 服务器:`amap``time-map``fetch`
![添加服务器](./assets/sr-servers.png)
然后我们需要选择一个支持 MCP 的客户端,这里选择国产的 DeepChat聊天模型选择 `Qwen3-14B`
接着,在 DeepChat 中添加 mcphub 的智能路由端点:
![添加智能路由](./assets/sr-dc.png)
添加成功后,就可以在工具中看到 `search_tools``call_tool` 两个工具了:
![工具列表](./assets/sr-tools.png)
### 示例 1地图导航
输入:从北京如何导航到上海。
结果:
![地图导航](./assets/sr-map-result.png)
可以看到DeepChat 先调用了 `search_tools` 工具:
![搜索工具](./assets/sr-map-search.png)
然后再调用 `call_tool` 查询具体的导航信息:
![调用工具](./assets/sr-map-call.png)
### 示例 2查询时间
输入:使用工具查询美国现在的时间是几点
结果:
![查询时间](./assets/sr-time.png)
需要说明的是,由于不同的模型对工具调用的支持程度不同,可能会出现一些差异。比如在这个例子中,为了提高准确性,我在输入中明确提到了“使用工具”。
### 示例 3查看网页
输入:打开 baidu.com 看看有什么
结果:
![查看网页](./assets/sr-web.png)
可以看到DeepChat 成功调用了工具,不过由于百度的 robots.txt 限制,无法获取到具体内容。
## 结语
借助 MCPHub 的智能路由功能AI 助手能够更高效地处理复杂任务,显著减少不必要的 token 消耗,同时提升工具调用的准确性与灵活性。作为面向未来的 AI 工具发现与调用基础设施,智能路由不仅使 AI 更聪明地选择和组合工具,还为多 Agent 协同提供了清晰、可控且可扩展的底层能力支撑。
> MCPHub 只是我一时兴起开发的小项目,没想到收获了这么多关注,非常感谢大家的支持!目前 MCPHub 还有不少地方需要优化和完善,我也专门建了个交流群,感兴趣的可以添加下面的微信。
![微信](../assets/wexin.png)
> 同时,欢迎大家一起参与建设!项目地址为:[https://github.com/samanhappy/mcphub](https://github.com/samanhappy/mcphub)。

View File

@@ -1,271 +0,0 @@
# 智能工具发现精准调用MCPHub 智能路由重新定义 AI 工具选择
## 概述
在现代 AI 应用中,随着 MCP 服务器数量的快速增长和工具种类的日益丰富如何从数百个可用工具中快速找到最适合当前任务的工具成为了一个日益突出的挑战。传统方式下AI 助手要么被迫处理所有可用工具的庞大列表,导致 token 消耗激增和响应延迟要么依赖开发者手动分组缺乏灵活性和智能化。MCPHub 的智能路由功能基于向量语义搜索技术,实现了自然语言驱动的工具发现与精准推荐,让 AI 助手能够像人类专家一样,根据任务描述智能地选择最合适的工具组合,大幅提升工作效率和用户体验。
## 智能路由是什么
### 技术原理
智能路由是 MCPHub 的核心创新功能,它基于现代向量语义搜索技术,将每个 MCP 工具的描述、参数和功能特征转换为高维向量表示。当用户提出任务需求时,系统将需求同样转换为向量,通过计算向量间的余弦相似度,快速定位最相关的工具集合。这种方法不依赖精确的关键词匹配,而是理解语义层面的相关性,能够处理自然语言的模糊性和多样性。
### 核心组件
**向量嵌入引擎**:支持 OpenAI text-embedding-3-small、BGE-M3 等多种主流嵌入模型,将工具描述转换为 1536 维或 1024 维向量表示,捕获工具功能的语义特征。
**PostgreSQL + pgvector 数据库**:采用业界领先的向量数据库解决方案,支持高效的向量索引和相似度搜索,能够在毫秒级时间内从大量工具中找到最相关的候选。
**动态阈值算法**根据查询复杂度和具体程度自动调整相似度阈值确保既不遗漏相关工具也不引入无关噪声。简单查询使用较低阈值0.2获得更多样化结果具体查询使用较高阈值0.4)确保精确匹配。
**两步工作流**`search_tools` 负责工具发现,`call_tool` 负责工具执行,清晰分离发现和执行逻辑,提供更好的可控性和调试体验。
## 为什么要使用智能路由
### 1. 解决工具选择的认知负荷
- **信息过载问题**:当 MCP 服务器数量超过 10 个、工具总数超过 100 个时AI 助手面临严重的信息过载,难以在合理时间内做出最优选择。
- **智能路由优势**:通过语义搜索将候选工具缩减到 5-10 个最相关的选项,让 AI 助手能够专注于理解和使用最合适的工具,而不是被迫处理庞大的工具清单。
### 2. 大幅降低 Token 消耗
- **传统方式的成本**:向 AI 模型传递完整的工具列表会消耗大量 token特别是当工具描述详细时单次请求可能消耗数千 token。
- **智能路由的效益**:只传递最相关的工具信息,通常可以将工具相关的 token 消耗降低 70-90%,显著减少 API 调用成本,特别是在频繁交互的场景中。
### 3. 提升工具使用的准确性
- **语义理解能力**:智能路由能够理解"图片处理"、"数据分析"、"文档转换"等抽象概念,将其映射到具体的工具实现,避免了传统关键词匹配的局限性。
- **上下文感知**:考虑工具的输入输出模式和使用场景,推荐最适合当前任务流程的工具组合。
![智能路由工作原理](../assets/smart-routing-flow.png)
## 如何使用智能路由
### 配置智能路由
#### 1. 数据库配置
智能路由需要 PostgreSQL 数据库支持 pgvector 扩展:
```bash
# 使用 Docker 快速启动支持 pgvector 的 PostgreSQL
docker run --name mcphub-postgres \
-e POSTGRES_DB=mcphub \
-e POSTGRES_USER=mcphub \
-e POSTGRES_PASSWORD=your_password \
-p 5432:5432 \
-d pgvector/pgvector:pg16
```
#### 2. 在 MCPHub 控制台配置智能路由
访问 MCPHub 设置页面http://localhost:3000/settings在"智能路由配置"部分填写:
- **数据库 URL**`postgresql://mcphub:your_password@localhost:5432/mcphub`
- **OpenAI API Key**:您的 OpenAI API 密钥
- **API Base URL**:可选,默认为 `https://api.openai.com/v1`
- **嵌入模型**:推荐使用 `text-embedding-3-small`1536 维,性价比最佳)
![智能路由配置界面](../assets/smart-routing-config.png)
#### 3. 启用智能路由
配置完成后,开启"启用智能路由"开关。系统将自动:
- 为所有已连接的 MCP 服务器工具生成向量嵌入
- 建立向量索引以支持快速搜索
- 在后续新增工具时自动更新向量数据库
### 智能工具发现的使用方式
启用智能路由后MCPHub 会自动提供两个核心工具:
#### search_tools - 智能工具搜索
```typescript
// 使用示例
{
"name": "search_tools",
"arguments": {
"query": "help me process images and resize them", // 自然语言查询
"limit": 10 // 返回结果数量
}
}
```
**查询策略建议**
- **宽泛查询**:使用较高的 limit20-30如"数据处理工具"
- **精确查询**:使用较低的 limit5-10如"将 PNG 图片转换为 WebP 格式"
- **分步查询**:复杂任务可以分解为多个具体查询
#### call_tool - 精准工具执行
```typescript
// 使用示例
{
"name": "call_tool",
"arguments": {
"toolName": "image_resize", // 从 search_tools 结果中获取的工具名
"arguments": { // 根据工具的 inputSchema 提供参数
"input_path": "/path/to/image.png",
"width": 800,
"height": 600
}
}
}
```
### 实际应用场景演示
#### 场景 1图像处理工作流
```markdown
用户请求:我需要批量处理一些产品图片,调整大小并转换格式
AI 工作流:
1. search_tools("image processing batch resize convert format")
→ 返回image_batch_processor, format_converter, image_optimizer
2. call_tool("image_batch_processor", {...})
→ 执行批量图像处理
```
#### 场景 2数据分析任务
```markdown
用户请求:分析这个 CSV 文件的销售数据,生成可视化图表
AI 工作流:
1. search_tools("CSV data analysis visualization charts")
→ 返回csv_analyzer, chart_generator, statistics_calculator
2. call_tool("csv_analyzer", {"file_path": "sales.csv"})
3. call_tool("chart_generator", {"data": analysis_result})
```
#### 场景 3文档处理流水线
```markdown
用户请求:将 Word 文档转换为 PDF然后提取其中的文本内容
AI 工作流:
1. search_tools("document conversion Word to PDF")
→ 返回doc_converter, pdf_generator
2. call_tool("doc_converter", {"input": "document.docx", "output_format": "pdf"})
3. search_tools("PDF text extraction")
→ 返回pdf_text_extractor, ocr_processor
4. call_tool("pdf_text_extractor", {"pdf_path": "document.pdf"})
```
### 高级配置选项
#### 多模型支持
智能路由支持多种嵌入模型,可根据需求选择:
```json
{
"embeddingModel": "text-embedding-3-small", // OpenAI1536维平衡性能和成本
"embeddingModel": "text-embedding-3-large", // OpenAI3072维最高精度
"embeddingModel": "bge-m3" // 开源模型1024维支持多语言
}
```
#### 自定义 API 端点
支持使用自建的嵌入服务或其他 OpenAI 兼容的 API
```json
{
"openaiApiBaseUrl": "https://your-api-endpoint.com/v1",
"openaiApiKey": "your-custom-api-key"
}
```
## 性能优化与最佳实践
### 查询优化策略
**分层查询**:对于复杂任务,使用从宽泛到具体的查询策略:
```
1. 宽泛查询:"文档处理工具" (limit: 20)
2. 具体查询:"PDF 转 Word 转换器" (limit: 5)
```
**上下文相关性**:在查询中包含任务上下文信息:
```
search_tools("为电商网站批量压缩产品图片")
较好search_tools("图片压缩工具")
```
**动态调整**:根据返回结果质量动态调整查询词和限制数量。
### 数据库性能调优
**索引优化**:智能路由自动创建最优的向量索引:
```sql
CREATE INDEX idx_vector_embeddings_embedding
ON vector_embeddings USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
```
**内存配置**:对于大规模部署,建议增加 PostgreSQL 内存配置:
```
shared_buffers = 256MB
effective_cache_size = 1GB
work_mem = 64MB
```
### 监控与调试
**相似度阈值监控**:观察搜索结果的相似度分数,调整阈值以获得最佳效果。
**查询效果分析**:定期检查常用查询的返回结果,优化工具描述以提高搜索准确性。
## 智能路由的技术优势
### 语义理解能力
与传统的关键词匹配相比,智能路由能够理解:
- **同义词和近义词**"图片"和"图像"、"转换"和"变换"
- **上下层级概念**"数据可视化"包含"图表生成"、"统计图绘制"等
- **任务意图推理**"我要做一个数据报告"自动关联数据分析、图表生成、文档创建等工具
### 多语言支持
智能路由支持中英文混合查询,能够处理:
```
search_tools("图片 resize 和 format conversion")
search_tools("将文档转换为 PDF 格式")
search_tools("image processing and 格式转换")
```
### 容错能力
具备一定的容错能力,能够处理:
- 拼写错误:自动纠正常见拼写错误
- 模糊描述:从不完整的描述中推导完整意图
- 领域术语:理解特定领域的专业术语
## 结语
MCPHub 的智能路由功能代表着 MCP 生态系统向智能化方向发展的重要一步。通过引入向量语义搜索技术,它不仅解决了工具数量激增带来的选择困难,更为 AI 助手提供了类似人类专家的工具发现和选择能力。
随着 MCP 服务器生态的不断丰富,智能路由将成为连接用户需求与丰富工具资源的关键桥梁。它让开发者无需担心工具管理的复杂性,让用户享受到更加智能和高效的 AI 助手体验。
未来,我们还将继续优化智能路由的算法,引入更多先进的 AI 技术,如基于强化学习的工具推荐、多模态工具理解等,为 MCP 生态系统注入更强的智能化动力。
智能路由不仅仅是一个技术功能,更是 MCPHub 对于"让 AI 工具使用变得简单而智能"这一愿景的具体实现。在这个工具爆炸的时代,智能路由让我们重新定义了 AI 与工具的交互方式。
项目地址https://github.com/samanhappy/mcphub
![智能路由架构图](../assets/smart-routing-architecture.png)

View File

@@ -0,0 +1,539 @@
---
title: 'Docker Setup'
description: 'Deploy MCPHub using Docker and Docker Compose'
---
# Docker Setup
This guide covers deploying MCPHub using Docker, including development and production configurations.
## Quick Start with Docker
### Using Pre-built Image
```bash
# Pull the latest image
docker pull mcphub/mcphub:latest
# Run with default configuration
docker run -d \
--name mcphub \
-p 3000:3000 \
-v $(pwd)/mcp_settings.json:/app/mcp_settings.json \
mcphub/mcphub:latest
```
### Building from Source
```bash
# Clone the repository
git clone https://github.com/your-username/mcphub.git
cd mcphub
# Build the Docker image
docker build -t mcphub:local .
# Run the container
docker run -d \
--name mcphub \
-p 3000:3000 \
-v $(pwd)/mcp_settings.json:/app/mcp_settings.json \
mcphub:local
```
## Docker Compose Setup
### Basic Configuration
Create a `docker-compose.yml` file:
```yaml
version: '3.8'
services:
mcphub:
image: mcphub/mcphub:latest
# For local development, use:
# build: .
container_name: mcphub
ports:
- '3000:3000'
environment:
- NODE_ENV=production
- PORT=3000
- JWT_SECRET=${JWT_SECRET:-your-jwt-secret}
- DATABASE_URL=postgresql://mcphub:password@postgres:5432/mcphub
volumes:
- ./mcp_settings.json:/app/mcp_settings.json:ro
- ./servers.json:/app/servers.json:ro
- mcphub_data:/app/data
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
networks:
- mcphub-network
postgres:
image: postgres:15-alpine
container_name: mcphub-postgres
environment:
- POSTGRES_DB=mcphub
- POSTGRES_USER=mcphub
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql:ro
ports:
- '5432:5432'
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U mcphub -d mcphub']
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- mcphub-network
volumes:
postgres_data:
mcphub_data:
networks:
mcphub-network:
driver: bridge
```
### Production Configuration with Nginx
```yaml
version: '3.8'
services:
nginx:
image: nginx:alpine
container_name: mcphub-nginx
ports:
- '80:80'
- '443:443'
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./ssl:/etc/nginx/ssl:ro
- nginx_logs:/var/log/nginx
depends_on:
- mcphub
restart: unless-stopped
networks:
- mcphub-network
mcphub:
image: mcphub/mcphub:latest
container_name: mcphub-app
expose:
- '3000'
environment:
- NODE_ENV=production
- PORT=3000
- JWT_SECRET=${JWT_SECRET}
- JWT_EXPIRES_IN=${JWT_EXPIRES_IN:-24h}
- DATABASE_URL=postgresql://mcphub:${POSTGRES_PASSWORD}@postgres:5432/mcphub
- OPENAI_API_KEY=${OPENAI_API_KEY}
- REDIS_URL=redis://redis:6379
volumes:
- ./mcp_settings.json:/app/mcp_settings.json:ro
- ./servers.json:/app/servers.json:ro
- mcphub_data:/app/data
- mcphub_logs:/app/logs
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
networks:
- mcphub-network
healthcheck:
test: ['CMD', 'wget', '--quiet', '--tries=1', '--spider', 'http://localhost:3000/health']
interval: 30s
timeout: 10s
retries: 3
postgres:
image: postgres:15-alpine
container_name: mcphub-postgres
environment:
- POSTGRES_DB=mcphub
- POSTGRES_USER=mcphub
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./backups:/backups
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U mcphub -d mcphub']
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- mcphub-network
redis:
image: redis:7-alpine
container_name: mcphub-redis
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- mcphub-network
volumes:
postgres_data:
redis_data:
mcphub_data:
mcphub_logs:
nginx_logs:
networks:
mcphub-network:
driver: bridge
```
### Environment Variables
Create a `.env` file for Docker Compose:
```env
# Application
NODE_ENV=production
JWT_SECRET=your-super-secret-jwt-key-change-this
JWT_EXPIRES_IN=24h
# Database
POSTGRES_PASSWORD=your-secure-database-password
# Redis
REDIS_PASSWORD=your-secure-redis-password
# External APIs
OPENAI_API_KEY=your-openai-api-key
# Optional: Custom port
# PORT=3000
```
## Development Setup
### Development Docker Compose
Create `docker-compose.dev.yml`:
```yaml
version: '3.8'
services:
mcphub-dev:
build:
context: .
dockerfile: Dockerfile.dev
container_name: mcphub-dev
ports:
- '3000:3000'
- '5173:5173' # Frontend dev server
- '9229:9229' # Debug port
environment:
- NODE_ENV=development
- PORT=3000
- DATABASE_URL=postgresql://mcphub:password@postgres:5432/mcphub
volumes:
- .:/app
- /app/node_modules
- /app/frontend/node_modules
depends_on:
- postgres
command: pnpm dev
networks:
- mcphub-dev
postgres:
image: postgres:15-alpine
container_name: mcphub-postgres-dev
environment:
- POSTGRES_DB=mcphub
- POSTGRES_USER=mcphub
- POSTGRES_PASSWORD=password
ports:
- '5432:5432'
volumes:
- postgres_dev_data:/var/lib/postgresql/data
networks:
- mcphub-dev
volumes:
postgres_dev_data:
networks:
mcphub-dev:
driver: bridge
```
### Development Dockerfile
Create `Dockerfile.dev`:
```dockerfile
FROM node:20-alpine
# Install pnpm
RUN npm install -g pnpm
# Set working directory
WORKDIR /app
# Copy package files
COPY package.json pnpm-lock.yaml ./
COPY frontend/package.json ./frontend/
# Install dependencies
RUN pnpm install
# Copy source code
COPY . .
# Expose ports
EXPOSE 3000 5173 9229
# Start development server
CMD ["pnpm", "dev"]
```
## Running the Application
### Development Mode
```bash
# Start development environment
docker-compose -f docker-compose.dev.yml up -d
# View logs
docker-compose -f docker-compose.dev.yml logs -f mcphub-dev
# Stop development environment
docker-compose -f docker-compose.dev.yml down
```
### Production Mode
```bash
# Start production environment
docker-compose up -d
# View logs
docker-compose logs -f mcphub
# Stop production environment
docker-compose down
```
## Configuration Management
### MCP Settings Volume Mount
Create your `mcp_settings.json`:
```json
{
"mcpServers": {
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"]
},
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest", "--headless"]
},
"amap": {
"command": "npx",
"args": ["-y", "@amap/amap-maps-mcp-server"],
"env": {
"AMAP_MAPS_API_KEY": "your-api-key"
}
}
}
}
```
### Secrets Management
For production, use Docker secrets:
```yaml
version: '3.8'
services:
mcphub:
image: mcphub/mcphub:latest
environment:
- JWT_SECRET_FILE=/run/secrets/jwt_secret
- DATABASE_PASSWORD_FILE=/run/secrets/db_password
secrets:
- jwt_secret
- db_password
secrets:
jwt_secret:
file: ./secrets/jwt_secret.txt
db_password:
file: ./secrets/db_password.txt
```
## Data Persistence
### Database Backups
Add backup service to your `docker-compose.yml`:
```yaml
services:
backup:
image: postgres:15-alpine
container_name: mcphub-backup
environment:
- PGPASSWORD=${POSTGRES_PASSWORD}
volumes:
- ./backups:/backups
- ./scripts/backup.sh:/backup.sh:ro
command: /bin/sh -c "chmod +x /backup.sh && /backup.sh"
depends_on:
- postgres
profiles:
- backup
networks:
- mcphub-network
```
Create `scripts/backup.sh`:
```bash
#!/bin/sh
BACKUP_FILE="/backups/mcphub_$(date +%Y%m%d_%H%M%S).sql"
pg_dump -h postgres -U mcphub -d mcphub > "$BACKUP_FILE"
echo "Backup created: $BACKUP_FILE"
# Keep only last 7 days of backups
find /backups -name "mcphub_*.sql" -mtime +7 -delete
```
Run backup:
```bash
docker-compose --profile backup run --rm backup
```
## Monitoring and Health Checks
### Health Check Endpoint
Add to your application:
```javascript
// In your Express app
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
memory: process.memoryUsage(),
version: process.env.npm_package_version,
});
});
```
### Docker Health Checks
```yaml
services:
mcphub:
# ... other config
healthcheck:
test: ['CMD', 'wget', '--quiet', '--tries=1', '--spider', 'http://localhost:3000/health']
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
```
### Monitoring with Watchtower
Add automatic updates:
```yaml
services:
watchtower:
image: containrrr/watchtower
container_name: mcphub-watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_POLL_INTERVAL=3600
- WATCHTOWER_INCLUDE_STOPPED=true
restart: unless-stopped
```
## Troubleshooting
### Common Issues
**Container fails to start**: Check logs with `docker-compose logs mcphub`
**Database connection errors**: Ensure PostgreSQL is healthy and accessible
**Port conflicts**: Check if ports 3000/5432 are already in use
**Volume mount issues**: Verify file paths and permissions
### Debug Commands
```bash
# Check container status
docker-compose ps
# View logs
docker-compose logs -f [service_name]
# Execute commands in container
docker-compose exec mcphub sh
# Check database connection
docker-compose exec postgres psql -U mcphub -d mcphub
# Restart specific service
docker-compose restart mcphub
# Rebuild and restart
docker-compose up --build -d
```
### Performance Optimization
```yaml
services:
mcphub:
# ... other config
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
reservations:
memory: 256M
cpus: '0.25'
```
This Docker setup provides a complete containerized environment for MCPHub with development and production configurations.

View File

@@ -0,0 +1,389 @@
---
title: 'Environment Variables'
description: 'Configure MCPHub using environment variables'
---
# Environment Variables
MCPHub uses environment variables for configuration. This guide covers all available variables and their usage.
## Core Application Settings
### Server Configuration
| Variable | Default | Description |
| ----------- | ------------- | ------------------------------------------------------------- |
| `PORT` | `3000` | Port number for the HTTP server |
| `HOST` | `0.0.0.0` | Host address to bind the server |
| `NODE_ENV` | `development` | Application environment (`development`, `production`, `test`) |
| `LOG_LEVEL` | `info` | Logging level (`error`, `warn`, `info`, `debug`) |
```env
PORT=3000
HOST=0.0.0.0
NODE_ENV=production
LOG_LEVEL=info
```
### Database Configuration
| Variable | Default | Description |
| -------------- | ----------- | ---------------------------------- |
| `DATABASE_URL` | - | PostgreSQL connection string |
| `DB_HOST` | `localhost` | Database host |
| `DB_PORT` | `5432` | Database port |
| `DB_NAME` | `mcphub` | Database name |
| `DB_USER` | `mcphub` | Database username |
| `DB_PASSWORD` | - | Database password |
| `DB_SSL` | `false` | Enable SSL for database connection |
| `DB_POOL_MIN` | `2` | Minimum database pool size |
| `DB_POOL_MAX` | `10` | Maximum database pool size |
```env
# Option 1: Full connection string
DATABASE_URL=postgresql://username:password@localhost:5432/mcphub
# Option 2: Individual components
DB_HOST=localhost
DB_PORT=5432
DB_NAME=mcphub
DB_USER=mcphub
DB_PASSWORD=your-password
DB_SSL=false
```
## Authentication & Security
### JWT Configuration
| Variable | Default | Description |
| ------------------------ | ------- | ------------------------------------------- |
| `JWT_SECRET` | - | Secret key for JWT token signing (required) |
| `JWT_EXPIRES_IN` | `24h` | JWT token expiration time |
| `JWT_REFRESH_EXPIRES_IN` | `7d` | Refresh token expiration time |
| `JWT_ALGORITHM` | `HS256` | JWT signing algorithm |
```env
JWT_SECRET=your-super-secret-key-change-this-in-production
JWT_EXPIRES_IN=24h
JWT_REFRESH_EXPIRES_IN=7d
```
### Session & Security
| Variable | Default | Description |
| ------------------- | ------- | ------------------------------- |
| `SESSION_SECRET` | - | Session encryption secret |
| `BCRYPT_ROUNDS` | `12` | bcrypt hashing rounds |
| `RATE_LIMIT_WINDOW` | `15` | Rate limiting window in minutes |
| `RATE_LIMIT_MAX` | `100` | Maximum requests per window |
| `CORS_ORIGIN` | `*` | Allowed CORS origins |
```env
SESSION_SECRET=your-session-secret
BCRYPT_ROUNDS=12
RATE_LIMIT_WINDOW=15
RATE_LIMIT_MAX=100
CORS_ORIGIN=https://your-domain.com,https://admin.your-domain.com
```
## External Services
### OpenAI Configuration
| Variable | Default | Description |
| ------------------------ | ------------------------ | -------------------------------- |
| `OPENAI_API_KEY` | - | OpenAI API key for smart routing |
| `OPENAI_MODEL` | `gpt-3.5-turbo` | OpenAI model for embeddings |
| `OPENAI_EMBEDDING_MODEL` | `text-embedding-ada-002` | Model for vector embeddings |
| `OPENAI_MAX_TOKENS` | `1000` | Maximum tokens per request |
| `OPENAI_TEMPERATURE` | `0.1` | Temperature for AI responses |
```env
OPENAI_API_KEY=sk-your-openai-api-key
OPENAI_MODEL=gpt-3.5-turbo
OPENAI_EMBEDDING_MODEL=text-embedding-ada-002
OPENAI_MAX_TOKENS=1000
OPENAI_TEMPERATURE=0.1
```
### Redis Configuration (Optional)
| Variable | Default | Description |
| ---------------- | ----------- | ----------------------- |
| `REDIS_URL` | - | Redis connection string |
| `REDIS_HOST` | `localhost` | Redis host |
| `REDIS_PORT` | `6379` | Redis port |
| `REDIS_PASSWORD` | - | Redis password |
| `REDIS_DB` | `0` | Redis database number |
| `REDIS_PREFIX` | `mcphub:` | Key prefix for Redis |
```env
# Option 1: Full connection string
REDIS_URL=redis://username:password@localhost:6379/0
# Option 2: Individual components
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=your-redis-password
REDIS_DB=0
REDIS_PREFIX=mcphub:
```
## MCP Server Configuration
### Default Settings
| Variable | Default | Description |
| ------------------- | ------------------- | -------------------------------------------- |
| `MCP_SETTINGS_FILE` | `mcp_settings.json` | Path to MCP settings file |
| `MCP_SERVERS_FILE` | `servers.json` | Path to servers configuration |
| `MCP_TIMEOUT` | `30000` | Default timeout for MCP operations (ms) |
| `MCP_MAX_RETRIES` | `3` | Maximum retry attempts for failed operations |
| `MCP_RESTART_DELAY` | `5000` | Delay before restarting failed servers (ms) |
```env
MCP_SETTINGS_FILE=./config/mcp_settings.json
MCP_SERVERS_FILE=./config/servers.json
MCP_TIMEOUT=30000
MCP_MAX_RETRIES=3
MCP_RESTART_DELAY=5000
```
### Smart Routing
| Variable | Default | Description |
| --------------------------- | ------- | -------------------------------- |
| `SMART_ROUTING_ENABLED` | `true` | Enable AI-powered smart routing |
| `SMART_ROUTING_THRESHOLD` | `0.7` | Similarity threshold for routing |
| `SMART_ROUTING_MAX_RESULTS` | `5` | Maximum tools to return |
| `VECTOR_CACHE_TTL` | `3600` | Vector cache TTL in seconds |
```env
SMART_ROUTING_ENABLED=true
SMART_ROUTING_THRESHOLD=0.7
SMART_ROUTING_MAX_RESULTS=5
VECTOR_CACHE_TTL=3600
```
## File Storage & Uploads
| Variable | Default | Description |
| -------------------- | ---------------- | ----------------------------------- |
| `UPLOAD_DIR` | `./uploads` | Directory for file uploads |
| `MAX_FILE_SIZE` | `10485760` | Maximum file size in bytes (10MB) |
| `ALLOWED_FILE_TYPES` | `image/*,text/*` | Allowed MIME types |
| `STORAGE_TYPE` | `local` | Storage type (`local`, `s3`, `gcs`) |
```env
UPLOAD_DIR=./data/uploads
MAX_FILE_SIZE=10485760
ALLOWED_FILE_TYPES=image/*,text/*,application/json
STORAGE_TYPE=local
```
### S3 Storage (Optional)
| Variable | Default | Description |
| ---------------------- | ----------- | ------------------ |
| `S3_BUCKET` | - | S3 bucket name |
| `S3_REGION` | `us-east-1` | S3 region |
| `S3_ACCESS_KEY_ID` | - | S3 access key |
| `S3_SECRET_ACCESS_KEY` | - | S3 secret key |
| `S3_ENDPOINT` | - | Custom S3 endpoint |
```env
S3_BUCKET=mcphub-uploads
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=your-access-key
S3_SECRET_ACCESS_KEY=your-secret-key
```
## Monitoring & Logging
### Application Monitoring
| Variable | Default | Description |
| ------------------------ | ------- | ----------------------------- |
| `METRICS_ENABLED` | `true` | Enable metrics collection |
| `METRICS_PORT` | `9090` | Port for metrics endpoint |
| `HEALTH_CHECK_INTERVAL` | `30000` | Health check interval (ms) |
| `PERFORMANCE_MONITORING` | `false` | Enable performance monitoring |
```env
METRICS_ENABLED=true
METRICS_PORT=9090
HEALTH_CHECK_INTERVAL=30000
PERFORMANCE_MONITORING=true
```
### Logging Configuration
| Variable | Default | Description |
| ------------------ | ------------ | --------------------------------------- |
| `LOG_FORMAT` | `json` | Log format (`json`, `text`) |
| `LOG_FILE` | - | Log file path (if file logging enabled) |
| `LOG_MAX_SIZE` | `10m` | Maximum log file size |
| `LOG_MAX_FILES` | `5` | Maximum number of log files |
| `LOG_DATE_PATTERN` | `YYYY-MM-DD` | Date pattern for log rotation |
```env
LOG_FORMAT=json
LOG_FILE=./logs/mcphub.log
LOG_MAX_SIZE=10m
LOG_MAX_FILES=5
LOG_DATE_PATTERN=YYYY-MM-DD
```
## Development & Debug
| Variable | Default | Description |
| ------------------------ | ------- | ----------------------------------- |
| `DEBUG` | - | Debug namespaces (e.g., `mcphub:*`) |
| `DEV_TOOLS_ENABLED` | `false` | Enable development tools |
| `HOT_RELOAD` | `true` | Enable hot reload in development |
| `MOCK_EXTERNAL_SERVICES` | `false` | Mock external API calls |
```env
DEBUG=mcphub:*
DEV_TOOLS_ENABLED=true
HOT_RELOAD=true
MOCK_EXTERNAL_SERVICES=false
```
## Production Optimization
| Variable | Default | Description |
| ------------------ | ------- | -------------------------------------- |
| `CLUSTER_MODE` | `false` | Enable cluster mode |
| `WORKER_PROCESSES` | `0` | Number of worker processes (0 = auto) |
| `MEMORY_LIMIT` | - | Memory limit per process |
| `CPU_LIMIT` | - | CPU limit per process |
| `GC_OPTIMIZE` | `false` | Enable garbage collection optimization |
```env
CLUSTER_MODE=true
WORKER_PROCESSES=4
MEMORY_LIMIT=512M
GC_OPTIMIZE=true
```
## Configuration Examples
### Development Environment
```env
# .env.development
NODE_ENV=development
PORT=3000
LOG_LEVEL=debug
# Database
DATABASE_URL=postgresql://mcphub:password@localhost:5432/mcphub_dev
# Auth
JWT_SECRET=dev-secret-key
JWT_EXPIRES_IN=24h
# OpenAI (optional for development)
# OPENAI_API_KEY=your-dev-key
# Debug
DEBUG=mcphub:*
DEV_TOOLS_ENABLED=true
HOT_RELOAD=true
```
### Production Environment
```env
# .env.production
NODE_ENV=production
PORT=3000
LOG_LEVEL=info
LOG_FORMAT=json
# Database
DATABASE_URL=postgresql://mcphub:secure-password@db.example.com:5432/mcphub
DB_SSL=true
DB_POOL_MAX=20
# Security
JWT_SECRET=your-super-secure-production-secret
SESSION_SECRET=your-session-secret
BCRYPT_ROUNDS=14
# External Services
OPENAI_API_KEY=your-production-openai-key
REDIS_URL=redis://redis.example.com:6379
# Monitoring
METRICS_ENABLED=true
PERFORMANCE_MONITORING=true
# Optimization
CLUSTER_MODE=true
GC_OPTIMIZE=true
```
### Docker Environment
```env
# .env.docker
NODE_ENV=production
HOST=0.0.0.0
PORT=3000
# Use service names for Docker networking
DATABASE_URL=postgresql://mcphub:password@postgres:5432/mcphub
REDIS_URL=redis://redis:6379
# Security
JWT_SECRET_FILE=/run/secrets/jwt_secret
DB_PASSWORD_FILE=/run/secrets/db_password
# File paths in container
MCP_SETTINGS_FILE=/app/mcp_settings.json
UPLOAD_DIR=/app/data/uploads
LOG_FILE=/app/logs/mcphub.log
```
## Environment Variable Loading
MCPHub loads environment variables in the following order:
1. System environment variables
2. `.env.local` (ignored by git)
3. `.env.{NODE_ENV}` (e.g., `.env.production`)
4. `.env`
### Using dotenv-expand
MCPHub supports variable expansion:
```env
BASE_URL=https://api.example.com
API_ENDPOINT=${BASE_URL}/v1
DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}
```
## Security Best Practices
1. **Never commit secrets** to version control
2. **Use strong, unique secrets** for production
3. **Rotate secrets regularly**
4. **Use environment-specific files**
5. **Validate all environment variables** at startup
6. **Use Docker secrets** for container deployments
## Validation
MCPHub validates environment variables at startup. Invalid configurations will prevent the application from starting with helpful error messages.
Required variables for production:
- `JWT_SECRET`
- `DATABASE_URL` or individual DB components
- `OPENAI_API_KEY` (if smart routing is enabled)
This comprehensive environment configuration ensures MCPHub can be properly configured for any deployment scenario.

View File

@@ -0,0 +1,564 @@
---
title: 'MCP Settings Configuration'
description: 'Configure MCP servers and their settings for MCPHub'
---
# MCP Settings Configuration
This guide explains how to configure MCP servers in MCPHub using the `mcp_settings.json` file and related configurations.
## Configuration Files Overview
MCPHub uses several configuration files:
- **`mcp_settings.json`**: Main MCP server configurations
- **`servers.json`**: Server metadata and grouping
- **`.env`**: Environment variables and secrets
## Basic MCP Settings Structure
### mcp_settings.json
```json
{
"mcpServers": {
"server-name": {
"command": "command-to-run",
"args": ["arg1", "arg2"],
"env": {
"ENV_VAR": "value"
},
"cwd": "/working/directory",
"timeout": 30000,
"restart": true
}
}
}
```
### Example Configuration
```json
{
"mcpServers": {
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"],
"env": {
"USER_AGENT": "MCPHub/1.0"
}
},
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest", "--headless"],
"timeout": 60000
},
"slack": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-slack"],
"env": {
"SLACK_BOT_TOKEN": "${SLACK_BOT_TOKEN}",
"SLACK_TEAM_ID": "${SLACK_TEAM_ID}"
}
}
}
}
```
## Server Configuration Options
### Required Fields
| Field | Type | Description |
| --------- | ------ | -------------------------- |
| `command` | string | Executable command or path |
| `args` | array | Command-line arguments |
### Optional Fields
| Field | Type | Default | Description |
| -------------- | ------- | --------------- | --------------------------- |
| `env` | object | `{}` | Environment variables |
| `cwd` | string | `process.cwd()` | Working directory |
| `timeout` | number | `30000` | Startup timeout (ms) |
| `restart` | boolean | `true` | Auto-restart on failure |
| `maxRestarts` | number | `5` | Maximum restart attempts |
| `restartDelay` | number | `5000` | Delay between restarts (ms) |
| `stdio` | string | `pipe` | stdio configuration |
## Common MCP Server Examples
### Web and API Servers
#### Fetch Server
```json
{
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"],
"env": {
"USER_AGENT": "MCPHub/1.0",
"MAX_REDIRECTS": "10"
}
}
}
```
#### Web Scraping with Playwright
```json
{
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest", "--headless"],
"timeout": 60000,
"env": {
"PLAYWRIGHT_BROWSERS_PATH": "/tmp/browsers"
}
}
}
```
### File and System Servers
#### Filesystem Server
```json
{
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/allowed/path"],
"env": {
"ALLOWED_OPERATIONS": "read,write,list"
}
}
}
```
#### SQLite Server
```json
{
"sqlite": {
"command": "uvx",
"args": ["mcp-server-sqlite", "--db-path", "/path/to/database.db"],
"env": {
"SQLITE_READONLY": "false"
}
}
}
```
### Communication Servers
#### Slack Server
```json
{
"slack": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-slack"],
"env": {
"SLACK_BOT_TOKEN": "${SLACK_BOT_TOKEN}",
"SLACK_TEAM_ID": "${SLACK_TEAM_ID}",
"SLACK_APP_TOKEN": "${SLACK_APP_TOKEN}"
}
}
}
```
#### Email Server
```json
{
"email": {
"command": "python",
"args": ["-m", "mcp_server_email"],
"env": {
"SMTP_HOST": "smtp.gmail.com",
"SMTP_PORT": "587",
"EMAIL_USER": "${EMAIL_USER}",
"EMAIL_PASSWORD": "${EMAIL_PASSWORD}"
}
}
}
```
### Development and API Servers
#### GitHub Server
```json
{
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
}
}
}
```
#### Google Drive Server
```json
{
"gdrive": {
"command": "npx",
"args": ["-y", "@google/mcp-server-gdrive"],
"env": {
"GOOGLE_CLIENT_ID": "${GOOGLE_CLIENT_ID}",
"GOOGLE_CLIENT_SECRET": "${GOOGLE_CLIENT_SECRET}",
"GOOGLE_REFRESH_TOKEN": "${GOOGLE_REFRESH_TOKEN}"
}
}
}
```
### Map and Location Services
#### Amap (高德地图) Server
```json
{
"amap": {
"command": "npx",
"args": ["-y", "@amap/amap-maps-mcp-server"],
"env": {
"AMAP_MAPS_API_KEY": "${AMAP_API_KEY}",
"AMAP_LANGUAGE": "zh-cn"
}
}
}
```
#### OpenStreetMap Server
```json
{
"osm": {
"command": "python",
"args": ["-m", "mcp_server_osm"],
"env": {
"OSM_USER_AGENT": "MCPHub/1.0"
}
}
}
```
## Advanced Configuration
### Environment Variable Substitution
MCPHub supports environment variable substitution using `${VAR_NAME}` syntax:
```json
{
"mcpServers": {
"api-server": {
"command": "python",
"args": ["-m", "api_server"],
"env": {
"API_KEY": "${API_KEY}",
"API_URL": "${API_BASE_URL}/v1",
"DEBUG": "${NODE_ENV:development}"
}
}
}
}
```
Default values can be specified with `${VAR_NAME:default}`:
```json
{
"timeout": "${MCP_TIMEOUT:30000}",
"maxRestarts": "${MCP_MAX_RESTARTS:5}"
}
```
### Conditional Configuration
Use different configurations based on environment:
```json
{
"mcpServers": {
"database": {
"command": "python",
"args": ["-m", "db_server"],
"env": {
"DB_URL": "${NODE_ENV:development == 'production' ? DATABASE_URL : DEV_DATABASE_URL}"
}
}
}
}
```
### Custom Server Scripts
#### Local Python Server
```json
{
"custom-python": {
"command": "python",
"args": ["./servers/custom_server.py"],
"cwd": "/app/custom-servers",
"env": {
"PYTHONPATH": "/app/custom-servers",
"CONFIG_FILE": "./config.json"
}
}
}
```
#### Local Node.js Server
```json
{
"custom-node": {
"command": "node",
"args": ["./servers/custom-server.js"],
"cwd": "/app/custom-servers",
"env": {
"NODE_ENV": "production"
}
}
}
```
## Server Metadata Configuration
### servers.json
Complement `mcp_settings.json` with server metadata:
```json
{
"servers": {
"fetch": {
"name": "Fetch Server",
"description": "HTTP client for web requests",
"category": "web",
"tags": ["http", "api", "web"],
"version": "1.0.0",
"author": "MCPHub Team",
"documentation": "https://docs.mcphub.com/servers/fetch",
"enabled": true
},
"playwright": {
"name": "Playwright Browser",
"description": "Web automation and scraping",
"category": "automation",
"tags": ["browser", "scraping", "automation"],
"version": "2.0.0",
"enabled": true
}
},
"groups": {
"web-tools": {
"name": "Web Tools",
"description": "Tools for web interaction",
"servers": ["fetch", "playwright"],
"access": "public"
},
"admin-tools": {
"name": "Admin Tools",
"description": "Administrative utilities",
"servers": ["filesystem", "database"],
"access": "admin"
}
}
}
```
## Group Management
### Group Configuration
```json
{
"groups": {
"production": {
"name": "Production Tools",
"description": "Stable production servers",
"servers": ["fetch", "slack", "github"],
"access": "authenticated",
"rateLimit": {
"requestsPerMinute": 100,
"burstLimit": 20
}
},
"experimental": {
"name": "Experimental Features",
"description": "Beta and experimental servers",
"servers": ["experimental-ai", "beta-search"],
"access": "admin",
"enabled": false
}
}
}
```
### Access Control
| Access Level | Description |
| --------------- | -------------------------- |
| `public` | No authentication required |
| `authenticated` | Valid JWT token required |
| `admin` | Admin role required |
| `custom` | Custom permission logic |
## Dynamic Configuration
### Hot Reloading
MCPHub supports hot reloading of configurations:
```bash
# Reload configurations without restart
curl -X POST http://localhost:3000/api/admin/reload-config \
-H "Authorization: Bearer your-admin-token"
```
### Configuration Validation
MCPHub validates configurations on startup and reload:
```json
{
"validation": {
"strict": true,
"allowUnknownServers": false,
"requireDocumentation": true
}
}
```
## Best Practices
### Security
1. **Use environment variables** for sensitive data:
```json
{
"env": {
"API_KEY": "${API_KEY}",
"DATABASE_PASSWORD": "${DB_PASSWORD}"
}
}
```
2. **Limit server permissions**:
```json
{
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/restricted/path"],
"env": {
"READONLY": "true"
}
}
}
```
### Performance
1. **Set appropriate timeouts**:
```json
{
"timeout": 30000,
"maxRestarts": 3,
"restartDelay": 5000
}
```
2. **Resource limits**:
```json
{
"env": {
"NODE_OPTIONS": "--max-old-space-size=512",
"MEMORY_LIMIT": "512MB"
}
}
```
### Monitoring
1. **Enable health checks**:
```json
{
"healthCheck": {
"enabled": true,
"interval": 30000,
"timeout": 5000
}
}
```
2. **Logging configuration**:
```json
{
"env": {
"LOG_LEVEL": "info",
"LOG_FORMAT": "json"
}
}
```
## Troubleshooting
### Common Issues
**Server won't start**: Check command and arguments
```bash
# Test command manually
uvx mcp-server-fetch
```
**Environment variables not found**: Verify `.env` file
```bash
# Check environment
printenv | grep API_KEY
```
**Permission errors**: Check file permissions and paths
```bash
# Verify executable permissions
ls -la /path/to/server
```
### Debug Configuration
Enable debug mode for detailed logging:
```json
{
"debug": {
"enabled": true,
"logLevel": "debug",
"includeEnv": false,
"logStartup": true
}
}
```
### Validation Errors
Common validation errors and solutions:
1. **Missing required fields**: Add `command` and `args`
2. **Invalid timeout**: Use number, not string
3. **Environment variable not found**: Check `.env` file
4. **Command not found**: Verify installation and PATH
This comprehensive guide covers all aspects of configuring MCP servers in MCPHub for various use cases and environments.

View File

@@ -0,0 +1,373 @@
---
title: 'Nginx Configuration'
description: 'Configure Nginx as a reverse proxy for MCPHub'
---
# Nginx Configuration
This guide explains how to configure Nginx as a reverse proxy for MCPHub, including SSL termination, load balancing, and caching strategies.
## Basic Reverse Proxy Setup
### Configuration File
Create or update your Nginx configuration file (`/etc/nginx/sites-available/mcphub`):
```nginx
server {
listen 80;
server_name your-domain.com;
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL Configuration
ssl_certificate /path/to/your/certificate.crt;
ssl_certificate_key /path/to/your/private.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# Security Headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Gzip Compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
# Main application
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 86400;
}
# API endpoints with longer timeout for MCP operations
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300;
proxy_connect_timeout 60;
proxy_send_timeout 60;
}
# Static assets caching
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
proxy_pass http://127.0.0.1:3000;
proxy_cache_valid 200 1d;
proxy_cache_valid 404 1m;
add_header Cache-Control "public, immutable";
expires 1y;
}
}
```
### Enable the Configuration
```bash
# Create symbolic link to enable the site
sudo ln -s /etc/nginx/sites-available/mcphub /etc/nginx/sites-enabled/
# Test configuration
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginx
```
## Load Balancing Configuration
For high-availability setups with multiple MCPHub instances:
```nginx
upstream mcphub_backend {
least_conn;
server 127.0.0.1:3000 weight=1 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3001 weight=1 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3002 weight=1 max_fails=3 fail_timeout=30s;
# Health check (Nginx Plus feature)
# health_check interval=5s fails=3 passes=2;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL and other configurations...
location / {
proxy_pass http://mcphub_backend;
# Other proxy settings...
}
}
```
## Caching Configuration
### Browser Caching
```nginx
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
proxy_pass http://127.0.0.1:3000;
expires 1y;
add_header Cache-Control "public, immutable";
}
# Cache API responses (be careful with dynamic content)
location /api/public/ {
proxy_pass http://127.0.0.1:3000;
proxy_cache mcphub_cache;
proxy_cache_valid 200 5m;
proxy_cache_key "$scheme$request_method$host$request_uri";
add_header X-Cache-Status $upstream_cache_status;
}
```
### Nginx Proxy Cache
Add to the `http` block in `nginx.conf`:
```nginx
http {
# Proxy cache configuration
proxy_cache_path /var/cache/nginx/mcphub
levels=1:2
keys_zone=mcphub_cache:10m
max_size=1g
inactive=60m
use_temp_path=off;
# Other configurations...
}
```
## WebSocket Support
For real-time features and SSE (Server-Sent Events):
```nginx
location /api/stream {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Disable buffering for real-time responses
proxy_buffering off;
proxy_cache off;
# Timeouts for long-lived connections
proxy_read_timeout 24h;
proxy_send_timeout 24h;
}
```
## Security Configuration
### Rate Limiting
```nginx
http {
# Define rate limiting zones
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
server {
# Apply rate limiting to API endpoints
location /api/ {
limit_req zone=api burst=20 nodelay;
# Other configurations...
}
# Strict rate limiting for login endpoints
location /api/auth/login {
limit_req zone=login burst=5;
# Other configurations...
}
}
}
```
### IP Whitelisting
```nginx
# Allow specific IPs for admin endpoints
location /api/admin/ {
allow 192.168.1.0/24;
allow 10.0.0.0/8;
deny all;
proxy_pass http://127.0.0.1:3000;
# Other proxy settings...
}
```
## Monitoring and Logging
### Access Logs
```nginx
http {
# Custom log format
log_format mcphub_format '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time';
server {
# Enable access logging
access_log /var/log/nginx/mcphub_access.log mcphub_format;
error_log /var/log/nginx/mcphub_error.log;
# Other configurations...
}
}
```
### Status Page
```nginx
location /nginx_status {
stub_status;
allow 127.0.0.1;
deny all;
}
```
## Docker Integration
When running MCPHub in Docker, update the proxy configuration:
```nginx
upstream mcphub_docker {
server mcphub:3000; # Docker service name
}
server {
location / {
proxy_pass http://mcphub_docker;
# Other proxy settings...
}
}
```
## Complete Example Configuration
Here's a production-ready example using the provided `nginx.conf.example`:
```bash
# Copy the example configuration
cp nginx.conf.example /etc/nginx/sites-available/mcphub
# Update the configuration with your domain and paths
sudo nano /etc/nginx/sites-available/mcphub
# Enable the site
sudo ln -s /etc/nginx/sites-available/mcphub /etc/nginx/sites-enabled/
# Test and reload
sudo nginx -t && sudo systemctl reload nginx
```
## Troubleshooting
### Common Issues
**502 Bad Gateway**: Check if MCPHub is running and accessible
**504 Gateway Timeout**: Increase `proxy_read_timeout` for long-running operations
**WebSocket connection failures**: Ensure proper `Upgrade` and `Connection` headers
**Cache issues**: Clear proxy cache or disable for development
### Debug Commands
```bash
# Test Nginx configuration
sudo nginx -t
# Check Nginx status
sudo systemctl status nginx
# View error logs
sudo tail -f /var/log/nginx/error.log
# Check if MCPHub is responding
curl -I http://localhost:3000
```
## Performance Optimization
### Worker Processes
```nginx
# In nginx.conf
worker_processes auto;
worker_connections 1024;
```
### Buffer Sizes
```nginx
proxy_buffering on;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
```
### Keep-Alive
```nginx
upstream mcphub_backend {
server 127.0.0.1:3000;
keepalive 32;
}
location / {
proxy_pass http://mcphub_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
```
This configuration provides a solid foundation for running MCPHub behind Nginx with proper security, performance, and reliability features.

View File

@@ -0,0 +1,724 @@
---
title: 'Architecture Overview'
description: "Understand MCPHub's system architecture and design principles"
---
## System Overview
MCPHub is designed as a scalable, modular platform for managing Model Context Protocol (MCP) servers. The architecture follows modern web application patterns with clear separation of concerns, microservices-ready design, and extensibility in mind.
## High-Level Architecture
```mermaid
graph TB
subgraph "Client Layer"
WEB[Web Dashboard]
API[External APIs]
CLI[CLI Tools]
end
subgraph "Application Layer"
LB[Load Balancer/Nginx]
APP[MCPHub Server]
WS[WebSocket Server]
end
subgraph "Service Layer"
MCP[MCP Service]
AUTH[Auth Service]
ROUTE[Smart Routing]
MON[Monitoring Service]
end
subgraph "Data Layer"
PG[(PostgreSQL)]
REDIS[(Redis)]
VECTOR[(Vector Store)]
end
subgraph "MCP Servers"
GITHUB[GitHub MCP]
FS[Filesystem MCP]
DB[Database MCP]
CUSTOM[Custom MCP]
end
WEB --> LB
API --> LB
CLI --> LB
LB --> APP
APP --> WS
APP --> MCP
APP --> AUTH
APP --> ROUTE
APP --> MON
MCP --> GITHUB
MCP --> FS
MCP --> DB
MCP --> CUSTOM
AUTH --> PG
AUTH --> REDIS
ROUTE --> VECTOR
MON --> PG
MON --> REDIS
```
## Core Components
### 1. Application Server
The main Node.js/Express application that handles all HTTP requests and coordinates between services.
```typescript
// src/server.ts - Main application entry point
class MCPHubServer {
private app: Express;
private httpServer: Server;
private wsServer: WebSocketServer;
async start(): Promise<void> {
await this.initializeDatabase();
await this.initializeServices();
await this.setupRoutes();
await this.startServer();
}
}
```
**Key Responsibilities:**
- HTTP request handling
- WebSocket connections for real-time features
- Service coordination
- Middleware chain management
- Error handling and logging
### 2. MCP Service Layer
Manages the lifecycle and communication with MCP servers.
```typescript
// src/services/mcpService.ts
class MCPService {
private servers: Map<string, MCPServerInstance> = new Map();
private processManager: ProcessManager;
async startServer(config: MCPServerConfig): Promise<void> {
const instance = await this.processManager.spawn(config);
this.servers.set(config.name, instance);
await this.waitForHealthy(instance);
}
async executeRequest(serverName: string, request: MCPRequest): Promise<MCPResponse> {
const server = this.servers.get(serverName);
return await server.sendRequest(request);
}
}
```
**Key Features:**
- Process lifecycle management
- Health monitoring
- Request routing
- Error recovery
- Resource management
### 3. Smart Routing Engine
Provides AI-powered tool discovery and routing using vector embeddings.
```typescript
// src/services/smartRouting.ts
class SmartRoutingService {
private vectorStore: VectorStore;
private embeddingService: EmbeddingService;
async findRelevantTools(query: string): Promise<ToolMatch[]> {
const queryEmbedding = await this.embeddingService.embed(query);
const matches = await this.vectorStore.similaritySearch(queryEmbedding);
return this.rankResults(matches, query);
}
async indexTool(tool: ToolDefinition): Promise<void> {
const embedding = await this.embeddingService.embed(tool.description);
await this.vectorStore.upsert(tool.id, embedding, tool);
}
}
```
**Components:**
- Vector embedding generation
- Similarity search
- Result ranking and filtering
- Tool metadata management
### 4. Authentication & Authorization
Handles user authentication, session management, and access control.
```typescript
// src/services/authService.ts
class AuthService {
async authenticate(credentials: Credentials): Promise<User> {
const user = await this.validateCredentials(credentials);
const token = await this.generateJWT(user);
await this.createSession(user, token);
return user;
}
async authorize(user: User, resource: string, action: string): Promise<boolean> {
const permissions = await this.getUserPermissions(user);
return this.checkPermission(permissions, resource, action);
}
}
```
**Features:**
- JWT-based authentication
- Role-based access control (RBAC)
- Session management
- API key authentication
- Group-based permissions
### 5. Monitoring & Logging
Provides comprehensive monitoring, metrics collection, and logging.
```typescript
// src/services/monitoringService.ts
class MonitoringService {
private metricsCollector: MetricsCollector;
private alertManager: AlertManager;
async collectMetrics(): Promise<void> {
const systemMetrics = await this.getSystemMetrics();
const serverMetrics = await this.getMCPServerMetrics();
await this.metricsCollector.record(systemMetrics, serverMetrics);
await this.checkAlerts(systemMetrics, serverMetrics);
}
}
```
**Capabilities:**
- Real-time metrics collection
- Performance monitoring
- Error tracking
- Alert management
- Audit logging
## Data Architecture
### Database Schema
```sql
-- Core entities
CREATE TABLE users (
id UUID PRIMARY KEY,
username VARCHAR UNIQUE NOT NULL,
email VARCHAR UNIQUE NOT NULL,
password_hash VARCHAR NOT NULL,
role VARCHAR NOT NULL DEFAULT 'user',
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE servers (
id UUID PRIMARY KEY,
name VARCHAR UNIQUE NOT NULL,
command VARCHAR NOT NULL,
args JSONB NOT NULL DEFAULT '[]',
env JSONB DEFAULT '{}',
group_name VARCHAR,
status VARCHAR DEFAULT 'stopped',
config JSONB DEFAULT '{}',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE groups (
id UUID PRIMARY KEY,
name VARCHAR UNIQUE NOT NULL,
description TEXT,
config JSONB DEFAULT '{}',
created_at TIMESTAMP DEFAULT NOW()
);
-- Vector search for smart routing
CREATE TABLE tool_embeddings (
id UUID PRIMARY KEY,
server_name VARCHAR NOT NULL,
tool_name VARCHAR NOT NULL,
description TEXT,
embedding vector(1536),
metadata JSONB DEFAULT '{}',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Monitoring and logging
CREATE TABLE request_logs (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
server_name VARCHAR NOT NULL,
tool_name VARCHAR,
request_data JSONB,
response_data JSONB,
status VARCHAR NOT NULL,
duration_ms INTEGER,
created_at TIMESTAMP DEFAULT NOW()
);
```
### Caching Strategy
```typescript
// src/services/cacheService.ts
class CacheService {
// Multi-layer caching strategy
private memoryCache: Map<string, CacheEntry> = new Map();
private redisCache: Redis;
async get<T>(key: string): Promise<T | null> {
// L1: Memory cache
const memoryEntry = this.memoryCache.get(key);
if (memoryEntry && !this.isExpired(memoryEntry)) {
return memoryEntry.value;
}
// L2: Redis cache
const redisValue = await this.redisCache.get(key);
if (redisValue) {
const value = JSON.parse(redisValue);
this.memoryCache.set(key, { value, expiry: Date.now() + 60000 });
return value;
}
return null;
}
}
```
**Cache Layers:**
- **L1 (Memory)**: Fast access for frequently used data
- **L2 (Redis)**: Shared cache across instances
- **L3 (Database)**: Persistent storage with query optimization
## Communication Patterns
### Request Flow
```mermaid
sequenceDiagram
participant Client
participant API
participant Auth
participant Router
participant MCP
participant Server
Client->>API: HTTP Request
API->>Auth: Validate Token
Auth-->>API: User Context
API->>Router: Route Request
Router->>Router: Find Target Server
Router->>MCP: Execute Request
MCP->>Server: MCP Protocol
Server-->>MCP: MCP Response
MCP-->>Router: Formatted Response
Router-->>API: Response Data
API-->>Client: HTTP Response
```
### WebSocket Communication
```typescript
// src/services/websocketService.ts
class WebSocketService {
private connections: Map<string, WebSocket> = new Map();
handleConnection(ws: WebSocket, userId: string): void {
this.connections.set(userId, ws);
ws.on('message', async (data) => {
const message = JSON.parse(data.toString());
await this.handleMessage(userId, message);
});
ws.on('close', () => {
this.connections.delete(userId);
});
}
broadcast(event: string, data: any): void {
this.connections.forEach((ws) => {
ws.send(JSON.stringify({ event, data }));
});
}
}
```
### Event-Driven Architecture
```typescript
// src/events/eventBus.ts
class EventBus {
private listeners: Map<string, EventListener[]> = new Map();
emit(event: string, data: any): void {
const handlers = this.listeners.get(event) || [];
handlers.forEach((handler) => handler(data));
}
on(event: string, handler: EventListener): void {
const handlers = this.listeners.get(event) || [];
handlers.push(handler);
this.listeners.set(event, handlers);
}
}
// Usage
eventBus.on('server.started', (data) => {
logger.info(`Server ${data.name} started`);
monitoringService.updateServerStatus(data.name, 'running');
});
```
## Security Architecture
### Authentication Flow
```mermaid
graph LR
A[Client] --> B[Login Request]
B --> C[Validate Credentials]
C --> D[Generate JWT]
D --> E[Create Session]
E --> F[Return Token]
F --> G[Store in Cookie/Header]
G --> H[Subsequent Requests]
H --> I[Validate Token]
I --> J[Check Permissions]
J --> K[Allow/Deny Access]
```
### Authorization Model
```typescript
// Role-Based Access Control (RBAC)
interface Permission {
resource: string; // e.g., 'servers', 'groups', 'users'
action: string; // e.g., 'create', 'read', 'update', 'delete'
scope?: string; // e.g., 'own', 'group', 'all'
}
interface Role {
name: string;
permissions: Permission[];
}
const roles: Role[] = [
{
name: 'admin',
permissions: [{ resource: '*', action: '*', scope: 'all' }],
},
{
name: 'manager',
permissions: [
{ resource: 'servers', action: '*', scope: 'group' },
{ resource: 'groups', action: 'read', scope: 'all' },
],
},
{
name: 'user',
permissions: [
{ resource: 'servers', action: 'read', scope: 'group' },
{ resource: 'tools', action: 'execute', scope: 'group' },
],
},
];
```
## Scalability Considerations
### Horizontal Scaling
```yaml
# Kubernetes deployment for scaling
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcphub
spec:
replicas: 3
selector:
matchLabels:
app: mcphub
template:
spec:
containers:
- name: mcphub
image: mcphub:latest
resources:
requests:
memory: '256Mi'
cpu: '200m'
limits:
memory: '512Mi'
cpu: '500m'
```
### Load Balancing Strategy
```typescript
// src/services/loadBalancer.ts
class LoadBalancer {
private servers: ServerInstance[] = [];
private algorithm: 'round-robin' | 'least-connections' | 'weighted';
selectServer(): ServerInstance {
switch (this.algorithm) {
case 'round-robin':
return this.roundRobin();
case 'least-connections':
return this.leastConnections();
case 'weighted':
return this.weighted();
}
}
}
```
### Database Scaling
```typescript
// Database connection management
class DatabaseManager {
private readPool: Pool; // Read replicas
private writePool: Pool; // Primary database
async query(sql: string, params: any[]): Promise<any> {
if (this.isReadOperation(sql)) {
return this.readPool.query(sql, params);
} else {
return this.writePool.query(sql, params);
}
}
}
```
## Performance Optimization
### Query Optimization
```sql
-- Optimized queries with proper indexing
CREATE INDEX CONCURRENTLY idx_servers_status_group
ON servers(status, group_name)
WHERE status IN ('running', 'starting');
CREATE INDEX CONCURRENTLY idx_tool_embeddings_similarity
ON tool_embeddings USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
CREATE INDEX CONCURRENTLY idx_request_logs_performance
ON request_logs(created_at, status, duration_ms)
WHERE created_at > NOW() - INTERVAL '30 days';
```
### Caching Strategy
```typescript
// Multi-level caching
class CacheManager {
// Cache server configurations
@Cache({ ttl: 300, key: 'server-config' })
async getServerConfig(name: string): Promise<ServerConfig> {
return this.database.getServerConfig(name);
}
// Cache tool metadata for smart routing
@Cache({ ttl: 3600, key: 'tool-metadata' })
async getToolMetadata(): Promise<ToolMetadata[]> {
return this.database.getToolMetadata();
}
// Cache user permissions
@Cache({ ttl: 600, key: 'user-permissions' })
async getUserPermissions(userId: string): Promise<Permission[]> {
return this.authService.getUserPermissions(userId);
}
}
```
## Monitoring & Observability
### Metrics Collection
```typescript
// src/services/metricsService.ts
class MetricsService {
private prometheus: PrometheusRegistry;
constructor() {
this.initializeMetrics();
}
private initializeMetrics(): void {
// Request metrics
this.requestCount = new Counter({
name: 'mcphub_requests_total',
help: 'Total number of requests',
labelNames: ['method', 'route', 'status'],
});
// Server metrics
this.serverStatus = new Gauge({
name: 'mcphub_server_status',
help: 'Status of MCP servers',
labelNames: ['server_name', 'status'],
});
// Performance metrics
this.responseTime = new Histogram({
name: 'mcphub_response_time_seconds',
help: 'Response time in seconds',
labelNames: ['route'],
});
}
}
```
### Distributed Tracing
```typescript
// OpenTelemetry integration
import { trace } from '@opentelemetry/api';
class MCPService {
async executeRequest(serverName: string, request: MCPRequest): Promise<MCPResponse> {
const span = trace.getActiveSpan();
span?.setAttributes({
'mcp.server': serverName,
'mcp.tool': request.tool,
'mcp.request_id': request.id,
});
try {
const response = await this.sendRequest(serverName, request);
span?.setStatus({ code: SpanStatusCode.OK });
return response;
} catch (error) {
span?.setStatus({
code: SpanStatusCode.ERROR,
message: error.message,
});
throw error;
}
}
}
```
## Extension Points
### Plugin Architecture
```typescript
// Plugin interface
interface MCPHubPlugin {
name: string;
version: string;
init(context: PluginContext): Promise<void>;
destroy(): Promise<void>;
}
// Plugin manager
class PluginManager {
private plugins: Map<string, MCPHubPlugin> = new Map();
async loadPlugin(plugin: MCPHubPlugin): Promise<void> {
await plugin.init(this.createContext());
this.plugins.set(plugin.name, plugin);
}
private createContext(): PluginContext {
return {
eventBus: this.eventBus,
logger: this.logger,
database: this.database,
// ... other services
};
}
}
```
### Custom Middleware
```typescript
// Custom middleware registration
class MiddlewareManager {
register(middleware: Middleware): void {
this.app.use(middleware);
}
registerRoute(path: string, middleware: Middleware): void {
this.app.use(path, middleware);
}
}
// Example custom middleware
const customAuthMiddleware: Middleware = (req, res, next) => {
// Custom authentication logic
next();
};
```
## Deployment Architecture
### Container Strategy
```dockerfile
# Multi-stage build for optimized images
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:18-alpine AS runtime
RUN addgroup -g 1001 -S nodejs
RUN adduser -S mcphub -u 1001
WORKDIR /app
COPY --from=builder --chown=mcphub:nodejs /app .
USER mcphub
EXPOSE 3000
CMD ["node", "dist/server.js"]
```
### Infrastructure as Code
```terraform
# Terraform configuration for AWS deployment
resource "aws_ecs_cluster" "mcphub" {
name = "mcphub-cluster"
}
resource "aws_ecs_service" "mcphub" {
name = "mcphub"
cluster = aws_ecs_cluster.mcphub.id
task_definition = aws_ecs_task_definition.mcphub.arn
desired_count = 3
load_balancer {
target_group_arn = aws_lb_target_group.mcphub.arn
container_name = "mcphub"
container_port = 3000
}
}
```
This architecture provides a solid foundation for building a scalable, maintainable, and extensible MCP server management platform while following modern software development best practices.

View File

@@ -0,0 +1,597 @@
---
title: 'Contributing to MCPHub'
description: 'Learn how to contribute to the MCPHub project'
---
## Welcome Contributors! 🎉
Thank you for your interest in contributing to MCPHub! This guide will help you get started with contributing to the project, whether you're fixing bugs, adding features, improving documentation, or helping with testing.
## Quick Start
1. **Fork the repository** on GitHub
2. **Clone your fork** locally
3. **Create a branch** for your changes
4. **Make your changes** following our guidelines
5. **Test your changes** thoroughly
6. **Submit a pull request**
## Ways to Contribute
### 🐛 Bug Reports
Help us improve MCPHub by reporting bugs:
- **Search existing issues** first to avoid duplicates
- **Use the bug report template** when creating issues
- **Provide detailed information** including steps to reproduce
- **Include system information** (OS, Node.js version, etc.)
```markdown
## Bug Report Template
**Description**
A clear description of what the bug is.
**Steps to Reproduce**
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected Behavior**
What you expected to happen.
**Actual Behavior**
What actually happened.
**Environment**
- OS: [e.g. macOS 12.0]
- Node.js: [e.g. 18.17.0]
- MCPHub Version: [e.g. 1.2.3]
- Browser: [e.g. Chrome 91.0]
**Additional Context**
Any other context about the problem.
```
### ✨ Feature Requests
We welcome feature suggestions:
- **Check existing feature requests** to avoid duplicates
- **Use the feature request template**
- **Explain the use case** and why it would be valuable
- **Consider implementation complexity**
### 🔧 Code Contributions
Ready to write some code? Here's how:
#### Setting Up Development Environment
```bash
# 1. Fork and clone the repository
git clone https://github.com/YOUR_USERNAME/mcphub.git
cd mcphub
# 2. Add upstream remote
git remote add upstream https://github.com/mcphub/mcphub.git
# 3. Install dependencies
pnpm install
# 4. Set up environment
cp .env.example .env.development
# 5. Start development environment
docker-compose -f docker-compose.dev.yml up -d
pnpm run migrate
pnpm run seed
# 6. Start development server
pnpm run dev
```
#### Branch Naming Convention
Use descriptive branch names with prefixes:
```bash
# Features
git checkout -b feature/smart-routing-improvements
git checkout -b feature/user-authentication
# Bug fixes
git checkout -b fix/server-startup-error
git checkout -b fix/memory-leak-in-cache
# Documentation
git checkout -b docs/api-reference-update
git checkout -b docs/deployment-guide
# Refactoring
git checkout -b refactor/auth-service-cleanup
git checkout -b refactor/database-queries
```
### 📚 Documentation
Help improve our documentation:
- **Fix typos and grammar**
- **Improve existing guides**
- **Add missing documentation**
- **Create tutorials and examples**
- **Translate documentation**
## Development Guidelines
### Code Style
We use ESLint and Prettier to maintain code quality:
```bash
# Check code style
pnpm run lint
# Fix automatically fixable issues
pnpm run lint:fix
# Format code
pnpm run format
# Type check
pnpm run type-check
```
#### TypeScript Best Practices
```typescript
// ✅ Good: Use proper types
interface MCPServerConfig {
name: string;
command: string;
args: string[];
env?: Record<string, string>;
}
// ✅ Good: Use async/await
async function startServer(config: MCPServerConfig): Promise<void> {
try {
await mcpService.start(config);
} catch (error) {
logger.error('Failed to start server', { error, config });
throw error;
}
}
// ❌ Bad: Using any type
function processData(data: any): any {
return data.something();
}
// ❌ Bad: Not handling errors
async function riskyOperation(): Promise<void> {
await dangerousFunction(); // Could throw
}
```
#### React/Frontend Guidelines
```tsx
// ✅ Good: Functional components with proper typing
interface ServerCardProps {
server: MCPServer;
onStart: (serverId: string) => void;
onStop: (serverId: string) => void;
}
const ServerCard: React.FC<ServerCardProps> = ({ server, onStart, onStop }) => {
const handleStart = useCallback(() => {
onStart(server.id);
}, [server.id, onStart]);
return (
<Card data-testid={`server-card-${server.id}`}>
<CardHeader>
<CardTitle>{server.name}</CardTitle>
<Badge variant={server.status === 'running' ? 'success' : 'secondary'}>
{server.status}
</Badge>
</CardHeader>
<CardContent>
<p>{server.description}</p>
</CardContent>
<CardActions>
{server.status === 'stopped' ? (
<Button onClick={handleStart}>Start</Button>
) : (
<Button onClick={() => onStop(server.id)}>Stop</Button>
)}
</CardActions>
</Card>
);
};
```
### Testing Requirements
All contributions must include appropriate tests:
#### Unit Tests
```typescript
// src/services/__tests__/mcpService.test.ts
import { MCPService } from '../mcpService';
import { mockLogger, mockDatabase } from '../../__mocks__';
describe('MCPService', () => {
let service: MCPService;
beforeEach(() => {
service = new MCPService(mockLogger, mockDatabase);
});
describe('startServer', () => {
it('should start a server successfully', async () => {
const config = {
name: 'test-server',
command: 'node',
args: ['server.js'],
};
await service.startServer(config);
expect(service.getServerStatus('test-server')).toBe('running');
});
it('should handle server startup failures', async () => {
const invalidConfig = {
name: 'invalid-server',
command: 'invalid-command',
args: [],
};
await expect(service.startServer(invalidConfig)).rejects.toThrow(
'Failed to start server: Command not found',
);
});
});
});
```
#### Integration Tests
```typescript
// src/__tests__/integration/server-api.test.ts
import request from 'supertest';
import { app } from '../../app';
import { setupTestDatabase, teardownTestDatabase } from '../helpers/database';
describe('Server API Integration', () => {
beforeAll(async () => {
await setupTestDatabase();
});
afterAll(async () => {
await teardownTestDatabase();
});
describe('POST /api/servers', () => {
it('should create a new server', async () => {
const serverData = {
name: 'test-server',
command: 'node',
args: ['server.js'],
group: 'development',
};
const response = await request(app).post('/api/servers').send(serverData).expect(201);
expect(response.body).toMatchObject({
name: 'test-server',
status: 'stopped',
group: 'development',
});
});
});
});
```
#### End-to-End Tests
```typescript
// tests/e2e/server-management.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Server Management', () => {
test('should create and manage MCP servers', async ({ page }) => {
await page.goto('/dashboard');
// Create new server
await page.click('[data-testid="add-server-button"]');
await page.fill('[data-testid="server-name-input"]', 'test-server');
await page.fill('[data-testid="server-command-input"]', 'node server.js');
await page.click('[data-testid="save-server-button"]');
// Verify server appears in list
await expect(page.locator('[data-testid="server-list"]')).toContainText('test-server');
// Start the server
await page.click('[data-testid="start-server-test-server"]');
// Verify server is running
await expect(page.locator('[data-testid="server-status-test-server"]')).toContainText(
'running',
);
});
});
```
### Commit Guidelines
We follow [Conventional Commits](https://www.conventionalcommits.org/):
```bash
# Format: <type>[optional scope]: <description>
# Features
git commit -m "feat(auth): add JWT token refresh functionality"
git commit -m "feat(ui): implement server status dashboard"
# Bug fixes
git commit -m "fix(api): resolve memory leak in server manager"
git commit -m "fix(db): handle connection timeout gracefully"
# Documentation
git commit -m "docs(api): add examples for server endpoints"
git commit -m "docs(readme): update installation instructions"
# Refactoring
git commit -m "refactor(services): extract auth logic into separate module"
# Tests
git commit -m "test(api): add integration tests for server management"
# Chores
git commit -m "chore(deps): update dependencies to latest versions"
```
#### Commit Types
- **feat**: New feature
- **fix**: Bug fix
- **docs**: Documentation changes
- **style**: Code style changes (formatting, etc.)
- **refactor**: Code refactoring
- **test**: Adding or updating tests
- **chore**: Maintenance tasks
- **perf**: Performance improvements
- **ci**: CI/CD changes
### Pull Request Process
#### Before Submitting
```bash
# 1. Sync with upstream
git fetch upstream
git checkout main
git merge upstream/main
# 2. Rebase your feature branch
git checkout feature/your-feature
git rebase main
# 3. Run all checks
pnpm run lint
pnpm run type-check
pnpm run test
pnpm run build
# 4. Update documentation if needed
# 5. Add/update tests for your changes
```
#### Pull Request Template
```markdown
## Description
Brief description of the changes and motivation.
## Type of Change
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update
## Testing
- [ ] Unit tests pass
- [ ] Integration tests pass
- [ ] E2E tests pass (if applicable)
- [ ] Manual testing completed
## Documentation
- [ ] Code is self-documenting
- [ ] API documentation updated
- [ ] User documentation updated
- [ ] README updated (if needed)
## Checklist
- [ ] My code follows the project's style guidelines
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
## Screenshots (if applicable)
Add screenshots to help explain your changes.
## Additional Notes
Any additional information that reviewers should know.
```
### Code Review Process
#### For Contributors
- **Be patient**: Reviews take time, and reviewers may have questions
- **Be responsive**: Address feedback promptly and clearly
- **Be open**: Accept constructive criticism and suggestions
- **Ask questions**: If feedback is unclear, ask for clarification
#### For Reviewers
- **Be constructive**: Provide helpful suggestions, not just criticism
- **Be specific**: Point out exact issues and suggest solutions
- **Be timely**: Review PRs within a reasonable timeframe
- **Be encouraging**: Recognize good work and improvements
## Community Guidelines
### Code of Conduct
We are committed to providing a welcoming and inspiring community for all:
- **Be respectful**: Treat everyone with respect and kindness
- **Be inclusive**: Welcome people of all backgrounds and skill levels
- **Be collaborative**: Work together towards common goals
- **Be patient**: Help others learn and grow
- **Be professional**: Maintain professional communication
### Communication Channels
- **GitHub Issues**: Bug reports and feature requests
- **GitHub Discussions**: General questions and community chat
- **Discord**: Real-time community chat (link in README)
- **Email**: Security issues and private matters
## Getting Help
### Documentation
- [Getting Started Guide](./getting-started.mdx)
- [Architecture Overview](./architecture.mdx)
- [API Reference](../api-reference/introduction.mdx)
- [Configuration Guide](../configuration/mcp-settings.mdx)
### Common Issues
**Build Failures**
```bash
# Clear and reinstall dependencies
rm -rf node_modules pnpm-lock.yaml
pnpm install
# Clear build cache
rm -rf dist/
pnpm run build
```
**Test Failures**
```bash
# Run tests with verbose output
pnpm run test -- --verbose
# Run specific test file
pnpm test src/services/mcpService.test.ts
# Debug tests
pnpm run test:debug
```
**Database Issues**
```bash
# Reset database
pnpm run db:reset
# Run migrations
pnpm run migrate
# Seed development data
pnpm run seed
```
### Getting Support
If you need help:
1. **Check the documentation** first
2. **Search existing issues** on GitHub
3. **Ask in GitHub Discussions** for general questions
4. **Create an issue** if you found a bug
5. **Join our Discord** for real-time help
## Recognition
Contributors will be recognized in several ways:
- **Contributors file**: All contributors are listed in CONTRIBUTORS.md
- **Release notes**: Significant contributions are mentioned in release notes
- **GitHub badges**: Active contributors receive special recognition
- **Community showcase**: Outstanding contributions are featured in our blog
## Advanced Topics
### Maintainer Guidelines
For project maintainers:
#### Release Process
```bash
# 1. Create release branch
git checkout -b release/v1.2.0
# 2. Update version
npm version 1.2.0 --no-git-tag-version
# 3. Update changelog
# Edit CHANGELOG.md
# 4. Commit changes
git add .
git commit -m "chore(release): prepare v1.2.0"
# 5. Create PR for review
# 6. After merge, tag release
git tag v1.2.0
git push origin v1.2.0
```
#### Security Handling
For security issues:
1. **Do not** create public issues
2. **Email** security@mcphub.dev
3. **Wait** for response before disclosure
4. **Coordinate** with maintainers on fixes
### Architectural Decisions
When making significant changes:
1. **Create an RFC** (Request for Comments) issue
2. **Discuss** with the community
3. **Get approval** from maintainers
4. **Document** decisions in ADR (Architecture Decision Records)
## Thank You! 🙏
Thank you for taking the time to contribute to MCPHub! Every contribution, no matter how small, helps make the project better for everyone. We look forward to collaborating with you!

View File

@@ -0,0 +1,244 @@
---
title: 'Getting Started with Development'
description: 'Learn how to set up your development environment for MCPHub'
---
# Getting Started with Development
This guide will help you set up your development environment for contributing to MCPHub.
## Prerequisites
Before you begin, ensure you have the following installed:
- **Node.js** (version 18 or higher)
- **pnpm** (recommended package manager)
- **Git**
- **Docker** (optional, for containerized development)
## Setting Up the Development Environment
### 1. Clone the Repository
```bash
git clone https://github.com/your-username/mcphub.git
cd mcphub
```
### 2. Install Dependencies
```bash
pnpm install
```
### 3. Environment Configuration
Create a `.env` file in the root directory:
```bash
cp .env.example .env
```
Configure the following environment variables:
```env
# Server Configuration
PORT=3000
NODE_ENV=development
# Database Configuration
DATABASE_URL=postgresql://username:password@localhost:5432/mcphub
# JWT Configuration
JWT_SECRET=your-secret-key
JWT_EXPIRES_IN=24h
# OpenAI Configuration (for smart routing)
OPENAI_API_KEY=your-openai-api-key
```
### 4. Database Setup
If using PostgreSQL, create a database:
```bash
createdb mcphub
```
### 5. MCP Server Configuration
Create or modify `mcp_settings.json`:
```json
{
"mcpServers": {
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"]
},
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest", "--headless"]
}
}
}
```
## Development Workflow
### Running the Development Server
Start both backend and frontend in development mode:
```bash
pnpm dev
```
This will start:
- Backend server on `http://localhost:3000`
- Frontend development server on `http://localhost:5173`
### Running Backend Only
```bash
pnpm backend:dev
```
### Running Frontend Only
```bash
pnpm frontend:dev
```
### Building the Project
Build both backend and frontend:
```bash
pnpm build
```
## Project Structure
```
mcphub/
├── src/ # Backend source code
│ ├── controllers/ # Express controllers
│ ├── routes/ # API routes
│ ├── services/ # Business logic
│ ├── models/ # Database models
│ └── utils/ # Utility functions
├── frontend/ # Frontend React application
│ ├── src/
│ │ ├── components/ # React components
│ │ ├── pages/ # Page components
│ │ ├── services/ # API services
│ │ └── utils/ # Frontend utilities
├── docs/ # Documentation
├── bin/ # CLI scripts
└── scripts/ # Build and utility scripts
```
## Development Tools
### Linting and Formatting
```bash
# Run ESLint
pnpm lint
# Format code with Prettier
pnpm format
```
### Testing
```bash
# Run tests
pnpm test
# Run tests in watch mode
pnpm test --watch
```
### Debugging
To debug the backend with Node.js inspector:
```bash
pnpm backend:debug
```
Then attach your debugger to `http://localhost:9229`.
## Making Changes
### Backend Development
1. **Controllers**: Handle HTTP requests and responses
2. **Services**: Implement business logic
3. **Models**: Define database schemas
4. **Routes**: Define API endpoints
### Frontend Development
1. **Components**: Reusable React components
2. **Pages**: Route-specific components
3. **Services**: API communication
4. **Hooks**: Custom React hooks
### Adding New MCP Servers
1. Update `mcp_settings.json` with the new server configuration
2. Test the server integration
3. Update documentation if needed
## Common Development Tasks
### Adding a New API Endpoint
1. Create a controller in `src/controllers/`
2. Define the route in `src/routes/`
3. Add any necessary middleware
4. Write tests for the new endpoint
### Adding a New Frontend Feature
1. Create components in `frontend/src/components/`
2. Add routes if needed
3. Implement API integration
4. Style with Tailwind CSS
### Database Migrations
When modifying database schemas:
1. Update models in `src/models/`
2. Create migration scripts if using TypeORM
3. Test migrations locally
## Troubleshooting
### Common Issues
**Port conflicts**: Ensure ports 3000 and 5173 are available
**Database connection**: Verify PostgreSQL is running and credentials are correct
**MCP server startup**: Check server configurations in `mcp_settings.json`
**Permission issues**: Ensure MCP servers have necessary permissions
### Getting Help
- Check the [Contributing Guide](/development/contributing)
- Review [Architecture Documentation](/development/architecture)
- Open an issue on GitHub for bugs
- Join our community discussions
## Next Steps
- Read the [Architecture Overview](/development/architecture)
- Learn about [Contributing Guidelines](/development/contributing)
- Explore [Configuration Options](/configuration/environment-variables)

View File

@@ -1,35 +1,92 @@
{
"$schema": "https://mintlify.com/docs.json",
"theme": "mint",
"name": "Mint Starter Kit",
"name": "MCPHub Documentation",
"description": "The Unified Hub for Model Context Protocol (MCP) Servers",
"colors": {
"primary": "#16A34A",
"light": "#07C983",
"dark": "#15803D"
},
"favicon": "/favicon.svg",
"favicon": "/favicon.ico",
"navigation": {
"tabs": [
{
"tab": "Guides",
"tab": "English",
"groups": [
{
"group": "Get Started",
"pages": [
"index",
"quickstart",
"development"
"installation"
]
},
{
"group": "Essentials",
"group": "Core Features",
"pages": [
"essentials/markdown",
"essentials/code",
"essentials/images",
"essentials/settings",
"essentials/navigation",
"essentials/reusable-snippets"
"features/server-management",
"features/group-management",
"features/smart-routing",
"features/authentication",
"features/monitoring"
]
},
{
"group": "Configuration",
"pages": [
"configuration/mcp-settings",
"configuration/environment-variables",
"configuration/docker-setup",
"configuration/nginx"
]
},
{
"group": "Development",
"pages": [
"development/getting-started",
"development/architecture",
"development/contributing"
]
}
]
},
{
"tab": "中文",
"groups": [
{
"group": "开始使用",
"pages": [
"zh/index",
"zh/quickstart",
"zh/installation"
]
},
{
"group": "核心功能",
"pages": [
"zh/features/server-management",
"zh/features/group-management",
"zh/features/smart-routing",
"zh/features/authentication",
"zh/features/monitoring"
]
},
{
"group": "配置指南",
"pages": [
"zh/configuration/mcp-settings",
"zh/configuration/environment-variables",
"zh/configuration/docker-setup",
"zh/configuration/nginx"
]
},
{
"group": "开发指南",
"pages": [
"zh/development/getting-started",
"zh/development/architecture",
"zh/development/contributing"
]
}
]
@@ -38,18 +95,22 @@
"tab": "API Reference",
"groups": [
{
"group": "API Documentation",
"group": "MCP Endpoints",
"pages": [
"api-reference/introduction"
"api-reference/introduction",
"api-reference/mcp-http",
"api-reference/mcp-sse",
"api-reference/smart-routing"
]
},
{
"group": "Endpoint Examples",
"group": "Management API",
"pages": [
"api-reference/endpoint/get",
"api-reference/endpoint/create",
"api-reference/endpoint/delete",
"api-reference/endpoint/webhook"
"api-reference/servers",
"api-reference/groups",
"api-reference/auth",
"api-reference/logs",
"api-reference/config"
]
}
]
@@ -58,19 +119,19 @@
"global": {
"anchors": [
{
"anchor": "Documentation",
"href": "https://mintlify.com/docs",
"icon": "book-open-cover"
"anchor": "GitHub",
"href": "https://github.com/samanhappy/mcphub",
"icon": "github"
},
{
"anchor": "Community",
"href": "https://mintlify.com/community",
"icon": "slack"
"anchor": "Discord",
"href": "https://discord.gg/qMKNsn5Q",
"icon": "discord"
},
{
"anchor": "Blog",
"href": "https://mintlify.com/blog",
"icon": "newspaper"
"anchor": "Sponsor",
"href": "https://ko-fi.com/samanhappy",
"icon": "heart"
}
]
}
@@ -82,21 +143,20 @@
"navbar": {
"links": [
{
"label": "Support",
"href": "mailto:hi@mintlify.com"
"label": "Demo",
"href": "http://localhost:3000"
}
],
"primary": {
"type": "button",
"label": "Dashboard",
"href": "https://dashboard.mintlify.com"
"label": "Get Started",
"href": "https://docs.hubmcp.dev/quickstart"
}
},
"footer": {
"socials": {
"x": "https://x.com/mintlify",
"github": "https://github.com/mintlify",
"linkedin": "https://linkedin.com/company/mintlify"
"github": "https://github.com/samanhappy/mcphub",
"discord": "https://discord.gg/qMKNsn5Q"
}
}
}

BIN
docs/favicon.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -0,0 +1,338 @@
---
title: 'Authentication & Security'
description: 'Configure authentication and security settings for MCPHub'
---
## Overview
MCPHub provides flexible authentication mechanisms to secure your MCP server management platform. The system supports multiple authentication methods and role-based access control.
## Authentication Methods
### Environment-based Authentication
Configure basic authentication using environment variables:
```bash
# Basic auth credentials
AUTH_USERNAME=admin
AUTH_PASSWORD=your-secure-password
# JWT settings
JWT_SECRET=your-jwt-secret-key
JWT_EXPIRES_IN=24h
```
### Database Authentication
For production deployments, enable database-backed user management:
```json
{
"auth": {
"provider": "database",
"database": {
"url": "postgresql://user:pass@localhost:5432/mcphub",
"userTable": "users"
}
}
}
```
## User Management
### Creating Users
Create users via the admin interface or API:
```bash
# Via API
curl -X POST http://localhost:3000/api/auth/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{
"username": "newuser",
"email": "user@example.com",
"password": "securepassword",
"role": "user"
}'
```
### User Roles
MCPHub supports role-based access control:
- **Admin**: Full system access, user management, server configuration
- **Manager**: Server management, group management, monitoring
- **User**: Basic server access within assigned groups
- **Viewer**: Read-only access to assigned resources
## Group-based Access Control
### Assigning Users to Groups
```bash
# Add user to group
curl -X POST http://localhost:3000/api/groups/{groupId}/users \
-H "Authorization: Bearer $TOKEN" \
-d '{"userId": "user123"}'
```
### Group Permissions
Configure group-level permissions:
```json
{
"groupId": "dev-team",
"permissions": {
"servers": ["read", "write", "execute"],
"tools": ["read", "execute"],
"logs": ["read"],
"config": ["read"]
}
}
```
## API Authentication
### JWT Token Authentication
```javascript
// Login to get token
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: 'your-username',
password: 'your-password',
}),
});
const { token } = await response.json();
// Use token for authenticated requests
const serversResponse = await fetch('/api/servers', {
headers: { Authorization: `Bearer ${token}` },
});
```
### API Key Authentication
For service-to-service communication:
```bash
# Generate API key
curl -X POST http://localhost:3000/api/auth/api-keys \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{
"name": "my-service",
"permissions": ["servers:read", "tools:execute"]
}'
# Use API key
curl -H "X-API-Key: your-api-key" \
http://localhost:3000/api/servers
```
## Security Configuration
### HTTPS Setup
Configure HTTPS for production:
```yaml
# docker-compose.yml
services:
mcphub:
environment:
- HTTPS_ENABLED=true
- SSL_CERT_PATH=/certs/cert.pem
- SSL_KEY_PATH=/certs/key.pem
volumes:
- ./certs:/certs:ro
```
### CORS Configuration
Configure CORS for web applications:
```json
{
"cors": {
"origin": ["https://your-frontend.com"],
"credentials": true,
"methods": ["GET", "POST", "PUT", "DELETE"]
}
}
```
### Rate Limiting
Protect against abuse with rate limiting:
```json
{
"rateLimit": {
"windowMs": 900000,
"max": 100,
"message": "Too many requests from this IP"
}
}
```
## Session Management
### Session Configuration
```json
{
"session": {
"secret": "your-session-secret",
"cookie": {
"secure": true,
"httpOnly": true,
"maxAge": 86400000
},
"store": "redis",
"redis": {
"host": "localhost",
"port": 6379
}
}
}
```
### Logout and Session Cleanup
```javascript
// Logout endpoint
app.post('/api/auth/logout', (req, res) => {
req.session.destroy();
res.json({ message: 'Logged out successfully' });
});
```
## Security Best Practices
### Password Security
- Use strong password requirements
- Implement password hashing with bcrypt
- Support password reset functionality
- Enable two-factor authentication (2FA)
### Token Security
- Use secure JWT secrets
- Implement token rotation
- Set appropriate expiration times
- Store tokens securely in httpOnly cookies
### Network Security
- Use HTTPS in production
- Implement proper CORS policies
- Enable request validation
- Use security headers (helmet.js)
### Monitoring Security Events
```javascript
// Log security events
const auditLog = {
event: 'login_attempt',
user: username,
ip: req.ip,
userAgent: req.headers['user-agent'],
success: true,
timestamp: new Date(),
};
```
## Troubleshooting
### Common Authentication Issues
**Invalid Credentials**
```bash
# Check user exists and password is correct
curl -X POST http://localhost:3000/api/auth/verify \
-d '{"username": "user", "password": "pass"}'
```
**Token Expiration**
```javascript
// Handle token refresh
if (response.status === 401) {
const newToken = await refreshToken();
// Retry request with new token
}
```
**Permission Denied**
```bash
# Check user permissions
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:3000/api/auth/permissions
```
### Debug Authentication
Enable authentication debugging:
```bash
DEBUG=mcphub:auth npm start
```
## Integration Examples
### Frontend Integration
```javascript
// React authentication hook
const useAuth = () => {
const [user, setUser] = useState(null);
const login = async (credentials) => {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials),
});
if (response.ok) {
const userData = await response.json();
setUser(userData.user);
return true;
}
return false;
};
return { user, login };
};
```
### Middleware Integration
```javascript
// Express middleware
const authMiddleware = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
};
```

View File

@@ -0,0 +1,588 @@
---
title: 'Group Management'
description: 'Organize servers into logical groups for streamlined access control'
---
## Overview
Group Management in MCPHub allows you to organize your MCP servers into logical collections based on functionality, use case, or access requirements. This enables fine-grained control over which tools are available to different AI clients and users.
## Core Concepts
### What are Groups?
Groups are named collections of MCP servers that can be accessed through dedicated endpoints. Instead of connecting to all servers at once, AI clients can connect to specific groups to access only relevant tools.
### Benefits of Groups
- **Focused Tool Access**: AI clients see only relevant tools for their use case
- **Better Performance**: Reduced tool discovery overhead
- **Enhanced Security**: Limit access to sensitive tools
- **Improved Organization**: Logical separation of functionality
- **Simplified Management**: Easier to manage related servers together
## Creating Groups
### Via Dashboard
1. **Navigate to Groups Section**: Click "Groups" in the main navigation
2. **Click "Create Group"**: Start the group creation process
3. **Fill Group Details**:
- **Name**: Unique identifier for the group
- **Display Name**: Human-readable name
- **Description**: Purpose and contents of the group
- **Access Level**: Public, Private, or Restricted
4. **Add Servers**: Select servers to include in the group
### Via API
Create groups programmatically:
```bash
curl -X POST http://localhost:3000/api/groups \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{
"name": "web-automation",
"displayName": "Web Automation Tools",
"description": "Browser automation and web scraping tools",
"servers": ["playwright", "fetch"],
"accessLevel": "public"
}'
```
### Via Configuration File
Define groups in your `mcp_settings.json`:
```json
{
"mcpServers": {
"fetch": { "command": "uvx", "args": ["mcp-server-fetch"] },
"playwright": { "command": "npx", "args": ["@playwright/mcp@latest"] },
"slack": { "command": "npx", "args": ["@modelcontextprotocol/server-slack"] }
},
"groups": {
"web-tools": {
"displayName": "Web Tools",
"description": "Web scraping and browser automation",
"servers": ["fetch", "playwright"],
"accessLevel": "public"
},
"communication": {
"displayName": "Communication Tools",
"description": "Messaging and collaboration tools",
"servers": ["slack"],
"accessLevel": "private"
}
}
}
```
## Group Types and Use Cases
<AccordionGroup>
<Accordion title="Web Automation Group">
**Purpose**: Browser automation and web scraping
**Servers**:
- `playwright`: Browser automation
- `fetch`: HTTP requests and web scraping
- `selenium`: Alternative browser automation
**Use Cases**:
- Automated testing
- Data collection
- Web monitoring
- Content analysis
**Endpoint**: `http://localhost:3000/mcp/web-automation`
</Accordion>
<Accordion title="Data Processing Group">
**Purpose**: Data manipulation and analysis
**Servers**:
- `sqlite`: Database operations
- `filesystem`: File operations
- `spreadsheet`: Excel/CSV processing
**Use Cases**:
- Data analysis
- Report generation
- File processing
- Database queries
**Endpoint**: `http://localhost:3000/mcp/data-processing`
</Accordion>
<Accordion title="Communication Group">
**Purpose**: Messaging and collaboration
**Servers**:
- `slack`: Slack integration
- `discord`: Discord bot
- `email`: Email sending
- `sms`: SMS notifications
**Use Cases**:
- Team notifications
- Customer communication
- Alert systems
- Social media management
**Endpoint**: `http://localhost:3000/mcp/communication`
</Accordion>
<Accordion title="Development Group">
**Purpose**: Software development tools
**Servers**:
- `github`: GitHub operations
- `gitlab`: GitLab integration
- `docker`: Container management
- `kubernetes`: K8s operations
**Use Cases**:
- Code deployment
- Repository management
- CI/CD operations
- Infrastructure management
**Endpoint**: `http://localhost:3000/mcp/development`
</Accordion>
<Accordion title="AI/ML Group">
**Purpose**: Machine learning and AI tools
**Servers**:
- `openai`: OpenAI API integration
- `huggingface`: Hugging Face models
- `vector-db`: Vector database operations
**Use Cases**:
- Model inference
- Data embeddings
- Natural language processing
- Computer vision
**Endpoint**: `http://localhost:3000/mcp/ai-ml`
</Accordion>
</AccordionGroup>
## Group Access Control
### Access Levels
<Tabs>
<Tab title="Public">
**Public Groups**:
- Accessible to all authenticated users
- No additional permissions required
- Visible in group listings
- Default access level
```json
{
"name": "public-tools",
"accessLevel": "public",
"servers": ["fetch", "calculator"]
}
```
</Tab>
<Tab title="Private">
**Private Groups**:
- Only visible to group members
- Requires explicit user assignment
- Hidden from public listings
- Admin-controlled membership
```json
{
"name": "internal-tools",
"accessLevel": "private",
"members": ["user1", "user2"],
"servers": ["internal-api", "database"]
}
```
</Tab>
<Tab title="Restricted">
**Restricted Groups**:
- Role-based access control
- Requires specific permissions
- Audit logging enabled
- Time-limited access
```json
{
"name": "admin-tools",
"accessLevel": "restricted",
"requiredRoles": ["admin", "operator"],
"servers": ["system-control", "user-management"]
}
```
</Tab>
</Tabs>
### User Management
Assign users to groups:
```bash
# Add user to group
curl -X POST http://localhost:3000/api/groups/web-tools/members \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{"userId": "user123"}'
# Remove user from group
curl -X DELETE http://localhost:3000/api/groups/web-tools/members/user123 \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
# List group members
curl http://localhost:3000/api/groups/web-tools/members \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
## Group Endpoints
### Accessing Groups
Each group gets its own MCP endpoint:
<Tabs>
<Tab title="HTTP MCP">
```
http://localhost:3000/mcp/{group-name}
```
Examples:
- `http://localhost:3000/mcp/web-tools`
- `http://localhost:3000/mcp/data-processing`
- `http://localhost:3000/mcp/communication`
</Tab>
<Tab title="SSE (Legacy)">
```
http://localhost:3000/sse/{group-name}
```
Examples:
- `http://localhost:3000/sse/web-tools`
- `http://localhost:3000/sse/data-processing`
- `http://localhost:3000/sse/communication`
</Tab>
</Tabs>
### Group Tool Discovery
When connecting to a group endpoint, AI clients will only see tools from servers within that group:
```bash
# List tools in web-tools group
curl -X POST http://localhost:3000/mcp/web-tools \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}'
```
Response will only include tools from `fetch` and `playwright` servers.
## Dynamic Group Management
### Adding Servers to Groups
<Tabs>
<Tab title="Dashboard">
1. Navigate to the group in the dashboard
2. Click "Manage Servers"
3. Select additional servers to add
4. Click "Save Changes"
</Tab>
<Tab title="API">
```bash
curl -X POST http://localhost:3000/api/groups/web-tools/servers \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{"serverId": "new-server"}'
```
</Tab>
</Tabs>
### Removing Servers from Groups
<Tabs>
<Tab title="Dashboard">
1. Navigate to the group in the dashboard
2. Click "Manage Servers"
3. Unselect servers to remove
4. Click "Save Changes"
</Tab>
<Tab title="API">
```bash
curl -X DELETE http://localhost:3000/api/groups/web-tools/servers/server-name \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
</Tab>
</Tabs>
### Batch Server Updates
Update multiple servers at once:
```bash
curl -X PUT http://localhost:3000/api/groups/web-tools/servers \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{
"servers": ["fetch", "playwright", "selenium"]
}'
```
## Group Monitoring
### Group Status
Monitor group health and activity:
```bash
# Get group status
curl http://localhost:3000/api/groups/web-tools/status \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
Response includes:
- Number of active servers
- Tool count
- Active connections
- Recent activity
### Group Analytics
Track group usage:
```bash
# Get group analytics
curl http://localhost:3000/api/groups/web-tools/analytics \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
Metrics include:
- Request count by tool
- Response times
- Error rates
- User activity
## Advanced Group Features
### Nested Groups
Create hierarchical group structures:
```json
{
"groups": {
"development": {
"displayName": "Development Tools",
"subGroups": ["frontend-dev", "backend-dev", "devops"]
},
"frontend-dev": {
"displayName": "Frontend Development",
"servers": ["playwright", "webpack-server"],
"parent": "development"
},
"backend-dev": {
"displayName": "Backend Development",
"servers": ["database", "api-server"],
"parent": "development"
}
}
}
```
### Group Templates
Use templates for quick group creation:
```json
{
"groupTemplates": {
"web-project": {
"description": "Standard web project toolset",
"servers": ["fetch", "playwright", "filesystem"],
"accessLevel": "public"
},
"data-science": {
"description": "Data science and ML tools",
"servers": ["python-tools", "jupyter", "vector-db"],
"accessLevel": "private"
}
}
}
```
Apply template:
```bash
curl -X POST http://localhost:3000/api/groups/from-template \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{
"name": "my-web-project",
"template": "web-project",
"displayName": "My Web Project Tools"
}'
```
### Group Policies
Define policies for group behavior:
```json
{
"groupPolicies": {
"web-tools": {
"maxConcurrentConnections": 10,
"requestTimeout": 30000,
"rateLimiting": {
"requestsPerMinute": 100,
"burstLimit": 20
},
"allowedOrigins": ["localhost", "myapp.com"]
}
}
}
```
## Best Practices
### Group Organization
<Tip>
**Organize by Use Case**: Group servers based on what users want to accomplish, not just technical
similarity.
</Tip>
<Tip>
**Keep Groups Focused**: Avoid creating groups with too many diverse tools. Smaller, focused
groups are more useful.
</Tip>
<Tip>
**Use Descriptive Names**: Choose names that clearly indicate the group's purpose and contents.
</Tip>
### Security Considerations
<Warning>
**Principle of Least Privilege**: Only give users access to groups they actually need.
</Warning>
<Warning>
**Sensitive Tool Isolation**: Keep sensitive tools in restricted groups with proper access
controls.
</Warning>
<Warning>
**Regular Access Reviews**: Periodically review group memberships and remove unnecessary access.
</Warning>
### Performance Optimization
<Info>
**Balance Group Size**: Very large groups may have slower tool discovery. Consider splitting into
smaller groups.
</Info>
<Info>
**Monitor Usage**: Use analytics to identify which groups are heavily used and optimize
accordingly.
</Info>
## Troubleshooting
<AccordionGroup>
<Accordion title="Group Not Accessible">
**Check:**
- User has proper permissions
- Group exists and is active
- Servers in group are running
- Network connectivity
**Solutions:**
1. Verify user group membership
2. Check group configuration
3. Test individual server connections
4. Review access logs
</Accordion>
<Accordion title="Tools Missing from Group">
**Possible causes:**
- Server not properly added to group
- Server is not running
- Tool discovery failed
- Caching issues
**Debug steps:**
1. Verify server is in group configuration
2. Check server status
3. Force refresh tool discovery
4. Clear group cache
</Accordion>
<Accordion title="Group Performance Issues">
**Common issues:**
- Too many servers in group
- Slow server responses
- Network latency
- Resource constraints
**Optimizations:**
1. Split large groups
2. Monitor server performance
3. Implement request caching
4. Use connection pooling
</Accordion>
</AccordionGroup>
## Next Steps
<CardGroup cols={2}>
<Card title="Smart Routing" icon="route" href="/features/smart-routing">
AI-powered tool discovery across groups
</Card>
<Card title="Authentication" icon="shield" href="/features/authentication">
User management and access control
</Card>
<Card title="API Reference" icon="code" href="/api-reference/groups">
Complete group management API
</Card>
<Card title="Configuration" icon="cog" href="/configuration/mcp-settings">
Advanced group configuration options
</Card>
</CardGroup>

View File

@@ -0,0 +1,526 @@
---
title: 'Monitoring & Logging'
description: 'Monitor your MCP servers and analyze system logs with MCPHub'
---
## Overview
MCPHub provides comprehensive monitoring and logging capabilities to help you track server performance, debug issues, and maintain system health.
## Real-time Monitoring
### Server Status Dashboard
The MCPHub dashboard provides real-time monitoring of all registered MCP servers:
- **Server Health**: Online/offline status with automatic health checks
- **Response Times**: Average, min, max response times per server
- **Request Volume**: Requests per second/minute/hour
- **Error Rates**: Success/failure ratios and error trends
- **Resource Usage**: Memory and CPU utilization (when available)
### Health Check Configuration
Configure health checks for your MCP servers:
```json
{
"healthCheck": {
"enabled": true,
"interval": 30000,
"timeout": 5000,
"retries": 3,
"endpoints": {
"ping": "/health",
"tools": "/tools/list"
}
}
}
```
### Monitoring API
Get monitoring data programmatically:
```bash
# Get server health status
curl http://localhost:3000/api/monitoring/health
# Get performance metrics
curl http://localhost:3000/api/monitoring/metrics?server=my-server&range=1h
# Get system overview
curl http://localhost:3000/api/monitoring/overview
```
## Logging System
### Log Levels
MCPHub supports standard log levels:
- **ERROR**: Critical errors requiring immediate attention
- **WARN**: Warning conditions that should be monitored
- **INFO**: General operational messages
- **DEBUG**: Detailed debugging information
- **TRACE**: Very detailed trace information
### Log Configuration
Configure logging in your environment:
```bash
# Set log level
LOG_LEVEL=info
# Enable structured logging
LOG_FORMAT=json
# Log file location
LOG_FILE=/var/log/mcphub/app.log
# Enable request logging
ENABLE_REQUEST_LOGS=true
```
### Structured Logging
MCPHub uses structured logging for better analysis:
```json
{
"timestamp": "2024-01-20T10:30:00Z",
"level": "info",
"message": "MCP server request completed",
"server": "github-mcp",
"tool": "search_repositories",
"duration": 245,
"status": "success",
"requestId": "req_123456",
"userId": "user_789"
}
```
## Log Management
### Log Storage Options
#### File-based Logging
```yaml
# docker-compose.yml
services:
mcphub:
volumes:
- ./logs:/app/logs
environment:
- LOG_FILE=/app/logs/mcphub.log
- LOG_ROTATION=daily
- LOG_MAX_SIZE=100MB
- LOG_MAX_FILES=7
```
#### Database Logging
```json
{
"logging": {
"database": {
"enabled": true,
"table": "logs",
"retention": "30d",
"indexes": ["timestamp", "level", "server"]
}
}
}
```
#### External Log Services
```bash
# Syslog integration
SYSLOG_ENABLED=true
SYSLOG_HOST=localhost
SYSLOG_PORT=514
SYSLOG_FACILITY=local0
# ELK Stack integration
ELASTICSEARCH_URL=http://localhost:9200
ELASTICSEARCH_INDEX=mcphub-logs
```
### Log Rotation
Automatic log rotation configuration:
```json
{
"logRotation": {
"enabled": true,
"maxSize": "100MB",
"maxFiles": 10,
"compress": true,
"interval": "daily"
}
}
```
## Metrics Collection
### System Metrics
MCPHub collects various system metrics:
```javascript
// Example metrics collected
{
"timestamp": "2024-01-20T10:30:00Z",
"metrics": {
"requests": {
"total": 1547,
"success": 1523,
"errors": 24,
"rate": 12.5
},
"servers": {
"online": 8,
"offline": 2,
"total": 10
},
"performance": {
"avgResponseTime": 156,
"p95ResponseTime": 324,
"p99ResponseTime": 567
},
"system": {
"memoryUsage": "245MB",
"cpuUsage": "15%",
"uptime": "72h 35m"
}
}
}
```
### Custom Metrics
Add custom metrics for your use case:
```javascript
// Custom metric example
const customMetric = {
name: 'tool_usage',
type: 'counter',
tags: {
server: 'github-mcp',
tool: 'search_repositories',
result: 'success',
},
value: 1,
};
// Send to metrics endpoint
await fetch('/api/monitoring/metrics', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(customMetric),
});
```
## Alerting
### Alert Configuration
Set up alerts for critical conditions:
```json
{
"alerts": {
"serverDown": {
"condition": "server.status == 'offline'",
"duration": "5m",
"severity": "critical",
"channels": ["email", "slack"]
},
"highErrorRate": {
"condition": "errors.rate > 0.1",
"duration": "2m",
"severity": "warning",
"channels": ["slack"]
},
"slowResponse": {
"condition": "response.p95 > 1000",
"duration": "5m",
"severity": "warning",
"channels": ["email"]
}
}
}
```
### Notification Channels
#### Email Notifications
```bash
# Email configuration
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=alerts@yourcompany.com
SMTP_PASS=your-app-password
ALERT_EMAIL_TO=admin@yourcompany.com
```
#### Slack Integration
```bash
# Slack webhook
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/...
SLACK_CHANNEL=#mcphub-alerts
```
#### Webhook Notifications
```json
{
"webhooks": [
{
"url": "https://your-service.com/webhooks/mcphub",
"events": ["server.down", "error.rate.high"],
"headers": {
"Authorization": "Bearer your-token"
}
}
]
}
```
## Log Analysis
### Query Logs
Use the logs API to query and analyze logs:
```bash
# Get recent errors
curl "http://localhost:3000/api/logs?level=error&since=1h"
# Search logs by server
curl "http://localhost:3000/api/logs?server=github-mcp&limit=100"
# Get logs for specific request
curl "http://localhost:3000/api/logs?requestId=req_123456"
# Filter by time range
curl "http://localhost:3000/api/logs?from=2024-01-20T00:00:00Z&to=2024-01-20T23:59:59Z"
```
### Log Aggregation
Aggregate logs for insights:
```bash
# Error summary by server
curl "http://localhost:3000/api/logs/aggregate?groupBy=server&level=error&since=24h"
# Request volume over time
curl "http://localhost:3000/api/logs/aggregate?groupBy=hour&type=request&since=7d"
```
## Performance Monitoring
### Response Time Tracking
Monitor MCP server response times:
```javascript
// Response time metrics
{
"server": "github-mcp",
"tool": "search_repositories",
"metrics": {
"calls": 156,
"avgTime": 234,
"minTime": 89,
"maxTime": 1205,
"p50": 201,
"p95": 567,
"p99": 892
}
}
```
### Error Rate Monitoring
Track error rates and patterns:
```bash
# Get error rates by server
curl "http://localhost:3000/api/monitoring/errors?groupBy=server&since=1h"
# Get error details
curl "http://localhost:3000/api/monitoring/errors?server=github-mcp&details=true"
```
## Integration with External Tools
### Prometheus Integration
Export metrics to Prometheus:
```yaml
# prometheus.yml
scrape_configs:
- job_name: 'mcphub'
static_configs:
- targets: ['localhost:3000']
metrics_path: '/api/monitoring/prometheus'
scrape_interval: 30s
```
### Grafana Dashboards
Import MCPHub Grafana dashboard:
```json
{
"dashboard": {
"title": "MCPHub Monitoring",
"panels": [
{
"title": "Server Status",
"type": "stat",
"targets": [
{
"expr": "mcphub_servers_online",
"legendFormat": "Online"
}
]
},
{
"title": "Request Rate",
"type": "graph",
"targets": [
{
"expr": "rate(mcphub_requests_total[5m])",
"legendFormat": "Requests/sec"
}
]
}
]
}
}
```
### ELK Stack Integration
Configure Logstash for log processing:
```ruby
# logstash.conf
input {
beats {
port => 5044
}
}
filter {
if [fields][service] == "mcphub" {
json {
source => "message"
}
date {
match => [ "timestamp", "ISO8601" ]
}
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
index => "mcphub-logs-%{+YYYY.MM.dd}"
}
}
```
## Troubleshooting
### Common Monitoring Issues
**Missing Metrics**
```bash
# Check metrics endpoint
curl http://localhost:3000/api/monitoring/health
# Verify configuration
grep -r "monitoring" /path/to/config/
```
**Log File Issues**
```bash
# Check log file permissions
ls -la /var/log/mcphub/
# Verify disk space
df -h /var/log/
# Check log rotation
logrotate -d /etc/logrotate.d/mcphub
```
**Performance Issues**
```bash
# Monitor system resources
top -p $(pgrep -f mcphub)
# Check database connections
curl http://localhost:3000/api/monitoring/database
# Analyze slow queries
curl http://localhost:3000/api/monitoring/slow-queries
```
### Debug Mode
Enable debug logging for troubleshooting:
```bash
# Enable debug mode
DEBUG=mcphub:* npm start
# Or set environment variable
export DEBUG=mcphub:monitoring,mcphub:logging
```
## Best Practices
### Log Management
- Use structured logging with consistent formats
- Implement proper log levels and filtering
- Set up log rotation and retention policies
- Monitor log file sizes and disk usage
### Monitoring Setup
- Configure appropriate health check intervals
- Set up alerts for critical conditions
- Monitor both system and application metrics
- Use dashboards for visual monitoring
### Performance Optimization
- Index log database tables appropriately
- Use log sampling for high-volume scenarios
- Implement proper caching for metrics
- Regular cleanup of old logs and metrics
### Security Considerations
- Sanitize sensitive data in logs
- Secure access to monitoring endpoints
- Use authentication for external integrations
- Encrypt log transmission when using external services

View File

@@ -0,0 +1,509 @@
---
title: 'Server Management'
description: 'Centrally manage multiple MCP servers with hot-swappable configuration'
---
## Overview
MCPHub's server management system allows you to centrally configure, monitor, and control multiple MCP (Model Context Protocol) servers from a single dashboard. All changes are applied in real-time without requiring server restarts.
## Adding MCP Servers
### Via Dashboard
1. **Access the Dashboard**: Navigate to `http://localhost:3000` and log in
2. **Click "Add Server"**: Located in the servers section
3. **Fill Server Details**:
- **Name**: Unique identifier for the server
- **Command**: Executable command (e.g., `npx`, `uvx`, `python`)
- **Arguments**: Array of command arguments
- **Environment Variables**: Key-value pairs for environment setup
- **Working Directory**: Optional working directory for the command
### Via Configuration File
Edit your `mcp_settings.json` file:
```json
{
"mcpServers": {
"server-name": {
"command": "command-to-run",
"args": ["arg1", "arg2"],
"env": {
"API_KEY": "your-api-key",
"CONFIG_VALUE": "some-value"
},
"cwd": "/optional/working/directory"
}
}
}
```
### Via API
Use the REST API to add servers programmatically:
```bash
curl -X POST http://localhost:3000/api/servers \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{
"name": "fetch-server",
"command": "uvx",
"args": ["mcp-server-fetch"],
"env": {}
}'
```
## Popular MCP Server Examples
<AccordionGroup>
<Accordion title="Web Fetch Server">
Provides web scraping and HTTP request capabilities:
```json
{
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"]
}
}
```
**Available Tools:**
- `fetch`: Make HTTP requests
- `fetch_html`: Scrape web pages
- `fetch_json`: Get JSON data from APIs
</Accordion>
<Accordion title="Playwright Browser Automation">
Browser automation for web interactions:
```json
{
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest", "--headless"]
}
}
```
**Available Tools:**
- `playwright_navigate`: Navigate to web pages
- `playwright_screenshot`: Take screenshots
- `playwright_click`: Click elements
- `playwright_fill`: Fill forms
</Accordion>
<Accordion title="File System Operations">
File and directory management:
```json
{
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/allowed/path"]
}
}
```
**Available Tools:**
- `read_file`: Read file contents
- `write_file`: Write to files
- `create_directory`: Create directories
- `list_directory`: List directory contents
</Accordion>
<Accordion title="SQLite Database">
Database operations:
```json
{
"sqlite": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-sqlite", "/path/to/database.db"]
}
}
```
**Available Tools:**
- `execute_query`: Execute SQL queries
- `describe_tables`: Get table schemas
- `create_table`: Create new tables
</Accordion>
<Accordion title="Slack Integration">
Slack workspace integration:
```json
{
"slack": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-slack"],
"env": {
"SLACK_BOT_TOKEN": "xoxb-your-bot-token",
"SLACK_TEAM_ID": "T1234567890"
}
}
}
```
**Available Tools:**
- `send_slack_message`: Send messages to channels
- `list_slack_channels`: List available channels
- `get_slack_thread`: Get thread messages
</Accordion>
<Accordion title="GitHub Integration">
GitHub repository operations:
```json
{
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_your_token"
}
}
}
```
**Available Tools:**
- `create_or_update_file`: Create/update repository files
- `search_repositories`: Search GitHub repositories
- `create_issue`: Create issues
- `create_pull_request`: Create pull requests
</Accordion>
<Accordion title="Google Drive">
Google Drive file operations:
```json
{
"gdrive": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-gdrive"],
"env": {
"GDRIVE_CLIENT_ID": "your-client-id",
"GDRIVE_CLIENT_SECRET": "your-client-secret",
"GDRIVE_REDIRECT_URI": "your-redirect-uri"
}
}
}
```
**Available Tools:**
- `gdrive_search`: Search files and folders
- `gdrive_read`: Read file contents
- `gdrive_create`: Create new files
</Accordion>
<Accordion title="Amap Maps (China)">
Chinese mapping and location services:
```json
{
"amap": {
"command": "npx",
"args": ["-y", "@amap/amap-maps-mcp-server"],
"env": {
"AMAP_MAPS_API_KEY": "your-api-key"
}
}
}
```
**Available Tools:**
- `search_location`: Search for locations
- `get_directions`: Get route directions
- `reverse_geocode`: Convert coordinates to addresses
</Accordion>
</AccordionGroup>
## Server Lifecycle Management
### Starting Servers
Servers are automatically started when:
- MCPHub boots up
- A server is added via the dashboard or API
- A server configuration is updated
- A stopped server is manually restarted
### Stopping Servers
You can stop servers:
- **Via Dashboard**: Toggle the server status switch
- **Via API**: Send a POST request to `/api/servers/{name}/toggle`
- **Automatically**: Servers stop if they crash or encounter errors
### Restarting Servers
Servers are automatically restarted:
- When configuration changes are made
- After environment variable updates
- When manually triggered via dashboard or API
## Server Status Monitoring
### Status Indicators
Each server displays a status indicator:
- 🟢 **Running**: Server is active and responding
- 🟡 **Starting**: Server is initializing
- 🔴 **Stopped**: Server is not running
- ⚠️ **Error**: Server encountered an error
### Real-time Logs
View server logs in real-time:
1. **Dashboard Logs**: Click on a server to view its logs
2. **API Logs**: Access logs via `/api/logs` endpoint
3. **Streaming Logs**: Subscribe to log streams via WebSocket
### Health Checks
MCPHub automatically performs health checks:
- **Initialization Check**: Verifies server starts successfully
- **Tool Discovery**: Confirms available tools are detected
- **Response Check**: Tests server responsiveness
- **Resource Monitoring**: Tracks CPU and memory usage
## Configuration Management
### Environment Variables
Servers can use environment variables for configuration:
```json
{
"server-name": {
"command": "python",
"args": ["server.py"],
"env": {
"API_KEY": "${YOUR_API_KEY}",
"DEBUG": "true",
"MAX_CONNECTIONS": "10"
}
}
}
```
**Environment Variable Expansion:**
- `${VAR_NAME}`: Expands to environment variable value
- `${VAR_NAME:-default}`: Uses default if variable not set
- `${VAR_NAME:+value}`: Uses value if variable is set
### Working Directory
Set the working directory for server execution:
```json
{
"server-name": {
"command": "./local-script.sh",
"args": [],
"cwd": "/path/to/server/directory"
}
}
```
### Command Variations
Different ways to specify server commands:
<Tabs>
<Tab title="npm/npx">
```json
{
"npm-server": {
"command": "npx",
"args": ["-y", "package-name", "--option", "value"]
}
}
```
</Tab>
<Tab title="Python/uvx">
```json
{
"python-server": {
"command": "uvx",
"args": ["package-name", "--config", "config.json"]
}
}
```
</Tab>
<Tab title="Direct Python">
```json
{
"direct-python": {
"command": "python",
"args": ["-m", "module_name", "--arg", "value"]
}
}
```
</Tab>
<Tab title="Local Script">
```json
{
"local-script": {
"command": "./server.sh",
"args": ["--port", "8080"],
"cwd": "/path/to/script"
}
}
```
</Tab>
</Tabs>
## Advanced Features
### Hot Reloading
MCPHub supports hot reloading of server configurations:
1. **Config File Changes**: Automatically detects changes to `mcp_settings.json`
2. **Dashboard Updates**: Immediately applies changes made through the web interface
3. **API Updates**: Real-time updates via REST API calls
4. **Zero Downtime**: Graceful server restarts without affecting other servers
### Resource Limits
Control server resource usage:
```json
{
"resource-limited-server": {
"command": "memory-intensive-server",
"args": [],
"limits": {
"memory": "512MB",
"cpu": "50%",
"timeout": "30s"
}
}
}
```
### Dependency Management
Handle server dependencies:
<AccordionGroup>
<Accordion title="Auto-installation">
MCPHub can automatically install missing packages:
```json
{
"auto-install-server": {
"command": "npx",
"args": ["-y", "package-that-might-not-exist"],
"autoInstall": true
}
}
```
</Accordion>
<Accordion title="Pre-installation Scripts">
Run setup scripts before starting servers:
```json
{
"setup-server": {
"preStart": ["npm install", "pip install -r requirements.txt"],
"command": "python",
"args": ["server.py"]
}
}
```
</Accordion>
</AccordionGroup>
## Troubleshooting
<AccordionGroup>
<Accordion title="Server Won't Start">
**Check the following:**
- Command is available in PATH
- All required environment variables are set
- Working directory exists and is accessible
- Network ports are not blocked
- Dependencies are installed
**Debug steps:**
1. Check server logs in the dashboard
2. Test command manually in terminal
3. Verify environment variable expansion
4. Check file permissions
</Accordion>
<Accordion title="Server Keeps Crashing">
**Common causes:**
- Invalid configuration parameters
- Missing API keys or credentials
- Resource limits exceeded
- Dependency conflicts
**Solutions:**
1. Review server logs for error messages
2. Test with minimal configuration
3. Verify all credentials and API keys
4. Check system resource availability
</Accordion>
<Accordion title="Tools Not Appearing">
**Possible issues:**
- Server not fully initialized
- Tool discovery timeout
- Communication protocol mismatch
- Server reporting errors
**Debug steps:**
1. Wait for server initialization to complete
2. Check server logs for tool registration messages
3. Test direct communication with server
4. Verify MCP protocol compatibility
</Accordion>
</AccordionGroup>
## Next Steps
<CardGroup cols={2}>
<Card title="Group Management" icon="users" href="/features/group-management">
Organize servers into logical groups
</Card>
<Card title="Smart Routing" icon="route" href="/features/smart-routing">
Set up AI-powered tool discovery
</Card>
<Card title="API Reference" icon="code" href="/api-reference/servers">
Server management API documentation
</Card>
<Card title="Configuration Guide" icon="cog" href="/configuration/mcp-settings">
Detailed configuration options
</Card>
</CardGroup>

View File

@@ -0,0 +1,720 @@
---
title: 'Smart Routing'
description: 'AI-powered tool discovery using vector semantic search'
---
## Overview
Smart Routing is MCPHub's intelligent tool discovery system that uses vector semantic search to automatically find the most relevant tools for any given task. Instead of manually specifying which tools to use, AI clients can describe what they want to accomplish, and Smart Routing will identify and provide access to the most appropriate tools.
## How Smart Routing Works
### 1. Tool Indexing
When servers start up, Smart Routing automatically:
- Discovers all available tools from MCP servers
- Extracts tool metadata (names, descriptions, parameters)
- Converts tool information to vector embeddings
- Stores embeddings in PostgreSQL with pgvector
### 2. Semantic Search
When a query is made:
- User queries are converted to vector embeddings
- Similarity search finds matching tools using cosine similarity
- Dynamic thresholds filter out irrelevant results
- Results are ranked by relevance score
### 3. Intelligent Filtering
Smart Routing applies several filters:
- **Relevance Threshold**: Only returns tools above similarity threshold
- **Context Awareness**: Considers conversation context
- **Tool Availability**: Ensures tools are currently accessible
- **Permission Filtering**: Respects user access permissions
### 4. Tool Execution
Found tools can be directly executed:
- Parameter validation ensures correct tool usage
- Error handling provides helpful feedback
- Response formatting maintains consistency
- Logging tracks tool usage for analytics
## Prerequisites
Smart Routing requires additional setup compared to basic MCPHub usage:
### Required Components
1. **PostgreSQL with pgvector**: Vector database for embeddings storage
2. **Embedding Service**: OpenAI API or compatible service
3. **Environment Configuration**: Proper configuration variables
### Quick Setup
<Tabs>
<Tab title="Docker Compose">
Use this `docker-compose.yml` for complete setup:
```yaml
version: '3.8'
services:
mcphub:
image: samanhappy/mcphub:latest
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://mcphub:password@postgres:5432/mcphub
- OPENAI_API_KEY=your_openai_api_key
- ENABLE_SMART_ROUTING=true
depends_on:
- postgres
volumes:
- ./mcp_settings.json:/app/mcp_settings.json
postgres:
image: pgvector/pgvector:pg16
environment:
- POSTGRES_DB=mcphub
- POSTGRES_USER=mcphub
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
postgres_data:
```
Start with:
```bash
docker-compose up -d
```
</Tab>
<Tab title="Manual Setup">
1. **Install PostgreSQL with pgvector**:
```bash
# Using Docker
docker run -d \
--name mcphub-postgres \
-e POSTGRES_DB=mcphub \
-e POSTGRES_USER=mcphub \
-e POSTGRES_PASSWORD=your_password \
-p 5432:5432 \
pgvector/pgvector:pg16
```
2. **Set Environment Variables**:
```bash
export DATABASE_URL="postgresql://mcphub:your_password@localhost:5432/mcphub"
export OPENAI_API_KEY="your_openai_api_key"
export ENABLE_SMART_ROUTING="true"
```
3. **Start MCPHub**:
```bash
mcphub
```
</Tab>
<Tab title="Kubernetes">
Deploy with these Kubernetes manifests:
```yaml
# postgres-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
spec:
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: pgvector/pgvector:pg16
env:
- name: POSTGRES_DB
value: mcphub
- name: POSTGRES_USER
value: mcphub
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
ports:
- containerPort: 5432
---
# mcphub-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcphub
spec:
selector:
matchLabels:
app: mcphub
template:
metadata:
labels:
app: mcphub
spec:
containers:
- name: mcphub
image: samanhappy/mcphub:latest
env:
- name: DATABASE_URL
value: "postgresql://mcphub:password@postgres:5432/mcphub"
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: openai-secret
key: api-key
- name: ENABLE_SMART_ROUTING
value: "true"
ports:
- containerPort: 3000
```
</Tab>
</Tabs>
## Configuration
### Environment Variables
Configure Smart Routing with these environment variables:
```bash
# Required
DATABASE_URL=postgresql://user:password@host:5432/database
OPENAI_API_KEY=your_openai_api_key
# Optional
ENABLE_SMART_ROUTING=true
EMBEDDING_MODEL=text-embedding-3-small
SIMILARITY_THRESHOLD=0.7
MAX_TOOLS_RETURNED=10
EMBEDDING_BATCH_SIZE=100
```
### Configuration Options
<AccordionGroup>
<Accordion title="Database Configuration">
```bash
# Full PostgreSQL connection string
DATABASE_URL=postgresql://username:password@host:port/database?schema=public
# SSL configuration for cloud databases
DATABASE_URL=postgresql://user:pass@host:5432/db?sslmode=require
# Connection pool settings
DATABASE_POOL_SIZE=20
DATABASE_TIMEOUT=30000
```
</Accordion>
<Accordion title="Embedding Service">
```bash
# OpenAI (default)
OPENAI_API_KEY=sk-your-api-key
EMBEDDING_MODEL=text-embedding-3-small
# Azure OpenAI
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com
AZURE_OPENAI_API_KEY=your-api-key
AZURE_OPENAI_DEPLOYMENT=your-embedding-deployment
# Custom embedding service
EMBEDDING_SERVICE_URL=https://your-embedding-service.com
EMBEDDING_SERVICE_API_KEY=your-api-key
```
</Accordion>
<Accordion title="Search Parameters">
```bash
# Similarity threshold (0.0 to 1.0)
SIMILARITY_THRESHOLD=0.7
# Maximum tools to return
MAX_TOOLS_RETURNED=10
# Minimum query length for smart routing
MIN_QUERY_LENGTH=5
# Cache TTL for embeddings (seconds)
EMBEDDING_CACHE_TTL=3600
```
</Accordion>
</AccordionGroup>
## Using Smart Routing
### Smart Routing Endpoint
Access Smart Routing through the special `$smart` endpoint:
<Tabs>
<Tab title="HTTP MCP">
```
http://localhost:3000/mcp/$smart
```
</Tab>
<Tab title="SSE (Legacy)">
```
http://localhost:3000/sse/$smart
```
</Tab>
</Tabs>
### Basic Usage
Connect your AI client to the Smart Routing endpoint and make natural language requests:
```bash
# Example: Find tools for web scraping
curl -X POST http://localhost:3000/mcp/$smart \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/search",
"params": {
"query": "scrape website content and extract text"
}
}'
```
Response:
```json
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "fetch_html",
"server": "fetch",
"description": "Fetch and parse HTML content from a URL",
"relevanceScore": 0.92,
"parameters": { ... }
},
{
"name": "playwright_navigate",
"server": "playwright",
"description": "Navigate to a web page and extract content",
"relevanceScore": 0.87,
"parameters": { ... }
}
]
}
}
```
### Advanced Queries
Smart Routing supports various query types:
<AccordionGroup>
<Accordion title="Task-Based Queries">
```bash
# What you want to accomplish
curl -X POST http://localhost:3000/mcp/$smart \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/search",
"params": {
"query": "send a message to a slack channel"
}
}'
```
</Accordion>
<Accordion title="Domain-Specific Queries">
```bash
# Specific domain or technology
curl -X POST http://localhost:3000/mcp/$smart \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/search",
"params": {
"query": "database operations SQL queries"
}
}'
```
</Accordion>
<Accordion title="Action-Oriented Queries">
```bash
# Specific actions
curl -X POST http://localhost:3000/mcp/$smart \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/search",
"params": {
"query": "create file upload to github repository"
}
}'
```
</Accordion>
<Accordion title="Context-Aware Queries">
```bash
# Include context for better results
curl -X POST http://localhost:3000/mcp/$smart \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/search",
"params": {
"query": "automated testing web application",
"context": {
"project": "e-commerce website",
"technologies": ["React", "Node.js"],
"environment": "staging"
}
}
}'
```
</Accordion>
</AccordionGroup>
### Tool Execution
Once Smart Routing finds relevant tools, you can execute them directly:
```bash
# Execute a found tool
curl -X POST http://localhost:3000/mcp/$smart \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "fetch_html",
"arguments": {
"url": "https://example.com"
}
}
}'
```
## Performance Optimization
### Embedding Cache
Smart Routing caches embeddings to improve performance:
```bash
# Configure cache settings
EMBEDDING_CACHE_TTL=3600 # Cache for 1 hour
EMBEDDING_CACHE_SIZE=10000 # Cache up to 10k embeddings
EMBEDDING_CACHE_CLEANUP=300 # Cleanup every 5 minutes
```
### Batch Processing
Tools are indexed in batches for efficiency:
```bash
# Batch size for embedding generation
EMBEDDING_BATCH_SIZE=100
# Concurrent embedding requests
EMBEDDING_CONCURRENCY=5
# Index update frequency
INDEX_UPDATE_INTERVAL=3600 # Re-index every hour
```
### Database Optimization
Optimize PostgreSQL for vector operations:
```sql
-- Create indexes for better performance
CREATE INDEX ON tool_embeddings USING hnsw (embedding vector_cosine_ops);
-- Adjust PostgreSQL settings
ALTER SYSTEM SET shared_preload_libraries = 'vector';
ALTER SYSTEM SET max_connections = 200;
ALTER SYSTEM SET shared_buffers = '256MB';
ALTER SYSTEM SET effective_cache_size = '1GB';
```
## Monitoring and Analytics
### Smart Routing Metrics
Monitor Smart Routing performance:
```bash
# Get Smart Routing statistics
curl http://localhost:3000/api/smart-routing/stats \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
Response includes:
- Query count and frequency
- Average response time
- Embedding cache hit rate
- Most popular tools
- Query patterns
### Tool Usage Analytics
Track which tools are found and used:
```bash
# Get tool usage analytics
curl http://localhost:3000/api/smart-routing/analytics \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
Metrics include:
- Tool discovery rates
- Execution success rates
- User satisfaction scores
- Query-to-execution conversion
### Performance Monitoring
Monitor system performance:
```bash
# Database performance
curl http://localhost:3000/api/smart-routing/db-stats \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
# Embedding service status
curl http://localhost:3000/api/smart-routing/embedding-stats \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
## Advanced Features
### Custom Embeddings
Use custom embedding models:
```bash
# Hugging Face models
EMBEDDING_SERVICE=huggingface
HUGGINGFACE_MODEL=sentence-transformers/all-MiniLM-L6-v2
HUGGINGFACE_API_KEY=your_api_key
# Local embedding service
EMBEDDING_SERVICE=local
EMBEDDING_SERVICE_URL=http://localhost:8080/embeddings
```
### Query Enhancement
Enhance queries for better results:
```json
{
"queryEnhancement": {
"enabled": true,
"expandAcronyms": true,
"addSynonyms": true,
"contextualExpansion": true
}
}
```
### Result Filtering
Filter results based on criteria:
```json
{
"resultFiltering": {
"minRelevanceScore": 0.7,
"maxResults": 10,
"preferredServers": ["fetch", "playwright"],
"excludeServers": ["deprecated-server"]
}
}
```
### Feedback Learning
Improve results based on user feedback:
```bash
# Provide feedback on search results
curl -X POST http://localhost:3000/api/smart-routing/feedback \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{
"queryId": "search-123",
"toolName": "fetch_html",
"rating": 5,
"successful": true,
"comments": "Perfect tool for the task"
}'
```
## Troubleshooting
<AccordionGroup>
<Accordion title="Database Connection Issues">
**Symptoms:**
- Smart Routing not available
- Database connection errors
- Embedding storage failures
**Solutions:**
1. Verify PostgreSQL is running
2. Check DATABASE_URL format
3. Ensure pgvector extension is installed
4. Test connection manually:
```bash
psql $DATABASE_URL -c "SELECT 1;"
```
</Accordion>
<Accordion title="Embedding Service Problems">
**Symptoms:**
- Tool indexing failures
- Query processing errors
- API rate limit errors
**Solutions:**
1. Verify API key validity
2. Check network connectivity
3. Monitor rate limits
4. Test embedding service:
```bash
curl -X POST https://api.openai.com/v1/embeddings \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input": "test", "model": "text-embedding-3-small"}'
```
</Accordion>
<Accordion title="Poor Search Results">
**Symptoms:**
- Irrelevant tools returned
- Low relevance scores
- Missing expected tools
**Solutions:**
1. Adjust similarity threshold
2. Re-index tools with better descriptions
3. Use more specific queries
4. Check tool metadata quality
```bash
# Re-index all tools
curl -X POST http://localhost:3000/api/smart-routing/reindex \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
</Accordion>
<Accordion title="Performance Issues">
**Symptoms:**
- Slow query responses
- High database load
- Memory usage spikes
**Solutions:**
1. Optimize database configuration
2. Increase cache sizes
3. Reduce batch sizes
4. Monitor system resources
```bash
# Check system performance
curl http://localhost:3000/api/smart-routing/performance \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
</Accordion>
</AccordionGroup>
## Best Practices
### Query Writing
<Tip>
**Be Descriptive**: Use specific, descriptive language in queries for better tool matching.
</Tip>
<Tip>
**Include Context**: Provide relevant context about your task or domain for more accurate results.
</Tip>
<Tip>**Use Natural Language**: Write queries as you would describe the task to a human.</Tip>
### Tool Descriptions
<Warning>
**Quality Metadata**: Ensure MCP servers provide high-quality tool descriptions and metadata.
</Warning>
<Warning>**Regular Updates**: Keep tool descriptions current as functionality evolves.</Warning>
<Warning>
**Consistent Naming**: Use consistent naming conventions across tools and servers.
</Warning>
### System Maintenance
<Info>**Regular Re-indexing**: Periodically re-index tools to ensure embedding quality.</Info>
<Info>**Monitor Performance**: Track query patterns and optimize based on usage.</Info>
<Info>
**Update Models**: Consider updating to newer embedding models as they become available.
</Info>
## Next Steps
<CardGroup cols={2}>
<Card title="Authentication" icon="shield" href="/features/authentication">
User management and access control
</Card>
<Card title="Monitoring" icon="chart-line" href="/features/monitoring">
System monitoring and analytics
</Card>
<Card title="API Reference" icon="code" href="/api-reference/smart-routing">
Complete Smart Routing API documentation
</Card>
<Card title="Configuration" icon="cog" href="/configuration/environment-variables">
Advanced configuration options
</Card>
</CardGroup>

View File

@@ -1,71 +1,95 @@
---
title: Introduction
description: "Welcome to the home of your new documentation"
title: MCPHub Documentation
description: 'The Unified Hub for Model Context Protocol (MCP) Servers'
---
<img
className="block dark:hidden"
src="/images/hero-light.png"
alt="Hero Light"
/>
<img
className="hidden dark:block"
src="/images/hero-dark.png"
alt="Hero Dark"
/>
<img className="block dark:hidden" src="/images/hero-light.png" alt="Hero Light" />
<img className="hidden dark:block" src="/images/hero-dark.png" alt="Hero Dark" />
## Setting up
# Welcome to MCPHub
The first step to world-class documentation is setting up your editing environments.
MCPHub makes it easy to manage and scale multiple MCP (Model Context Protocol) servers by organizing them into flexible Streamable HTTP (SSE) endpoints—supporting access to all servers, individual servers, or logical server groups.
## Key Features
<CardGroup cols={2}>
<Card
title="Edit Your Docs"
icon="pen-to-square"
href="https://mintlify.com/docs/quickstart"
>
Get your docs set up locally for easy development
<Card title="Unified Management" icon="server" href="/features/server-management">
Centrally manage multiple MCP servers with hot-swappable configuration
</Card>
<Card
title="Preview Changes"
icon="image"
href="https://mintlify.com/docs/development"
>
Preview your changes before you push to make sure they're perfect
<Card title="Smart Routing" icon="route" href="/features/smart-routing">
AI-powered tool discovery using vector semantic search
</Card>
<Card title="Group Management" icon="users" href="/features/group-management">
Organize servers into logical groups for streamlined access control
</Card>
<Card title="Real-time Monitoring" icon="chart-line" href="/features/monitoring">
Monitor server status and performance from a unified dashboard
</Card>
</CardGroup>
## Make it yours
## Quick Start
Update your docs to your brand and add valuable content for the best user conversion.
Get MCPHub running in minutes with Docker:
```bash
docker run -p 3000:3000 samanhappy/mcphub
```
Or with custom configuration:
```bash
docker run -p 3000:3000 -v $(pwd)/mcp_settings.json:/app/mcp_settings.json samanhappy/mcphub
```
Access the dashboard at `http://localhost:3000` with default credentials:
- Username: `admin`
- Password: `admin123`
## Core Concepts
### MCP Endpoints
MCPHub provides multiple ways to access your MCP servers:
- **Unified Access**: `http://localhost:3000/mcp` - Access all servers
- **Group Access**: `http://localhost:3000/mcp/{group}` - Access specific groups
- **Server Access**: `http://localhost:3000/mcp/{server}` - Access individual servers
- **Smart Routing**: `http://localhost:3000/mcp/$smart` - AI-powered tool discovery
### Protocol Support
- **HTTP MCP**: Modern streamable HTTP interface (recommended)
- **SSE**: Server-Sent Events for legacy compatibility
- **stdio**: Native MCP protocol for server communication
## Getting Started
<CardGroup cols={2}>
<Card
title="Customize Style"
icon="palette"
href="https://mintlify.com/docs/settings/global"
>
Customize your docs to your company's colors and brands
<Card title="Quick Start Guide" icon="rocket" href="/quickstart">
Get MCPHub running in 5 minutes
</Card>
<Card
title="Reference APIs"
icon="code"
href="https://mintlify.com/docs/api-playground/openapi"
>
Automatically generate endpoints from an OpenAPI spec
<Card title="Installation Guide" icon="download" href="/installation">
Detailed installation instructions for all platforms
</Card>
<Card
title="Add Components"
icon="screwdriver-wrench"
href="https://mintlify.com/docs/content/components/accordions"
>
Build interactive features and designs to guide your users
<Card title="Configuration" icon="cog" href="/configuration/mcp-settings">
Learn how to configure your MCP servers
</Card>
<Card
title="Get Inspiration"
icon="stars"
href="https://mintlify.com/customers"
>
Check out our showcase of our favorite documentation
<Card title="API Reference" icon="code" href="/api-reference/introduction">
Complete API documentation
</Card>
</CardGroup>
## Community & Support
<CardGroup cols={3}>
<Card title="GitHub" icon="github" href="https://github.com/samanhappy/mcphub">
Source code and issue tracking
</Card>
<Card title="Discord" icon="discord" href="https://discord.gg/qMKNsn5Q">
Join our community discussions
</Card>
<Card title="Sponsor" icon="heart" href="https://ko-fi.com/samanhappy">
Support the project development
</Card>
</CardGroup>

575
docs/installation.mdx Normal file
View File

@@ -0,0 +1,575 @@
---
title: 'Installation Guide'
description: 'Detailed installation instructions for all platforms'
---
## Prerequisites
Before installing MCPHub, ensure you have the following prerequisites:
- **Node.js** 18+ (for local development)
- **Docker** (recommended for production)
- **pnpm** (for local development)
Optional for Smart Routing:
- **PostgreSQL** with pgvector extension
- **OpenAI API Key** or compatible embedding service
## Installation Methods
<Tabs>
<Tab title="Docker (Recommended)">
### Docker Installation
Docker is the recommended way to deploy MCPHub in production.
#### 1. Basic Installation
```bash
# Pull the latest image
docker pull samanhappy/mcphub:latest
# Run with default settings
docker run -d \
--name mcphub \
-p 3000:3000 \
samanhappy/mcphub:latest
```
#### 2. With Custom Configuration
```bash
# Create your configuration file
cat > mcp_settings.json << 'EOF'
{
"mcpServers": {
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"]
},
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest", "--headless"]
}
}
}
EOF
# Run with mounted config
docker run -d \
--name mcphub \
-p 3000:3000 \
-v $(pwd)/mcp_settings.json:/app/mcp_settings.json \
samanhappy/mcphub:latest
```
#### 3. With Environment Variables
```bash
docker run -d \
--name mcphub \
-p 3000:3000 \
-e PORT=3000 \
-e BASE_PATH="" \
-e REQUEST_TIMEOUT=60000 \
samanhappy/mcphub:latest
```
#### 4. Docker Compose
Create a `docker-compose.yml` file:
```yaml
version: '3.8'
services:
mcphub:
image: samanhappy/mcphub:latest
ports:
- "3000:3000"
volumes:
- ./mcp_settings.json:/app/mcp_settings.json
environment:
- PORT=3000
- BASE_PATH=""
- REQUEST_TIMEOUT=60000
restart: unless-stopped
# Optional: PostgreSQL for Smart Routing
postgres:
image: pgvector/pgvector:pg16
environment:
POSTGRES_DB: mcphub
POSTGRES_USER: mcphub
POSTGRES_PASSWORD: mcphub_password
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
postgres_data:
```
Run with:
```bash
docker-compose up -d
```
</Tab>
<Tab title="npm Package">
### npm Package Installation
Install MCPHub as a global npm package:
#### 1. Global Installation
```bash
# Install globally
npm install -g @samanhappy/mcphub
# Or with yarn
yarn global add @samanhappy/mcphub
# Or with pnpm
pnpm add -g @samanhappy/mcphub
```
#### 2. Running MCPHub
```bash
# Run with default settings
mcphub
# Run with custom port
PORT=8080 mcphub
# Run with custom config path
MCP_SETTINGS_PATH=/path/to/mcp_settings.json mcphub
```
#### 3. Local Installation
You can also install MCPHub locally in a project:
```bash
# Create a new directory
mkdir my-mcphub
cd my-mcphub
# Initialize package.json
npm init -y
# Install MCPHub locally
npm install @samanhappy/mcphub
# Create a start script
echo '#!/bin/bash\nnpx mcphub' > start.sh
chmod +x start.sh
# Run MCPHub
./start.sh
```
</Tab>
<Tab title="Local Development">
### Local Development Setup
For development, customization, or contribution:
#### 1. Clone Repository
```bash
# Clone the repository
git clone https://github.com/samanhappy/mcphub.git
cd mcphub
```
#### 2. Install Dependencies
```bash
# Install dependencies with pnpm (recommended)
pnpm install
# Or with npm
npm install
# Or with yarn
yarn install
```
#### 3. Development Mode
```bash
# Start both backend and frontend in development mode
pnpm dev
# This will start:
# - Backend on http://localhost:3001
# - Frontend on http://localhost:5173
# - Frontend proxies API calls to backend
```
#### 4. Build for Production
```bash
# Build both backend and frontend
pnpm build
# Start production server
pnpm start
```
#### 5. Development Scripts
```bash
# Backend only (for API development)
pnpm backend:dev
# Frontend only (when backend is running separately)
pnpm frontend:dev
# Run tests
pnpm test
# Lint code
pnpm lint
# Format code
pnpm format
```
<Note>
On Windows, you may need to run backend and frontend separately:
```bash
# Terminal 1: Backend
pnpm backend:dev
# Terminal 2: Frontend
pnpm frontend:dev
```
</Note>
</Tab>
<Tab title="Kubernetes">
### Kubernetes Deployment
Deploy MCPHub on Kubernetes with these manifests:
#### 1. ConfigMap for Settings
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mcphub-config
data:
mcp_settings.json: |
{
"mcpServers": {
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"]
},
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest", "--headless"]
}
}
}
```
#### 2. Deployment
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcphub
spec:
replicas: 1
selector:
matchLabels:
app: mcphub
template:
metadata:
labels:
app: mcphub
spec:
containers:
- name: mcphub
image: samanhappy/mcphub:latest
ports:
- containerPort: 3000
env:
- name: PORT
value: "3000"
volumeMounts:
- name: config
mountPath: /app/mcp_settings.json
subPath: mcp_settings.json
volumes:
- name: config
configMap:
name: mcphub-config
```
#### 3. Service
```yaml
apiVersion: v1
kind: Service
metadata:
name: mcphub-service
spec:
selector:
app: mcphub
ports:
- port: 80
targetPort: 3000
type: ClusterIP
```
#### 4. Ingress (Optional)
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: mcphub-ingress
annotations:
nginx.ingress.kubernetes.io/proxy-buffering: "off"
spec:
rules:
- host: mcphub.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: mcphub-service
port:
number: 80
```
Deploy with:
```bash
kubectl apply -f mcphub-configmap.yaml
kubectl apply -f mcphub-deployment.yaml
kubectl apply -f mcphub-service.yaml
kubectl apply -f mcphub-ingress.yaml
```
</Tab>
</Tabs>
## Smart Routing Setup (Optional)
Smart Routing provides AI-powered tool discovery using vector semantic search.
### Prerequisites
1. **PostgreSQL with pgvector**
2. **OpenAI API Key** (or compatible embedding service)
### Database Setup
<Tabs>
<Tab title="Docker PostgreSQL">
```bash
# Run PostgreSQL with pgvector
docker run -d \
--name mcphub-postgres \
-e POSTGRES_DB=mcphub \
-e POSTGRES_USER=mcphub \
-e POSTGRES_PASSWORD=your_password \
-p 5432:5432 \
pgvector/pgvector:pg16
```
</Tab>
<Tab title="Existing PostgreSQL">
If you have an existing PostgreSQL instance:
```sql
-- Connect to your PostgreSQL instance
-- Create database
CREATE DATABASE mcphub;
-- Connect to the mcphub database
\c mcphub;
-- Enable pgvector extension
CREATE EXTENSION IF NOT EXISTS vector;
```
</Tab>
<Tab title="Cloud PostgreSQL">
For cloud providers (AWS RDS, Google Cloud SQL, etc.):
1. Enable the pgvector extension in your cloud provider's console
2. Create a database named `mcphub`
3. Note down the connection details
</Tab>
</Tabs>
### Environment Configuration
Set the following environment variables:
```bash
# Database connection
DATABASE_URL=postgresql://mcphub:your_password@localhost:5432/mcphub
# OpenAI API for embeddings
OPENAI_API_KEY=your_openai_api_key
# Optional: Custom embedding model
EMBEDDING_MODEL=text-embedding-3-small
# Optional: Enable smart routing
ENABLE_SMART_ROUTING=true
```
## Verification
After installation, verify MCPHub is working:
### 1. Health Check
```bash
curl http://localhost:3000/api/health
```
Expected response:
```json
{
"status": "ok",
"version": "x.x.x",
"uptime": 123
}
```
### 2. Dashboard Access
Open your browser and navigate to:
```
http://localhost:3000
```
### 3. API Test
```bash
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}'
```
## Troubleshooting
<AccordionGroup>
<Accordion title="Docker Issues">
**Port already in use:**
```bash
# Check what's using port 3000
lsof -i :3000
# Use a different port
docker run -p 8080:3000 samanhappy/mcphub
```
**Container won't start:**
```bash
# Check container logs
docker logs mcphub
# Run interactively for debugging
docker run -it --rm samanhappy/mcphub /bin/bash
```
</Accordion>
<Accordion title="npm Installation Issues">
**Permission errors:**
```bash
# Use npx instead of global install
npx @samanhappy/mcphub
# Or fix npm permissions
npm config set prefix ~/.npm-global
export PATH=~/.npm-global/bin:$PATH
```
**Node version issues:**
```bash
# Check Node version
node --version
# Install Node 18+ using nvm
nvm install 18
nvm use 18
```
</Accordion>
<Accordion title="Network Issues">
**Can't access dashboard:**
- Check if MCPHub is running: `ps aux | grep mcphub`
- Verify port binding: `netstat -tlnp | grep 3000`
- Check firewall settings
- Try accessing via `127.0.0.1:3000` instead of `localhost:3000`
**AI clients can't connect:**
- Ensure the endpoint URL is correct
- Check if MCPHub is behind a proxy
- Verify network policies in Kubernetes/Docker environments
</Accordion>
<Accordion title="Smart Routing Issues">
**Database connection failed:**
```bash
# Test database connection
psql $DATABASE_URL -c "SELECT 1;"
# Check if pgvector is installed
psql $DATABASE_URL -c "CREATE EXTENSION IF NOT EXISTS vector;"
```
**Embedding service errors:**
- Verify OpenAI API key is valid
- Check internet connectivity
- Monitor rate limits
</Accordion>
</AccordionGroup>
## Next Steps
<CardGroup cols={2}>
<Card title="Configuration" icon="cog" href="/configuration/mcp-settings">
Configure your MCP servers and settings
</Card>
<Card title="Quick Start" icon="rocket" href="/quickstart">
Get up and running in 5 minutes
</Card>
<Card title="Server Management" icon="server" href="/features/server-management">
Learn how to manage your MCP servers
</Card>
<Card title="API Reference" icon="code" href="/api-reference/introduction">
Explore the complete API documentation
</Card>
</CardGroup>

View File

@@ -0,0 +1,149 @@
# OpenAPI Schema Support in MCPHub
MCPHub now supports both OpenAPI specification URLs and complete JSON schemas for OpenAPI server configuration. This allows you to either reference an external OpenAPI specification file or embed the complete schema directly in your configuration.
## Configuration Options
### 1. Using OpenAPI Specification URL (Traditional)
```json
{
"type": "openapi",
"openapi": {
"url": "https://api.example.com/openapi.json",
"version": "3.1.0",
"security": {
"type": "apiKey",
"apiKey": {
"name": "X-API-Key",
"in": "header",
"value": "your-api-key"
}
}
}
}
```
### 2. Using Complete JSON Schema (New)
```json
{
"type": "openapi",
"openapi": {
"schema": {
"openapi": "3.1.0",
"info": {
"title": "My API",
"version": "1.0.0"
},
"servers": [
{
"url": "https://api.example.com"
}
],
"paths": {
"/users": {
"get": {
"operationId": "getUsers",
"summary": "Get all users",
"responses": {
"200": {
"description": "List of users"
}
}
}
}
}
},
"version": "3.1.0",
"security": {
"type": "apiKey",
"apiKey": {
"name": "X-API-Key",
"in": "header",
"value": "your-api-key"
}
}
}
}
```
## Benefits of JSON Schema Support
1. **Offline Development**: No need for external URLs during development
2. **Version Control**: Schema changes can be tracked in your configuration
3. **Security**: No external dependencies or network calls required
4. **Customization**: Full control over the API specification
5. **Testing**: Easy to create test configurations with mock schemas
## Frontend Form Support
The web interface now includes:
- **Input Mode Selection**: Choose between "Specification URL" or "JSON Schema"
- **URL Input**: Traditional URL input field for external specifications
- **Schema Editor**: Large text area with syntax highlighting for JSON schema input
- **Validation**: Client-side JSON validation before submission
- **Help Text**: Contextual help for schema format
## API Validation
The backend validates that:
- At least one of `url` or `schema` is provided for OpenAPI servers
- JSON schemas are properly formatted when provided
- Security configurations are valid for both input modes
- All required OpenAPI fields are present
## Migration Guide
### From URL to Schema
If you want to convert an existing URL-based configuration to schema-based:
1. Download your OpenAPI specification from the URL
2. Copy the JSON content
3. Update your configuration to use the `schema` field instead of `url`
4. Paste the JSON content as the value of the `schema` field
### Maintaining Both
You can include both `url` and `schema` in your configuration. The system will prioritize the `schema` field if both are present.
## Examples
See the `examples/openapi-schema-config.json` file for complete configuration examples showing both URL and schema-based configurations.
## Technical Implementation
- **Backend**: OpenAPI client supports both SwaggerParser.dereference() with URLs and direct schema objects
- **Frontend**: Dynamic form rendering based on selected input mode
- **Validation**: Enhanced validation logic in server controllers
- **Type Safety**: Updated TypeScript interfaces for both input modes
## Security Considerations
When using JSON schemas:
- Ensure schemas are properly validated before use
- Be aware that large schemas increase configuration file size
- Consider using URL-based approach for frequently changing APIs
- Store sensitive information (like API keys) in environment variables, not in schemas
## Troubleshooting
### Common Issues
1. **Invalid JSON**: Ensure your schema is valid JSON format
2. **Missing Required Fields**: OpenAPI schemas must include `openapi`, `info`, and `paths` fields
3. **Schema Size**: Very large schemas may impact performance
4. **Server Configuration**: Ensure the `servers` field in your schema points to the correct endpoints
### Validation Errors
The system provides detailed error messages for:
- Malformed JSON in schema field
- Missing required OpenAPI fields
- Invalid security configurations
- Network issues with URL-based configurations

172
docs/openapi-support.md Normal file
View File

@@ -0,0 +1,172 @@
# OpenAPI Support in MCPHub
MCPHub now supports OpenAPI 3.1.1 servers as a new server type, allowing you to integrate REST APIs directly into your MCP workflow.
## Features
-**Full OpenAPI 3.1.1 Support**: Load and parse OpenAPI specifications
-**Multiple Security Types**: None, API Key, HTTP Authentication, OAuth 2.0, OpenID Connect
-**Dynamic Tool Generation**: Automatically creates MCP tools from OpenAPI operations
-**Type Safety**: Full TypeScript support with proper type definitions
-**Frontend Integration**: Easy-to-use forms for configuring OpenAPI servers
-**Internationalization**: Support for English and Chinese languages
## Configuration
### Basic Configuration
```json
{
"type": "openapi",
"openapi": {
"url": "https://api.example.com/v1/openapi.json",
"version": "3.1.0",
"security": {
"type": "none"
}
}
}
```
### With API Key Authentication
```json
{
"type": "openapi",
"openapi": {
"url": "https://api.example.com/v1/openapi.json",
"version": "3.1.0",
"security": {
"type": "apiKey",
"apiKey": {
"name": "X-API-Key",
"in": "header",
"value": "your-api-key-here"
}
}
},
"headers": {
"Accept": "application/json"
}
}
```
### With HTTP Bearer Authentication
```json
{
"type": "openapi",
"openapi": {
"url": "https://api.example.com/v1/openapi.json",
"version": "3.1.0",
"security": {
"type": "http",
"http": {
"scheme": "bearer",
"credentials": "your-bearer-token-here"
}
}
}
}
```
## Supported Security Types
1. **None**: No authentication required
2. **API Key**: API key in header or query parameter
3. **HTTP**: Basic, Bearer, or Digest authentication
4. **OAuth 2.0**: OAuth 2.0 access tokens
5. **OpenID Connect**: OpenID Connect ID tokens
## How It Works
1. **Specification Loading**: The OpenAPI client fetches and parses the OpenAPI specification
2. **Tool Generation**: Each operation in the spec becomes an MCP tool
3. **Request Handling**: Tools handle parameter validation and API calls
4. **Response Processing**: API responses are returned as tool results
## Frontend Usage
1. Navigate to the Servers page
2. Click "Add Server"
3. Select "OpenAPI" as the server type
4. Enter the OpenAPI specification URL
5. Configure security settings if needed
6. Add any additional headers
7. Save the configuration
## Testing
You can test the OpenAPI integration using the provided test scripts:
```bash
# Test OpenAPI client directly
npx tsx test-openapi.ts
# Test full integration
npx tsx test-integration.ts
```
## Example: Swagger Petstore
The Swagger Petstore API is a perfect example for testing:
```json
{
"type": "openapi",
"openapi": {
"url": "https://petstore3.swagger.io/api/v3/openapi.json",
"version": "3.1.0",
"security": {
"type": "none"
}
}
}
```
This will create tools like:
- `addPet`: Add a new pet to the store
- `findPetsByStatus`: Find pets by status
- `getPetById`: Find pet by ID
- And many more...
## Error Handling
The OpenAPI client includes comprehensive error handling:
- Network errors are properly caught and reported
- Invalid specifications are rejected with clear error messages
- API errors include response status and body information
- Type validation ensures proper parameter handling
## Limitations
- Only supports OpenAPI 3.x specifications (3.0.0 and above)
- Complex authentication flows (like OAuth 2.0 authorization code flow) require manual token management
- Large specifications may take time to parse initially
- Some advanced OpenAPI features may not be fully supported
## Contributing
To add new features or fix bugs in the OpenAPI integration:
1. Backend types: `src/types/index.ts`
2. OpenAPI client: `src/clients/openapi.ts`
3. Service integration: `src/services/mcpService.ts`
4. Frontend forms: `frontend/src/components/ServerForm.tsx`
5. Internationalization: `frontend/src/locales/`
## Troubleshooting
**Q: My OpenAPI server won't connect**
A: Check that the specification URL is accessible and returns valid JSON/YAML
**Q: Tools aren't showing up**
A: Verify that your OpenAPI specification includes valid operations with required fields
**Q: Authentication isn't working**
A: Double-check your security configuration matches the API's requirements
**Q: Getting CORS errors**
A: The API server needs to allow CORS requests from your MCPHub domain

View File

@@ -1,97 +1,228 @@
---
title: 'Quickstart'
description: 'Start building awesome documentation in under 5 minutes'
title: 'Quick Start Guide'
description: 'Get MCPHub running in 5 minutes'
---
## Setup your development
## Installation
Learn how to update your docs locally and deploy them to the public.
<Tabs>
<Tab title="Docker (Recommended)">
The fastest way to get started with MCPHub is using Docker:
### Edit and preview
```bash
# Run with default configuration
docker run -p 3000:3000 samanhappy/mcphub
```
Or mount your custom configuration:
```bash
# Run with custom MCP settings
docker run -p 3000:3000 \
-v $(pwd)/mcp_settings.json:/app/mcp_settings.json \
samanhappy/mcphub
```
</Tab>
<Tab title="Local Development">
For development or customization:
```bash
# Clone the repository
git clone https://github.com/samanhappy/mcphub.git
cd mcphub
# Install dependencies
pnpm install
# Start development servers
pnpm dev
```
This starts both backend (port 3001) and frontend (port 5173) in development mode.
</Tab>
<Tab title="npm Package">
Install MCPHub as a global package:
```bash
# Install globally
npm install -g @samanhappy/mcphub
# Run MCPHub
mcphub
```
</Tab>
</Tabs>
## Initial Setup
### 1. Access the Dashboard
Open your browser and navigate to:
```
http://localhost:3000
```
### 2. Login
Use the default credentials:
- **Username**: `admin`
- **Password**: `admin123`
<Warning>Change these default credentials immediately after first login for security.</Warning>
### 3. Configure Your First MCP Server
1. Click **"Add Server"** in the dashboard
2. Enter server details:
- **Name**: A unique identifier (e.g., `fetch`)
- **Command**: The executable command (`uvx`)
- **Args**: Command arguments (`["mcp-server-fetch"]`)
- **Environment**: Any required environment variables
Example configuration for a fetch server:
```json
{
"name": "fetch",
"command": "uvx",
"args": ["mcp-server-fetch"],
"env": {}
}
```
## Basic Usage
### Connecting AI Clients
Once your servers are configured, connect your AI clients using MCPHub endpoints:
<Tabs>
<Tab title="All Servers">
Access all configured MCP servers: ``` http://localhost:3000/mcp ```
</Tab>
<Tab title="Specific Group">
Access servers in a specific group: ``` http://localhost:3000/mcp/{group - name}
```
</Tab>
<Tab title="Individual Server">
Access a single server: ``` http://localhost:3000/mcp/{server - name}
```
</Tab>
<Tab title="Smart Routing">
Use AI-powered tool discovery: ``` http://localhost:3000/mcp/$smart ```
<Info>Smart routing requires PostgreSQL with pgvector and an OpenAI API key.</Info>
</Tab>
</Tabs>
### Example: Adding Popular MCP Servers
Here are some popular MCP servers you can add:
<AccordionGroup>
<Accordion icon="github" title="Clone your docs locally">
During the onboarding process, we created a repository on your Github with
your docs content. You can find this repository on our
[dashboard](https://dashboard.mintlify.com). To clone the repository
locally, follow these
[instructions](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository)
in your terminal.
<Accordion title="Web Fetch Server">
```json
{
"name": "fetch",
"command": "uvx",
"args": ["mcp-server-fetch"]
}
```
</Accordion>
<Accordion icon="rectangle-terminal" title="Preview changes">
Previewing helps you make sure your changes look as intended. We built a
command line interface to render these changes locally.
1. Install the
[Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview the
documentation changes locally with this command: ``` npm i -g mintlify ```
2. Run the following command at the root of your documentation (where
`docs.json` is): ``` mintlify dev ```
<Note>
If youre currently using the legacy ```mint.json``` configuration file, please update the Mintlify CLI:
```npm i -g mintlify@latest```
And run the new upgrade command in your docs repository:
```mintlify upgrade```
You should now be using the new ```docs.json``` configuration file. Feel free to delete the ```mint.json``` file from your repository.
</Note>
<Accordion title="Playwright Browser Automation">
```json
{
"name": "playwright",
"command": "npx",
"args": ["@playwright/mcp@latest", "--headless"]
}
```
</Accordion>
<Accordion title="Amap Maps (with API key)">
```json
{
"name": "amap",
"command": "npx",
"args": ["-y", "@amap/amap-maps-mcp-server"],
"env": {
"AMAP_MAPS_API_KEY": "your-api-key-here"
}
}
```
</Accordion>
<Accordion title="Slack Integration">
```json
{
"name": "slack",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-slack"],
"env": {
"SLACK_BOT_TOKEN": "your-bot-token",
"SLACK_TEAM_ID": "your-team-id"
}
}
```
</Accordion>
</AccordionGroup>
### Deploy your changes
## Verification
<AccordionGroup>
Test your setup by making a simple request:
<Accordion icon="message-bot" title="Install our Github app">
Our Github app automatically deploys your changes to your docs site, so you
don't need to manage deployments yourself. You can find the link to install on
your [dashboard](https://dashboard.mintlify.com). Once the bot has been
successfully installed, there should be a check mark next to the commit hash
of the repo.
</Accordion>
<Accordion icon="rocket" title="Push your changes">
[Commit and push your changes to
Git](https://docs.github.com/en/get-started/using-git/pushing-commits-to-a-remote-repository#about-git-push)
for your changes to update in your docs site. If you push and don't see that
the Github app successfully deployed your changes, you can also manually
update your docs through our [dashboard](https://dashboard.mintlify.com).
</Accordion>
```bash
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}'
```
</AccordionGroup>
You should receive a list of available tools from your configured MCP servers.
## Update your docs
Add content directly in your files with MDX syntax and React components. You can use any of our components, or even build your own.
<CardGroup>
<Card title="Add Content With MDX" icon="file" href="/essentials/markdown">
Add content to your docs with MDX syntax.
</Card>
<Card
title="Add Code Blocks"
icon="square-code"
href="/essentials/code"
>
Add code directly to your docs with syntax highlighting.
</Card>
<Card
title="Add Images"
icon="image"
href="/essentials/images"
>
Add images to your docs to make them more engaging.
</Card>
<Card
title="Add Custom Components"
icon="puzzle-piece"
href="/essentials/reusable-snippets"
>
Add templates to your docs to make them more reusable.
</Card>
## Next Steps
<CardGroup cols={2}>
<Card title="Server Management" icon="server" href="/features/server-management">
Learn advanced server configuration and management
</Card>
<Card title="Group Management" icon="users" href="/features/group-management">
Organize servers into logical groups
</Card>
<Card title="Smart Routing" icon="route" href="/features/smart-routing">
Set up AI-powered tool discovery
</Card>
<Card title="API Reference" icon="code" href="/api-reference/introduction">
Explore the complete API documentation
</Card>
</CardGroup>
## Troubleshooting
<AccordionGroup>
<Accordion title="Server won't start">
- Check if the MCP server command is accessible in your PATH - Verify environment variables are
correctly set - Check MCPHub logs for detailed error messages
</Accordion>
<Accordion title="Can't connect from AI client">
- Ensure MCPHub is running on the correct port - Check firewall settings - Verify the endpoint
URL format
</Accordion>
<Accordion title="Authentication issues">
- Verify credentials are correct - Check if JWT token is valid - Try clearing browser cache and
cookies
</Accordion>
</AccordionGroup>
Need more help? Join our [Discord community](https://discord.gg/qMKNsn5Q) for support!

View File

@@ -0,0 +1,572 @@
---
title: '创建资源'
description: '创建新的 MCP 服务器、用户和组'
---
## 创建服务器
### 端点
```http
POST /api/servers
```
### 请求
#### 请求头
```http
Content-Type: application/json
Authorization: Bearer YOUR_JWT_TOKEN
```
#### 请求体
```json
{
"name": "文件系统服务器",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/allowed/path"],
"env": {
"NODE_ENV": "production",
"DEBUG": "mcp:*",
"MAX_FILES": "1000"
},
"cwd": "/app/workspace",
"timeout": 30000,
"retries": 3,
"enabled": true,
"description": "提供文件系统访问的 MCP 服务器",
"tags": ["filesystem", "production"],
"healthCheck": {
"enabled": true,
"interval": 30000,
"timeout": 5000,
"retries": 3,
"endpoint": "/health"
},
"resources": {
"memory": {
"limit": "512MB",
"warning": "400MB"
},
"cpu": {
"limit": "50%"
}
},
"logging": {
"level": "info",
"file": "/var/log/mcphub/server.log",
"maxSize": "100MB",
"maxFiles": 5
}
}
```
#### 必填字段
- `name` (string): 服务器唯一名称
- `command` (string): 执行命令
- `args` (array): 命令参数数组
#### 可选字段
- `env` (object): 环境变量键值对
- `cwd` (string): 工作目录
- `timeout` (number): 超时时间(毫秒)
- `retries` (number): 重试次数
- `enabled` (boolean): 是否启用(默认 true
- `description` (string): 服务器描述
- `tags` (array): 标签数组
- `healthCheck` (object): 健康检查配置
- `resources` (object): 资源限制配置
- `logging` (object): 日志配置
### 响应
#### 成功响应 (201 Created)
```json
{
"success": true,
"data": {
"id": "server-abc123",
"name": "文件系统服务器",
"status": "stopped",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/allowed/path"],
"env": {
"NODE_ENV": "production",
"DEBUG": "mcp:*",
"MAX_FILES": "1000"
},
"cwd": "/app/workspace",
"timeout": 30000,
"retries": 3,
"enabled": true,
"description": "提供文件系统访问的 MCP 服务器",
"tags": ["filesystem", "production"],
"healthCheck": {
"enabled": true,
"interval": 30000,
"timeout": 5000,
"retries": 3,
"endpoint": "/health",
"status": "unknown"
},
"resources": {
"memory": {
"limit": "512MB",
"warning": "400MB",
"current": "0MB"
},
"cpu": {
"limit": "50%",
"current": "0%"
}
},
"logging": {
"level": "info",
"file": "/var/log/mcphub/server.log",
"maxSize": "100MB",
"maxFiles": 5,
"currentSize": "0MB"
},
"createdAt": "2024-01-01T12:00:00Z",
"updatedAt": "2024-01-01T12:00:00Z",
"createdBy": "user123"
},
"message": "服务器创建成功"
}
```
#### 错误响应
**400 Bad Request - 参数错误**
```json
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "请求数据验证失败",
"details": [
{
"field": "name",
"message": "服务器名称不能为空"
},
{
"field": "command",
"message": "执行命令不能为空"
}
]
}
}
```
**409 Conflict - 名称冲突**
```json
{
"success": false,
"error": {
"code": "RESOURCE_CONFLICT",
"message": "服务器名称已存在",
"details": {
"field": "name",
"value": "文件系统服务器",
"conflictingResourceId": "server-xyz789"
}
}
}
```
### 示例
#### cURL
```bash
curl -X POST http://localhost:3000/api/servers \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{
"name": "文件系统服务器",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"],
"env": {
"NODE_ENV": "production"
},
"description": "生产环境文件系统服务器"
}'
```
#### JavaScript
```javascript
const response = await fetch('/api/servers', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
name: '文件系统服务器',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-filesystem', '/data'],
env: {
NODE_ENV: 'production',
},
description: '生产环境文件系统服务器',
}),
});
const result = await response.json();
if (result.success) {
console.log('服务器创建成功:', result.data);
} else {
console.error('创建失败:', result.error);
}
```
#### Python
```python
import requests
response = requests.post(
'http://localhost:3000/api/servers',
headers={
'Content-Type': 'application/json',
'Authorization': f'Bearer {token}'
},
json={
'name': '文件系统服务器',
'command': 'npx',
'args': ['-y', '@modelcontextprotocol/server-filesystem', '/data'],
'env': {
'NODE_ENV': 'production'
},
'description': '生产环境文件系统服务器'
}
)
if response.status_code == 201:
result = response.json()
print('服务器创建成功:', result['data'])
else:
error = response.json()
print('创建失败:', error['error'])
```
## 创建用户
### 端点
```http
POST /api/users
```
### 请求体
```json
{
"username": "newuser",
"email": "user@example.com",
"password": "SecurePassword123!",
"role": "user",
"groups": ["dev-team", "qa-team"],
"profile": {
"firstName": "张",
"lastName": "三",
"department": "开发部",
"title": "软件工程师",
"phone": "+86-138-0013-8000",
"location": "北京"
},
"preferences": {
"language": "zh-CN",
"timezone": "Asia/Shanghai",
"notifications": {
"email": true,
"slack": false,
"browser": true
}
},
"enabled": true
}
```
### 响应 (201 Created)
```json
{
"success": true,
"data": {
"id": "user-abc123",
"username": "newuser",
"email": "user@example.com",
"role": "user",
"groups": [
{
"id": "dev-team",
"name": "开发团队",
"role": "member"
}
],
"profile": {
"firstName": "张",
"lastName": "三",
"fullName": "张三",
"department": "开发部",
"title": "软件工程师",
"phone": "+86-138-0013-8000",
"location": "北京",
"avatar": null
},
"preferences": {
"language": "zh-CN",
"timezone": "Asia/Shanghai",
"notifications": {
"email": true,
"slack": false,
"browser": true
}
},
"enabled": true,
"lastLoginAt": null,
"createdAt": "2024-01-01T12:00:00Z",
"updatedAt": "2024-01-01T12:00:00Z"
},
"message": "用户创建成功"
}
```
## 创建组
### 端点
```http
POST /api/groups
```
### 请求体
```json
{
"name": "dev-team",
"displayName": "开发团队",
"description": "前端和后端开发人员",
"parentGroup": null,
"permissions": {
"servers": {
"create": false,
"read": true,
"update": true,
"delete": false,
"execute": true
},
"tools": {
"filesystem": {
"read": true,
"write": true,
"paths": ["/app/data", "/tmp"]
},
"web-search": {
"enabled": true,
"maxQueries": 100
}
},
"monitoring": {
"viewLogs": true,
"viewMetrics": true,
"exportData": false
}
},
"settings": {
"autoAssign": false,
"maxMembers": 50,
"requireApproval": true,
"sessionTimeout": "8h"
},
"quotas": {
"requests": {
"daily": 1000,
"monthly": 30000
},
"storage": {
"maxSize": "10GB"
}
}
}
```
### 响应 (201 Created)
```json
{
"success": true,
"data": {
"id": "group-abc123",
"name": "dev-team",
"displayName": "开发团队",
"description": "前端和后端开发人员",
"parentGroup": null,
"permissions": {
"servers": {
"create": false,
"read": true,
"update": true,
"delete": false,
"execute": true
},
"tools": {
"filesystem": {
"read": true,
"write": true,
"paths": ["/app/data", "/tmp"]
},
"web-search": {
"enabled": true,
"maxQueries": 100
}
},
"monitoring": {
"viewLogs": true,
"viewMetrics": true,
"exportData": false
}
},
"settings": {
"autoAssign": false,
"maxMembers": 50,
"requireApproval": true,
"sessionTimeout": "8h"
},
"quotas": {
"requests": {
"daily": 1000,
"monthly": 30000
},
"storage": {
"maxSize": "10GB"
}
},
"memberCount": 0,
"serverCount": 0,
"createdAt": "2024-01-01T12:00:00Z",
"updatedAt": "2024-01-01T12:00:00Z",
"createdBy": "admin"
},
"message": "组创建成功"
}
```
## 批量创建
### 批量创建服务器
```http
POST /api/servers/bulk
```
#### 请求体
```json
{
"servers": [
{
"name": "dev-server-1",
"command": "python",
"args": ["-m", "mcp_server"],
"env": { "ENV": "development" }
},
{
"name": "dev-server-2",
"command": "node",
"args": ["server.js"],
"env": { "ENV": "development" }
}
],
"options": {
"skipExisting": true,
"validateAll": true,
"startAfterCreate": false
}
}
```
#### 响应 (201 Created)
```json
{
"success": true,
"data": {
"created": [
{
"id": "server-1",
"name": "dev-server-1",
"status": "created"
},
{
"id": "server-2",
"name": "dev-server-2",
"status": "created"
}
],
"skipped": [],
"failed": [],
"summary": {
"total": 2,
"created": 2,
"skipped": 0,
"failed": 0
}
},
"message": "批量创建完成,成功创建 2 个服务器"
}
```
## 验证
### 预验证创建请求
在实际创建资源之前验证请求:
```http
POST /api/servers/validate
```
#### 请求体
```json
{
"name": "test-server",
"command": "invalid-command",
"args": []
}
```
#### 响应
```json
{
"success": false,
"data": {
"valid": false,
"errors": [
{
"field": "command",
"message": "命令 'invalid-command' 不存在或无法执行"
}
],
"warnings": [
{
"field": "args",
"message": "参数数组为空,服务器可能无法正常启动"
}
]
}
}
```
有关更多 API 端点信息,请参阅 [获取资源](/zh/api-reference/endpoint/get)、[删除资源](/zh/api-reference/endpoint/delete) 和 [WebHooks](/zh/api-reference/endpoint/webhook) 文档。

View File

@@ -0,0 +1,303 @@
---
title: 删除资源 API
description: 删除各种资源的 API 端点,包括服务器、组和配置等
---
# 删除资源 API
本文档描述了用于删除各种资源的 API 端点。
## 删除 MCP 服务器
删除指定的 MCP 服务器配置。
### 端点
```http
DELETE /api/servers/{id}
```
### 参数
| 参数名 | 类型 | 位置 | 必需 | 描述 |
| ------ | ------ | ---- | ---- | ------------------ |
| id | string | path | 是 | 服务器的唯一标识符 |
### 请求示例
```bash
curl -X DELETE \
'https://api.mcphub.io/api/servers/mcp-server-123' \
-H 'Authorization: Bearer YOUR_API_TOKEN' \
-H 'Content-Type: application/json'
```
### 响应
#### 成功响应 (204 No Content)
```json
{
"success": true,
"message": "服务器已成功删除",
"data": {
"id": "mcp-server-123",
"deletedAt": "2024-01-15T10:30:00Z"
}
}
```
#### 错误响应
**404 Not Found**
```json
{
"error": {
"code": "SERVER_NOT_FOUND",
"message": "指定的服务器不存在",
"details": {
"serverId": "mcp-server-123"
}
}
}
```
**409 Conflict**
```json
{
"error": {
"code": "SERVER_IN_USE",
"message": "服务器正在使用中,无法删除",
"details": {
"activeConnections": 5,
"associatedGroups": ["group-1", "group-2"]
}
}
}
```
## 删除服务器组
删除指定的服务器组。
### 端点
```http
DELETE /api/groups/{id}
```
### 参数
| 参数名 | 类型 | 位置 | 必需 | 描述 |
| ------ | ------- | ----- | ---- | ------------------------------ |
| id | string | path | 是 | 组的唯一标识符 |
| force | boolean | query | 否 | 是否强制删除(包含服务器的组) |
### 请求示例
```bash
curl -X DELETE \
'https://api.mcphub.io/api/groups/production-group?force=true' \
-H 'Authorization: Bearer YOUR_API_TOKEN' \
-H 'Content-Type: application/json'
```
### 响应
#### 成功响应 (204 No Content)
```json
{
"success": true,
"message": "服务器组已成功删除",
"data": {
"id": "production-group",
"deletedServers": ["server-1", "server-2"],
"deletedAt": "2024-01-15T10:30:00Z"
}
}
```
## 删除配置项
删除指定的配置项。
### 端点
```http
DELETE /api/config/{key}
```
### 参数
| 参数名 | 类型 | 位置 | 必需 | 描述 |
| ------ | ------ | ---- | ---- | -------- |
| key | string | path | 是 | 配置键名 |
### 请求示例
```bash
curl -X DELETE \
'https://api.mcphub.io/api/config/custom-setting' \
-H 'Authorization: Bearer YOUR_API_TOKEN'
```
### 响应
#### 成功响应 (200 OK)
```json
{
"success": true,
"message": "配置项已删除",
"data": {
"key": "custom-setting",
"previousValue": "old-value",
"deletedAt": "2024-01-15T10:30:00Z"
}
}
```
## 批量删除
### 批量删除服务器
删除多个 MCP 服务器。
#### 端点
```http
DELETE /api/servers/batch
```
#### 请求体
```json
{
"serverIds": ["server-1", "server-2", "server-3"],
"force": false
}
```
#### 响应
```json
{
"success": true,
"message": "批量删除完成",
"data": {
"deleted": ["server-1", "server-3"],
"failed": [
{
"id": "server-2",
"reason": "服务器正在使用中"
}
],
"summary": {
"total": 3,
"deleted": 2,
"failed": 1
}
}
}
```
## 软删除 vs 硬删除
### 软删除
默认情况下MCPHub 使用软删除机制:
- 资源被标记为已删除但保留在数据库中
- 可以通过恢复 API 恢复删除的资源
- 删除的资源在列表 API 中默认不显示
### 硬删除
使用 `permanent=true` 参数执行硬删除:
```bash
curl -X DELETE \
'https://api.mcphub.io/api/servers/mcp-server-123?permanent=true' \
-H 'Authorization: Bearer YOUR_API_TOKEN'
```
<Warning>硬删除操作不可逆,请谨慎使用。</Warning>
## 权限要求
| 操作 | 所需权限 |
| ---------- | ------------------------ |
| 删除服务器 | `servers:delete` |
| 删除组 | `groups:delete` |
| 删除配置 | `config:delete` |
| 硬删除 | `admin:permanent_delete` |
## 错误代码
| 错误代码 | HTTP 状态码 | 描述 |
| -------------------------- | ----------- | ---------------- |
| `RESOURCE_NOT_FOUND` | 404 | 资源不存在 |
| `RESOURCE_IN_USE` | 409 | 资源正在使用中 |
| `INSUFFICIENT_PERMISSIONS` | 403 | 权限不足 |
| `VALIDATION_ERROR` | 400 | 请求参数验证失败 |
| `INTERNAL_ERROR` | 500 | 服务器内部错误 |
## 最佳实践
### 1. 删除前检查
在删除资源前,建议先检查资源的使用情况:
```bash
# 检查服务器使用情况
curl -X GET \
'https://api.mcphub.io/api/servers/mcp-server-123/usage' \
-H 'Authorization: Bearer YOUR_API_TOKEN'
```
### 2. 备份重要数据
对于重要资源,建议在删除前进行备份:
```bash
# 导出服务器配置
curl -X GET \
'https://api.mcphub.io/api/servers/mcp-server-123/export' \
-H 'Authorization: Bearer YOUR_API_TOKEN' \
> server-backup.json
```
### 3. 使用事务删除
对于复杂的删除操作,使用事务确保数据一致性:
```json
{
"transaction": true,
"operations": [
{
"type": "delete",
"resource": "server",
"id": "server-1"
},
{
"type": "delete",
"resource": "group",
"id": "group-1"
}
]
}
```
## 恢复删除的资源
软删除的资源可以通过恢复 API 恢复:
```bash
curl -X POST \
'https://api.mcphub.io/api/servers/mcp-server-123/restore' \
-H 'Authorization: Bearer YOUR_API_TOKEN'
```

View File

@@ -0,0 +1,607 @@
---
title: '获取资源'
description: '查询和检索 MCP 服务器、用户和组信息'
---
## 获取服务器列表
### 端点
```http
GET /api/servers
```
### 查询参数
| 参数 | 类型 | 描述 | 示例 |
| ---------------- | ------- | ------------------------------- | ---------------------------- |
| `page` | integer | 页码(从 1 开始) | `?page=2` |
| `limit` | integer | 每页记录数(默认 20最大 100 | `?limit=50` |
| `sort` | string | 排序字段 | `?sort=name` |
| `order` | string | 排序顺序asc/desc | `?order=desc` |
| `status` | string | 过滤服务器状态 | `?status=running` |
| `search` | string | 搜索服务器名称或描述 | `?search=python` |
| `group` | string | 过滤所属组 | `?group=dev-team` |
| `tags` | string | 过滤标签(逗号分隔) | `?tags=python,production` |
| `enabled` | boolean | 过滤启用状态 | `?enabled=true` |
| `created_after` | string | 创建时间起始 | `?created_after=2024-01-01` |
| `created_before` | string | 创建时间结束 | `?created_before=2024-01-31` |
### 响应
```json
{
"success": true,
"data": {
"items": [
{
"id": "server-abc123",
"name": "文件系统服务器",
"status": "running",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"],
"env": {
"NODE_ENV": "production"
},
"cwd": "/app",
"pid": 12345,
"uptime": 3600000,
"enabled": true,
"description": "提供文件系统访问的 MCP 服务器",
"tags": ["filesystem", "production"],
"health": {
"status": "healthy",
"lastCheck": "2024-01-01T12:00:00Z",
"responseTime": "45ms"
},
"resources": {
"memory": {
"used": "128MB",
"limit": "512MB",
"percentage": 25
},
"cpu": {
"used": "15%",
"limit": "50%"
}
},
"stats": {
"totalRequests": 1523,
"errorCount": 2,
"avgResponseTime": "234ms"
},
"lastRestart": "2024-01-01T08:00:00Z",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T12:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 45,
"pages": 3,
"hasNext": true,
"hasPrev": false
},
"filters": {
"status": "running",
"totalFiltered": 12
}
}
}
```
### 示例
```bash
# 获取运行中的服务器,按名称排序
curl -X GET "http://localhost:3000/api/servers?status=running&sort=name&order=asc" \
-H "Authorization: Bearer $TOKEN"
# 搜索包含 "python" 的服务器
curl -X GET "http://localhost:3000/api/servers?search=python&limit=10" \
-H "Authorization: Bearer $TOKEN"
# 获取开发团队的服务器
curl -X GET "http://localhost:3000/api/servers?group=dev-team" \
-H "Authorization: Bearer $TOKEN"
```
## 获取服务器详情
### 端点
```http
GET /api/servers/{serverId}
```
### 路径参数
- `serverId` (string): 服务器唯一标识符
### 查询参数
| 参数 | 类型 | 描述 |
| --------------- | ------ | ----------------------------------------------- |
| `include` | string | 包含额外信息(逗号分隔):`logs,metrics,events` |
| `metrics_range` | string | 指标时间范围:`1h`, `24h`, `7d` |
### 响应
```json
{
"success": true,
"data": {
"id": "server-abc123",
"name": "文件系统服务器",
"status": "running",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"],
"env": {
"NODE_ENV": "production",
"DEBUG": "mcp:*"
},
"cwd": "/app",
"pid": 12345,
"uptime": 3600000,
"enabled": true,
"description": "提供文件系统访问的 MCP 服务器",
"tags": ["filesystem", "production"],
"healthCheck": {
"enabled": true,
"interval": 30000,
"timeout": 5000,
"retries": 3,
"endpoint": "/health",
"status": "healthy",
"lastCheck": "2024-01-01T12:00:00Z",
"responseTime": "45ms",
"consecutiveFailures": 0
},
"resources": {
"memory": {
"used": "128MB",
"limit": "512MB",
"warning": "400MB",
"percentage": 25
},
"cpu": {
"used": "15%",
"limit": "50%",
"cores": 4
},
"network": {
"bytesIn": "1.2GB",
"bytesOut": "890MB"
}
},
"stats": {
"totalRequests": 1523,
"successfulRequests": 1521,
"errorCount": 2,
"avgResponseTime": "234ms",
"p95ResponseTime": "450ms",
"requestsPerMinute": 25,
"lastError": {
"timestamp": "2024-01-01T11:30:00Z",
"message": "Temporary connection timeout",
"count": 1
}
},
"capabilities": [
{
"type": "tool",
"name": "read_file",
"description": "读取文件内容",
"schema": {
"type": "object",
"properties": {
"path": { "type": "string" }
}
}
},
{
"type": "tool",
"name": "write_file",
"description": "写入文件内容",
"schema": {
"type": "object",
"properties": {
"path": { "type": "string" },
"content": { "type": "string" }
}
}
}
],
"groups": [
{
"id": "dev-team",
"name": "开发团队",
"permissions": ["read", "write", "execute"]
}
],
"events": [
{
"id": "event-123",
"type": "started",
"timestamp": "2024-01-01T08:00:00Z",
"message": "服务器启动成功",
"metadata": {
"pid": 12345,
"startupTime": "2.3s"
}
}
],
"lastRestart": "2024-01-01T08:00:00Z",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T12:00:00Z",
"createdBy": "admin"
}
}
```
### 示例
```bash
# 获取服务器基本信息
curl -X GET "http://localhost:3000/api/servers/server-abc123" \
-H "Authorization: Bearer $TOKEN"
# 获取服务器详情包含日志和指标
curl -X GET "http://localhost:3000/api/servers/server-abc123?include=logs,metrics&metrics_range=24h" \
-H "Authorization: Bearer $TOKEN"
```
## 获取服务器状态
### 端点
```http
GET /api/servers/{serverId}/status
```
### 响应
```json
{
"success": true,
"data": {
"serverId": "server-abc123",
"status": "running",
"health": "healthy",
"pid": 12345,
"uptime": 3600000,
"startedAt": "2024-01-01T08:00:00Z",
"lastHealthCheck": "2024-01-01T12:00:00Z",
"resources": {
"memory": {
"rss": 134217728,
"heapTotal": 67108864,
"heapUsed": 45088768,
"external": 8388608
},
"cpu": {
"user": 1000000,
"system": 500000,
"percentage": 15.5
}
},
"connections": {
"active": 5,
"total": 127
},
"performance": {
"requestsPerSecond": 12.5,
"avgResponseTime": "234ms",
"errorRate": "0.1%"
}
}
}
```
## 获取服务器日志
### 端点
```http
GET /api/servers/{serverId}/logs
```
### 查询参数
| 参数 | 类型 | 描述 |
| -------- | ------- | ---------------------------------------------- |
| `level` | string | 日志级别过滤:`error`, `warn`, `info`, `debug` |
| `limit` | integer | 返回日志条数(默认 100最大 1000 |
| `since` | string | 开始时间ISO 8601 格式) |
| `until` | string | 结束时间ISO 8601 格式) |
| `follow` | boolean | 实时跟踪日志流 |
| `search` | string | 搜索日志内容 |
### 响应
```json
{
"success": true,
"data": {
"logs": [
{
"id": "log-123",
"timestamp": "2024-01-01T12:00:00Z",
"level": "info",
"message": "处理请求: read_file",
"source": "mcp-server",
"metadata": {
"requestId": "req-456",
"userId": "user-789",
"duration": "45ms"
}
},
{
"id": "log-124",
"timestamp": "2024-01-01T12:00:05Z",
"level": "error",
"message": "文件不存在: /nonexistent/file.txt",
"source": "filesystem",
"metadata": {
"requestId": "req-457",
"path": "/nonexistent/file.txt",
"error": "ENOENT"
}
}
],
"pagination": {
"limit": 100,
"total": 1523,
"hasMore": true,
"nextCursor": "cursor-abc123"
}
}
}
```
### 实时日志流
```bash
# 实时跟踪日志
curl -X GET "http://localhost:3000/api/servers/server-abc123/logs?follow=true" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: text/event-stream"
```
## 获取服务器指标
### 端点
```http
GET /api/servers/{serverId}/metrics
```
### 查询参数
| 参数 | 类型 | 描述 |
| ------------- | ------ | ------------------------------------------- |
| `timeRange` | string | 时间范围:`1h`, `24h`, `7d`, `30d` |
| `granularity` | string | 数据粒度:`1m`, `5m`, `1h`, `1d` |
| `metrics` | string | 指定指标(逗号分隔):`cpu,memory,requests` |
### 响应
```json
{
"success": true,
"data": {
"timeRange": "1h",
"granularity": "5m",
"metrics": {
"cpu": {
"data": [
{ "timestamp": "2024-01-01T11:00:00Z", "value": 12.5 },
{ "timestamp": "2024-01-01T11:05:00Z", "value": 15.2 }
],
"summary": {
"avg": 13.8,
"min": 8.1,
"max": 18.5,
"current": 15.2
}
},
"memory": {
"data": [
{ "timestamp": "2024-01-01T11:00:00Z", "value": 125 },
{ "timestamp": "2024-01-01T11:05:00Z", "value": 128 }
],
"summary": {
"avg": 126.5,
"min": 120,
"max": 135,
"current": 128
}
},
"requests": {
"data": [
{ "timestamp": "2024-01-01T11:00:00Z", "value": 45 },
{ "timestamp": "2024-01-01T11:05:00Z", "value": 52 }
],
"summary": {
"total": 2847,
"avg": 48.5,
"peak": 67
}
},
"responseTime": {
"data": [
{ "timestamp": "2024-01-01T11:00:00Z", "avg": 230, "p95": 450 },
{ "timestamp": "2024-01-01T11:05:00Z", "avg": 245, "p95": 480 }
],
"summary": {
"avgResponseTime": "237ms",
"p95ResponseTime": "465ms"
}
}
}
}
}
```
## 获取用户列表
### 端点
```http
GET /api/users
```
### 查询参数
| 参数 | 类型 | 描述 |
| ------------------ | ------- | ---------------- |
| `role` | string | 过滤用户角色 |
| `group` | string | 过滤所属组 |
| `enabled` | boolean | 过滤启用状态 |
| `search` | string | 搜索用户名或邮箱 |
| `last_login_after` | string | 最后登录时间起始 |
### 响应
```json
{
"success": true,
"data": {
"items": [
{
"id": "user-abc123",
"username": "zhangsan",
"email": "zhangsan@example.com",
"role": "user",
"enabled": true,
"profile": {
"firstName": "张",
"lastName": "三",
"fullName": "张三",
"department": "开发部",
"title": "软件工程师"
},
"groups": [
{
"id": "dev-team",
"name": "开发团队",
"role": "member"
}
],
"stats": {
"totalSessions": 45,
"totalRequests": 1234,
"lastRequestAt": "2024-01-01T11:30:00Z"
},
"lastLoginAt": "2024-01-01T08:00:00Z",
"createdAt": "2023-12-01T00:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 89,
"pages": 5
}
}
}
```
## 获取组列表
### 端点
```http
GET /api/groups
```
### 响应
```json
{
"success": true,
"data": {
"items": [
{
"id": "group-abc123",
"name": "dev-team",
"displayName": "开发团队",
"description": "前端和后端开发人员",
"memberCount": 12,
"serverCount": 8,
"parentGroup": null,
"children": [],
"permissions": {
"servers": ["read", "write", "execute"],
"tools": ["read", "execute"]
},
"quotas": {
"requests": {
"used": 750,
"limit": 1000
}
},
"createdAt": "2023-12-01T00:00:00Z"
}
]
}
}
```
## 搜索
### 全局搜索
```http
GET /api/search
```
### 查询参数
| 参数 | 类型 | 描述 |
| ------- | ------- | ---------------------------------------------- |
| `q` | string | 搜索关键词 |
| `type` | string | 资源类型:`servers`, `users`, `groups`, `logs` |
| `limit` | integer | 每种类型的最大结果数 |
### 响应
```json
{
"success": true,
"data": {
"query": "python",
"results": {
"servers": [
{
"id": "server-1",
"name": "Python MCP Server",
"type": "server",
"relevance": 0.95
}
],
"users": [],
"groups": [
{
"id": "python-devs",
"name": "Python 开发者",
"type": "group",
"relevance": 0.8
}
],
"logs": [
{
"id": "log-123",
"message": "Starting Python server...",
"type": "log",
"relevance": 0.7
}
]
},
"total": 3
}
}
```
有关更多信息,请参阅 [创建资源](/zh/api-reference/endpoint/create)、[删除资源](/zh/api-reference/endpoint/delete) 和 [WebHooks](/zh/api-reference/endpoint/webhook) 文档。

View File

@@ -0,0 +1,615 @@
---
title: WebHooks API
description: 配置和管理 WebHook 事件通知的完整指南
---
# WebHooks API
WebHooks 允许 MCPHub 在特定事件发生时向您的应用程序发送实时通知。
## 概述
MCPHub WebHooks 系统支持以下功能:
- 实时事件通知
- 自定义过滤器
- 重试机制
- 签名验证
- 批量事件处理
## 支持的事件类型
| 事件类型 | 描述 |
| ----------------------- | -------------- |
| `server.created` | MCP 服务器创建 |
| `server.updated` | MCP 服务器更新 |
| `server.deleted` | MCP 服务器删除 |
| `server.status_changed` | 服务器状态变更 |
| `group.created` | 服务器组创建 |
| `group.updated` | 服务器组更新 |
| `group.deleted` | 服务器组删除 |
| `user.login` | 用户登录 |
| `user.logout` | 用户登出 |
| `config.changed` | 配置变更 |
| `system.error` | 系统错误 |
## 创建 WebHook
### 端点
```http
POST /api/webhooks
```
### 请求体
```json
{
"url": "https://your-app.com/webhook",
"events": ["server.created", "server.status_changed"],
"secret": "your-webhook-secret",
"active": true,
"config": {
"contentType": "application/json",
"insecureSsl": false,
"retryCount": 3,
"timeout": 30
},
"filters": {
"serverGroups": ["production", "staging"],
"serverTypes": ["ai-assistant", "data-processor"]
}
}
```
### 响应
```json
{
"success": true,
"data": {
"id": "webhook-123",
"url": "https://your-app.com/webhook",
"events": ["server.created", "server.status_changed"],
"active": true,
"secret": "your-webhook-secret",
"config": {
"contentType": "application/json",
"insecureSsl": false,
"retryCount": 3,
"timeout": 30
},
"filters": {
"serverGroups": ["production", "staging"],
"serverTypes": ["ai-assistant", "data-processor"]
},
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
}
```
## 获取 WebHook 列表
### 端点
```http
GET /api/webhooks
```
### 查询参数
| 参数名 | 类型 | 描述 |
| ------ | ------- | -------------------- |
| page | integer | 页码默认1 |
| limit | integer | 每页数量默认20 |
| active | boolean | 过滤活跃状态 |
| event | string | 过滤事件类型 |
### 请求示例
```bash
curl -X GET \
'https://api.mcphub.io/api/webhooks?active=true&limit=10' \
-H 'Authorization: Bearer YOUR_API_TOKEN'
```
### 响应
```json
{
"success": true,
"data": {
"webhooks": [
{
"id": "webhook-123",
"url": "https://your-app.com/webhook",
"events": ["server.created", "server.status_changed"],
"active": true,
"lastDelivery": "2024-01-15T09:30:00Z",
"deliveryCount": 145,
"failureCount": 2,
"createdAt": "2024-01-10T10:30:00Z"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 25,
"pages": 3
}
}
}
```
## 获取单个 WebHook
### 端点
```http
GET /api/webhooks/{id}
```
### 响应
```json
{
"success": true,
"data": {
"id": "webhook-123",
"url": "https://your-app.com/webhook",
"events": ["server.created", "server.status_changed"],
"active": true,
"secret": "your-webhook-secret",
"config": {
"contentType": "application/json",
"insecureSsl": false,
"retryCount": 3,
"timeout": 30
},
"filters": {
"serverGroups": ["production", "staging"],
"serverTypes": ["ai-assistant", "data-processor"]
},
"stats": {
"totalDeliveries": 145,
"successfulDeliveries": 143,
"failedDeliveries": 2,
"lastDelivery": "2024-01-15T09:30:00Z",
"lastSuccess": "2024-01-15T09:30:00Z",
"lastFailure": "2024-01-14T15:20:00Z"
},
"createdAt": "2024-01-10T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
}
```
## 更新 WebHook
### 端点
```http
PUT /api/webhooks/{id}
```
### 请求体
```json
{
"url": "https://your-app.com/new-webhook",
"events": ["server.created", "server.updated", "server.deleted"],
"active": true,
"config": {
"retryCount": 5,
"timeout": 45
}
}
```
## 删除 WebHook
### 端点
```http
DELETE /api/webhooks/{id}
```
### 响应
```json
{
"success": true,
"message": "WebHook 已成功删除"
}
```
## WebHook 事件格式
### 基本结构
所有 WebHook 事件都遵循以下基本结构:
```json
{
"id": "event-123",
"type": "server.created",
"timestamp": "2024-01-15T10:30:00Z",
"version": "1.0",
"data": {
// 事件特定数据
},
"metadata": {
"source": "mcphub",
"environment": "production",
"triggeredBy": "user-456"
}
}
```
### 服务器事件示例
#### server.created
```json
{
"id": "event-123",
"type": "server.created",
"timestamp": "2024-01-15T10:30:00Z",
"version": "1.0",
"data": {
"server": {
"id": "mcp-server-123",
"name": "AI Assistant Server",
"type": "ai-assistant",
"endpoint": "https://ai-assistant.example.com",
"group": "production",
"status": "active",
"capabilities": ["chat", "completion"],
"createdAt": "2024-01-15T10:30:00Z"
}
},
"metadata": {
"source": "mcphub",
"environment": "production",
"triggeredBy": "user-456"
}
}
```
#### server.status_changed
```json
{
"id": "event-124",
"type": "server.status_changed",
"timestamp": "2024-01-15T11:30:00Z",
"version": "1.0",
"data": {
"server": {
"id": "mcp-server-123",
"name": "AI Assistant Server",
"previousStatus": "active",
"currentStatus": "inactive",
"reason": "Health check failed",
"lastHealthCheck": "2024-01-15T11:25:00Z"
}
},
"metadata": {
"source": "mcphub",
"environment": "production",
"triggeredBy": "system"
}
}
```
## 签名验证
MCPHub 使用 HMAC-SHA256 签名来验证 WebHook 的真实性。
### 签名生成
签名在 `X-MCPHub-Signature-256` 头中发送:
```
X-MCPHub-Signature-256: sha256=5757107ea39eca8e35d1e8...
```
### 验证示例
#### Node.js
```javascript
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
const actualSignature = signature.replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(actualSignature, 'hex'),
);
}
// Express.js 中间件示例
app.use('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-mcphub-signature-256'];
const payload = req.body;
if (!verifySignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Unauthorized');
}
// 处理 WebHook 事件
const event = JSON.parse(payload);
console.log('收到事件:', event.type);
res.status(200).send('OK');
});
```
#### Python
```python
import hmac
import hashlib
def verify_signature(payload, signature, secret):
expected_signature = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
actual_signature = signature.replace('sha256=', '')
return hmac.compare_digest(expected_signature, actual_signature)
# Flask 示例
from flask import Flask, request, jsonify
import json
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-MCPHub-Signature-256')
payload = request.get_data()
if not verify_signature(payload, signature, 'your-webhook-secret'):
return jsonify({'error': 'Unauthorized'}), 401
event = json.loads(payload)
print(f'收到事件: {event["type"]}')
return jsonify({'status': 'success'}), 200
```
## 重试机制
MCPHub 对失败的 WebHook 交付实施指数退避重试:
- **重试次数**: 可配置(默认 3 次)
- **重试间隔**: 2^n 秒n 为重试次数)
- **最大间隔**: 300 秒5 分钟)
- **超时设置**: 可配置(默认 30 秒)
### 重试时间表
| 尝试次数 | 延迟时间 |
| -------- | -------- |
| 1 | 立即 |
| 2 | 2 秒 |
| 3 | 4 秒 |
| 4 | 8 秒 |
| 5 | 16 秒 |
## 获取交付历史
### 端点
```http
GET /api/webhooks/{id}/deliveries
```
### 查询参数
| 参数名 | 类型 | 描述 |
| ---------- | ------- | ------------------------------------ |
| page | integer | 页码 |
| limit | integer | 每页数量 |
| status | string | 过滤状态success, failed, pending |
| event_type | string | 过滤事件类型 |
### 响应
```json
{
"success": true,
"data": {
"deliveries": [
{
"id": "delivery-123",
"eventId": "event-123",
"eventType": "server.created",
"url": "https://your-app.com/webhook",
"status": "success",
"responseCode": 200,
"responseTime": 145,
"attempts": 1,
"deliveredAt": "2024-01-15T10:30:15Z",
"nextRetry": null
},
{
"id": "delivery-124",
"eventId": "event-124",
"eventType": "server.status_changed",
"url": "https://your-app.com/webhook",
"status": "failed",
"responseCode": 500,
"responseTime": 30000,
"attempts": 3,
"error": "Connection timeout",
"deliveredAt": null,
"nextRetry": "2024-01-15T11:45:00Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 145,
"pages": 8
}
}
}
```
## 测试 WebHook
### 端点
```http
POST /api/webhooks/{id}/test
```
### 请求体
```json
{
"eventType": "server.created",
"customData": {
"test": true,
"message": "这是一个测试事件"
}
}
```
### 响应
```json
{
"success": true,
"data": {
"deliveryId": "delivery-test-123",
"status": "delivered",
"responseCode": 200,
"responseTime": 124,
"sentAt": "2024-01-15T10:30:00Z"
}
}
```
## 最佳实践
### 1. 幂等性处理
确保您的 WebHook 端点能够处理重复事件:
```javascript
const processedEvents = new Set();
app.post('/webhook', (req, res) => {
const event = req.body;
// 检查事件是否已处理
if (processedEvents.has(event.id)) {
return res.status(200).send('Already processed');
}
// 处理事件
processEvent(event);
// 记录已处理的事件
processedEvents.add(event.id);
res.status(200).send('OK');
});
```
### 2. 异步处理
对于复杂的处理逻辑,使用异步处理避免阻塞:
```javascript
app.post('/webhook', async (req, res) => {
const event = req.body;
// 立即响应
res.status(200).send('OK');
// 异步处理事件
setImmediate(() => {
processEventAsync(event);
});
});
```
### 3. 错误处理
实施适当的错误处理和日志记录:
```javascript
app.post('/webhook', (req, res) => {
try {
const event = req.body;
processEvent(event);
res.status(200).send('OK');
} catch (error) {
console.error('WebHook 处理错误:', error);
res.status(500).send('Internal Server Error');
}
});
```
### 4. 监控和告警
监控 WebHook 的交付状态:
```bash
# 检查失败的交付
curl -X GET \
'https://api.mcphub.io/api/webhooks/webhook-123/deliveries?status=failed' \
-H 'Authorization: Bearer YOUR_API_TOKEN'
```
## 故障排除
### 常见问题
1. **签名验证失败**
- 检查密钥是否正确
- 确保使用原始请求体进行验证
- 验证 HMAC 计算实现
2. **超时错误**
- 增加 WebHook 超时设置
- 优化端点响应时间
- 使用异步处理
3. **重复事件**
- 实施幂等性检查
- 使用事件 ID 去重
- 记录处理状态
### 调试工具
使用 MCPHub 提供的调试工具:
```bash
# 查看最近的交付日志
curl -X GET \
'https://api.mcphub.io/api/webhooks/webhook-123/deliveries?limit=5' \
-H 'Authorization: Bearer YOUR_API_TOKEN'
# 重新发送失败的事件
curl -X POST \
'https://api.mcphub.io/api/webhooks/delivery-124/redeliver' \
-H 'Authorization: Bearer YOUR_API_TOKEN'
```

View File

@@ -0,0 +1,717 @@
---
title: 'API 参考'
description: 'MCPHub REST API 完整参考文档'
---
## 概述
MCPHub 提供全面的 REST API用于管理 MCP 服务器、用户、组和监控。所有 API 端点都需要身份验证,并支持 JSON 格式的请求和响应。
## 基础信息
### 基础 URL
```
https://your-mcphub-instance.com/api
```
### 身份验证
所有 API 请求都需要身份验证。支持以下方法:
#### JWT 令牌认证
```bash
curl -X GET https://api.mcphub.com/servers \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
#### API 密钥认证
```bash
curl -X GET https://api.mcphub.com/servers \
-H "X-API-Key: YOUR_API_KEY"
```
### 请求格式
- **Content-Type**: `application/json`
- **Accept**: `application/json`
- **User-Agent**: 建议包含您的应用程序名称和版本
### 响应格式
所有响应都采用 JSON 格式:
```json
{
"success": true,
"data": {
// 响应数据
},
"message": "操作成功",
"timestamp": "2024-01-01T12:00:00Z"
}
```
错误响应格式:
```json
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "请求数据无效",
"details": {
"field": "name",
"reason": "名称不能为空"
}
},
"timestamp": "2024-01-01T12:00:00Z"
}
```
## 状态码
| 状态码 | 说明 |
| ------ | -------------------- |
| 200 | 请求成功 |
| 201 | 资源创建成功 |
| 204 | 请求成功,无返回内容 |
| 400 | 请求参数错误 |
| 401 | 未授权访问 |
| 403 | 权限不足 |
| 404 | 资源不存在 |
| 409 | 资源冲突 |
| 422 | 请求数据验证失败 |
| 429 | 请求频率超限 |
| 500 | 服务器内部错误 |
## 分页
支持分页的端点使用以下参数:
- `page`: 页码(从 1 开始)
- `limit`: 每页记录数(默认 20最大 100
- `sort`: 排序字段
- `order`: 排序顺序(`asc` 或 `desc`
```bash
curl -X GET "https://api.mcphub.com/servers?page=2&limit=50&sort=name&order=asc" \
-H "Authorization: Bearer $TOKEN"
```
分页响应格式:
```json
{
"success": true,
"data": {
"items": [...],
"pagination": {
"page": 2,
"limit": 50,
"total": 234,
"pages": 5,
"hasNext": true,
"hasPrev": true
}
}
}
```
## 过滤和搜索
支持过滤的端点可以使用以下参数:
- `search`: 全文搜索
- `filter[field]`: 字段过滤
- `status`: 状态过滤
- `created_after`: 创建时间筛选
- `created_before`: 创建时间筛选
```bash
curl -X GET "https://api.mcphub.com/servers?search=python&filter[status]=running&created_after=2024-01-01" \
-H "Authorization: Bearer $TOKEN"
```
## API 端点
### 服务器管理
#### 获取服务器列表
```http
GET /api/servers
```
参数:
- `status` (可选): 过滤服务器状态 (`running`, `stopped`, `error`)
- `group` (可选): 过滤所属组
- `search` (可选): 搜索服务器名称或描述
示例响应:
```json
{
"success": true,
"data": {
"items": [
{
"id": "server-1",
"name": "文件系统服务器",
"status": "running",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"],
"env": {
"NODE_ENV": "production"
},
"cwd": "/app",
"pid": 12345,
"uptime": 3600000,
"lastRestart": "2024-01-01T12:00:00Z",
"createdAt": "2024-01-01T10:00:00Z",
"updatedAt": "2024-01-01T12:00:00Z"
}
]
}
}
```
#### 创建服务器
```http
POST /api/servers
```
请求体:
```json
{
"name": "新服务器",
"command": "python",
"args": ["-m", "mcp_server"],
"env": {
"API_KEY": "your-api-key",
"LOG_LEVEL": "INFO"
},
"cwd": "/app/python-server",
"enabled": true,
"description": "Python MCP 服务器",
"tags": ["python", "production"]
}
```
#### 获取服务器详情
```http
GET /api/servers/{serverId}
```
#### 更新服务器
```http
PUT /api/servers/{serverId}
```
#### 删除服务器
```http
DELETE /api/servers/{serverId}
```
#### 启动服务器
```http
POST /api/servers/{serverId}/start
```
#### 停止服务器
```http
POST /api/servers/{serverId}/stop
```
请求体(可选):
```json
{
"graceful": true,
"timeout": 30000
}
```
#### 重启服务器
```http
POST /api/servers/{serverId}/restart
```
#### 获取服务器日志
```http
GET /api/servers/{serverId}/logs
```
参数:
- `level` (可选): 日志级别过滤
- `limit` (可选): 返回日志条数
- `since` (可选): 开始时间
- `follow` (可选): 实时跟踪日志
### 用户管理
#### 获取用户列表
```http
GET /api/users
```
#### 创建用户
```http
POST /api/users
```
请求体:
```json
{
"username": "newuser",
"email": "user@example.com",
"password": "securepassword",
"role": "user",
"groups": ["dev-team"],
"profile": {
"firstName": "张",
"lastName": "三",
"department": "开发部"
}
}
```
#### 获取用户详情
```http
GET /api/users/{userId}
```
#### 更新用户
```http
PUT /api/users/{userId}
```
#### 删除用户
```http
DELETE /api/users/{userId}
```
### 组管理
#### 获取组列表
```http
GET /api/groups
```
#### 创建组
```http
POST /api/groups
```
请求体:
```json
{
"name": "dev-team",
"displayName": "开发团队",
"description": "前端和后端开发人员",
"parentGroup": null,
"permissions": {
"servers": ["read", "write", "execute"],
"tools": ["read", "execute"]
},
"settings": {
"autoAssign": false,
"maxMembers": 50,
"requireApproval": true
}
}
```
#### 添加用户到组
```http
POST /api/groups/{groupId}/members
```
请求体:
```json
{
"userId": "user123",
"role": "member"
}
```
#### 从组中移除用户
```http
DELETE /api/groups/{groupId}/members/{userId}
```
#### 分配服务器到组
```http
POST /api/groups/{groupId}/servers
```
请求体:
```json
{
"serverId": "server-1",
"permissions": ["read", "write", "execute"]
}
```
### 身份验证
#### 登录
```http
POST /api/auth/login
```
请求体:
```json
{
"username": "admin",
"password": "password",
"mfaCode": "123456"
}
```
响应:
```json
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "refresh_token_here",
"expiresIn": 86400,
"user": {
"id": "user123",
"username": "admin",
"role": "admin",
"permissions": ["*"]
}
}
}
```
#### 刷新令牌
```http
POST /api/auth/refresh
```
#### 注销
```http
POST /api/auth/logout
```
#### 验证令牌
```http
GET /api/auth/verify
```
### 监控
#### 获取系统状态
```http
GET /api/monitoring/status
```
响应:
```json
{
"success": true,
"data": {
"system": {
"uptime": 86400,
"version": "2.1.0",
"nodeVersion": "18.17.0"
},
"servers": {
"total": 12,
"running": 10,
"stopped": 1,
"error": 1
},
"performance": {
"requestsPerMinute": 85,
"avgResponseTime": "245ms",
"errorRate": "0.3%"
}
}
}
```
#### 获取性能指标
```http
GET /api/monitoring/metrics
```
参数:
- `timeRange`: 时间范围 (`1h`, `24h`, `7d`, `30d`)
- `granularity`: 数据粒度 (`1m`, `5m`, `1h`, `1d`)
- `metrics`: 指定指标名称(逗号分隔)
#### 获取日志
```http
GET /api/monitoring/logs
```
参数:
- `level`: 日志级别
- `source`: 日志源
- `limit`: 返回条数
- `since`: 开始时间
- `until`: 结束时间
### 配置管理
#### 获取系统配置
```http
GET /api/config
```
#### 更新系统配置
```http
PUT /api/config
```
请求体:
```json
{
"smtp": {
"host": "smtp.example.com",
"port": 587,
"secure": false,
"auth": {
"user": "noreply@example.com",
"pass": "password"
}
},
"notifications": {
"email": true,
"slack": true,
"webhook": "https://hooks.example.com/notifications"
}
}
```
## WebSocket API
MCPHub 支持 WebSocket 连接以获取实时更新。
### 连接
```javascript
const ws = new WebSocket('wss://api.mcphub.com/ws');
ws.onopen = function () {
// 发送认证消息
ws.send(
JSON.stringify({
type: 'auth',
token: 'YOUR_JWT_TOKEN',
}),
);
};
```
### 订阅事件
```javascript
// 订阅服务器状态更新
ws.send(
JSON.stringify({
type: 'subscribe',
channel: 'server-status',
filters: {
serverId: 'server-1',
},
}),
);
// 订阅系统监控
ws.send(
JSON.stringify({
type: 'subscribe',
channel: 'monitoring',
metrics: ['cpu', 'memory', 'requests'],
}),
);
```
### 事件类型
- `server-status`: 服务器状态变化
- `server-logs`: 实时日志流
- `monitoring`: 系统监控指标
- `alerts`: 系统警报
- `user-activity`: 用户活动事件
## 错误处理
### 错误代码
| 错误代码 | 描述 |
| ----------------------- | -------------- |
| `INVALID_REQUEST` | 请求格式无效 |
| `AUTHENTICATION_FAILED` | 身份验证失败 |
| `AUTHORIZATION_FAILED` | 权限不足 |
| `RESOURCE_NOT_FOUND` | 资源不存在 |
| `RESOURCE_CONFLICT` | 资源冲突 |
| `VALIDATION_ERROR` | 数据验证失败 |
| `RATE_LIMIT_EXCEEDED` | 请求频率超限 |
| `SERVER_ERROR` | 服务器内部错误 |
### 错误处理示例
```javascript
async function handleApiRequest() {
try {
const response = await fetch('/api/servers', {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
const data = await response.json();
if (!data.success) {
switch (data.error.code) {
case 'AUTHENTICATION_FAILED':
// 重新登录
redirectToLogin();
break;
case 'RATE_LIMIT_EXCEEDED':
// 延迟重试
setTimeout(() => handleApiRequest(), 5000);
break;
default:
// 显示错误消息
showError(data.error.message);
}
return;
}
// 处理成功响应
handleSuccessResponse(data.data);
} catch (error) {
// 处理网络错误
console.error('网络请求失败:', error);
}
}
```
## 速率限制
API 实施速率限制以防止滥用:
- **默认限制**: 每分钟 100 请求
- **认证用户**: 每分钟 1000 请求
- **管理员**: 每分钟 5000 请求
响应头包含速率限制信息:
```
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1609459200
```
## SDK 和客户端库
### JavaScript/Node.js
```bash
npm install @mcphub/sdk
```
```javascript
import { MCPHubClient } from '@mcphub/sdk';
const client = new MCPHubClient({
baseURL: 'https://api.mcphub.com',
token: 'YOUR_JWT_TOKEN',
});
// 获取服务器列表
const servers = await client.servers.list();
// 创建服务器
const newServer = await client.servers.create({
name: '新服务器',
command: 'python',
args: ['-m', 'mcp_server'],
});
```
### Python
```bash
pip install mcphub-sdk
```
```python
from mcphub_sdk import MCPHubClient
client = MCPHubClient(
base_url='https://api.mcphub.com',
token='YOUR_JWT_TOKEN'
)
# 获取服务器列表
servers = client.servers.list()
# 创建服务器
new_server = client.servers.create(
name='新服务器',
command='python',
args=['-m', 'mcp_server']
)
```
## 最佳实践
1. **使用 HTTPS**: 始终通过 HTTPS 访问 API
2. **安全存储令牌**: 不要在客户端代码中硬编码令牌
3. **处理错误**: 实施适当的错误处理和重试逻辑
4. **遵守速率限制**: 监控速率限制并实施退避策略
5. **使用分页**: 对于大数据集使用分页参数
6. **缓存响应**: 适当缓存 API 响应以减少请求
7. **版本控制**: 使用 API 版本号以确保兼容性
有关更多信息,请参阅我们的 [SDK 文档](https://docs.mcphub.com/sdk) 和 [示例代码](https://github.com/mcphub/examples)。

View File

@@ -0,0 +1,539 @@
---
title: 'Docker 部署'
description: '使用 Docker 和 Docker Compose 部署 MCPHub'
---
# Docker 部署
本指南介绍使用 Docker 部署 MCPHub包括开发和生产配置。
## Docker 快速开始
### 使用预构建镜像
```bash
# 拉取最新镜像
docker pull mcphub/mcphub:latest
# 使用默认配置运行
docker run -d \
--name mcphub \
-p 3000:3000 \
-v $(pwd)/mcp_settings.json:/app/mcp_settings.json \
mcphub/mcphub:latest
```
### 从源码构建
```bash
# 克隆仓库
git clone https://github.com/your-username/mcphub.git
cd mcphub
# 构建 Docker 镜像
docker build -t mcphub:local .
# 运行容器
docker run -d \
--name mcphub \
-p 3000:3000 \
-v $(pwd)/mcp_settings.json:/app/mcp_settings.json \
mcphub:local
```
## Docker Compose 设置
### 基本配置
创建 `docker-compose.yml` 文件:
```yaml
version: '3.8'
services:
mcphub:
image: mcphub/mcphub:latest
# 本地开发时使用:
# build: .
container_name: mcphub
ports:
- '3000:3000'
environment:
- NODE_ENV=production
- PORT=3000
- JWT_SECRET=${JWT_SECRET:-your-jwt-secret}
- DATABASE_URL=postgresql://mcphub:password@postgres:5432/mcphub
volumes:
- ./mcp_settings.json:/app/mcp_settings.json:ro
- ./servers.json:/app/servers.json:ro
- mcphub_data:/app/data
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
networks:
- mcphub-network
postgres:
image: postgres:15-alpine
container_name: mcphub-postgres
environment:
- POSTGRES_DB=mcphub
- POSTGRES_USER=mcphub
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql:ro
ports:
- '5432:5432'
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U mcphub -d mcphub']
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- mcphub-network
volumes:
postgres_data:
mcphub_data:
networks:
mcphub-network:
driver: bridge
```
### 生产配置(包含 Nginx
```yaml
version: '3.8'
services:
nginx:
image: nginx:alpine
container_name: mcphub-nginx
ports:
- '80:80'
- '443:443'
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./ssl:/etc/nginx/ssl:ro
- nginx_logs:/var/log/nginx
depends_on:
- mcphub
restart: unless-stopped
networks:
- mcphub-network
mcphub:
image: mcphub/mcphub:latest
container_name: mcphub-app
expose:
- '3000'
environment:
- NODE_ENV=production
- PORT=3000
- JWT_SECRET=${JWT_SECRET}
- JWT_EXPIRES_IN=${JWT_EXPIRES_IN:-24h}
- DATABASE_URL=postgresql://mcphub:${POSTGRES_PASSWORD}@postgres:5432/mcphub
- OPENAI_API_KEY=${OPENAI_API_KEY}
- REDIS_URL=redis://redis:6379
volumes:
- ./mcp_settings.json:/app/mcp_settings.json:ro
- ./servers.json:/app/servers.json:ro
- mcphub_data:/app/data
- mcphub_logs:/app/logs
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
networks:
- mcphub-network
healthcheck:
test: ['CMD', 'wget', '--quiet', '--tries=1', '--spider', 'http://localhost:3000/health']
interval: 30s
timeout: 10s
retries: 3
postgres:
image: postgres:15-alpine
container_name: mcphub-postgres
environment:
- POSTGRES_DB=mcphub
- POSTGRES_USER=mcphub
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./backups:/backups
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U mcphub -d mcphub']
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- mcphub-network
redis:
image: redis:7-alpine
container_name: mcphub-redis
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- mcphub-network
volumes:
postgres_data:
redis_data:
mcphub_data:
mcphub_logs:
nginx_logs:
networks:
mcphub-network:
driver: bridge
```
### 环境变量
为 Docker Compose 创建 `.env` 文件:
```env
# 应用程序
NODE_ENV=production
JWT_SECRET=your-super-secret-jwt-key-change-this
JWT_EXPIRES_IN=24h
# 数据库
POSTGRES_PASSWORD=your-secure-database-password
# Redis
REDIS_PASSWORD=your-secure-redis-password
# 外部 API
OPENAI_API_KEY=your-openai-api-key
# 可选:自定义端口
# PORT=3000
```
## 开发设置
### 开发 Docker Compose
创建 `docker-compose.dev.yml`
```yaml
version: '3.8'
services:
mcphub-dev:
build:
context: .
dockerfile: Dockerfile.dev
container_name: mcphub-dev
ports:
- '3000:3000'
- '5173:5173' # 前端开发服务器
- '9229:9229' # 调试端口
environment:
- NODE_ENV=development
- PORT=3000
- DATABASE_URL=postgresql://mcphub:password@postgres:5432/mcphub
volumes:
- .:/app
- /app/node_modules
- /app/frontend/node_modules
depends_on:
- postgres
command: pnpm dev
networks:
- mcphub-dev
postgres:
image: postgres:15-alpine
container_name: mcphub-postgres-dev
environment:
- POSTGRES_DB=mcphub
- POSTGRES_USER=mcphub
- POSTGRES_PASSWORD=password
ports:
- '5432:5432'
volumes:
- postgres_dev_data:/var/lib/postgresql/data
networks:
- mcphub-dev
volumes:
postgres_dev_data:
networks:
mcphub-dev:
driver: bridge
```
### 开发 Dockerfile
创建 `Dockerfile.dev`
```dockerfile
FROM node:20-alpine
# 安装 pnpm
RUN npm install -g pnpm
# 设置工作目录
WORKDIR /app
# 复制包文件
COPY package.json pnpm-lock.yaml ./
COPY frontend/package.json ./frontend/
# 安装依赖
RUN pnpm install
# 复制源代码
COPY . .
# 暴露端口
EXPOSE 3000 5173 9229
# 启动开发服务器
CMD ["pnpm", "dev"]
```
## 运行应用程序
### 开发模式
```bash
# 启动开发环境
docker-compose -f docker-compose.dev.yml up -d
# 查看日志
docker-compose -f docker-compose.dev.yml logs -f mcphub-dev
# 停止开发环境
docker-compose -f docker-compose.dev.yml down
```
### 生产模式
```bash
# 启动生产环境
docker-compose up -d
# 查看日志
docker-compose logs -f mcphub
# 停止生产环境
docker-compose down
```
## 配置管理
### MCP 设置卷挂载
创建您的 `mcp_settings.json`
```json
{
"mcpServers": {
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"]
},
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest", "--headless"]
},
"amap": {
"command": "npx",
"args": ["-y", "@amap/amap-maps-mcp-server"],
"env": {
"AMAP_MAPS_API_KEY": "your-api-key"
}
}
}
}
```
### 密钥管理
对于生产环境,使用 Docker 密钥:
```yaml
version: '3.8'
services:
mcphub:
image: mcphub/mcphub:latest
environment:
- JWT_SECRET_FILE=/run/secrets/jwt_secret
- DATABASE_PASSWORD_FILE=/run/secrets/db_password
secrets:
- jwt_secret
- db_password
secrets:
jwt_secret:
file: ./secrets/jwt_secret.txt
db_password:
file: ./secrets/db_password.txt
```
## 数据持久化
### 数据库备份
在 `docker-compose.yml` 中添加备份服务:
```yaml
services:
backup:
image: postgres:15-alpine
container_name: mcphub-backup
environment:
- PGPASSWORD=${POSTGRES_PASSWORD}
volumes:
- ./backups:/backups
- ./scripts/backup.sh:/backup.sh:ro
command: /bin/sh -c "chmod +x /backup.sh && /backup.sh"
depends_on:
- postgres
profiles:
- backup
networks:
- mcphub-network
```
创建 `scripts/backup.sh`
```bash
#!/bin/sh
BACKUP_FILE="/backups/mcphub_$(date +%Y%m%d_%H%M%S).sql"
pg_dump -h postgres -U mcphub -d mcphub > "$BACKUP_FILE"
echo "备份已创建:$BACKUP_FILE"
# 只保留最近 7 天的备份
find /backups -name "mcphub_*.sql" -mtime +7 -delete
```
运行备份:
```bash
docker-compose --profile backup run --rm backup
```
## 监控和健康检查
### 健康检查端点
在您的应用程序中添加:
```javascript
// 在您的 Express 应用中
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
memory: process.memoryUsage(),
version: process.env.npm_package_version,
});
});
```
### Docker 健康检查
```yaml
services:
mcphub:
# ... 其他配置
healthcheck:
test: ['CMD', 'wget', '--quiet', '--tries=1', '--spider', 'http://localhost:3000/health']
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
```
### 使用 Watchtower 监控
添加自动更新:
```yaml
services:
watchtower:
image: containrrr/watchtower
container_name: mcphub-watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_POLL_INTERVAL=3600
- WATCHTOWER_INCLUDE_STOPPED=true
restart: unless-stopped
```
## 故障排除
### 常见问题
**容器启动失败**:使用 `docker-compose logs mcphub` 检查日志
**数据库连接错误**:确保 PostgreSQL 健康且可访问
**端口冲突**:检查端口 3000/5432 是否已被占用
**卷挂载问题**:验证文件路径和权限
### 调试命令
```bash
# 检查容器状态
docker-compose ps
# 查看日志
docker-compose logs -f [service_name]
# 在容器中执行命令
docker-compose exec mcphub sh
# 检查数据库连接
docker-compose exec postgres psql -U mcphub -d mcphub
# 重启特定服务
docker-compose restart mcphub
# 重新构建并重启
docker-compose up --build -d
```
### 性能优化
```yaml
services:
mcphub:
# ... 其他配置
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
reservations:
memory: 256M
cpus: '0.25'
```
此 Docker 设置为 MCPHub 提供了完整的容器化环境,包含开发和生产配置。

View File

@@ -0,0 +1,389 @@
---
title: '环境变量配置'
description: '使用环境变量配置 MCPHub'
---
# 环境变量配置
MCPHub 使用环境变量进行配置。本指南涵盖所有可用变量及其用法。
## 核心应用设置
### 服务器配置
| 变量 | 默认值 | 描述 |
| ----------- | ------------- | ----------------------------------------------- |
| `PORT` | `3000` | HTTP 服务器端口号 |
| `HOST` | `0.0.0.0` | 服务器绑定的主机地址 |
| `NODE_ENV` | `development` | 应用环境(`development`、`production`、`test` |
| `LOG_LEVEL` | `info` | 日志级别(`error`、`warn`、`info`、`debug` |
```env
PORT=3000
HOST=0.0.0.0
NODE_ENV=production
LOG_LEVEL=info
```
### 数据库配置
| 变量 | 默认值 | 描述 |
| -------------- | ----------- | --------------------- |
| `DATABASE_URL` | - | PostgreSQL 连接字符串 |
| `DB_HOST` | `localhost` | 数据库主机 |
| `DB_PORT` | `5432` | 数据库端口 |
| `DB_NAME` | `mcphub` | 数据库名称 |
| `DB_USER` | `mcphub` | 数据库用户名 |
| `DB_PASSWORD` | - | 数据库密码 |
| `DB_SSL` | `false` | 启用数据库 SSL 连接 |
| `DB_POOL_MIN` | `2` | 最小数据库连接池大小 |
| `DB_POOL_MAX` | `10` | 最大数据库连接池大小 |
```env
# 选项 1完整连接字符串
DATABASE_URL=postgresql://username:password@localhost:5432/mcphub
# 选项 2单独组件
DB_HOST=localhost
DB_PORT=5432
DB_NAME=mcphub
DB_USER=mcphub
DB_PASSWORD=your-password
DB_SSL=false
```
## 认证与安全
### JWT 配置
| 变量 | 默认值 | 描述 |
| ------------------------ | ------- | ------------------------ |
| `JWT_SECRET` | - | JWT 令牌签名密钥(必需) |
| `JWT_EXPIRES_IN` | `24h` | JWT 令牌过期时间 |
| `JWT_REFRESH_EXPIRES_IN` | `7d` | 刷新令牌过期时间 |
| `JWT_ALGORITHM` | `HS256` | JWT 签名算法 |
```env
JWT_SECRET=your-super-secret-key-change-this-in-production
JWT_EXPIRES_IN=24h
JWT_REFRESH_EXPIRES_IN=7d
```
### 会话与安全
| 变量 | 默认值 | 描述 |
| ------------------- | ------ | -------------------- |
| `SESSION_SECRET` | - | 会话加密密钥 |
| `BCRYPT_ROUNDS` | `12` | bcrypt 哈希轮数 |
| `RATE_LIMIT_WINDOW` | `15` | 速率限制窗口(分钟) |
| `RATE_LIMIT_MAX` | `100` | 每个窗口最大请求数 |
| `CORS_ORIGIN` | `*` | 允许的 CORS 来源 |
```env
SESSION_SECRET=your-session-secret
BCRYPT_ROUNDS=12
RATE_LIMIT_WINDOW=15
RATE_LIMIT_MAX=100
CORS_ORIGIN=https://your-domain.com,https://admin.your-domain.com
```
## 外部服务
### OpenAI 配置
| 变量 | 默认值 | 描述 |
| ------------------------ | ------------------------ | ------------------------------- |
| `OPENAI_API_KEY` | - | OpenAI API 密钥(用于智能路由) |
| `OPENAI_MODEL` | `gpt-3.5-turbo` | OpenAI 嵌入模型 |
| `OPENAI_EMBEDDING_MODEL` | `text-embedding-ada-002` | 向量嵌入模型 |
| `OPENAI_MAX_TOKENS` | `1000` | 每个请求最大令牌数 |
| `OPENAI_TEMPERATURE` | `0.1` | AI 响应温度 |
```env
OPENAI_API_KEY=sk-your-openai-api-key
OPENAI_MODEL=gpt-3.5-turbo
OPENAI_EMBEDDING_MODEL=text-embedding-ada-002
OPENAI_MAX_TOKENS=1000
OPENAI_TEMPERATURE=0.1
```
### Redis 配置(可选)
| 变量 | 默认值 | 描述 |
| ---------------- | ----------- | ---------------- |
| `REDIS_URL` | - | Redis 连接字符串 |
| `REDIS_HOST` | `localhost` | Redis 主机 |
| `REDIS_PORT` | `6379` | Redis 端口 |
| `REDIS_PASSWORD` | - | Redis 密码 |
| `REDIS_DB` | `0` | Redis 数据库编号 |
| `REDIS_PREFIX` | `mcphub:` | Redis 键前缀 |
```env
# 选项 1完整连接字符串
REDIS_URL=redis://username:password@localhost:6379/0
# 选项 2单独组件
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=your-redis-password
REDIS_DB=0
REDIS_PREFIX=mcphub:
```
## MCP 服务器配置
### 默认设置
| 变量 | 默认值 | 描述 |
| ------------------- | ------------------- | ---------------------------- |
| `MCP_SETTINGS_FILE` | `mcp_settings.json` | MCP 设置文件路径 |
| `MCP_SERVERS_FILE` | `servers.json` | 服务器配置文件路径 |
| `MCP_TIMEOUT` | `30000` | MCP 操作默认超时(毫秒) |
| `MCP_MAX_RETRIES` | `3` | 失败操作最大重试次数 |
| `MCP_RESTART_DELAY` | `5000` | 重启失败服务器的延迟(毫秒) |
```env
MCP_SETTINGS_FILE=./config/mcp_settings.json
MCP_SERVERS_FILE=./config/servers.json
MCP_TIMEOUT=30000
MCP_MAX_RETRIES=3
MCP_RESTART_DELAY=5000
```
### 智能路由
| 变量 | 默认值 | 描述 |
| --------------------------- | ------ | ---------------------- |
| `SMART_ROUTING_ENABLED` | `true` | 启用 AI 驱动的智能路由 |
| `SMART_ROUTING_THRESHOLD` | `0.7` | 路由相似度阈值 |
| `SMART_ROUTING_MAX_RESULTS` | `5` | 返回的最大工具数 |
| `VECTOR_CACHE_TTL` | `3600` | 向量缓存 TTL |
```env
SMART_ROUTING_ENABLED=true
SMART_ROUTING_THRESHOLD=0.7
SMART_ROUTING_MAX_RESULTS=5
VECTOR_CACHE_TTL=3600
```
## 文件存储与上传
| 变量 | 默认值 | 描述 |
| -------------------- | ---------------- | -------------------------------- |
| `UPLOAD_DIR` | `./uploads` | 文件上传目录 |
| `MAX_FILE_SIZE` | `10485760` | 最大文件大小字节10MB |
| `ALLOWED_FILE_TYPES` | `image/*,text/*` | 允许的 MIME 类型 |
| `STORAGE_TYPE` | `local` | 存储类型(`local`、`s3`、`gcs` |
```env
UPLOAD_DIR=./data/uploads
MAX_FILE_SIZE=10485760
ALLOWED_FILE_TYPES=image/*,text/*,application/json
STORAGE_TYPE=local
```
### S3 存储(可选)
| 变量 | 默认值 | 描述 |
| ---------------------- | ----------- | -------------- |
| `S3_BUCKET` | - | S3 存储桶名称 |
| `S3_REGION` | `us-east-1` | S3 区域 |
| `S3_ACCESS_KEY_ID` | - | S3 访问密钥 |
| `S3_SECRET_ACCESS_KEY` | - | S3 密钥 |
| `S3_ENDPOINT` | - | 自定义 S3 端点 |
```env
S3_BUCKET=mcphub-uploads
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=your-access-key
S3_SECRET_ACCESS_KEY=your-secret-key
```
## 监控与日志
### 应用监控
| 变量 | 默认值 | 描述 |
| ------------------------ | ------- | -------------------- |
| `METRICS_ENABLED` | `true` | 启用指标收集 |
| `METRICS_PORT` | `9090` | 指标端点端口 |
| `HEALTH_CHECK_INTERVAL` | `30000` | 健康检查间隔(毫秒) |
| `PERFORMANCE_MONITORING` | `false` | 启用性能监控 |
```env
METRICS_ENABLED=true
METRICS_PORT=9090
HEALTH_CHECK_INTERVAL=30000
PERFORMANCE_MONITORING=true
```
### 日志配置
| 变量 | 默认值 | 描述 |
| ------------------ | ------------ | -------------------------------- |
| `LOG_FORMAT` | `json` | 日志格式(`json`、`text` |
| `LOG_FILE` | - | 日志文件路径(如果启用文件日志) |
| `LOG_MAX_SIZE` | `10m` | 最大日志文件大小 |
| `LOG_MAX_FILES` | `5` | 最大日志文件数 |
| `LOG_DATE_PATTERN` | `YYYY-MM-DD` | 日志轮换日期模式 |
```env
LOG_FORMAT=json
LOG_FILE=./logs/mcphub.log
LOG_MAX_SIZE=10m
LOG_MAX_FILES=5
LOG_DATE_PATTERN=YYYY-MM-DD
```
## 开发与调试
| 变量 | 默认值 | 描述 |
| ------------------------ | ------- | ------------------------------- |
| `DEBUG` | - | 调试命名空间(例如 `mcphub:*` |
| `DEV_TOOLS_ENABLED` | `false` | 启用开发工具 |
| `HOT_RELOAD` | `true` | 在开发中启用热重载 |
| `MOCK_EXTERNAL_SERVICES` | `false` | 模拟外部 API 调用 |
```env
DEBUG=mcphub:*
DEV_TOOLS_ENABLED=true
HOT_RELOAD=true
MOCK_EXTERNAL_SERVICES=false
```
## 生产优化
| 变量 | 默认值 | 描述 |
| ------------------ | ------- | ---------------------- |
| `CLUSTER_MODE` | `false` | 启用集群模式 |
| `WORKER_PROCESSES` | `0` | 工作进程数0 = 自动) |
| `MEMORY_LIMIT` | - | 每个进程内存限制 |
| `CPU_LIMIT` | - | 每个进程 CPU 限制 |
| `GC_OPTIMIZE` | `false` | 启用垃圾回收优化 |
```env
CLUSTER_MODE=true
WORKER_PROCESSES=4
MEMORY_LIMIT=512M
GC_OPTIMIZE=true
```
## 配置示例
### 开发环境
```env
# .env.development
NODE_ENV=development
PORT=3000
LOG_LEVEL=debug
# 数据库
DATABASE_URL=postgresql://mcphub:password@localhost:5432/mcphub_dev
# 认证
JWT_SECRET=dev-secret-key
JWT_EXPIRES_IN=24h
# OpenAI开发时可选
# OPENAI_API_KEY=your-dev-key
# 调试
DEBUG=mcphub:*
DEV_TOOLS_ENABLED=true
HOT_RELOAD=true
```
### 生产环境
```env
# .env.production
NODE_ENV=production
PORT=3000
LOG_LEVEL=info
LOG_FORMAT=json
# 数据库
DATABASE_URL=postgresql://mcphub:secure-password@db.example.com:5432/mcphub
DB_SSL=true
DB_POOL_MAX=20
# 安全
JWT_SECRET=your-super-secure-production-secret
SESSION_SECRET=your-session-secret
BCRYPT_ROUNDS=14
# 外部服务
OPENAI_API_KEY=your-production-openai-key
REDIS_URL=redis://redis.example.com:6379
# 监控
METRICS_ENABLED=true
PERFORMANCE_MONITORING=true
# 优化
CLUSTER_MODE=true
GC_OPTIMIZE=true
```
### Docker 环境
```env
# .env.docker
NODE_ENV=production
HOST=0.0.0.0
PORT=3000
# 使用 Docker 网络的服务名
DATABASE_URL=postgresql://mcphub:password@postgres:5432/mcphub
REDIS_URL=redis://redis:6379
# 安全
JWT_SECRET_FILE=/run/secrets/jwt_secret
DB_PASSWORD_FILE=/run/secrets/db_password
# 容器中的文件路径
MCP_SETTINGS_FILE=/app/mcp_settings.json
UPLOAD_DIR=/app/data/uploads
LOG_FILE=/app/logs/mcphub.log
```
## 环境变量加载
MCPHub 按以下顺序加载环境变量:
1. 系统环境变量
2. `.env.local`(被 git 忽略)
3. `.env.{NODE_ENV}`(例如 `.env.production`
4. `.env`
### 使用 dotenv-expand
MCPHub 支持变量扩展:
```env
BASE_URL=https://api.example.com
API_ENDPOINT=${BASE_URL}/v1
DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}
```
## 安全最佳实践
1. **永远不要提交密钥**到版本控制
2. **为生产使用强唯一密钥**
3. **定期轮换密钥**
4. **使用特定于环境的文件**
5. **在启动时验证所有环境变量**
6. **为容器部署使用 Docker 密钥**
## 验证
MCPHub 在启动时验证环境变量。无效配置将阻止应用程序启动并提供有用的错误消息。
生产环境必需变量:
- `JWT_SECRET`
- `DATABASE_URL` 或单独的数据库组件
- `OPENAI_API_KEY`(如果启用智能路由)
这个全面的环境配置确保 MCPHub 可以为任何部署场景正确配置。

View File

@@ -0,0 +1,564 @@
---
title: 'MCP 设置配置'
description: '配置 MCPHub 的 MCP 服务器及其设置'
---
# MCP 设置配置
本指南说明如何使用 `mcp_settings.json` 文件和相关配置在 MCPHub 中配置 MCP 服务器。
## 配置文件概述
MCPHub 使用几个配置文件:
- **`mcp_settings.json`**:主要的 MCP 服务器配置
- **`servers.json`**:服务器元数据和分组
- **`.env`**:环境变量和密钥
## 基本 MCP 设置结构
### mcp_settings.json
```json
{
"mcpServers": {
"server-name": {
"command": "command-to-run",
"args": ["arg1", "arg2"],
"env": {
"ENV_VAR": "value"
},
"cwd": "/working/directory",
"timeout": 30000,
"restart": true
}
}
}
```
### 示例配置
```json
{
"mcpServers": {
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"],
"env": {
"USER_AGENT": "MCPHub/1.0"
}
},
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest", "--headless"],
"timeout": 60000
},
"slack": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-slack"],
"env": {
"SLACK_BOT_TOKEN": "${SLACK_BOT_TOKEN}",
"SLACK_TEAM_ID": "${SLACK_TEAM_ID}"
}
}
}
}
```
## 服务器配置选项
### 必需字段
| 字段 | 类型 | 描述 |
| --------- | ------ | ---------------- |
| `command` | string | 可执行命令或路径 |
| `args` | array | 命令行参数 |
### 可选字段
| 字段 | 类型 | 默认值 | 描述 |
| -------------- | ------- | --------------- | ------------------ |
| `env` | object | `{}` | 环境变量 |
| `cwd` | string | `process.cwd()` | 工作目录 |
| `timeout` | number | `30000` | 启动超时(毫秒) |
| `restart` | boolean | `true` | 失败时自动重启 |
| `maxRestarts` | number | `5` | 最大重启次数 |
| `restartDelay` | number | `5000` | 重启间延迟(毫秒) |
| `stdio` | string | `pipe` | stdio 配置 |
## 常见 MCP 服务器示例
### Web 和 API 服务器
#### Fetch 服务器
```json
{
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"],
"env": {
"USER_AGENT": "MCPHub/1.0",
"MAX_REDIRECTS": "10"
}
}
}
```
#### 使用 Playwright 进行网页抓取
```json
{
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest", "--headless"],
"timeout": 60000,
"env": {
"PLAYWRIGHT_BROWSERS_PATH": "/tmp/browsers"
}
}
}
```
### 文件和系统服务器
#### 文件系统服务器
```json
{
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/allowed/path"],
"env": {
"ALLOWED_OPERATIONS": "read,write,list"
}
}
}
```
#### SQLite 服务器
```json
{
"sqlite": {
"command": "uvx",
"args": ["mcp-server-sqlite", "--db-path", "/path/to/database.db"],
"env": {
"SQLITE_READONLY": "false"
}
}
}
```
### 通信服务器
#### Slack 服务器
```json
{
"slack": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-slack"],
"env": {
"SLACK_BOT_TOKEN": "${SLACK_BOT_TOKEN}",
"SLACK_TEAM_ID": "${SLACK_TEAM_ID}",
"SLACK_APP_TOKEN": "${SLACK_APP_TOKEN}"
}
}
}
```
#### 邮件服务器
```json
{
"email": {
"command": "python",
"args": ["-m", "mcp_server_email"],
"env": {
"SMTP_HOST": "smtp.gmail.com",
"SMTP_PORT": "587",
"EMAIL_USER": "${EMAIL_USER}",
"EMAIL_PASSWORD": "${EMAIL_PASSWORD}"
}
}
}
```
### 开发和 API 服务器
#### GitHub 服务器
```json
{
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
}
}
}
```
#### Google Drive 服务器
```json
{
"gdrive": {
"command": "npx",
"args": ["-y", "@google/mcp-server-gdrive"],
"env": {
"GOOGLE_CLIENT_ID": "${GOOGLE_CLIENT_ID}",
"GOOGLE_CLIENT_SECRET": "${GOOGLE_CLIENT_SECRET}",
"GOOGLE_REFRESH_TOKEN": "${GOOGLE_REFRESH_TOKEN}"
}
}
}
```
### 地图和位置服务
#### 高德地图服务器
```json
{
"amap": {
"command": "npx",
"args": ["-y", "@amap/amap-maps-mcp-server"],
"env": {
"AMAP_MAPS_API_KEY": "${AMAP_API_KEY}",
"AMAP_LANGUAGE": "zh-cn"
}
}
}
```
#### OpenStreetMap 服务器
```json
{
"osm": {
"command": "python",
"args": ["-m", "mcp_server_osm"],
"env": {
"OSM_USER_AGENT": "MCPHub/1.0"
}
}
}
```
## 高级配置
### 环境变量替换
MCPHub 支持使用 `${VAR_NAME}` 语法进行环境变量替换:
```json
{
"mcpServers": {
"api-server": {
"command": "python",
"args": ["-m", "api_server"],
"env": {
"API_KEY": "${API_KEY}",
"API_URL": "${API_BASE_URL}/v1",
"DEBUG": "${NODE_ENV:development}"
}
}
}
}
```
可以使用 `${VAR_NAME:default}` 指定默认值:
```json
{
"timeout": "${MCP_TIMEOUT:30000}",
"maxRestarts": "${MCP_MAX_RESTARTS:5}"
}
```
### 条件配置
根据环境使用不同配置:
```json
{
"mcpServers": {
"database": {
"command": "python",
"args": ["-m", "db_server"],
"env": {
"DB_URL": "${NODE_ENV:development == 'production' ? DATABASE_URL : DEV_DATABASE_URL}"
}
}
}
}
```
### 自定义服务器脚本
#### 本地 Python 服务器
```json
{
"custom-python": {
"command": "python",
"args": ["./servers/custom_server.py"],
"cwd": "/app/custom-servers",
"env": {
"PYTHONPATH": "/app/custom-servers",
"CONFIG_FILE": "./config.json"
}
}
}
```
#### 本地 Node.js 服务器
```json
{
"custom-node": {
"command": "node",
"args": ["./servers/custom-server.js"],
"cwd": "/app/custom-servers",
"env": {
"NODE_ENV": "production"
}
}
}
```
## 服务器元数据配置
### servers.json
使用服务器元数据补充 `mcp_settings.json`
```json
{
"servers": {
"fetch": {
"name": "Fetch 服务器",
"description": "用于网络请求的 HTTP 客户端",
"category": "web",
"tags": ["http", "api", "web"],
"version": "1.0.0",
"author": "MCPHub 团队",
"documentation": "https://docs.mcphub.com/servers/fetch",
"enabled": true
},
"playwright": {
"name": "Playwright 浏览器",
"description": "网页自动化和抓取",
"category": "automation",
"tags": ["browser", "scraping", "automation"],
"version": "2.0.0",
"enabled": true
}
},
"groups": {
"web-tools": {
"name": "网页工具",
"description": "用于网页交互的工具",
"servers": ["fetch", "playwright"],
"access": "public"
},
"admin-tools": {
"name": "管理工具",
"description": "管理实用程序",
"servers": ["filesystem", "database"],
"access": "admin"
}
}
}
```
## 组管理
### 组配置
```json
{
"groups": {
"production": {
"name": "生产工具",
"description": "稳定的生产服务器",
"servers": ["fetch", "slack", "github"],
"access": "authenticated",
"rateLimit": {
"requestsPerMinute": 100,
"burstLimit": 20
}
},
"experimental": {
"name": "实验功能",
"description": "测试版和实验性服务器",
"servers": ["experimental-ai", "beta-search"],
"access": "admin",
"enabled": false
}
}
}
```
### 访问控制
| 访问级别 | 描述 |
| --------------- | ------------------- |
| `public` | 无需认证 |
| `authenticated` | 需要有效的 JWT 令牌 |
| `admin` | 需要管理员角色 |
| `custom` | 自定义权限逻辑 |
## 动态配置
### 热重载
MCPHub 支持配置热重载:
```bash
# 不重启重新加载配置
curl -X POST http://localhost:3000/api/admin/reload-config \
-H "Authorization: Bearer your-admin-token"
```
### 配置验证
MCPHub 在启动和重新加载时验证配置:
```json
{
"validation": {
"strict": true,
"allowUnknownServers": false,
"requireDocumentation": true
}
}
```
## 最佳实践
### 安全
1. **对敏感数据使用环境变量**
```json
{
"env": {
"API_KEY": "${API_KEY}",
"DATABASE_PASSWORD": "${DB_PASSWORD}"
}
}
```
2. **限制服务器权限**
```json
{
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/restricted/path"],
"env": {
"READONLY": "true"
}
}
}
```
### 性能
1. **设置适当的超时**
```json
{
"timeout": 30000,
"maxRestarts": 3,
"restartDelay": 5000
}
```
2. **资源限制**
```json
{
"env": {
"NODE_OPTIONS": "--max-old-space-size=512",
"MEMORY_LIMIT": "512MB"
}
}
```
### 监控
1. **启用健康检查**
```json
{
"healthCheck": {
"enabled": true,
"interval": 30000,
"timeout": 5000
}
}
```
2. **日志配置**
```json
{
"env": {
"LOG_LEVEL": "info",
"LOG_FORMAT": "json"
}
}
```
## 故障排除
### 常见问题
**服务器无法启动**:检查命令和参数
```bash
# 手动测试命令
uvx mcp-server-fetch
```
**找不到环境变量**:验证 `.env` 文件
```bash
# 检查环境
printenv | grep API_KEY
```
**权限错误**:检查文件权限和路径
```bash
# 验证可执行权限
ls -la /path/to/server
```
### 调试配置
启用调试模式进行详细日志记录:
```json
{
"debug": {
"enabled": true,
"logLevel": "debug",
"includeEnv": false,
"logStartup": true
}
}
```
### 验证错误
常见验证错误和解决方案:
1. **缺少必需字段**:添加 `command` 和 `args`
2. **无效超时**:使用数字,不是字符串
3. **找不到环境变量**:检查 `.env` 文件
4. **找不到命令**:验证安装和 PATH
这个全面的指南涵盖了在 MCPHub 中为各种用例和环境配置 MCP 服务器的所有方面。

View File

@@ -0,0 +1,373 @@
---
title: 'Nginx 配置'
description: '配置 Nginx 作为 MCPHub 的反向代理'
---
# Nginx 配置
本指南说明如何配置 Nginx 作为 MCPHub 的反向代理,包括 SSL 终止、负载均衡和缓存策略。
## 基本反向代理设置
### 配置文件
创建或更新您的 Nginx 配置文件(`/etc/nginx/sites-available/mcphub`
```nginx
server {
listen 80;
server_name your-domain.com;
# 将 HTTP 重定向到 HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL 配置
ssl_certificate /path/to/your/certificate.crt;
ssl_certificate_key /path/to/your/private.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# 安全头
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Gzip 压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
# 主应用程序
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 86400;
}
# API 端点,为 MCP 操作设置更长的超时
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300;
proxy_connect_timeout 60;
proxy_send_timeout 60;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
proxy_pass http://127.0.0.1:3000;
proxy_cache_valid 200 1d;
proxy_cache_valid 404 1m;
add_header Cache-Control "public, immutable";
expires 1y;
}
}
```
### 启用配置
```bash
# 创建符号链接启用站点
sudo ln -s /etc/nginx/sites-available/mcphub /etc/nginx/sites-enabled/
# 测试配置
sudo nginx -t
# 重新加载 Nginx
sudo systemctl reload nginx
```
## 负载均衡配置
对于具有多个 MCPHub 实例的高可用性设置:
```nginx
upstream mcphub_backend {
least_conn;
server 127.0.0.1:3000 weight=1 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3001 weight=1 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3002 weight=1 max_fails=3 fail_timeout=30s;
# 健康检查Nginx Plus 功能)
# health_check interval=5s fails=3 passes=2;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL 和其他配置...
location / {
proxy_pass http://mcphub_backend;
# 其他代理设置...
}
}
```
## 缓存配置
### 浏览器缓存
```nginx
# 缓存静态资源
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
proxy_pass http://127.0.0.1:3000;
expires 1y;
add_header Cache-Control "public, immutable";
}
# 缓存 API 响应(小心动态内容)
location /api/public/ {
proxy_pass http://127.0.0.1:3000;
proxy_cache mcphub_cache;
proxy_cache_valid 200 5m;
proxy_cache_key "$scheme$request_method$host$request_uri";
add_header X-Cache-Status $upstream_cache_status;
}
```
### Nginx 代理缓存
在 `nginx.conf` 的 `http` 块中添加:
```nginx
http {
# 代理缓存配置
proxy_cache_path /var/cache/nginx/mcphub
levels=1:2
keys_zone=mcphub_cache:10m
max_size=1g
inactive=60m
use_temp_path=off;
# 其他配置...
}
```
## WebSocket 支持
对于实时功能和 SSE服务器发送事件
```nginx
location /api/stream {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 禁用实时响应的缓冲
proxy_buffering off;
proxy_cache off;
# 长连接超时
proxy_read_timeout 24h;
proxy_send_timeout 24h;
}
```
## 安全配置
### 速率限制
```nginx
http {
# 定义速率限制区域
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
server {
# 对 API 端点应用速率限制
location /api/ {
limit_req zone=api burst=20 nodelay;
# 其他配置...
}
# 登录端点的严格速率限制
location /api/auth/login {
limit_req zone=login burst=5;
# 其他配置...
}
}
}
```
### IP 白名单
```nginx
# 为管理端点允许特定 IP
location /api/admin/ {
allow 192.168.1.0/24;
allow 10.0.0.0/8;
deny all;
proxy_pass http://127.0.0.1:3000;
# 其他代理设置...
}
```
## 监控和日志
### 访问日志
```nginx
http {
# 自定义日志格式
log_format mcphub_format '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time';
server {
# 启用访问日志
access_log /var/log/nginx/mcphub_access.log mcphub_format;
error_log /var/log/nginx/mcphub_error.log;
# 其他配置...
}
}
```
### 状态页面
```nginx
location /nginx_status {
stub_status;
allow 127.0.0.1;
deny all;
}
```
## Docker 集成
当在 Docker 中运行 MCPHub 时,更新代理配置:
```nginx
upstream mcphub_docker {
server mcphub:3000; # Docker 服务名
}
server {
location / {
proxy_pass http://mcphub_docker;
# 其他代理设置...
}
}
```
## 完整示例配置
使用提供的 `nginx.conf.example` 的生产就绪示例:
```bash
# 复制示例配置
cp nginx.conf.example /etc/nginx/sites-available/mcphub
# 使用您的域名和路径更新配置
sudo nano /etc/nginx/sites-available/mcphub
# 启用站点
sudo ln -s /etc/nginx/sites-available/mcphub /etc/nginx/sites-enabled/
# 测试并重新加载
sudo nginx -t && sudo systemctl reload nginx
```
## 故障排除
### 常见问题
**502 Bad Gateway**:检查 MCPHub 是否正在运行且可访问
**504 Gateway Timeout**:为长时间运行的操作增加 `proxy_read_timeout`
**WebSocket 连接失败**:确保正确的 `Upgrade` 和 `Connection` 头
**缓存问题**:清除代理缓存或在开发中禁用
### 调试命令
```bash
# 测试 Nginx 配置
sudo nginx -t
# 检查 Nginx 状态
sudo systemctl status nginx
# 查看错误日志
sudo tail -f /var/log/nginx/error.log
# 检查 MCPHub 是否响应
curl -I http://localhost:3000
```
## 性能优化
### 工作进程
```nginx
# 在 nginx.conf 中
worker_processes auto;
worker_connections 1024;
```
### 缓冲区大小
```nginx
proxy_buffering on;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
```
### Keep-Alive
```nginx
upstream mcphub_backend {
server 127.0.0.1:3000;
keepalive 32;
}
location / {
proxy_pass http://mcphub_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
```
此配置为在 Nginx 后运行 MCPHub 提供了坚实的基础,具有适当的安全性、性能和可靠性功能。

421
docs/zh/development.mdx Normal file
View File

@@ -0,0 +1,421 @@
---
title: '开发指南'
description: 'MCPHub 本地开发环境搭建和开发工作流指南'
---
## 概述
本指南将帮助您搭建 MCPHub 的本地开发环境,了解项目结构,并掌握开发工作流。
<Info>**前提条件**:请确保已安装 Node.js 18+ 和 Git。</Info>
## 环境准备
### 系统要求
在开始开发之前,请确保您的系统满足以下要求:
<CardGroup cols={2}>
<Card title="软件依赖" icon="download">
- **Node.js**: 18.0+ 版本 - **npm**: 8.0+ 版本 - **Git**: 最新版本 - **Docker**:
可选,用于容器化开发
</Card>
<Card title="推荐工具" icon="toolbox">
- **VS Code**: 推荐的代码编辑器 - **Postman**: API 测试工具 - **TablePlus**: 数据库管理工具 -
**Docker Desktop**: 容器管理
</Card>
</CardGroup>
### 验证环境
```bash
# 检查 Node.js 版本
node --version # 应该 >= 18.0.0
# 检查 npm 版本
npm --version # 应该 >= 8.0.0
# 检查 Git 版本
git --version
# 检查 Docker可选
docker --version
```
## 克隆项目
### 获取源代码
```bash
# 克隆主仓库
git clone https://github.com/mcphub/mcphub.git
cd mcphub
# 或者克隆您的 fork
git clone https://github.com/YOUR_USERNAME/mcphub.git
cd mcphub
```
### 项目结构
```
mcphub/
├── src/ # 源代码目录
│ ├── controllers/ # 控制器层
│ ├── middleware/ # 中间件
│ ├── models/ # 数据模型
│ ├── routes/ # 路由定义
│ ├── services/ # 业务逻辑层
│ ├── utils/ # 工具函数
│ └── index.ts # 应用入口
├── tests/ # 测试文件
├── docs/ # 文档源码
├── docker/ # Docker 配置
├── scripts/ # 构建脚本
├── prisma/ # 数据库模式
├── package.json # 项目依赖
├── tsconfig.json # TypeScript 配置
├── .env.example # 环境变量示例
└── README.md # 项目说明
```
## 安装依赖
### 安装项目依赖
```bash
# 安装生产和开发依赖
npm install
# 仅安装生产依赖
npm ci --only=production
```
### 全局工具安装
```bash
# 安装 TypeScript 编译器
npm install -g typescript
# 安装开发工具
npm install -g tsx nodemon prisma
# 安装 MCPHub CLI可选
npm install -g @mcphub/cli
```
## 配置开发环境
### 环境变量配置
```bash
# 复制环境变量模板
cp .env.example .env
# 编辑环境变量
nano .env
```
开发环境的 `.env` 配置示例:
```bash title=".env"
# 应用配置
NODE_ENV=development
PORT=3000
HOST=localhost
# 数据库配置
DATABASE_URL=sqlite:./data/dev.db
# JWT 配置
JWT_SECRET=dev-jwt-secret-key
JWT_EXPIRES_IN=7d
# 日志配置
LOG_LEVEL=debug
LOG_FORMAT=dev
# CORS 配置
CORS_ORIGIN=http://localhost:3000,http://localhost:3001
# 管理员账户
ADMIN_EMAIL=dev@mcphub.io
ADMIN_PASSWORD=dev123
# 开发功能开关
ENABLE_DEBUG_ROUTES=true
ENABLE_SWAGGER=true
ENABLE_HOT_RELOAD=true
```
### 数据库初始化
```bash
# 生成 Prisma 客户端
npx prisma generate
# 运行数据库迁移
npx prisma migrate dev --name init
# 填充测试数据
npm run db:seed
```
## 启动开发服务器
### 开发模式启动
```bash
# 启动开发服务器(带热重载)
npm run dev
# 或者使用 tsx 直接运行
npx tsx watch src/index.ts
```
### 后台模式启动
```bash
# 使用 PM2 启动(需要先安装 PM2
npm install -g pm2
npm run dev:pm2
# 查看进程状态
pm2 status
# 查看日志
pm2 logs mcphub-dev
```
### 验证启动
访问以下 URL 验证服务是否正常启动:
- **主页**: http://localhost:3000
- **健康检查**: http://localhost:3000/health
- **API 文档**: http://localhost:3000/api/docs
- **管理界面**: http://localhost:3000/admin
## 开发工作流
### 1. 功能开发流程
```bash
# 1. 创建功能分支
git checkout -b feature/your-feature-name
# 2. 进行开发...
# 3. 运行测试
npm test
# 4. 代码格式化
npm run lint:fix
# 5. 提交代码
git add .
git commit -m "feat: add your feature description"
# 6. 推送分支
git push origin feature/your-feature-name
# 7. 创建 Pull Request
```
### 2. 代码规范
MCPHub 项目使用以下代码规范工具:
```bash
# 代码检查
npm run lint
# 自动修复
npm run lint:fix
# 格式化代码
npm run format
# 类型检查
npm run type-check
```
### 3. 测试开发
```bash
# 运行所有测试
npm test
# 运行单元测试
npm run test:unit
# 运行集成测试
npm run test:integration
# 运行测试并生成覆盖率报告
npm run test:coverage
# 监听模式运行测试
npm run test:watch
```
## 调试技巧
### 1. VS Code 调试配置
创建 `.vscode/launch.json` 文件:
```json title=".vscode/launch.json"
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug MCPHub",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/src/index.ts",
"runtimeArgs": ["-r", "tsx/cjs"],
"env": {
"NODE_ENV": "development"
},
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"]
}
]
}
```
### 2. 日志调试
使用内置的日志系统进行调试:
```typescript
import { logger } from '@/utils/logger';
// 不同级别的日志
logger.debug('调试信息', { data });
logger.info('信息日志', { userId });
logger.warn('警告信息', { error });
logger.error('错误信息', { error, stack });
```
### 3. 数据库调试
```bash
# 查看数据库内容
npx prisma studio
# 重置数据库
npx prisma migrate reset
# 查看迁移状态
npx prisma migrate status
```
## 常用开发命令
### 项目管理
```bash
# 安装新依赖
npm install package-name
npm install -D package-name # 开发依赖
# 更新依赖
npm update
# 清理缓存
npm cache clean --force
# 重新安装依赖
rm -rf node_modules package-lock.json
npm install
```
### 构建和部署
```bash
# 构建项目
npm run build
# 预览构建结果
npm run preview
# 构建 Docker 镜像
npm run docker:build
# 运行 Docker 容器
npm run docker:run
```
### 数据库操作
```bash
# 创建新迁移
npx prisma migrate dev --name your-migration-name
# 重置数据库
npx prisma migrate reset
# 推送模式变更
npx prisma db push
# 生成客户端
npx prisma generate
```
## 常见问题
<AccordionGroup>
<Accordion icon="question" title="端口被占用">
**错误信息**: `Error: listen EADDRINUSE :::3000`
**解决方案**:
```bash
# 查找占用端口的进程
lsof -i :3000
# 杀死进程
kill -9 PID
# 或者使用不同端口
PORT=3001 npm run dev
```
</Accordion>
<Accordion icon="question" title="数据库连接失败">
**可能原因**: 数据库文件权限或路径问题 **解决方案**: ```bash # 检查数据库文件 ls -la data/ #
重新初始化数据库 rm data/dev.db npx prisma migrate dev ```
</Accordion>
<Accordion icon="question" title="TypeScript 编译错误">
**解决方案**:
```bash
# 清理构建缓存
npm run clean
# 重新安装类型定义
npm install @types/node @types/express
# 重新生成 Prisma 客户端
npx prisma generate
```
</Accordion>
</AccordionGroup>
## 进阶主题
<CardGroup cols={2}>
<Card title="架构设计" icon="building" href="/zh/development/architecture">
了解 MCPHub 的整体架构和设计模式
</Card>
<Card title="API 开发" icon="code" href="/zh/development/api-development">
学习如何开发和设计 RESTful API
</Card>
<Card title="性能优化" icon="rocket" href="/zh/development/performance">
掌握性能分析和优化技巧
</Card>
<Card title="部署指南" icon="cloud" href="/zh/deployment/production">
了解生产环境部署的最佳实践
</Card>
</CardGroup>

View File

@@ -0,0 +1,244 @@
---
title: '开发环境搭建'
description: '学习如何为 MCPHub 搭建开发环境'
---
# 开发环境搭建
本指南将帮助您搭建 MCPHub 的开发环境,为项目贡献代码。
## 先决条件
在开始之前,请确保您已安装以下软件:
- **Node.js**(版本 18 或更高)
- **pnpm**(推荐的包管理器)
- **Git**
- **Docker**(可选,用于容器化开发)
## 搭建开发环境
### 1. 克隆仓库
```bash
git clone https://github.com/your-username/mcphub.git
cd mcphub
```
### 2. 安装依赖
```bash
pnpm install
```
### 3. 环境配置
在根目录创建 `.env` 文件:
```bash
cp .env.example .env
```
配置以下环境变量:
```env
# 服务器配置
PORT=3000
NODE_ENV=development
# 数据库配置
DATABASE_URL=postgresql://username:password@localhost:5432/mcphub
# JWT 配置
JWT_SECRET=your-secret-key
JWT_EXPIRES_IN=24h
# OpenAI 配置(用于智能路由)
OPENAI_API_KEY=your-openai-api-key
```
### 4. 数据库设置
如果使用 PostgreSQL创建数据库
```bash
createdb mcphub
```
### 5. MCP 服务器配置
创建或修改 `mcp_settings.json`
```json
{
"mcpServers": {
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"]
},
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest", "--headless"]
}
}
}
```
## 开发工作流
### 运行开发服务器
同时启动后端和前端开发模式:
```bash
pnpm dev
```
这将启动:
- 后端服务器:`http://localhost:3000`
- 前端开发服务器:`http://localhost:5173`
### 仅运行后端
```bash
pnpm backend:dev
```
### 仅运行前端
```bash
pnpm frontend:dev
```
### 构建项目
构建后端和前端:
```bash
pnpm build
```
## 项目结构
```
mcphub/
├── src/ # 后端源代码
│ ├── controllers/ # Express 控制器
│ ├── routes/ # API 路由
│ ├── services/ # 业务逻辑
│ ├── models/ # 数据库模型
│ └── utils/ # 工具函数
├── frontend/ # 前端 React 应用
│ ├── src/
│ │ ├── components/ # React 组件
│ │ ├── pages/ # 页面组件
│ │ ├── services/ # API 服务
│ │ └── utils/ # 前端工具
├── docs/ # 文档
├── bin/ # CLI 脚本
└── scripts/ # 构建和工具脚本
```
## 开发工具
### 代码检查和格式化
```bash
# 运行 ESLint
pnpm lint
# 使用 Prettier 格式化代码
pnpm format
```
### 测试
```bash
# 运行测试
pnpm test
# 监视模式运行测试
pnpm test --watch
```
### 调试
使用 Node.js 检查器调试后端:
```bash
pnpm backend:debug
```
然后将调试器连接到 `http://localhost:9229`。
## 进行修改
### 后端开发
1. **控制器**:处理 HTTP 请求和响应
2. **服务**:实现业务逻辑
3. **模型**:定义数据库架构
4. **路由**:定义 API 端点
### 前端开发
1. **组件**:可重用的 React 组件
2. **页面**:特定路由的组件
3. **服务**API 通信
4. **钩子**:自定义 React 钩子
### 添加新的 MCP 服务器
1. 使用新的服务器配置更新 `mcp_settings.json`
2. 测试服务器集成
3. 必要时更新文档
## 常见开发任务
### 添加新的 API 端点
1. 在 `src/controllers/` 中创建控制器
2. 在 `src/routes/` 中定义路由
3. 添加必要的中间件
4. 为新端点编写测试
### 添加新的前端功能
1. 在 `frontend/src/components/` 中创建组件
2. 根据需要添加路由
3. 实现 API 集成
4. 使用 Tailwind CSS 进行样式设计
### 数据库迁移
修改数据库架构时:
1. 更新 `src/models/` 中的模型
2. 如果使用 TypeORM创建迁移脚本
3. 在本地测试迁移
## 故障排除
### 常见问题
**端口冲突**:确保端口 3000 和 5173 可用
**数据库连接**:验证 PostgreSQL 正在运行且凭据正确
**MCP 服务器启动**:检查 `mcp_settings.json` 中的服务器配置
**权限问题**:确保 MCP 服务器具有必要的权限
### 获取帮助
- 查看[贡献指南](/zh/development/contributing)
- 阅读[架构文档](/zh/development/architecture)
- 在 GitHub 上提交问题报告 bug
- 加入我们的社区讨论
## 下一步
- 阅读[架构概述](/zh/development/architecture)
- 了解[贡献指南](/zh/development/contributing)
- 探索[配置选项](/zh/configuration/environment-variables)

892
docs/zh/essentials/code.mdx Normal file
View File

@@ -0,0 +1,892 @@
---
title: '代码块'
description: 'MCPHub 文档中代码块的编写和展示指南'
---
## 内联代码
在 MCPHub 文档中使用内联代码来标记命令、配置键、文件名或短代码片段:
```md
使用 `mcphub start` 命令启动服务器,配置 `MCPHUB_PORT` 环境变量。
```
使用 `mcphub start` 命令启动服务器,配置 `MCPHUB_PORT` 环境变量。
## 代码块语法
### 基本代码块
MCPHub 支持多种编程语言的语法高亮:
````md
```javascript
// JavaScript 示例
const mcpClient = new MCPClient({
endpoint: process.env.MCPHUB_ENDPOINT,
apiKey: process.env.MCPHUB_API_KEY,
});
```
````
```javascript
// JavaScript 示例
const mcpClient = new MCPClient({
endpoint: process.env.MCPHUB_ENDPOINT,
apiKey: process.env.MCPHUB_API_KEY,
});
```
### TypeScript 代码
````md
```typescript
interface MCPServerConfig {
id: string;
name: string;
endpoint: string;
capabilities: string[];
metadata?: Record<string, any>;
}
class MCPServer implements MCPServerConfig {
constructor(
public id: string,
public name: string,
public endpoint: string,
public capabilities: string[],
) {}
}
```
````
```typescript
interface MCPServerConfig {
id: string;
name: string;
endpoint: string;
capabilities: string[];
metadata?: Record<string, any>;
}
class MCPServer implements MCPServerConfig {
constructor(
public id: string,
public name: string,
public endpoint: string,
public capabilities: string[],
) {}
}
```
### Python 代码
````md
```python
import requests
from typing import Dict, List, Optional
class MCPHubClient:
def __init__(self, endpoint: str, api_key: str):
self.endpoint = endpoint
self.api_key = api_key
self.headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
def create_server(self, config: Dict) -> Dict:
response = requests.post(
f'{self.endpoint}/api/servers',
json=config,
headers=self.headers
)
return response.json()
```
````
```python
import requests
from typing import Dict, List, Optional
class MCPHubClient:
def __init__(self, endpoint: str, api_key: str):
self.endpoint = endpoint
self.api_key = api_key
self.headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
def create_server(self, config: Dict) -> Dict:
response = requests.post(
f'{self.endpoint}/api/servers',
json=config,
headers=self.headers
)
return response.json()
```
## 配置文件
### YAML 配置
````md
```yaml title="mcphub.yml"
server:
port: 3000
host: 0.0.0.0
database:
type: postgresql
host: localhost
port: 5432
database: mcphub
username: mcphub_user
password: secure_password
mcp:
servers:
- id: ai-assistant
name: AI Assistant Server
endpoint: https://ai.example.com
capabilities:
- chat
- completion
- id: data-processor
name: Data Processing Server
endpoint: https://data.example.com
capabilities:
- analysis
- transformation
routing:
strategy: round_robin
health_check:
enabled: true
interval: 30s
timeout: 5s
logging:
level: info
format: json
file: /var/log/mcphub/app.log
```
````
```yaml title="mcphub.yml"
server:
port: 3000
host: 0.0.0.0
database:
type: postgresql
host: localhost
port: 5432
database: mcphub
username: mcphub_user
password: secure_password
mcp:
servers:
- id: ai-assistant
name: AI Assistant Server
endpoint: https://ai.example.com
capabilities:
- chat
- completion
- id: data-processor
name: Data Processing Server
endpoint: https://data.example.com
capabilities:
- analysis
- transformation
routing:
strategy: round_robin
health_check:
enabled: true
interval: 30s
timeout: 5s
logging:
level: info
format: json
file: /var/log/mcphub/app.log
```
### JSON 配置
````md
```json title="package.json"
{
"name": "@mcphub/server",
"version": "2.1.0",
"description": "Model Context Protocol Hub Server",
"main": "dist/index.js",
"scripts": {
"start": "node dist/index.js",
"dev": "tsx watch src/index.ts",
"build": "tsc",
"test": "jest",
"test:watch": "jest --watch",
"lint": "eslint src/**/*.ts",
"migrate": "prisma migrate deploy"
},
"dependencies": {
"@prisma/client": "^5.7.0",
"express": "^4.18.2",
"helmet": "^7.1.0",
"cors": "^2.8.5",
"jsonwebtoken": "^9.0.2",
"bcryptjs": "^2.4.3",
"winston": "^3.11.0"
},
"devDependencies": {
"@types/node": "^20.10.0",
"@types/express": "^4.17.21",
"typescript": "^5.3.0",
"tsx": "^4.6.0",
"jest": "^29.7.0",
"eslint": "^8.55.0"
}
}
```
````
```json title="package.json"
{
"name": "@mcphub/server",
"version": "2.1.0",
"description": "Model Context Protocol Hub Server",
"main": "dist/index.js",
"scripts": {
"start": "node dist/index.js",
"dev": "tsx watch src/index.ts",
"build": "tsc",
"test": "jest",
"test:watch": "jest --watch",
"lint": "eslint src/**/*.ts",
"migrate": "prisma migrate deploy"
},
"dependencies": {
"@prisma/client": "^5.7.0",
"express": "^4.18.2",
"helmet": "^7.1.0",
"cors": "^2.8.5",
"jsonwebtoken": "^9.0.2",
"bcryptjs": "^2.4.3",
"winston": "^3.11.0"
},
"devDependencies": {
"@types/node": "^20.10.0",
"@types/express": "^4.17.21",
"typescript": "^5.3.0",
"tsx": "^4.6.0",
"jest": "^29.7.0",
"eslint": "^8.55.0"
}
}
```
### Docker 配置
````md
```dockerfile title="Dockerfile"
FROM node:18-alpine AS builder
WORKDIR /app
# 复制依赖文件
COPY package*.json ./
COPY tsconfig.json ./
# 安装依赖
RUN npm ci --only=production
# 复制源码
COPY src/ ./src/
# 构建应用
RUN npm run build
# 生产环境镜像
FROM node:18-alpine AS production
WORKDIR /app
# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S mcphub -u 1001
# 复制构建产物
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package*.json ./
# 设置权限
USER mcphub
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node dist/health-check.js
EXPOSE 3000
CMD ["node", "dist/index.js"]
```
````
```dockerfile title="Dockerfile"
FROM node:18-alpine AS builder
WORKDIR /app
# 复制依赖文件
COPY package*.json ./
COPY tsconfig.json ./
# 安装依赖
RUN npm ci --only=production
# 复制源码
COPY src/ ./src/
# 构建应用
RUN npm run build
# 生产环境镜像
FROM node:18-alpine AS production
WORKDIR /app
# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S mcphub -u 1001
# 复制构建产物
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package*.json ./
# 设置权限
USER mcphub
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node dist/health-check.js
EXPOSE 3000
CMD ["node", "dist/index.js"]
```
## 终端命令
### Bash/Shell 命令
````md
```bash
# 克隆 MCPHub 仓库
git clone https://github.com/mcphub/mcphub.git
cd mcphub
# 安装依赖
npm install
# 复制环境变量文件
cp .env.example .env
# 设置数据库
npm run db:setup
# 启动开发服务器
npm run dev
# 构建生产版本
npm run build
# 启动生产服务器
npm start
```
````
```bash
# 克隆 MCPHub 仓库
git clone https://github.com/mcphub/mcphub.git
cd mcphub
# 安装依赖
npm install
# 复制环境变量文件
cp .env.example .env
# 设置数据库
npm run db:setup
# 启动开发服务器
npm run dev
# 构建生产版本
npm run build
# 启动生产服务器
npm start
```
### PowerShell 命令
````md
```powershell
# Windows PowerShell 安装步骤
# 克隆仓库
git clone https://github.com/mcphub/mcphub.git
Set-Location mcphub
# 安装 Node.js 依赖
npm install
# 复制环境变量文件
Copy-Item .env.example .env
# 启动开发服务器
npm run dev
```
````
```powershell
# Windows PowerShell 安装步骤
# 克隆仓库
git clone https://github.com/mcphub/mcphub.git
Set-Location mcphub
# 安装 Node.js 依赖
npm install
# 复制环境变量文件
Copy-Item .env.example .env
# 启动开发服务器
npm run dev
```
### Docker 命令
````md
```bash
# 使用 Docker 运行 MCPHub
docker run -d \
--name mcphub \
-p 3000:3000 \
-e NODE_ENV=production \
-e DATABASE_URL=postgresql://user:pass@host:5432/mcphub \
-e JWT_SECRET=your-secret-key \
mcphub/server:latest
# 查看日志
docker logs mcphub
# 进入容器
docker exec -it mcphub sh
# 停止容器
docker stop mcphub
# 使用 Docker Compose
docker-compose up -d
```
````
```bash
# 使用 Docker 运行 MCPHub
docker run -d \
--name mcphub \
-p 3000:3000 \
-e NODE_ENV=production \
-e DATABASE_URL=postgresql://user:pass@host:5432/mcphub \
-e JWT_SECRET=your-secret-key \
mcphub/server:latest
# 查看日志
docker logs mcphub
# 进入容器
docker exec -it mcphub sh
# 停止容器
docker stop mcphub
# 使用 Docker Compose
docker-compose up -d
```
## API 请求示例
### cURL 命令
````md
```bash
# 创建新的 MCP 服务器
curl -X POST https://api.mcphub.io/api/servers \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "AI Assistant Server",
"endpoint": "https://ai.example.com",
"capabilities": ["chat", "completion"],
"groupId": "production"
}'
# 获取服务器列表
curl -X GET "https://api.mcphub.io/api/servers?limit=10&active=true" \
-H "Authorization: Bearer YOUR_API_TOKEN"
# 更新服务器配置
curl -X PUT https://api.mcphub.io/api/servers/server-123 \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated AI Assistant",
"active": true
}'
# 删除服务器
curl -X DELETE https://api.mcphub.io/api/servers/server-123 \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
````
```bash
# 创建新的 MCP 服务器
curl -X POST https://api.mcphub.io/api/servers \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "AI Assistant Server",
"endpoint": "https://ai.example.com",
"capabilities": ["chat", "completion"],
"groupId": "production"
}'
# 获取服务器列表
curl -X GET "https://api.mcphub.io/api/servers?limit=10&active=true" \
-H "Authorization: Bearer YOUR_API_TOKEN"
# 更新服务器配置
curl -X PUT https://api.mcphub.io/api/servers/server-123 \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated AI Assistant",
"active": true
}'
# 删除服务器
curl -X DELETE https://api.mcphub.io/api/servers/server-123 \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
### HTTP 请求示例
````md
```http
POST /api/servers HTTP/1.1
Host: api.mcphub.io
Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json
{
"name": "AI Assistant Server",
"endpoint": "https://ai.example.com",
"capabilities": ["chat", "completion"],
"groupId": "production"
}
```
````
```http
POST /api/servers HTTP/1.1
Host: api.mcphub.io
Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json
{
"name": "AI Assistant Server",
"endpoint": "https://ai.example.com",
"capabilities": ["chat", "completion"],
"groupId": "production"
}
```
## 数据库查询
### SQL 查询
````md
```sql
-- 查询活跃的 MCP 服务器
SELECT
id,
name,
endpoint,
status,
created_at
FROM mcp_servers
WHERE status = 'active'
ORDER BY created_at DESC;
-- 统计每个组的服务器数量
SELECT
g.name as group_name,
COUNT(s.id) as server_count
FROM server_groups g
LEFT JOIN mcp_servers s ON g.id = s.group_id
GROUP BY g.id, g.name
ORDER BY server_count DESC;
-- 查询最近的错误日志
SELECT
timestamp,
level,
message,
metadata
FROM logs
WHERE level = 'error'
AND timestamp >= NOW() - INTERVAL '1 hour'
ORDER BY timestamp DESC
LIMIT 50;
```
````
```sql
-- 查询活跃的 MCP 服务器
SELECT
id,
name,
endpoint,
status,
created_at
FROM mcp_servers
WHERE status = 'active'
ORDER BY created_at DESC;
-- 统计每个组的服务器数量
SELECT
g.name as group_name,
COUNT(s.id) as server_count
FROM server_groups g
LEFT JOIN mcp_servers s ON g.id = s.group_id
GROUP BY g.id, g.name
ORDER BY server_count DESC;
-- 查询最近的错误日志
SELECT
timestamp,
level,
message,
metadata
FROM logs
WHERE level = 'error'
AND timestamp >= NOW() - INTERVAL '1 hour'
ORDER BY timestamp DESC
LIMIT 50;
```
## 代码块最佳实践
### 1. 语言标识
始终为代码块指定正确的语言:
````md
````javascript // ✅ 正确
```js // ✅ 也可以
```; // ❌ 避免无语言标识
````
````
### 2. 文件名标题
为配置文件和示例添加文件名:
````md
```yaml title="docker-compose.yml"
version: '3.8'
services:
mcphub:
image: mcphub/server:latest
```
````
### 3. 突出显示重要行
使用行号高亮重要代码:
````md
```javascript {3,7-9}
const express = require('express');
const app = express();
const port = process.env.PORT || 3000; // 重要:端口配置
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
app.listen(port, () => {
// 重要:服务器启动
console.log(`Server running on port ${port}`);
}); // 重要:结束
```
````
### 4. 代码注释
添加有意义的中文注释:
```javascript
// 初始化 MCPHub 客户端
const client = new MCPHubClient({
endpoint: 'https://api.mcphub.io',
apiKey: process.env.API_KEY,
timeout: 30000, // 30 秒超时
retries: 3, // 重试 3 次
});
// 配置路由策略
client.setRoutingStrategy({
type: 'weighted', // 加权轮询
healthCheck: true, // 启用健康检查
fallback: 'round_robin', // 降级策略
});
```
### 5. 错误处理示例
展示完整的错误处理:
```javascript
try {
const server = await mcpClient.createServer({
name: 'AI Assistant',
endpoint: 'https://ai.example.com',
});
console.log('服务器创建成功:', server.id);
} catch (error) {
if (error.code === 'DUPLICATE_SERVER') {
console.log('服务器已存在,跳过创建');
} else if (error.code === 'INVALID_ENDPOINT') {
console.error('无效的端点地址:', error.message);
} else {
console.error('创建失败:', error.message);
throw error; // 重新抛出未知错误
}
}
```
## 支持的语言
MCPHub 文档支持以下编程语言的语法高亮:
- **JavaScript/TypeScript**: `javascript`, `js`, `typescript`, `ts`
- **Python**: `python`, `py`
- **Shell/Bash**: `bash`, `shell`, `sh`
- **PowerShell**: `powershell`, `ps1`
- **SQL**: `sql`, `postgresql`, `mysql`
- **YAML**: `yaml`, `yml`
- **JSON**: `json`
- **XML**: `xml`
- **HTML**: `html`
- **CSS**: `css`
- **Dockerfile**: `dockerfile`
- **Go**: `go`
- **Rust**: `rust`
- **Java**: `java`
- **C#**: `csharp`, `cs`
- **PHP**: `php`
- **Ruby**: `ruby`
- **HTTP**: `http`
- **Markdown**: `markdown`, `md`
`````
### 使用三个反引号
````md
```javascript
console.log('Hello World');
`````
`````
### 语法高亮
我们使用 [Prism](https://prismjs.com/) 来语法高亮显示。Prism 支持 [各种编程语言](https://prismjs.com/#supported-languages)。
要添加语法高亮显示,请在代码块的第一行指定语言。
````md
```python
def hello():
print("Hello World")
```
`````
```python
def hello():
print("Hello World")
```
## 代码组
<CodeGroup>
```bash npm
npm i mintlify
```
```bash yarn
yarn add mintlify
```
```bash pnpm
pnpm add mintlify
```
</CodeGroup>
`CodeGroup` 允许您将多个代码块组合在一起,并为它们提供选项卡。
````md
<CodeGroup>
```bash npm
npm i mintlify
```
```bash yarn
yarn add mintlify
```
```bash pnpm
pnpm add mintlify
```
</CodeGroup>
````
### 代码标题
您也可以为代码块设置标题:
```javascript hello.js
const hello = 'world';
console.log(hello);
```
````md
```javascript hello.js
const hello = 'world';
console.log(hello);
```
````

View File

@@ -0,0 +1,134 @@
---
title: '图片和视频'
description: '在您的文档中添加图片和视频'
---
## 图片
### 使用 Markdown
您可以使用标准的 Markdown 语法添加图片:
![描述](/images/hero-light.png)
```md
![描述](/images/hero-light.png)
```
### 使用 HTML
您也可以使用原始 HTML 获得更多自定义选项:
<img height="200" src="/images/hero-light.png" />
```html
<img height="200" src="/images/hero-light.png" />
```
### 图片组件
使用内置的 `<img>` 组件来显示响应式的明暗主题图片:
<img className="block dark:hidden" src="/images/hero-light.png" alt="Hero Light" />
<img className="hidden dark:block" src="/images/hero-dark.png" alt="Hero Dark" />
```jsx
<img
className="block dark:hidden"
src="/images/hero-light.png"
alt="Hero Light"
/>
<img
className="hidden dark:block"
src="/images/hero-dark.png"
alt="Hero Dark"
/>
```
## 图片缩放
您可以使图片在点击时可缩放(类似于中等缩放)使用 `zoom` 属性。
<img src="/images/hero-light.png" alt="可缩放" zoom />
```jsx
<img src="/images/hero-light.png" alt="可缩放" zoom />
```
## 嵌入视频
### YouTube
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/4KzFe50RQkQ"
title="YouTube 视频播放器"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
```html
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/4KzFe50RQkQ"
title="YouTube 视频播放器"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
```
### Loom
<iframe
src="https://www.loom.com/embed/9019ef5b27ae417798d65b41749227ac"
frameborder="0"
webkitallowfullscreen
mozallowfullscreen
allowfullscreen
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
}}
></iframe>
```html
<iframe
src="https://www.loom.com/embed/9019ef5b27ae417798d65b41749227ac"
frameborder="0"
webkitallowfullscreen
mozallowfullscreen
allowfullscreen
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
}}
></iframe>
```
## 图片最佳实践
### 大小优化
- 保持图片尺寸在合理范围内(通常不超过 1000px 宽度)
- 使用适当的图片格式PNG 用于图标JPG 用于照片)
- 考虑使用现代格式如 WebP 来减少文件大小
### 无障碍性
- 始终包含描述性的 `alt` 文本
- 确保图片在各种屏幕尺寸下都能正常显示
### 组织
- 将图片存储在 `/images` 或 `/assets` 文件夹中
- 使用描述性的文件名

View File

@@ -0,0 +1,412 @@
---
title: 'Markdown 语法'
description: 'MCPHub 文档的 Markdown 编写指南和最佳实践'
---
## 标题
在 MCPHub 文档中,每个页面应该只使用一个 `#` 标题,它会自动成为页面标题。
```md
# MCP 服务器配置指南
## 快速开始
### 安装依赖
#### 系统要求
##### Node.js 版本
###### 推荐版本
```
<ResponseExample>
# 标题 1
## 标题 2
### 标题 3
#### 标题 4
##### 标题 5
###### 标题 6
</ResponseExample>
## 文本格式
### 基本格式
MCPHub 文档支持标准的 Markdown 文本格式:
```md
**粗体文本** - 用于强调重要概念
_斜体文本_ - 用于强调或引用
`行内代码` - 用于命令、配置键或代码片段
~~删除线~~ - 用于标记过时的内容
```
**粗体文本** - 用于强调重要概念
_斜体文本_ - 用于强调或引用
`行内代码` - 用于命令、配置键或代码片段
~~删除线~~ - 用于标记过时的内容
### 链接
#### 内部链接
链接到其他文档页面:
```md
查看 [服务器配置指南](/zh/configuration/mcp-settings) 获取详细信息。
```
查看 [服务器配置指南](/zh/configuration/mcp-settings) 获取详细信息。
#### 外部链接
```md
访问 [Model Context Protocol 官网](https://modelcontextprotocol.io) 了解更多。
```
访问 [Model Context Protocol 官网](https://modelcontextprotocol.io) 了解更多。
## 列表
### 无序列表
适用于功能列表、要求等:
```md
MCPHub 主要功能:
- 智能路由分发
- 服务器组管理
- 实时监控
- 身份认证
- JWT 令牌
- API 密钥
- OAuth 2.0
```
MCPHub 主要功能:
- 智能路由分发
- 服务器组管理
- 实时监控
- 身份认证
- JWT 令牌
- API 密钥
- OAuth 2.0
### 有序列表
适用于步骤说明、安装指南等:
```md
快速部署步骤:
1. 克隆仓库
2. 安装依赖
3. 配置环境变量
4. 启动服务
5. 验证部署
```
快速部署步骤:
1. 克隆仓库
2. 安装依赖
3. 配置环境变量
4. 启动服务
5. 验证部署
## 代码块
### 基本代码块
````md
```javascript
// MCPHub 客户端初始化
const mcpClient = new MCPClient({
endpoint: 'https://api.mcphub.io',
apiKey: process.env.MCPHUB_API_KEY,
});
```
````
```javascript
// MCPHub 客户端初始化
const mcpClient = new MCPClient({
endpoint: 'https://api.mcphub.io',
apiKey: process.env.MCPHUB_API_KEY,
});
```
### 配置文件示例
````md
```yaml title="docker-compose.yml"
version: '3.8'
services:
mcphub:
image: mcphub/server:latest
ports:
- '3000:3000'
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:pass@db:5432/mcphub
```
````
```yaml title="docker-compose.yml"
version: '3.8'
services:
mcphub:
image: mcphub/server:latest
ports:
- '3000:3000'
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:pass@db:5432/mcphub
```
### 终端命令
````md
```bash
# 安装 MCPHub CLI
npm install -g @mcphub/cli
# 初始化项目
mcphub init my-project
# 启动开发服务器
mcphub dev
```
````
```bash
# 安装 MCPHub CLI
npm install -g @mcphub/cli
# 初始化项目
mcphub init my-project
# 启动开发服务器
mcphub dev
```
## 表格
### 基本表格
```md
| 功能 | 开源版 | 企业版 |
| ------------ | ------ | ------ |
| 基础路由 | ✅ | ✅ |
| 智能负载均衡 | ❌ | ✅ |
| 高级监控 | ❌ | ✅ |
| 24/7 支持 | ❌ | ✅ |
```
| 功能 | 开源版 | 企业版 |
| ------------ | ------ | ------ |
| 基础路由 | ✅ | ✅ |
| 智能负载均衡 | ❌ | ✅ |
| 高级监控 | ❌ | ✅ |
| 24/7 支持 | ❌ | ✅ |
### API 参数表格
```md
| 参数名 | 类型 | 必需 | 描述 |
| ---------- | ------- | ---- | ---------------------- |
| `serverId` | string | 是 | 服务器唯一标识符 |
| `groupId` | string | 否 | 服务器组 ID |
| `active` | boolean | 否 | 是否激活默认true |
```
| 参数名 | 类型 | 必需 | 描述 |
| ---------- | ------- | ---- | ---------------------- |
| `serverId` | string | 是 | 服务器唯一标识符 |
| `groupId` | string | 否 | 服务器组 ID |
| `active` | boolean | 否 | 是否激活默认true |
## 引用块
### 信息提示
```md
> 📝 **提示**
> 在生产环境中部署前,请确保已正确配置所有环境变量。
```
> 📝 **提示**
> 在生产环境中部署前,请确保已正确配置所有环境变量。
### 警告信息
```md
> ⚠️ **警告**
> 修改核心配置可能会影响系统稳定性,请谨慎操作。
```
> ⚠️ **警告**
> 修改核心配置可能会影响系统稳定性,请谨慎操作。
## 任务列表
```md
- [x] 完成服务器配置
- [x] 设置数据库连接
- [ ] 配置负载均衡
- [ ] 设置监控告警
- [ ] 编写单元测试
```
- [x] 完成服务器配置
- [x] 设置数据库连接
- [ ] 配置负载均衡
- [ ] 设置监控告警
- [ ] 编写单元测试
## 水平分割线
用于分隔不同的内容部分:
```md
## 第一部分
内容...
---
## 第二部分
更多内容...
```
---
## 转义字符
当需要显示 Markdown 特殊字符时:
```md
\*这不是斜体\*
\`这不是代码\`
\[这不是链接\]
```
\*这不是斜体\*
\`这不是代码\`
\[这不是链接\]
## MCPHub 文档特定约定
### 配置项格式
环境变量和配置项使用特定格式:
```md
设置 `MCPHUB_PORT` 环境变量为 `3000`。
```
设置 `MCPHUB_PORT` 环境变量为 `3000`。
### API 端点格式
```md
`GET /api/servers/{id}` - 获取服务器详情
```
`GET /api/servers/{id}` - 获取服务器详情
### 版本标记
```md
该功能在 v2.1.0+ 版本中可用。
```
该功能在 v2.1.0+ 版本中可用。
## 最佳实践
1. **标题层级**:保持清晰的标题层级结构
2. **代码示例**:为所有代码块指定语言
3. **链接检查**:确保所有内部链接有效
4. **图片描述**:为图片添加有意义的 alt 文本
5. **一致性**:在整个文档中保持术语和格式一致
### 文档模板示例
````md
---
title: '功能名称'
description: '简短的功能描述'
---
## 概述
简要介绍该功能的用途和重要性。
## 快速开始
### 前提条件
- 系统要求
- 依赖软件
### 安装步骤
1. 第一步
2. 第二步
3. 第三步
```bash
# 示例命令
npm install example
```
## 配置
### 基本配置
| 配置项 | 类型 | 描述 |
| --------- | ------ | -------- |
| `option1` | string | 选项描述 |
### 高级配置
详细的配置说明...
## 示例
### 基本用法
```javascript
// 代码示例
const example = new Example();
```
### 高级用法
更复杂的使用场景...
## 故障排除
### 常见问题
**问题**:描述问题
**解决方案**:解决步骤
## 参考资料
- [相关文档链接](/link)
- [外部资源](https://example.com)
````

View File

@@ -0,0 +1,596 @@
---
title: '导航配置'
description: 'MCPHub 文档的导航结构配置指南'
---
## 基础导航
MCPHub 文档的导航在 `docs.json` 文件中配置。基本导航结构包含组和页面:
```json title="docs.json"
{
"navigation": [
{
"group": "快速开始",
"pages": ["zh/index", "zh/quickstart"]
},
{
"group": "开发指南",
"pages": [
"zh/development/getting-started",
"zh/development/api-integration",
"zh/development/testing"
]
},
{
"group": "配置管理",
"pages": [
"zh/configuration/environment-variables",
"zh/configuration/mcp-settings",
"zh/configuration/docker-setup",
"zh/configuration/nginx"
]
}
]
}
```
## 标签导航
当您的文档有多个主要部分时,可以使用标签来组织内容。
```json docs.json
{
"navigation": {
"tabs": [
{
"tab": "指南",
"groups": [
{
"group": "基础",
"pages": ["basics/introduction"]
}
]
},
{
"tab": "API 参考",
"groups": [
{
"group": "端点",
"pages": ["api/users", "api/products"]
}
]
}
]
}
}
```
## 页面引用
### 文件路径引用
最常见的方式是通过文件路径引用页面(不包含 `.mdx` 扩展名):
```json
{
"pages": ["quickstart", "advanced/configuration"]
}
```
### 外部链接
您也可以在导航中包含外部链接:
```json
{
"pages": [
"introduction",
{
"page": "GitHub",
"href": "https://github.com"
}
]
}
```
## 分组
### 基本分组
每个组都有一个名称和页面列表:
```json
{
"group": "API 基础",
"pages": ["api/authentication", "api/errors", "api/rate-limits"]
}
```
### 分组版本控制
您可以为组指定版本:
```json
{
"group": "API v2",
"version": "v2.0",
"pages": ["api/v2/users"]
}
```
## 全局导航元素
### 锚点
在所有页面上显示的持久链接:
```json docs.json
{
"navigation": {
"global": {
"anchors": [
{
"anchor": "API 参考",
"href": "/api-reference",
"icon": "square-terminal"
},
{
"anchor": "社区",
"href": "https://community.example.com",
"icon": "discord"
}
]
}
}
}
```
### 导航栏
配置顶部导航栏的链接:
```json docs.json
{
"navbar": {
"links": [
{
"label": "支持",
"href": "mailto:support@example.com"
}
],
"primary": {
"type": "button",
"label": "仪表板",
"href": "https://dashboard.example.com"
}
}
}
```
## 分层导航结构
### 多级导航
MCPHub 文档支持多级分层导航:
```json title="docs.json"
{
"navigation": [
{
"group": "核心功能",
"pages": [
{
"group": "服务器管理",
"pages": [
"zh/features/server-management",
"zh/features/server-health",
"zh/features/server-scaling"
]
},
{
"group": "智能路由",
"pages": [
"zh/features/smart-routing",
"zh/features/load-balancing",
"zh/features/failover"
]
}
]
}
]
}
```
### 条件导航
根据用户权限或版本显示不同的导航项:
```json title="docs.json"
{
"navigation": [
{
"group": "API 参考",
"pages": [
"zh/api-reference/introduction",
"zh/api-reference/authentication",
{
"group": "端点",
"pages": [
"zh/api-reference/endpoint/get",
"zh/api-reference/endpoint/create",
"zh/api-reference/endpoint/delete",
"zh/api-reference/endpoint/webhook"
]
}
]
},
{
"group": "企业功能",
"icon": "crown",
"version": "enterprise",
"pages": ["zh/enterprise/sso", "zh/enterprise/audit-logs", "zh/enterprise/compliance"]
}
]
}
```
## 标签导航
对于多产品或多语言文档,使用标签组织内容:
```json title="docs.json"
{
"tabs": [
{
"name": "文档",
"url": "https://docs.mcphub.io"
},
{
"name": "API",
"url": "https://api.mcphub.io"
},
{
"name": "SDK",
"url": "https://sdk.mcphub.io"
}
],
"navigation": {
"文档": [
{
"group": "开始使用",
"pages": ["zh/index", "zh/quickstart"]
}
],
"API": [
{
"group": "API 参考",
"pages": ["zh/api-reference/introduction", "zh/api-reference/authentication"]
}
]
}
}
```
## 导航图标
为导航项添加图标以提高可读性:
```json title="docs.json"
{
"navigation": [
{
"group": "快速开始",
"icon": "rocket",
"pages": ["zh/index", "zh/quickstart"]
},
{
"group": "配置",
"icon": "gear",
"pages": ["zh/configuration/environment-variables", "zh/configuration/mcp-settings"]
},
{
"group": "监控",
"icon": "chart-line",
"pages": ["zh/features/monitoring", "zh/features/analytics"]
}
]
}
```
### 支持的图标
MCPHub 文档支持以下图标库的图标:
- **Heroicons**: `hero-icon-name`
- **Font Awesome**: `fa-icon-name`
- **Feather**: `feather-icon-name`
- **Lucide**: `lucide-icon-name`
常用图标示例:
| 功能 | 图标 | 代码 |
| ---- | ---- | ------------- |
| 首页 | 🏠 | `"home"` |
| 设置 | ⚙️ | `"gear"` |
| API | 🔌 | `"plug"` |
| 安全 | 🔒 | `"lock"` |
| 监控 | 📊 | `"chart-bar"` |
| 文档 | 📖 | `"book"` |
| 开发 | 💻 | `"code"` |
## 外部链接
在导航中包含外部资源链接:
```json title="docs.json"
{
"navigation": [
{
"group": "资源",
"pages": [
{
"name": "GitHub 仓库",
"url": "https://github.com/mcphub/mcphub",
"icon": "github"
},
{
"name": "Discord 社区",
"url": "https://discord.gg/mcphub",
"icon": "discord"
},
{
"name": "状态页面",
"url": "https://status.mcphub.io",
"icon": "status"
}
]
}
]
}
```
## 导航排序
### 自动排序
默认情况下,导航项按字母顺序排列。可以通过文件名前缀控制排序:
```
zh/
├── 01-index.mdx
├── 02-quickstart.mdx
├── development/
│ ├── 01-getting-started.mdx
│ ├── 02-api-integration.mdx
│ └── 03-testing.mdx
└── configuration/
├── 01-environment-variables.mdx
├── 02-mcp-settings.mdx
└── 03-docker-setup.mdx
```
### 手动排序
在 `docs.json` 中明确指定顺序:
```json title="docs.json"
{
"navigation": [
{
"group": "核心概念",
"pages": [
"zh/concepts/introduction",
"zh/concepts/architecture",
"zh/concepts/mcp-protocol",
"zh/concepts/routing"
]
}
]
}
```
## 隐藏导航项
### 草稿页面
使用 `draft: true` 隐藏未完成的页面:
```yaml title="draft-page.mdx"
---
title: '开发中的功能'
description: '此功能正在开发中'
draft: true
---
```
### 条件显示
根据用户角色或环境显示导航:
```json title="docs.json"
{
"navigation": [
{
"group": "管理功能",
"hidden": "user",
"pages": ["zh/admin/user-management", "zh/admin/system-settings"]
}
]
}
```
## 导航元数据
### 页面元数据
在页面头部添加导航相关的元数据:
```yaml title="page.mdx"
---
title: '服务器管理'
description: 'MCPHub 服务器管理功能详解'
icon: 'server'
order: 1
hidden: false
version: '2.0+'
tags: ['管理', '服务器', '配置']
---
```
### 组元数据
为导航组添加描述和图标:
```json title="docs.json"
{
"navigation": [
{
"group": "API 参考",
"icon": "api",
"description": "完整的 API 接口文档",
"version": "v2",
"pages": ["zh/api-reference/introduction"]
}
]
}
```
## 搜索优化
### 搜索关键词
为页面添加搜索关键词:
```yaml title="page.mdx"
---
title: 'Docker 部署'
description: '使用 Docker 部署 MCPHub'
keywords: ['docker', '部署', '容器', '生产环境']
searchable: true
---
```
### 搜索权重
控制页面在搜索结果中的权重:
```yaml title="important-page.mdx"
---
title: '快速开始'
description: '5 分钟快速部署 MCPHub'
searchWeight: 10
featured: true
---
```
## 面包屑导航
自动生成面包屑导航:
```json title="docs.json"
{
"breadcrumbs": {
"enabled": true,
"separator": "",
"home": "首页"
},
"navigation": [
{
"group": "配置管理",
"pages": ["zh/configuration/environment-variables"]
}
]
}
```
显示效果:`首页 配置管理 环境变量`
## 导航最佳实践
### 1. 逻辑分组
按功能和用户需求逻辑分组:
```json
{
"navigation": [
{
"group": "新手指南",
"pages": ["introduction", "quickstart", "first-server"]
},
{
"group": "进阶配置",
"pages": ["advanced-routing", "scaling", "monitoring"]
},
{
"group": "参考文档",
"pages": ["api-reference", "cli-reference", "troubleshooting"]
}
]
}
```
### 2. 渐进式学习路径
设计符合学习曲线的导航结构:
1. **入门** → 快速开始、基础概念
2. **实践** → 配置、部署、集成
3. **进阶** → 优化、监控、故障排除
4. **参考** → API 文档、CLI 手册
### 3. 移动端友好
确保导航在移动设备上的可用性:
```json title="docs.json"
{
"navigation": [
{
"group": "快速开始",
"collapsed": false,
"pages": ["zh/index", "zh/quickstart"]
},
{
"group": "详细文档",
"collapsed": true,
"pages": ["zh/advanced/..."]
}
]
}
```
### 4. 国际化支持
为多语言文档配置导航:
```json title="docs.json"
{
"i18n": {
"defaultLocale": "zh",
"locales": ["zh", "en"]
},
"navigation": {
"zh": [
{
"group": "快速开始",
"pages": ["zh/index", "zh/quickstart"]
}
],
"en": [
{
"group": "Getting Started",
"pages": ["en/index", "en/quickstart"]
}
]
}
}
```
### 5. 性能优化
- 使用懒加载减少初始加载时间
- 合理设置导航深度(建议不超过 3 层)
- 避免过多的外部链接
- 定期清理无效的导航项

View File

@@ -0,0 +1,144 @@
---
title: '可重用代码片段'
description: '学习如何创建和使用代码片段来保持文档的一致性'
---
## 什么是代码片段?
代码片段允许您在文档的多个位置重用内容块。这有助于保持一致性并减少重复内容的维护工作。
## 创建代码片段
代码片段存储在 `snippets/` 文件夹中,使用 `.mdx` 扩展名。
### 基本代码片段
创建 `snippets/api-key-setup.mdx`
```md
获取您的 API 密钥:
1. 登录到您的仪表板
2. 导航到 **设置** > **API 密钥**
3. 点击 **生成新密钥**
4. 复制密钥并安全地存储
```
### 带参数的代码片段
您可以创建接受参数的动态代码片段。创建 `snippets/code-example.mdx`
````jsx
<CodeGroup>
```bash {props.packageManager}
{props.packageManager} install {props.packageName}
````
</CodeGroup>
```
## 使用代码片段
### 基本使用
使用 `<Snippet>` 组件来包含代码片段:
```jsx
<Snippet file="api-key-setup.mdx" />
```
<Snippet file="snippet-intro.mdx" />
### 带参数使用
```jsx
<Snippet file="code-example.mdx" packageManager="npm" packageName="mintlify" />
```
## 代码片段最佳实践
### 文件组织
```
snippets/
├── setup/
│ ├── installation.mdx
│ └── configuration.mdx
├── examples/
│ ├── basic-usage.mdx
│ └── advanced-usage.mdx
└── common/
├── prerequisites.mdx
└── troubleshooting.mdx
```
### 命名约定
- 使用描述性文件名
- 使用连字符分隔单词
- 按主题分组到子文件夹
### 内容指导原则
1. **保持简洁** - 代码片段应该是独立的内容块
2. **避免硬编码** - 对可变内容使用参数
3. **文档化参数** - 在代码片段中注释必需的参数
### 参数文档
在代码片段文件的顶部记录所需参数:
```md
<!--
参数:
- packageManager: 包管理器名称npm, yarn, pnpm
- packageName: 要安装的包名称
-->
安装说明...
```
## 高级代码片段
### 条件内容
您可以使用条件逻辑来根据参数显示不同的内容:
```jsx
{
props.framework === 'react' && <div>React 特定的内容...</div>;
}
{
props.framework === 'vue' && <div>Vue 特定的内容...</div>;
}
```
### 嵌套代码片段
代码片段可以包含其他代码片段:
```jsx
<Snippet file="common/prerequisites.mdx" />
## 安装步骤
<Snippet file="setup/installation.mdx" framework={props.framework} />
```
## 维护代码片段
### 版本控制
当更新代码片段时:
1. 考虑向后兼容性
2. 更新所有使用该代码片段的页面
3. 测试更改在所有上下文中的效果
### 重构检查清单
- [ ] 确认所有参数仍然有效
- [ ] 验证代码片段在所有使用位置正确渲染
- [ ] 更新相关文档

View File

@@ -0,0 +1,172 @@
---
title: '设置'
description: '了解如何配置您的文档'
---
## 全局配置
所有的全局配置都在项目根目录的 `docs.json` 文件中设置。
### 名称
在配置的顶层设置文档的名称。
```json docs.json
{
"name": "Mintlify 文档"
}
```
### Logo
您可以显示浅色和深色模式的 logo。
```json docs.json
{
"logo": {
"light": "/logo/light.svg",
"dark": "/logo/dark.svg"
}
}
```
### Favicon
```json docs.json
{
"favicon": "/favicon.ico"
}
```
### 颜色
自定义文档的主色调以匹配您的品牌。
```json docs.json
{
"colors": {
"primary": "#9563FF",
"light": "#AE87FF",
"dark": "#9563FF"
}
}
```
<Tip>设置一种颜色系统,通过仅更改主色调来协调您文档的配色方案。</Tip>
### 导航
您的侧边栏导航在 `navigation` 字段中设置。文档页面必须嵌套在组下,组必须嵌套在导航下。
```json docs.json
{
"navigation": [
{
"group": "开始使用",
"pages": ["introduction", "quickstart", "development"]
}
]
}
```
#### 标签
您可以将页面分组为不同的标签。当您想要将概念或 API 参考组织到不同的部分时,这很有用。
```json docs.json
{
"navigation": {
"tabs": [
{
"tab": "主要",
"groups": [
{
"group": "开始使用",
"pages": ["introduction"]
}
]
},
{
"tab": "API 参考",
"groups": [
{
"group": "端点",
"pages": ["api-reference/users"]
}
]
}
]
}
}
```
### 页脚
您可以在 `footer` 字段中配置页脚链接。
```json docs.json
{
"footer": {
"socials": {
"website": "https://mintlify.com",
"github": "https://github.com/mintlify",
"slack": "https://mintlify.com/community"
}
}
}
```
### 搜索
您可以通过多种方式配置搜索,包括替换默认搜索或添加搜索锚点。
```json docs.json
{
"search": {
"prompt": "搜索..."
}
}
```
## 页面配置
页面配置在每个 MDX 文件顶部的 frontmatter 中设置。
### 标题和描述
```md
---
title: '介绍'
description: '欢迎来到我们的产品!'
---
```
### 侧边栏标题
```md
---
sidebarTitle: '主页'
---
```
设置不同于页面标题的侧边栏标题。
### 图标
```md
---
icon: 'star'
---
```
为侧边栏中的页面设置 [FontAwesome](https://fontawesome.com/search?s=solid&m=free) 图标。
### 模式
```md
---
mode: 'wide'
---
```
设置页面的显示模式。选项包括 `"default"` 和 `"wide"`。

View File

@@ -0,0 +1,330 @@
---
title: '身份认证与安全'
description: '为 MCPHub 配置身份认证和安全设置'
---
## 概述
MCPHub 提供灵活的身份认证机制来保护您的 MCP 服务器管理平台。系统支持多种身份认证方法和基于角色的访问控制。
## 身份认证方法
### 基于环境变量的认证
使用环境变量配置基础认证:
```bash
# 基础认证凭据
AUTH_USERNAME=admin
AUTH_PASSWORD=your-secure-password
# JWT 设置
JWT_SECRET=your-jwt-secret-key
JWT_EXPIRES_IN=24h
```
### 数据库认证
对于生产环境部署,启用基于数据库的用户管理:
```json
{
"auth": {
"provider": "database",
"database": {
"url": "postgresql://user:pass@localhost:5432/mcphub",
"userTable": "users"
}
}
}
```
## 用户管理
### 创建用户
通过管理界面或 API 创建用户:
```bash
# 通过 API
curl -X POST http://localhost:3000/api/auth/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{
"username": "newuser",
"email": "user@example.com",
"password": "securepassword",
"role": "user"
}'
```
### 用户角色
MCPHub 支持基于角色的访问控制:
- **管理员**: 完整系统访问权限、用户管理、服务器配置
- **管理者**: 服务器管理、组管理、监控
- **用户**: 在分配组内的基本服务器访问权限
- **查看者**: 对分配资源的只读访问权限
## 基于组的访问控制
### 将用户分配到组
```bash
# 添加用户到组
curl -X POST http://localhost:3000/api/groups/{groupId}/users \
-H "Authorization: Bearer $TOKEN" \
-d '{"userId": "user123"}'
```
### 组权限
配置组级别权限:
```json
{
"groupId": "dev-team",
"permissions": {
"servers": ["read", "write", "execute"],
"tools": ["read", "execute"],
"logs": ["read"],
"config": ["read"]
}
}
```
## API 认证
### JWT 令牌认证
```javascript
// 获取认证令牌
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: 'your-username',
password: 'your-password',
}),
});
const { token } = await response.json();
// 在后续请求中使用令牌
const protectedResponse = await fetch('/api/servers', {
headers: {
Authorization: `Bearer ${token}`,
},
});
```
### API 密钥认证
为系统集成生成 API 密钥:
```bash
# 生成新的 API 密钥
curl -X POST http://localhost:3000/api/auth/api-keys \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "Integration Key",
"permissions": ["servers:read", "servers:write"],
"expiresAt": "2024-12-31T23:59:59.000Z"
}'
```
## 安全设置
### HTTPS 配置
为生产环境启用 HTTPS
```nginx
server {
listen 443 ssl http2;
server_name mcphub.example.com;
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
### 会话安全
配置安全的会话设置:
```javascript
// 会话配置
{
"session": {
"secret": "your-session-secret",
"secure": true, // 生产环境中需要 HTTPS
"httpOnly": true,
"maxAge": 86400000, // 24 小时
"sameSite": "strict"
}
}
```
### 速率限制
实施 API 速率限制:
```javascript
{
"rateLimit": {
"windowMs": 900000, // 15 分钟
"max": 100, // 每个 IP 限制 100 个请求
"message": "请求过于频繁,请稍后再试",
"standardHeaders": true,
"legacyHeaders": false
}
}
```
## 多因素认证 (MFA)
### 启用 TOTP
为管理员帐户启用基于时间的一次性密码:
```bash
# 启用 MFA
curl -X POST http://localhost:3000/api/auth/mfa/enable \
-H "Authorization: Bearer $TOKEN" \
-d '{
"type": "totp",
"appName": "MCPHub"
}'
```
### 验证 MFA 代码
```javascript
// 登录时验证 MFA
const loginResponse = await fetch('/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: 'admin',
password: 'password',
mfaCode: '123456', // 来自认证器应用的 6 位数字
}),
});
```
## 审计日志
### 启用审计日志
跟踪所有认证和授权事件:
```json
{
"audit": {
"enabled": true,
"logLevel": "info",
"events": [
"login",
"logout",
"password_change",
"role_change",
"permission_change",
"server_access",
"config_change"
],
"storage": {
"type": "database",
"retention": "90d"
}
}
}
```
### 查看审计日志
```bash
# 获取审计日志
curl -X GET "http://localhost:3000/api/audit/logs?startDate=2024-01-01&endDate=2024-01-31" \
-H "Authorization: Bearer $TOKEN"
```
## 密码策略
### 配置密码要求
```json
{
"passwordPolicy": {
"minLength": 12,
"requireUppercase": true,
"requireLowercase": true,
"requireNumbers": true,
"requireSpecialChars": true,
"preventCommonPasswords": true,
"preventReuse": 5, // 防止重复使用最近 5 个密码
"maxAge": 7776000 // 90 天后过期
}
}
```
## 故障排除
### 常见认证问题
1. **JWT 令牌过期**
```bash
# 检查令牌有效期
curl -X GET http://localhost:3000/api/auth/verify \
-H "Authorization: Bearer $TOKEN"
```
2. **权限被拒绝**
```bash
# 检查用户权限
curl -X GET http://localhost:3000/api/auth/permissions \
-H "Authorization: Bearer $TOKEN"
```
3. **会话问题**
- 清除浏览器 cookies
- 检查会话配置
- 验证服务器时间同步
### 调试认证流程
启用调试日志:
```bash
# 设置环境变量
export DEBUG=auth:*
export LOG_LEVEL=debug
# 启动服务器
npm start
```
## 安全最佳实践
1. **定期更新凭据**: 定期轮换 JWT 密钥和 API 密钥
2. **最小权限原则**: 只授予用户执行其任务所需的最小权限
3. **监控异常活动**: 设置警报以检测可疑的登录模式
4. **备份配置**: 定期备份认证配置和用户数据
5. **安全更新**: 保持 MCPHub 和依赖项的最新状态
更多安全配置选项,请参阅 [环境变量配置](/zh/configuration/environment-variables) 和 [Docker 设置](/zh/configuration/docker-setup) 文档。

View File

@@ -0,0 +1,567 @@
---
title: '组管理'
description: '组织用户和服务器为逻辑组,实现高效的访问控制'
---
## 概述
MCPHub 的组管理系统允许您将用户和服务器组织成逻辑组,从而简化权限管理和访问控制。组提供了一种灵活的方式来管理大规模部署中的资源。
## 创建组
### 通过仪表板
1. **导航到组部分**: 在主仪表板中点击"组"
2. **点击"创建组"**: 开始组创建流程
3. **填写组详细信息**:
- **组名**: 唯一的组标识符
- **显示名称**: 用户友好的组名称
- **描述**: 组的目的和范围
- **父组**: 可选的层次结构
### 通过 API
```bash
curl -X POST http://localhost:3000/api/groups \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "development-team",
"displayName": "开发团队",
"description": "前端和后端开发人员",
"parentGroup": null,
"settings": {
"autoAssign": false,
"maxMembers": 50,
"requireApproval": true
}
}'
```
### 通过配置文件
在 `groups.json` 中定义组:
```json
{
"groups": {
"dev-team": {
"displayName": "开发团队",
"description": "应用程序开发人员",
"permissions": {
"servers": ["read", "write", "execute"],
"tools": ["read", "execute"],
"logs": ["read"]
},
"members": ["user1", "user2"],
"servers": ["dev-server-1", "dev-server-2"]
},
"qa-team": {
"displayName": "质量保证团队",
"description": "测试和质量保证",
"permissions": {
"servers": ["read", "execute"],
"tools": ["read", "execute"],
"logs": ["read"]
},
"members": ["qa1", "qa2"],
"servers": ["test-server", "staging-server"]
}
}
}
```
## 组层次结构
### 嵌套组
创建组层次结构以实现更好的组织:
```json
{
"groups": {
"engineering": {
"displayName": "工程部",
"description": "所有工程团队",
"children": ["frontend", "backend", "devops"]
},
"frontend": {
"displayName": "前端团队",
"parent": "engineering",
"servers": ["frontend-dev", "frontend-staging"]
},
"backend": {
"displayName": "后端团队",
"parent": "engineering",
"servers": ["api-server", "database-server"]
},
"devops": {
"displayName": "运维团队",
"parent": "engineering",
"servers": ["monitoring", "deployment"]
}
}
}
```
### 继承权限
子组从父组继承权限:
```bash
# 检查继承的权限
curl -X GET http://localhost:3000/api/groups/frontend/permissions?inherited=true \
-H "Authorization: Bearer $TOKEN"
```
## 用户管理
### 添加用户到组
```bash
# 添加单个用户
curl -X POST http://localhost:3000/api/groups/dev-team/members \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"userId": "user123",
"role": "member"
}'
# 批量添加用户
curl -X POST http://localhost:3000/api/groups/dev-team/members/bulk \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"users": [
{"userId": "user1", "role": "member"},
{"userId": "user2", "role": "admin"},
{"userId": "user3", "role": "member"}
]
}'
```
### 用户角色
每个组内支持不同的用户角色:
- **组管理员**: 完整的组管理权限
- **成员**: 标准组访问权限
- **查看者**: 只读访问权限
- **访客**: 有限的临时访问权限
### 移除用户
```bash
# 从组中移除用户
curl -X DELETE http://localhost:3000/api/groups/dev-team/members/user123 \
-H "Authorization: Bearer $TOKEN"
```
## 服务器分配
### 分配服务器到组
```bash
# 分配单个服务器
curl -X POST http://localhost:3000/api/groups/dev-team/servers \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"serverId": "my-server",
"permissions": ["read", "write", "execute"]
}'
# 批量分配服务器
curl -X POST http://localhost:3000/api/groups/dev-team/servers/bulk \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"servers": [
{
"serverId": "server1",
"permissions": ["read", "write"]
},
{
"serverId": "server2",
"permissions": ["read", "execute"]
}
]
}'
```
### 服务器权限级别
为每个组-服务器对定义细粒度权限:
```json
{
"serverId": "my-server",
"permissions": {
"execute": {
"allowed": true,
"tools": ["filesystem", "web-search"],
"restrictions": {
"maxRequests": 100,
"timeWindow": "1h"
}
},
"configure": {
"allowed": false
},
"logs": {
"allowed": true,
"level": ["info", "warn", "error"]
}
}
}
```
## 权限管理
### 组权限模型
```json
{
"groupId": "dev-team",
"permissions": {
"servers": {
"create": false,
"read": true,
"update": true,
"delete": false,
"execute": true
},
"tools": {
"filesystem": {
"read": true,
"write": true,
"paths": ["/app/data", "/tmp"]
},
"web-search": {
"enabled": true,
"maxQueries": 50
}
},
"monitoring": {
"viewLogs": true,
"viewMetrics": true,
"exportData": false
},
"administration": {
"manageUsers": false,
"manageServers": true,
"manageGroups": false
}
}
}
```
### 动态权限
基于条件的动态权限:
```json
{
"permissions": {
"servers": {
"execute": {
"condition": "time.hour >= 9 && time.hour <= 17",
"message": "服务器执行仅在工作时间内允许"
}
},
"tools": {
"filesystem": {
"write": {
"condition": "user.role === 'admin' || group.name === 'senior-devs'",
"message": "写入权限需要管理员或高级开发者角色"
}
}
}
}
}
```
## 配额管理
### 设置组配额
```bash
curl -X PUT http://localhost:3000/api/groups/dev-team/quotas \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"requests": {
"daily": 1000,
"monthly": 30000
},
"storage": {
"maxSize": "10GB",
"retention": "30d"
},
"compute": {
"maxConcurrentRequests": 10,
"maxExecutionTime": "5m"
}
}'
```
### 监控配额使用
```bash
# 获取当前配额使用情况
curl -X GET http://localhost:3000/api/groups/dev-team/quotas/usage \
-H "Authorization: Bearer $TOKEN"
```
响应示例:
```json
{
"groupId": "dev-team",
"period": "2024-01-01T00:00:00Z",
"usage": {
"requests": {
"used": 750,
"limit": 1000,
"remaining": 250
},
"storage": {
"used": "7.2GB",
"limit": "10GB",
"remaining": "2.8GB"
},
"compute": {
"currentConcurrent": 3,
"maxConcurrent": 10,
"avgExecutionTime": "2m 15s"
}
}
}
```
## 组策略
### 定义组策略
```json
{
"groupId": "dev-team",
"policies": {
"security": {
"requireMFA": false,
"sessionTimeout": "8h",
"ipWhitelist": ["192.168.1.0/24", "10.0.0.0/8"]
},
"usage": {
"allowWeekendAccess": true,
"restrictHolidays": false,
"maxSessionDuration": "12h"
},
"data": {
"encryptionRequired": true,
"dataRetention": "90d",
"exportAllowed": false
}
}
}
```
### 策略继承
```bash
# 应用策略模板
curl -X POST http://localhost:3000/api/groups/dev-team/policies/apply \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"template": "development-team-template",
"overrides": {
"security.sessionTimeout": "4h"
}
}'
```
## 自动化组管理
### 自动用户分配
基于属性自动分配用户:
```json
{
"autoAssignment": {
"enabled": true,
"rules": [
{
"condition": "user.department === '开发'",
"action": {
"addToGroup": "dev-team",
"role": "member"
}
},
{
"condition": "user.title.includes('高级')",
"action": {
"addToGroup": "senior-devs",
"role": "admin"
}
}
]
}
}
```
### 定时任务
```bash
# 创建定时清理任务
curl -X POST http://localhost:3000/api/groups/dev-team/jobs \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "cleanup-inactive-users",
"schedule": "0 2 * * *",
"action": "removeInactiveUsers",
"params": {
"inactiveDays": 30
}
}'
```
## 组通知
### 配置通知
```json
{
"groupId": "dev-team",
"notifications": {
"channels": {
"email": {
"enabled": true,
"recipients": ["team-lead@company.com"]
},
"slack": {
"enabled": true,
"webhook": "https://hooks.slack.com/...",
"channel": "#dev-team"
}
},
"events": ["userJoined", "userLeft", "serverAdded", "quotaExceeded", "securityAlert"]
}
}
```
### 发送组通知
```bash
curl -X POST http://localhost:3000/api/groups/dev-team/notifications \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"type": "announcement",
"title": "维护通知",
"message": "服务器将在今晚 10 点进行维护",
"priority": "high",
"channels": ["email", "slack"]
}'
```
## 组分析
### 使用统计
```bash
# 获取组使用统计
curl -X GET http://localhost:3000/api/groups/dev-team/analytics \
-H "Authorization: Bearer $TOKEN"
```
响应示例:
```json
{
"groupId": "dev-team",
"period": "30d",
"stats": {
"activeUsers": 12,
"totalRequests": 15750,
"avgResponseTime": "250ms",
"errorRate": "0.5%",
"mostUsedTools": [
{ "name": "filesystem", "usage": 8500 },
{ "name": "web-search", "usage": 4200 },
{ "name": "database", "usage": 3050 }
],
"peakUsageHours": [9, 10, 14, 15, 16]
}
}
```
### 生成报告
```bash
# 生成月度报告
curl -X POST http://localhost:3000/api/groups/dev-team/reports \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"type": "monthly",
"format": "pdf",
"includeDetails": true,
"recipients": ["manager@company.com"]
}'
```
## 故障排除
### 常见问题
1. **用户无法访问组资源**
```bash
# 检查用户组成员身份
curl -X GET http://localhost:3000/api/users/user123/groups \
-H "Authorization: Bearer $TOKEN"
```
2. **权限配置错误**
```bash
# 验证权限设置
curl -X GET http://localhost:3000/api/groups/dev-team/permissions/validate \
-H "Authorization: Bearer $TOKEN"
```
3. **配额超限**
```bash
# 检查配额状态
curl -X GET http://localhost:3000/api/groups/dev-team/quotas/status \
-H "Authorization: Bearer $TOKEN"
```
### 调试组权限
启用权限调试:
```bash
# 调试用户权限
curl -X GET http://localhost:3000/api/debug/permissions \
-H "Authorization: Bearer $TOKEN" \
-d '{
"userId": "user123",
"resource": "server:my-server",
"action": "execute"
}'
```
## 最佳实践
1. **组织结构**: 使用层次化组结构镜像您的组织架构
2. **权限最小化**: 只授予执行任务所需的最小权限
3. **定期审核**: 定期审核组成员身份和权限
4. **自动化**: 使用自动化规则减少手动管理开销
5. **监控**: 设置监控和警报以跟踪组活动
有关更多信息,请参阅 [身份认证与安全](/zh/features/authentication) 和 [监控](/zh/features/monitoring) 文档。

View File

@@ -0,0 +1,613 @@
---
title: '监控与分析'
description: '全面监控 MCP 服务器性能、健康状况和使用情况'
---
## 概述
MCPHub 提供全面的监控和分析功能,帮助您跟踪 MCP 服务器的性能、健康状况和使用模式。系统提供实时指标、历史分析和智能警报。
## 实时监控
### 仪表板概览
主监控仪表板显示关键指标:
```bash
# 访问监控仪表板
curl -X GET http://localhost:3000/api/monitoring/dashboard \
-H "Authorization: Bearer $TOKEN"
```
响应示例:
```json
{
"overview": {
"totalServers": 12,
"activeServers": 10,
"totalRequests": 15420,
"avgResponseTime": "245ms",
"errorRate": "0.3%",
"uptime": "99.9%"
},
"realtime": {
"requestsPerMinute": 85,
"activeConnections": 156,
"memoryUsage": "68%",
"cpuUsage": "42%"
}
}
```
### 服务器健康状态
```bash
# 获取所有服务器状态
curl -X GET http://localhost:3000/api/monitoring/servers/health \
-H "Authorization: Bearer $TOKEN"
```
## 性能指标
### 响应时间监控
```json
{
"responseTime": {
"metrics": [
{
"serverId": "server-1",
"average": "180ms",
"p50": "150ms",
"p95": "300ms",
"p99": "500ms"
}
],
"alerts": {
"slowResponse": {
"threshold": "1s",
"current": "180ms",
"status": "normal"
}
}
}
}
```
### 吞吐量分析
```bash
# 获取吞吐量统计
curl -X GET http://localhost:3000/api/monitoring/throughput \
-H "Authorization: Bearer $TOKEN" \
-G -d "timeRange=1h" -d "granularity=5m"
```
响应示例:
```json
{
"timeRange": "1h",
"granularity": "5m",
"data": [
{
"timestamp": "2024-01-01T12:00:00Z",
"totalRequests": 450,
"successfulRequests": 448,
"failedRequests": 2,
"avgResponseTime": "230ms"
}
],
"summary": {
"peakThroughput": 520,
"averageThroughput": 412,
"totalRequests": 4944
}
}
```
### 资源使用监控
```json
{
"resources": {
"cpu": {
"usage": "42%",
"cores": 8,
"processes": [
{ "name": "mcp-server-1", "cpu": "15%" },
{ "name": "mcp-server-2", "cpu": "12%" }
]
},
"memory": {
"total": "32GB",
"used": "21.6GB",
"available": "10.4GB",
"byProcess": [
{ "name": "mcp-server-1", "memory": "2.1GB" },
{ "name": "mcp-server-2", "memory": "1.8GB" }
]
},
"network": {
"bytesIn": "1.2GB",
"bytesOut": "890MB",
"packetsIn": 1250000,
"packetsOut": 980000
},
"disk": {
"total": "1TB",
"used": "340GB",
"available": "660GB",
"iops": 1200
}
}
}
```
## 日志管理
### 集中化日志收集
配置日志聚合:
```json
{
"logging": {
"centralized": true,
"storage": {
"type": "elasticsearch",
"config": {
"hosts": ["localhost:9200"],
"index": "mcphub-logs",
"retention": "30d"
}
},
"levels": ["error", "warn", "info", "debug"],
"sources": ["application", "mcp-servers", "nginx", "system"]
}
}
```
### 实时日志查看
```bash
# 获取实时日志流
curl -X GET http://localhost:3000/api/monitoring/logs/stream \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: text/event-stream"
# 过滤日志
curl -X GET http://localhost:3000/api/monitoring/logs \
-H "Authorization: Bearer $TOKEN" \
-G -d "level=error" -d "server=server-1" -d "limit=100"
```
### 日志分析
```bash
# 错误模式分析
curl -X GET http://localhost:3000/api/monitoring/logs/analysis \
-H "Authorization: Bearer $TOKEN" \
-G -d "type=error-patterns" -d "timeRange=24h"
```
响应示例:
```json
{
"analysis": {
"errorPatterns": [
{
"pattern": "Connection timeout",
"occurrences": 45,
"trend": "increasing",
"affectedServers": ["server-2", "server-3"]
},
{
"pattern": "Memory allocation failed",
"occurrences": 12,
"trend": "stable",
"affectedServers": ["server-1"]
}
],
"recommendations": ["检查服务器网络连接", "增加内存限制配置"]
}
}
```
## 警报系统
### 配置警报规则
```json
{
"alerts": [
{
"name": "high-response-time",
"description": "响应时间过高警报",
"condition": "avg(response_time) > 1000ms over 5m",
"severity": "warning",
"notifications": {
"email": ["admin@company.com"],
"slack": "#alerts",
"webhook": "https://hooks.company.com/alerts"
}
},
{
"name": "server-down",
"description": "服务器宕机警报",
"condition": "server_status == 'down'",
"severity": "critical",
"notifications": {
"email": ["oncall@company.com"],
"sms": ["+1234567890"],
"pagerduty": "service-key"
}
},
{
"name": "high-error-rate",
"description": "错误率过高",
"condition": "error_rate > 5% over 10m",
"severity": "error",
"notifications": {
"slack": "#dev-team"
}
}
]
}
```
### 警报通知
```bash
# 测试警报通知
curl -X POST http://localhost:3000/api/monitoring/alerts/test \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"alertName": "high-response-time",
"channel": "email"
}'
```
### 警报历史
```bash
# 获取警报历史
curl -X GET http://localhost:3000/api/monitoring/alerts/history \
-H "Authorization: Bearer $TOKEN" \
-G -d "timeRange=7d" -d "severity=critical"
```
## 用户活动监控
### 用户会话跟踪
```json
{
"userActivity": {
"activeSessions": 156,
"totalUsers": 89,
"sessionsByUser": [
{
"userId": "user123",
"sessions": 3,
"lastActivity": "2024-01-01T12:30:00Z",
"totalRequests": 245
}
],
"topUsers": [
{ "userId": "power-user", "requests": 1250 },
{ "userId": "regular-user", "requests": 890 }
]
}
}
```
### 使用模式分析
```bash
# 获取使用模式分析
curl -X GET http://localhost:3000/api/monitoring/usage-patterns \
-H "Authorization: Bearer $TOKEN" \
-G -d "timeRange=30d"
```
响应示例:
```json
{
"patterns": {
"peakHours": [9, 10, 14, 15, 16],
"peakDays": ["tuesday", "wednesday", "thursday"],
"toolUsage": [
{"tool": "filesystem", "usage": 45%},
{"tool": "web-search", "usage": 30%},
{"tool": "database", "usage": 25%}
],
"userBehavior": {
"avgSessionDuration": "45m",
"avgRequestsPerSession": 23,
"returnRate": "78%"
}
}
}
```
## 容量规划
### 资源预测
```json
{
"capacityPlanning": {
"currentCapacity": {
"cpu": "42%",
"memory": "68%",
"network": "35%",
"storage": "34%"
},
"predictions": {
"timeHorizon": "30d",
"cpuForecast": [
{ "date": "2024-01-07", "usage": "48%" },
{ "date": "2024-01-14", "usage": "52%" },
{ "date": "2024-01-21", "usage": "56%" },
{ "date": "2024-01-28", "usage": "61%" }
],
"recommendations": [
"考虑在第3周增加CPU资源",
"内存使用稳定,暂不需要扩容",
"建议监控网络带宽趋势"
]
}
}
}
```
### 自动扩缩容
```json
{
"autoScaling": {
"enabled": true,
"policies": [
{
"metric": "cpu_usage",
"scaleUp": {
"threshold": "80%",
"duration": "5m",
"action": "add_instance"
},
"scaleDown": {
"threshold": "30%",
"duration": "15m",
"action": "remove_instance"
}
},
{
"metric": "request_queue_length",
"scaleUp": {
"threshold": 100,
"duration": "2m",
"action": "add_instance"
}
}
]
}
}
```
## 性能分析报告
### 生成性能报告
```bash
# 生成周报
curl -X POST http://localhost:3000/api/monitoring/reports \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"type": "performance",
"timeRange": "7d",
"format": "pdf",
"sections": [
"overview",
"response_times",
"error_analysis",
"resource_usage",
"recommendations"
],
"recipients": ["manager@company.com"]
}'
```
### 自定义报告
```json
{
"customReport": {
"name": "monthly-summary",
"schedule": "0 0 1 * *",
"template": "executive-summary",
"data": {
"kpis": ["uptime", "avg_response_time", "total_requests", "error_rate", "user_satisfaction"],
"comparisons": {
"previousMonth": true,
"yearOverYear": true
}
},
"distribution": {
"email": ["executives@company.com"],
"dashboard": true,
"archive": true
}
}
}
```
## 监控 API
### 指标查询
```bash
# 查询自定义指标
curl -X POST http://localhost:3000/api/monitoring/query \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"query": "avg(response_time) by (server_id)",
"timeRange": "1h",
"step": "5m"
}'
```
### 事件跟踪
```bash
# 记录自定义事件
curl -X POST http://localhost:3000/api/monitoring/events \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"type": "deployment",
"title": "服务器更新",
"description": "部署新版本 v2.1.0",
"tags": ["deployment", "version-2.1.0"],
"metadata": {
"version": "2.1.0",
"servers": ["server-1", "server-2"]
}
}'
```
## 第三方集成
### Prometheus 集成
```yaml
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'mcphub'
static_configs:
- targets: ['localhost:3000']
metrics_path: '/api/monitoring/prometheus'
scrape_interval: 30s
```
### Grafana 仪表板
```json
{
"dashboard": {
"title": "MCPHub 监控",
"panels": [
{
"title": "请求率",
"type": "graph",
"targets": [
{
"expr": "rate(mcphub_requests_total[5m])",
"legendFormat": "{{server_id}}"
}
]
},
{
"title": "响应时间",
"type": "graph",
"targets": [
{
"expr": "histogram_quantile(0.95, mcphub_response_time_histogram)",
"legendFormat": "95th percentile"
}
]
}
]
}
}
```
### ELK Stack 集成
```json
{
"logstash": {
"input": {
"beats": {
"port": 5044
}
},
"filter": {
"if": "[fields][service] == 'mcphub'",
"json": {
"source": "message"
},
"date": {
"match": ["timestamp", "ISO8601"]
}
},
"output": {
"elasticsearch": {
"hosts": ["localhost:9200"],
"index": "mcphub-logs-%{+YYYY.MM.dd}"
}
}
}
}
```
## 故障排除
### 监控问题诊断
```bash
# 检查监控服务状态
curl -X GET http://localhost:3000/api/monitoring/health \
-H "Authorization: Bearer $TOKEN"
# 验证指标收集
curl -X GET http://localhost:3000/api/monitoring/metrics/validation \
-H "Authorization: Bearer $TOKEN"
```
### 性能问题分析
```bash
# 性能瓶颈分析
curl -X GET http://localhost:3000/api/monitoring/performance/bottlenecks \
-H "Authorization: Bearer $TOKEN" \
-G -d "timeRange=1h"
```
### 常见监控问题
1. **指标丢失**
- 检查收集器配置
- 验证网络连接
- 检查存储空间
2. **警报不触发**
- 验证警报规则语法
- 检查通知配置
- 测试通知渠道
3. **仪表板加载缓慢**
- 优化查询时间范围
- 增加数据聚合
- 检查数据库性能
## 最佳实践
1. **监控层次化**: 建立应用、基础设施和业务三层监控
2. **合理的警报**: 避免警报疲劳,设置合适的阈值
3. **数据保留**: 根据业务需求设置适当的数据保留期
4. **安全监控**: 监控安全相关的指标和事件
5. **持续优化**: 定期审查和优化监控配置
有关更多信息,请参阅 [智能路由](/zh/features/smart-routing) 和 [服务器管理](/zh/features/server-management) 文档。

View File

@@ -0,0 +1,496 @@
---
title: '服务器管理'
description: '通过热插拔配置集中管理多个 MCP 服务器'
---
## 概述
MCPHub 的服务器管理系统允许您从单个仪表板集中配置、监控和控制多个 MCP模型上下文协议服务器。所有更改都会实时应用无需重启服务器。
## 添加 MCP 服务器
### 通过仪表板
1. **访问仪表板**: 导航到 `http://localhost:3000` 并登录
2. **点击"添加服务器"**: 位于服务器部分
3. **填写服务器详细信息**:
- **名称**: 服务器的唯一标识符
- **命令**: 可执行命令(例如 `npx`、`uvx`、`python`
- **参数**: 命令参数数组
- **环境变量**: 环境设置的键值对
- **工作目录**: 命令的可选工作目录
### 通过配置文件
编辑您的 `mcp_settings.json` 文件:
```json
{
"mcpServers": {
"server-name": {
"command": "command-to-run",
"args": ["arg1", "arg2"],
"env": {
"API_KEY": "your-api-key",
"CONFIG_VALUE": "some-value"
},
"cwd": "/optional/working/directory"
}
}
}
```
### 通过 API
使用 REST API 以编程方式添加服务器:
```bash
curl -X POST http://localhost:3000/api/servers \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{
"name": "my-server",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/files"],
"env": {
"NODE_ENV": "production"
},
"cwd": "/app"
}'
```
## 服务器配置
### 通用配置选项
```json
{
"name": "filesystem-server",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/allowed/path"],
"env": {
"NODE_ENV": "production",
"DEBUG": "mcp:*",
"MAX_FILES": "1000"
},
"cwd": "/app/workspace",
"timeout": 30000,
"retries": 3,
"enabled": true
}
```
### Python 服务器示例
```json
{
"name": "python-server",
"command": "python",
"args": ["-m", "mcp_server", "--config", "config.json"],
"env": {
"PYTHONPATH": "/app/python",
"API_KEY": "${API_KEY}",
"LOG_LEVEL": "INFO"
},
"cwd": "/app/python-server"
}
```
### Node.js 服务器示例
```json
{
"name": "node-server",
"command": "node",
"args": ["server.js", "--port", "3001"],
"env": {
"NODE_ENV": "production",
"PORT": "3001",
"DATABASE_URL": "${DATABASE_URL}"
},
"cwd": "/app/node-server"
}
```
## 服务器生命周期管理
### 启动服务器
```bash
# 启动特定服务器
curl -X POST http://localhost:3000/api/servers/my-server/start \
-H "Authorization: Bearer $TOKEN"
# 启动所有服务器
curl -X POST http://localhost:3000/api/servers/start-all \
-H "Authorization: Bearer $TOKEN"
```
### 停止服务器
```bash
# 停止特定服务器
curl -X POST http://localhost:3000/api/servers/my-server/stop \
-H "Authorization: Bearer $TOKEN"
# 优雅停止(等待当前请求完成)
curl -X POST http://localhost:3000/api/servers/my-server/stop \
-H "Authorization: Bearer $TOKEN" \
-d '{"graceful": true, "timeout": 30000}'
```
### 重启服务器
```bash
# 重启服务器
curl -X POST http://localhost:3000/api/servers/my-server/restart \
-H "Authorization: Bearer $TOKEN"
```
## 热配置重载
### 更新服务器配置
无需重启即可更新配置:
```bash
curl -X PUT http://localhost:3000/api/servers/my-server/config \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"env": {
"DEBUG": "mcp:verbose",
"NEW_SETTING": "value"
},
"args": ["--verbose", "--new-flag"]
}'
```
### 批量配置更新
```bash
curl -X PUT http://localhost:3000/api/servers/bulk-update \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"servers": ["server1", "server2"],
"config": {
"env": {
"LOG_LEVEL": "DEBUG"
}
}
}'
```
## 服务器状态监控
### 检查服务器状态
```bash
# 获取所有服务器状态
curl -X GET http://localhost:3000/api/servers/status \
-H "Authorization: Bearer $TOKEN"
# 获取特定服务器状态
curl -X GET http://localhost:3000/api/servers/my-server/status \
-H "Authorization: Bearer $TOKEN"
```
响应示例:
```json
{
"name": "my-server",
"status": "running",
"pid": 12345,
"uptime": 3600000,
"memory": {
"rss": 123456789,
"heapTotal": 98765432,
"heapUsed": 87654321
},
"cpu": {
"user": 1000000,
"system": 500000
},
"lastRestart": "2024-01-01T12:00:00.000Z"
}
```
### 健康检查
配置自动健康检查:
```json
{
"name": "my-server",
"command": "node",
"args": ["server.js"],
"healthCheck": {
"enabled": true,
"interval": 30000,
"timeout": 5000,
"retries": 3,
"endpoint": "/health",
"expectedStatus": 200
}
}
```
## 负载均衡
### 配置多实例
```json
{
"name": "load-balanced-server",
"instances": 3,
"command": "node",
"args": ["server.js"],
"loadBalancer": {
"strategy": "round-robin",
"healthCheck": true,
"stickySession": false
},
"env": {
"PORT": "${PORT}"
}
}
```
### 负载均衡策略
- **round-robin**: 轮询分发请求
- **least-connections**: 分发到连接数最少的实例
- **weighted**: 基于权重分发
- **ip-hash**: 基于客户端 IP 的一致性哈希
## 资源限制
### 设置资源限制
```json
{
"name": "resource-limited-server",
"command": "python",
"args": ["server.py"],
"resources": {
"memory": {
"limit": "512MB",
"warning": "400MB"
},
"cpu": {
"limit": "50%",
"priority": "normal"
},
"processes": {
"max": 10
}
}
}
```
### 监控资源使用
```bash
# 获取资源使用统计
curl -X GET http://localhost:3000/api/servers/my-server/resources \
-H "Authorization: Bearer $TOKEN"
```
## 日志管理
### 配置日志记录
```json
{
"name": "my-server",
"command": "node",
"args": ["server.js"],
"logging": {
"level": "info",
"file": "/var/log/mcphub/my-server.log",
"maxSize": "100MB",
"maxFiles": 5,
"rotate": true,
"format": "json"
}
}
```
### 查看日志
```bash
# 获取实时日志
curl -X GET http://localhost:3000/api/servers/my-server/logs \
-H "Authorization: Bearer $TOKEN"
# 获取带过滤器的日志
curl -X GET "http://localhost:3000/api/servers/my-server/logs?level=error&limit=100" \
-H "Authorization: Bearer $TOKEN"
```
## 环境变量管理
### 动态环境变量
```json
{
"name": "dynamic-server",
"command": "python",
"args": ["server.py"],
"env": {
"API_KEY": "${secrets:api_key}",
"DATABASE_URL": "${vault:db_url}",
"CURRENT_TIME": "${time:iso}",
"SERVER_ID": "${server:id}",
"HOSTNAME": "${system:hostname}"
}
}
```
### 环境变量模板
支持的模板变量:
- `${secrets:key}`: 从密钥存储获取
- `${vault:path}`: 从 Vault 获取
- `${env:VAR}`: 从系统环境变量获取
- `${time:format}`: 当前时间戳
- `${server:property}`: 服务器属性
- `${system:property}`: 系统属性
## 服务发现
### 自动服务发现
```json
{
"serviceDiscovery": {
"enabled": true,
"provider": "consul",
"config": {
"host": "localhost",
"port": 8500,
"serviceName": "mcp-server",
"tags": ["mcp", "ai", "api"]
}
}
}
```
### 注册服务
```bash
# 手动注册服务
curl -X POST http://localhost:3000/api/servers/my-server/register \
-H "Authorization: Bearer $TOKEN" \
-d '{
"service": {
"name": "my-mcp-service",
"tags": ["mcp", "production"],
"port": 3001,
"check": {
"http": "http://localhost:3001/health",
"interval": "30s"
}
}
}'
```
## 故障排除
### 常见问题
1. **服务器启动失败**
```bash
# 检查服务器日志
curl -X GET http://localhost:3000/api/servers/my-server/logs?level=error \
-H "Authorization: Bearer $TOKEN"
```
2. **配置无效**
```bash
# 验证配置
curl -X POST http://localhost:3000/api/servers/validate \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d @server-config.json
```
3. **性能问题**
```bash
# 获取性能指标
curl -X GET http://localhost:3000/api/servers/my-server/metrics \
-H "Authorization: Bearer $TOKEN"
```
### 调试模式
启用详细调试:
```json
{
"name": "debug-server",
"command": "node",
"args": ["--inspect=0.0.0.0:9229", "server.js"],
"env": {
"DEBUG": "*",
"LOG_LEVEL": "debug",
"NODE_ENV": "development"
},
"debugging": {
"enabled": true,
"port": 9229,
"breakOnStart": false
}
}
```
## 高级配置
### 自定义钩子
```json
{
"name": "hooked-server",
"command": "node",
"args": ["server.js"],
"hooks": {
"beforeStart": ["./scripts/setup.sh"],
"afterStart": ["./scripts/notify.sh"],
"beforeStop": ["./scripts/cleanup.sh"],
"onError": ["./scripts/alert.sh"]
}
}
```
### 配置模板
```json
{
"templates": {
"python-server": {
"command": "python",
"args": ["-m", "mcp_server"],
"env": {
"PYTHONPATH": "/app/python",
"LOG_LEVEL": "INFO"
}
}
},
"servers": {
"my-python-server": {
"extends": "python-server",
"args": ["-m", "mcp_server", "--config", "custom.json"],
"env": {
"API_KEY": "custom-key"
}
}
}
}
```
有关更多配置选项,请参阅 [MCP 设置配置](/zh/configuration/mcp-settings) 和 [环境变量](/zh/configuration/environment-variables) 文档。

View File

@@ -0,0 +1,691 @@
---
title: '智能路由'
description: '自动负载均衡和请求路由到最佳的 MCP 服务器实例'
---
## 概述
MCPHub 的智能路由系统自动将传入请求路由到最适合的 MCP 服务器实例。系统考虑服务器负载、响应时间、功能可用性和业务规则来做出路由决策。
## 路由策略
### 轮询路由
最简单的路由策略,按顺序分发请求:
```json
{
"routing": {
"strategy": "round-robin",
"targets": [
{
"serverId": "server-1",
"weight": 1,
"enabled": true
},
{
"serverId": "server-2",
"weight": 1,
"enabled": true
},
{
"serverId": "server-3",
"weight": 1,
"enabled": true
}
]
}
}
```
### 加权轮询
基于服务器容量分配不同权重:
```json
{
"routing": {
"strategy": "weighted-round-robin",
"targets": [
{
"serverId": "high-performance-server",
"weight": 3,
"specs": {
"cpu": "8 cores",
"memory": "32GB"
}
},
{
"serverId": "standard-server-1",
"weight": 2,
"specs": {
"cpu": "4 cores",
"memory": "16GB"
}
},
{
"serverId": "standard-server-2",
"weight": 1,
"specs": {
"cpu": "2 cores",
"memory": "8GB"
}
}
]
}
}
```
### 最少连接数
将请求路由到当前连接数最少的服务器:
```json
{
"routing": {
"strategy": "least-connections",
"balancingMode": "dynamic",
"healthCheck": {
"enabled": true,
"interval": 10000
}
}
}
```
### 基于响应时间
路由到响应时间最短的服务器:
```json
{
"routing": {
"strategy": "fastest-response",
"metrics": {
"measurementWindow": "5m",
"sampleSize": 100,
"excludeSlowRequests": true,
"slowRequestThreshold": "5s"
}
}
}
```
## 基于功能的路由
### 工具特定路由
根据请求的工具类型路由到专门的服务器:
```json
{
"routing": {
"strategy": "capability-based",
"rules": [
{
"condition": {
"tool": "filesystem"
},
"targets": ["filesystem-server-1", "filesystem-server-2"],
"strategy": "least-connections"
},
{
"condition": {
"tool": "web-search"
},
"targets": ["search-server-1", "search-server-2"],
"strategy": "round-robin"
},
{
"condition": {
"tool": "database"
},
"targets": ["db-server"],
"strategy": "single"
}
],
"fallback": {
"targets": ["general-server-1", "general-server-2"],
"strategy": "round-robin"
}
}
}
```
### 内容感知路由
基于请求内容进行智能路由:
```json
{
"routing": {
"strategy": "content-aware",
"rules": [
{
"condition": {
"content.language": "python"
},
"targets": ["python-specialized-server"],
"reason": "Python代码分析专用服务器"
},
{
"condition": {
"content.size": "> 1MB"
},
"targets": ["high-memory-server"],
"reason": "大文件处理专用服务器"
},
{
"condition": {
"content.type": "image"
},
"targets": ["image-processing-server"],
"reason": "图像处理专用服务器"
}
]
}
}
```
## 地理位置路由
### 基于客户端位置
根据客户端地理位置路由到最近的服务器:
```json
{
"routing": {
"strategy": "geo-location",
"regions": [
{
"name": "北美",
"countries": ["US", "CA", "MX"],
"servers": ["us-east-1", "us-west-1", "ca-central-1"],
"strategy": "least-latency"
},
{
"name": "欧洲",
"countries": ["DE", "FR", "UK", "NL"],
"servers": ["eu-west-1", "eu-central-1"],
"strategy": "round-robin"
},
{
"name": "亚太",
"countries": ["CN", "JP", "KR", "SG"],
"servers": ["ap-southeast-1", "ap-northeast-1"],
"strategy": "fastest-response"
}
],
"fallback": {
"servers": ["global-server-1"],
"strategy": "single"
}
}
}
```
### 延迟优化
```bash
# 配置延迟监控
curl -X PUT http://localhost:3000/api/routing/latency-config \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"enabled": true,
"measurementInterval": 30000,
"regions": [
{"id": "us-east", "endpoint": "ping.us-east.example.com"},
{"id": "eu-west", "endpoint": "ping.eu-west.example.com"},
{"id": "ap-southeast", "endpoint": "ping.ap-southeast.example.com"}
],
"routing": {
"preferLowLatency": true,
"maxLatencyThreshold": "200ms",
"fallbackOnTimeout": true
}
}'
```
## 负载感知路由
### 实时负载监控
```json
{
"routing": {
"strategy": "load-aware",
"loadMetrics": {
"cpu": {
"threshold": 80,
"weight": 0.4
},
"memory": {
"threshold": 85,
"weight": 0.3
},
"connections": {
"threshold": 1000,
"weight": 0.2
},
"responseTime": {
"threshold": "2s",
"weight": 0.1
}
},
"adaptation": {
"enabled": true,
"adjustmentInterval": 60000,
"emergencyThreshold": 95
}
}
}
```
### 预测性负载均衡
```json
{
"routing": {
"strategy": "predictive",
"prediction": {
"algorithm": "linear-regression",
"trainingWindow": "7d",
"predictionHorizon": "1h",
"factors": ["historical_load", "time_of_day", "day_of_week", "seasonal_patterns"]
},
"adaptation": {
"preemptiveScaling": true,
"scaleUpThreshold": 70,
"scaleDownThreshold": 30
}
}
}
```
## 故障转移和恢复
### 自动故障转移
```json
{
"routing": {
"strategy": "high-availability",
"failover": {
"enabled": true,
"detection": {
"healthCheckFailures": 3,
"timeoutThreshold": "10s",
"checkInterval": 5000
},
"recovery": {
"automaticRecovery": true,
"recoveryChecks": 5,
"recoveryInterval": 30000
}
},
"clusters": [
{
"name": "primary",
"servers": ["server-1", "server-2"],
"priority": 1
},
{
"name": "secondary",
"servers": ["backup-server-1", "backup-server-2"],
"priority": 2
}
]
}
}
```
### 断路器模式
```json
{
"routing": {
"circuitBreaker": {
"enabled": true,
"failureThreshold": 10,
"timeWindow": 60000,
"halfOpenRetries": 3,
"fallback": {
"type": "cached-response",
"ttl": 300000
}
}
}
}
```
## 会话亲和性
### 粘性会话
保持用户会话与特定服务器的关联:
```json
{
"routing": {
"strategy": "session-affinity",
"affinity": {
"type": "cookie",
"cookieName": "mcphub-server-id",
"ttl": 3600000,
"fallbackOnUnavailable": true
},
"sessionStore": {
"type": "redis",
"config": {
"host": "localhost",
"port": 6379,
"db": 1
}
}
}
}
```
### 基于用户 ID 的路由
```json
{
"routing": {
"strategy": "user-based",
"userRouting": {
"algorithm": "consistent-hashing",
"hashFunction": "sha256",
"virtualNodes": 100,
"replicationFactor": 2
}
}
}
```
## 动态路由配置
### 运行时配置更新
```bash
# 更新路由配置
curl -X PUT http://localhost:3000/api/routing/config \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"strategy": "weighted-round-robin",
"weights": {
"server-1": 3,
"server-2": 2,
"server-3": 1
},
"applyImmediately": true
}'
```
### A/B 测试路由
```json
{
"routing": {
"strategy": "ab-testing",
"experiments": [
{
"name": "new-algorithm-test",
"enabled": true,
"trafficSplit": {
"control": 70,
"variant": 30
},
"rules": {
"control": {
"strategy": "round-robin",
"servers": ["stable-server-1", "stable-server-2"]
},
"variant": {
"strategy": "ai-optimized",
"servers": ["experimental-server-1"]
}
},
"metrics": ["response_time", "error_rate", "user_satisfaction"]
}
]
}
}
```
## 路由分析和监控
### 实时路由指标
```bash
# 获取路由统计
curl -X GET http://localhost:3000/api/routing/metrics \
-H "Authorization: Bearer $TOKEN"
```
响应示例:
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"totalRequests": 15420,
"routingDistribution": {
"server-1": { "requests": 6168, "percentage": 40 },
"server-2": { "requests": 4626, "percentage": 30 },
"server-3": { "requests": 3084, "percentage": 20 },
"backup-server": { "requests": 1542, "percentage": 10 }
},
"performance": {
"avgResponseTime": "245ms",
"p95ResponseTime": "580ms",
"errorRate": "0.3%"
},
"failovers": {
"total": 2,
"byServer": {
"server-2": 1,
"server-3": 1
}
}
}
```
### 路由决策日志
```bash
# 启用路由决策日志
curl -X PUT http://localhost:3000/api/routing/logging \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"enabled": true,
"level": "info",
"includeDecisionFactors": true,
"sampleRate": 0.1
}'
```
## 自定义路由规则
### 基于业务逻辑的路由
```json
{
"routing": {
"strategy": "custom-rules",
"rules": [
{
"name": "premium-users",
"priority": 1,
"condition": "user.tier === 'premium'",
"action": {
"targetServers": ["premium-server-1", "premium-server-2"],
"strategy": "least-connections",
"qos": {
"maxResponseTime": "1s",
"priority": "high"
}
}
},
{
"name": "high-volume-requests",
"priority": 2,
"condition": "request.size > 10MB",
"action": {
"targetServers": ["high-capacity-server"],
"strategy": "single",
"timeout": "60s"
}
},
{
"name": "batch-processing",
"priority": 3,
"condition": "request.type === 'batch'",
"action": {
"targetServers": ["batch-server-1", "batch-server-2"],
"strategy": "queue-based",
"queueConfig": {
"maxSize": 1000,
"timeout": "5m"
}
}
}
]
}
}
```
### JavaScript 路由函数
```javascript
// 自定义路由函数
function customRouting(request, servers, metrics) {
const { user, content, timestamp } = request;
// 工作时间优先使用高性能服务器
const isBusinessHours =
new Date(timestamp).getHours() >= 9 && new Date(timestamp).getHours() <= 17;
if (isBusinessHours && user.priority === 'high') {
return servers.filter((s) => s.tags.includes('high-performance'));
}
// 基于内容类型的特殊路由
if (content.type === 'code-analysis') {
return servers.filter((s) => s.capabilities.includes('code-analysis'));
}
// 默认负载均衡
return servers.sort((a, b) => a.currentLoad - b.currentLoad);
}
```
## 路由优化
### 机器学习优化
```json
{
"routing": {
"strategy": "ml-optimized",
"mlConfig": {
"algorithm": "reinforcement-learning",
"rewardFunction": "response_time_weighted",
"trainingData": {
"features": [
"server_load",
"response_time_history",
"request_complexity",
"user_pattern",
"time_of_day"
],
"targetMetric": "overall_satisfaction"
},
"updateFrequency": "hourly",
"explorationRate": 0.1
}
}
}
```
### 缓存感知路由
```json
{
"routing": {
"strategy": "cache-aware",
"caching": {
"enabled": true,
"levels": [
{
"type": "local",
"ttl": 300,
"maxSize": "100MB"
},
{
"type": "distributed",
"provider": "redis",
"ttl": 3600,
"maxSize": "1GB"
}
],
"routing": {
"preferCachedServers": true,
"cacheHitBonus": 0.3,
"cacheMissThreshold": 0.8
}
}
}
}
```
## 故障排除
### 路由调试
```bash
# 调试特定请求的路由决策
curl -X POST http://localhost:3000/api/routing/debug \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"request": {
"userId": "user123",
"tool": "filesystem",
"content": {"type": "read", "path": "/data/file.txt"}
},
"traceRoute": true
}'
```
### 路由性能分析
```bash
# 获取路由性能报告
curl -X GET http://localhost:3000/api/routing/performance \
-H "Authorization: Bearer $TOKEN" \
-G -d "timeRange=1h" -d "detailed=true"
```
### 常见问题
1. **不均匀的负载分布**
- 检查服务器权重配置
- 验证健康检查设置
- 分析请求模式
2. **频繁的故障转移**
- 调整健康检查阈值
- 检查网络连接稳定性
- 优化服务器资源
3. **路由延迟过高**
- 简化路由规则
- 优化路由算法
- 使用缓存加速决策
有关更多信息,请参阅 [监控](/zh/features/monitoring) 和 [服务器管理](/zh/features/server-management) 文档。

97
docs/zh/index.mdx Normal file
View File

@@ -0,0 +1,97 @@
---
title: '欢迎使用 MCPHub'
description: 'MCPHub 是一个强大的 Model Context Protocol (MCP) 服务器管理平台,提供智能路由、负载均衡和实时监控功能'
---
<img className="block dark:hidden" src="/images/hero-light.png" alt="MCPHub Hero Light" />
<img className="hidden dark:block" src="/images/hero-dark.png" alt="MCPHub Hero Dark" />
## 什么是 MCPHub
MCPHub 是一个现代化的 Model Context Protocol (MCP) 服务器管理平台,旨在简化 AI 模型服务的部署、管理和监控。通过智能路由和负载均衡技术MCPHub 帮助您构建高可用、可扩展的 AI 服务架构。
### 核心功能
- **🚀 智能路由** - 基于负载、延迟和健康状态的智能请求分发
- **⚖️ 负载均衡** - 多种负载均衡策略,确保最优性能
- **📊 实时监控** - 全面的性能指标和健康检查
- **🔐 安全认证** - 企业级身份认证和访问控制
- **🏗️ 服务器组管理** - 灵活的服务器分组和配置管理
- **🔄 故障转移** - 自动故障检测和流量切换
## 快速开始
立即开始使用 MCPHub只需几分钟即可部署您的第一个 MCP 服务器。
<CardGroup cols={2}>
<Card title="5 分钟快速部署" icon="rocket" href="/zh/quickstart">
跟随我们的快速开始指南5 分钟内部署 MCPHub 并连接您的第一个 MCP 服务器
</Card>
<Card title="开发环境搭建" icon="code" href="/zh/development/getting-started">
设置本地开发环境,了解 MCPHub 的架构和开发工作流
</Card>
</CardGroup>
## 核心概念
了解 MCPHub 的核心概念,为深入使用做好准备。
<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">
学习 MCPHub 的智能路由算法和配置策略
</Card>
<Card title="服务器管理" icon="server" href="/zh/features/server-management">
掌握 MCP 服务器的添加、配置和管理技巧
</Card>
<Card title="监控与分析" icon="chart-line" href="/zh/features/monitoring">
使用内置的监控工具跟踪性能和识别问题
</Card>
</CardGroup>
## 部署选项
MCPHub 支持多种部署方式,满足不同规模和场景的需求。
<CardGroup cols={3}>
<Card title="Docker 部署" icon="docker" href="/zh/configuration/docker-setup">
使用 Docker 容器快速部署,支持单机和集群模式
</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>
## API 和集成
MCPHub 提供完整的 RESTful API 和多语言 SDK方便与现有系统集成。
<CardGroup cols={2}>
<Card title="API 参考文档" icon="code" href="/zh/api-reference/introduction">
完整的 API 接口文档,包含详细的请求示例和响应格式
</Card>
<Card title="SDK 和工具" icon="toolbox" href="/zh/sdk">
官方 SDK 和命令行工具,加速开发集成
</Card>
</CardGroup>
## 社区和支持
加入 MCPHub 社区,获取帮助和分享经验。
<CardGroup cols={2}>
<Card title="GitHub 仓库" icon="github" href="https://github.com/mcphub/mcphub">
查看源代码、提交问题和贡献代码
</Card>
<Card title="Discord 社区" icon="discord" href="https://discord.gg/mcphub">
与其他开发者交流,获取实时帮助
</Card>
<Card title="Sponsor" icon="heart" href="https://ko-fi.com/samanhappy">
支持 MCPHub 的开发和维护,帮助我们持续改进
</Card>
</CardGroup>

304
docs/zh/quickstart.mdx Normal file
View File

@@ -0,0 +1,304 @@
---
title: '快速开始'
description: '5 分钟内部署 MCPHub 并连接您的第一个 MCP 服务器'
---
## 欢迎使用 MCPHub
本指南将帮助您在 5 分钟内完成 MCPHub 的部署和配置,并连接您的第一个 MCP 服务器。
## 前提条件
在开始之前,请确保您的系统满足以下要求:
<AccordionGroup>
<Accordion icon="desktop" title="系统要求">
- **操作系统**: Linux、macOS 或 Windows
- **内存**: 最少 2GB RAM推荐 4GB+
- **存储**: 至少 1GB 可用空间
- **网络**: 稳定的互联网连接
</Accordion>
<Accordion icon="code" title="软件依赖">
- **Node.js**: 18.0+ 版本
- **Docker**: 最新版本(可选,用于容器化部署)
- **Git**: 用于代码管理
检查版本:
```bash
node --version # 应该 >= 18.0.0
npm --version # 应该 >= 8.0.0
docker --version # 可选
```
</Accordion>
</AccordionGroup>
## 安装 MCPHub
### 方式一:使用 npm推荐
<AccordionGroup>
<Accordion icon="download" title="安装 MCPHub CLI">
首先安装 MCPHub 命令行工具:
```bash
npm install -g @mcphub/cli
```
验证安装:
```bash
mcphub --version
```
</Accordion>
<Accordion icon="folder-plus" title="创建新项目">
创建一个新的 MCPHub 项目:
```bash
# 创建项目
mcphub init my-mcphub-project
cd my-mcphub-project
# 安装依赖
npm install
```
</Accordion>
<Accordion icon="gear" title="配置环境">
复制并编辑环境变量文件:
```bash
cp .env.example .env
```
编辑 `.env` 文件,设置基本配置:
```bash
# 服务器配置
PORT=3000
NODE_ENV=development
# 数据库配置(使用内置 SQLite
DATABASE_URL=sqlite:./data/mcphub.db
# JWT 密钥(请更改为安全的随机字符串)
JWT_SECRET=your-super-secret-jwt-key-change-me
# 管理员账户
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=admin123
```
</Accordion>
</AccordionGroup>
### 方式二:使用 Docker
<AccordionGroup>
<Accordion icon="docker" title="Docker 快速部署">
使用 Docker Compose 一键部署:
```bash
# 下载配置文件
curl -O https://raw.githubusercontent.com/mcphub/mcphub/main/docker-compose.yml
# 启动服务
docker-compose up -d
```
或者直接运行 Docker 容器:
```bash
docker run -d \
--name mcphub \
-p 3000:3000 \
-e NODE_ENV=production \
-e JWT_SECRET=your-secret-key \
mcphub/server:latest
```
</Accordion>
</AccordionGroup>
## 启动 MCPHub
### 开发模式启动
```bash
# 初始化数据库
npm run db:setup
# 启动开发服务器
npm run dev
```
### 生产模式启动
```bash
# 构建应用
npm run build
# 启动生产服务器
npm start
```
<Note>开发模式下MCPHub 会在 `http://localhost:3000` 启动,并具有热重载功能。</Note>
## 首次访问和配置
### 1. 访问管理界面
打开浏览器,访问 `http://localhost:3000`,您将看到 MCPHub 的欢迎页面。
### 2. 登录管理员账户
使用您在 `.env` 文件中设置的管理员凭据登录:
- **邮箱**: `admin@example.com`
- **密码**: `admin123`
<Warning>首次登录后,请立即更改默认密码以确保安全!</Warning>
### 3. 完成初始配置
登录后,系统会引导您完成初始配置:
1. **更改管理员密码**
2. **设置组织信息**
3. **配置基本设置**
## 添加您的第一个 MCP 服务器
### 1. 准备 MCP 服务器
如果您还没有 MCP 服务器,可以使用我们的示例服务器进行测试:
```bash
# 克隆示例服务器
git clone https://github.com/mcphub/example-mcp-server.git
cd example-mcp-server
# 安装依赖并启动
npm install
npm start
```
示例服务器将在 `http://localhost:3001` 启动。
### 2. 在 MCPHub 中添加服务器
在 MCPHub 管理界面中:
1. 点击 **"添加服务器"** 按钮
2. 填写服务器信息:
```
名称: Example MCP Server
端点: http://localhost:3001
描述: 示例 MCP 服务器用于测试
```
3. 选择功能类型chat、completion、analysis
4. 点击 **"测试连接"** 验证服务器可达性
5. 点击 **"保存"** 完成添加
### 3. 验证服务器状态
添加成功后,您应该能在服务器列表中看到新添加的服务器,状态显示为 **"活跃"**(绿色)。
## 测试路由功能
### 发送测试请求
使用 cURL 或其他 HTTP 客户端测试路由功能:
```bash
# 发送聊天请求
curl -X POST http://localhost:3000/api/chat \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"role": "user",
"content": "Hello, this is a test message!"
}
]
}'
```
### 查看请求日志
在 MCPHub 管理界面的 **"监控"** 页面中,您可以实时查看:
- 请求数量和响应时间
- 服务器健康状态
- 错误日志和统计
## 后续步骤
恭喜!您已经成功部署了 MCPHub 并添加了第一个 MCP 服务器。接下来您可以:
<CardGroup cols={2}>
<Card title="配置负载均衡" icon="balance-scale" href="/zh/features/smart-routing">
学习如何配置智能路由和负载均衡策略
</Card>
<Card title="添加更多服务器" icon="plus" href="/zh/features/server-management">
了解服务器管理的高级功能
</Card>
<Card title="设置监控告警" icon="bell" href="/zh/features/monitoring">
配置性能监控和告警通知
</Card>
<Card title="API 集成" icon="code" href="/zh/api-reference/introduction">
将 MCPHub 集成到您的应用程序中
</Card>
</CardGroup>
## 常见问题
<AccordionGroup>
<Accordion icon="question" title="无法连接到 MCP 服务器">
**可能原因**
- 服务器地址错误或服务器未启动
- 防火墙阻止连接
- 网络配置问题
**解决方案**
1. 验证服务器是否正在运行:`curl http://localhost:3001/health`
2. 检查防火墙设置
3. 确认网络连接正常
</Accordion>
<Accordion icon="question" title="服务器状态显示为离线">
**可能原因**
- 健康检查失败
- 服务器响应超时
- 服务器崩溃或重启
**解决方案**
1. 检查服务器日志
2. 调整健康检查间隔
3. 重启服务器进程
</Accordion>
<Accordion icon="question" title="忘记管理员密码">
**解决方案**
```bash
# 重置管理员密码
npm run reset-admin-password
```
或者删除数据库文件重新初始化:
```bash
rm data/mcphub.db
npm run db:setup
```
</Accordion>
</AccordionGroup>
## 获取帮助
如果您在设置过程中遇到问题:
- 📖 查看 [完整文档](/zh/development/getting-started)
- 🐛 在 [GitHub](https://github.com/mcphub/mcphub/issues) 上报告问题
- 💬 加入 [Discord 社区](https://discord.gg/mcphub) 获取实时帮助
- 📧 发送邮件至 support@mcphub.io

View File

@@ -0,0 +1,256 @@
{
"mcpServers": {
"example-api-url": {
"type": "openapi",
"openapi": {
"url": "https://api.example.com/openapi.json",
"version": "3.1.0",
"security": {
"type": "apiKey",
"apiKey": {
"name": "X-API-Key",
"in": "header",
"value": "your-api-key-here"
}
}
},
"headers": {
"User-Agent": "MCPHub/1.0"
},
"enabled": true
},
"example-api-schema": {
"type": "openapi",
"openapi": {
"schema": {
"openapi": "3.1.0",
"info": {
"title": "Example API",
"version": "1.0.0",
"description": "A sample API for demonstration"
},
"servers": [
{
"url": "https://api.example.com",
"description": "Production server"
}
],
"paths": {
"/users": {
"get": {
"operationId": "listUsers",
"summary": "List all users",
"description": "Retrieve a list of all users in the system",
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Maximum number of users to return",
"required": false,
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 10
}
},
{
"name": "offset",
"in": "query",
"description": "Number of users to skip",
"required": false,
"schema": {
"type": "integer",
"minimum": 0,
"default": 0
}
}
],
"responses": {
"200": {
"description": "List of users",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"users": {
"type": "array",
"items": {
"$ref": "#/components/schemas/User"
}
},
"total": {
"type": "integer",
"description": "Total number of users"
}
}
}
}
}
}
}
},
"post": {
"operationId": "createUser",
"summary": "Create a new user",
"description": "Create a new user in the system",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateUserRequest"
}
}
}
},
"responses": {
"201": {
"description": "User created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
}
}
}
},
"/users/{userId}": {
"get": {
"operationId": "getUserById",
"summary": "Get user by ID",
"description": "Retrieve a specific user by their ID",
"parameters": [
{
"name": "userId",
"in": "path",
"required": true,
"description": "ID of the user to retrieve",
"schema": {
"type": "integer",
"minimum": 1
}
}
],
"responses": {
"200": {
"description": "User details",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
},
"404": {
"description": "User not found"
}
}
}
}
},
"components": {
"schemas": {
"User": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "Unique identifier for the user"
},
"name": {
"type": "string",
"description": "Full name of the user"
},
"email": {
"type": "string",
"format": "email",
"description": "Email address of the user"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the user was created"
},
"status": {
"type": "string",
"enum": [
"active",
"inactive",
"suspended"
],
"description": "Current status of the user"
}
},
"required": [
"id",
"name",
"email"
]
},
"CreateUserRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100,
"description": "Full name of the user"
},
"email": {
"type": "string",
"format": "email",
"description": "Email address of the user"
},
"status": {
"type": "string",
"enum": [
"active",
"inactive"
],
"default": "active",
"description": "Initial status of the user"
}
},
"required": [
"name",
"email"
]
}
},
"securitySchemes": {
"ApiKeyAuth": {
"type": "apiKey",
"in": "header",
"name": "X-API-Key"
}
}
},
"security": [
{
"ApiKeyAuth": []
}
]
},
"version": "3.1.0",
"security": {
"type": "apiKey",
"apiKey": {
"name": "X-API-Key",
"in": "header",
"value": "your-api-key-here"
}
}
},
"headers": {
"User-Agent": "MCPHub/1.0"
},
"enabled": true
}
}
}

View File

@@ -69,9 +69,12 @@ const AddServerForm = ({ onAdd }: AddServerFormProps) => {
<div>
<button
onClick={toggleModal}
className="w-full bg-blue-100 text-blue-800 rounded hover:bg-blue-200 py-2 px-4"
className="w-full bg-blue-100 text-blue-800 rounded hover:bg-blue-200 py-2 px-4 flex items-center justify-center"
>
{t('server.addServer')}
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-2" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clipRule="evenodd" />
</svg>
{t('server.add')}
</button>
{modalVisible && (

View File

@@ -3,10 +3,12 @@ import { useTranslation } from 'react-i18next';
import { MarketServer, MarketServerInstallation } from '@/types';
import ServerForm from './ServerForm';
import { ServerConfig } from '@/types';
interface MarketServerDetailProps {
server: MarketServer;
onBack: () => void;
onInstall: (server: MarketServer) => void;
onInstall: (server: MarketServer, config: ServerConfig) => void;
installing?: boolean;
isInstalled?: boolean;
}
@@ -83,8 +85,8 @@ const MarketServerDetail: React.FC<MarketServerDetailProps> = ({
const handleSubmit = async (payload: any) => {
try {
setError(null);
// Pass the server object to the parent component for installation
onInstall(server);
// Pass the server object and the payload (includes env changes) for installation
onInstall(server, payload.config);
setModalVisible(false);
} catch (err) {
console.error('Error installing server:', err);
@@ -294,4 +296,4 @@ const MarketServerDetail: React.FC<MarketServerDetailProps> = ({
);
};
export default MarketServerDetail;
export default MarketServerDetail;

View File

@@ -11,10 +11,11 @@ interface ServerCardProps {
server: Server
onRemove: (serverName: string) => void
onEdit: (server: Server) => void
onToggle?: (server: Server, enabled: boolean) => void
onToggle?: (server: Server, enabled: boolean) => Promise<boolean>
onRefresh?: () => void
}
const ServerCard = ({ server, onRemove, onEdit, onToggle }: ServerCardProps) => {
const ServerCard = ({ server, onRemove, onEdit, onToggle, onRefresh }: ServerCardProps) => {
const { t } = useTranslation()
const { showToast } = useToast()
const [isExpanded, setIsExpanded] = useState(false)
@@ -50,7 +51,7 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle }: ServerCardProps) =>
const handleToggle = async (e: React.MouseEvent) => {
e.stopPropagation()
if (isToggling || !onToggle) return
setIsToggling(true)
try {
await onToggle(server, !(server.enabled !== false))
@@ -102,6 +103,29 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle }: ServerCardProps) =>
setShowDeleteDialog(false)
}
const handleToolToggle = async (toolName: string, enabled: boolean) => {
try {
const { toggleTool } = await import('@/services/toolService')
const result = await toggleTool(server.name, toolName, enabled)
if (result.success) {
showToast(
t(enabled ? 'tool.enableSuccess' : 'tool.disableSuccess', { name: toolName }),
'success'
)
// Trigger refresh to update the tool's state in the UI
if (onRefresh) {
onRefresh()
}
} else {
showToast(result.error || t('tool.toggleFailed'), 'error')
}
} catch (error) {
console.error('Error toggling tool:', error)
showToast(t('tool.toggleFailed'), 'error')
}
}
return (
<>
<div className={`bg-white shadow rounded-lg p-6 mb-6 ${server.enabled === false ? 'opacity-60' : ''}`}>
@@ -112,26 +136,34 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle }: ServerCardProps) =>
<div className="flex items-center space-x-3">
<h2 className={`text-xl font-semibold ${server.enabled === false ? 'text-gray-600' : 'text-gray-900'}`}>{server.name}</h2>
<StatusBadge status={server.status} />
{/* Tool count display */}
<div className="flex items-center px-2 py-1 bg-blue-50 text-blue-700 rounded-full text-sm">
<svg className="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clipRule="evenodd" />
</svg>
<span>{server.tools?.length || 0} {t('server.tools')}</span>
</div>
{server.error && (
<div className="relative">
<div
className="cursor-pointer"
<div
className="cursor-pointer"
onClick={handleErrorIconClick}
aria-label={t('server.viewErrorDetails')}
>
<AlertCircle className="text-red-500 hover:text-red-600" size={18} />
</div>
{showErrorPopover && (
<div
<div
ref={errorPopoverRef}
className="absolute z-10 mt-2 bg-white border border-gray-200 rounded-md shadow-lg p-0 w-120"
style={{
left: '-231px',
top: '24px',
maxHeight: '300px',
overflowY: 'auto',
style={{
left: '-231px',
top: '24px',
maxHeight: '300px',
overflowY: 'auto',
width: '480px',
transform: 'translateX(50%)'
}}
@@ -140,7 +172,7 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle }: ServerCardProps) =>
<div className="flex justify-between items-center sticky top-0 bg-white py-2 px-4 border-b border-gray-200 z-20 shadow-sm">
<div className="flex items-center space-x-2">
<h4 className="text-sm font-medium text-red-600">{t('server.errorDetails')}</h4>
<button
<button
onClick={copyToClipboard}
className="p-1 text-gray-400 hover:text-gray-600 transition-colors"
title={t('common.copy')}
@@ -148,7 +180,7 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle }: ServerCardProps) =>
{copied ? <Check size={14} className="text-green-500" /> : <Copy size={14} />}
</button>
</div>
<button
<button
onClick={(e) => {
e.stopPropagation()
setShowErrorPopover(false)
@@ -176,19 +208,18 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle }: ServerCardProps) =>
<div className="flex items-center">
<button
onClick={handleToggle}
className={`px-3 py-1 text-sm rounded transition-colors ${
isToggling
? 'bg-gray-200 text-gray-500'
: server.enabled !== false
? 'bg-green-100 text-green-800 hover:bg-green-200'
: 'bg-gray-100 text-gray-800 hover:bg-gray-200'
}`}
className={`px-3 py-1 text-sm rounded transition-colors ${isToggling
? 'bg-gray-200 text-gray-500'
: server.enabled !== false
? 'bg-green-100 text-green-800 hover:bg-green-200'
: 'bg-gray-100 text-gray-800 hover:bg-gray-200'
}`}
disabled={isToggling}
>
{isToggling
{isToggling
? t('common.processing')
: server.enabled !== false
? t('server.disable')
: server.enabled !== false
? t('server.disable')
: t('server.enable')
}
</button>
@@ -207,10 +238,10 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle }: ServerCardProps) =>
{isExpanded && server.tools && (
<div className="mt-6">
<h3 className={`text-lg font-medium ${server.enabled === false ? 'text-gray-600' : 'text-gray-900'} mb-4`}>{t('server.tools')}</h3>
<h6 className={`font-medium ${server.enabled === false ? 'text-gray-600' : 'text-gray-900'} mb-4`}>{t('server.tools')}</h6>
<div className="space-y-4">
{server.tools.map((tool, index) => (
<ToolCard key={index} tool={tool} />
<ToolCard key={index} server={server.name} tool={tool} onToggle={handleToolToggle} />
))}
</div>
</div>

View File

@@ -26,7 +26,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr
}
};
const [serverType, setServerType] = useState<'stdio' | 'sse' | 'streamable-http'>(getInitialServerType());
const [serverType, setServerType] = useState<'stdio' | 'sse' | 'streamable-http' | 'openapi'>(getInitialServerType());
const [formData, setFormData] = useState<ServerFormData>({
name: (initialData && initialData.name) || '',
@@ -40,7 +40,39 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr
: '',
args: (initialData && initialData.config && initialData.config.args) || [],
type: getInitialServerType(), // Initialize the type field
env: []
env: [],
headers: [],
options: {
timeout: (initialData && initialData.config && initialData.config.options && initialData.config.options.timeout) || 60000,
resetTimeoutOnProgress: (initialData && initialData.config && initialData.config.options && initialData.config.options.resetTimeoutOnProgress) || false,
maxTotalTimeout: (initialData && initialData.config && initialData.config.options && initialData.config.options.maxTotalTimeout) || undefined,
},
// OpenAPI configuration initialization
openapi: initialData && initialData.config && initialData.config.openapi ? {
url: initialData.config.openapi.url || '',
schema: initialData.config.openapi.schema ? JSON.stringify(initialData.config.openapi.schema, null, 2) : '',
inputMode: initialData.config.openapi.url ? 'url' : (initialData.config.openapi.schema ? 'schema' : 'url'),
version: initialData.config.openapi.version || '3.1.0',
securityType: initialData.config.openapi.security?.type || 'none',
// API Key initialization
apiKeyName: initialData.config.openapi.security?.apiKey?.name || '',
apiKeyIn: initialData.config.openapi.security?.apiKey?.in || 'header',
apiKeyValue: initialData.config.openapi.security?.apiKey?.value || '',
// HTTP auth initialization
httpScheme: initialData.config.openapi.security?.http?.scheme || 'bearer',
httpCredentials: initialData.config.openapi.security?.http?.credentials || '',
// OAuth2 initialization
oauth2Token: initialData.config.openapi.security?.oauth2?.token || '',
// OpenID Connect initialization
openIdConnectUrl: initialData.config.openapi.security?.openIdConnect?.url || '',
openIdConnectToken: initialData.config.openapi.security?.openIdConnect?.token || ''
} : {
inputMode: 'url',
url: '',
schema: '',
version: '3.1.0',
securityType: 'none'
}
})
const [envVars, setEnvVars] = useState<EnvVar[]>(
@@ -49,6 +81,13 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr
: [],
)
const [headerVars, setHeaderVars] = useState<EnvVar[]>(
initialData && initialData.config && initialData.config.headers
? Object.entries(initialData.config.headers).map(([key, value]) => ({ key, value }))
: [],
)
const [isRequestOptionsExpanded, setIsRequestOptionsExpanded] = useState<boolean>(false)
const [error, setError] = useState<string | null>(null)
const isEdit = !!initialData
@@ -59,11 +98,11 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr
// Transform space-separated arguments string into array
const handleArgsChange = (value: string) => {
let args = value.split(' ').filter((arg) => arg.trim() !== '')
const args = value.split(' ').filter((arg) => arg.trim() !== '')
setFormData({ ...formData, arguments: value, args })
}
const updateServerType = (type: 'stdio' | 'sse' | 'streamable-http') => {
const updateServerType = (type: 'stdio' | 'sse' | 'streamable-http' | 'openapi') => {
setServerType(type);
setFormData(prev => ({ ...prev, type }));
}
@@ -84,6 +123,33 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr
setEnvVars(newEnvVars)
}
const handleHeaderVarChange = (index: number, field: 'key' | 'value', value: string) => {
const newHeaderVars = [...headerVars]
newHeaderVars[index][field] = value
setHeaderVars(newHeaderVars)
}
const addHeaderVar = () => {
setHeaderVars([...headerVars, { key: '', value: '' }])
}
const removeHeaderVar = (index: number) => {
const newHeaderVars = [...headerVars]
newHeaderVars.splice(index, 1)
setHeaderVars(newHeaderVars)
}
// Handle options changes
const handleOptionsChange = (field: 'timeout' | 'resetTimeoutOnProgress' | 'maxTotalTimeout', value: number | boolean | undefined) => {
setFormData(prev => ({
...prev,
options: {
...prev.options,
[field]: value
}
}))
}
// Submit handler for server configuration
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
@@ -97,18 +163,94 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr
}
})
const headers: Record<string, string> = {}
headerVars.forEach(({ key, value }) => {
if (key.trim()) {
headers[key.trim()] = value
}
})
// Prepare options object, only include defined values
const options: any = {}
if (formData.options?.timeout && formData.options.timeout !== 60000) {
options.timeout = formData.options.timeout
}
if (formData.options?.resetTimeoutOnProgress) {
options.resetTimeoutOnProgress = formData.options.resetTimeoutOnProgress
}
if (formData.options?.maxTotalTimeout) {
options.maxTotalTimeout = formData.options.maxTotalTimeout
}
const payload = {
name: formData.name,
config: {
type: serverType, // Always include the type
...(serverType === 'sse' || serverType === 'streamable-http'
? { url: formData.url }
: {
command: formData.command,
args: formData.args,
env: Object.keys(env).length > 0 ? env : undefined,
...(serverType === 'openapi'
? {
openapi: (() => {
const openapi: any = {
version: formData.openapi?.version || '3.1.0'
};
// Add URL or schema based on input mode
if (formData.openapi?.inputMode === 'url') {
openapi.url = formData.openapi?.url || '';
} else if (formData.openapi?.inputMode === 'schema' && formData.openapi?.schema) {
try {
openapi.schema = JSON.parse(formData.openapi.schema);
} catch (e) {
throw new Error('Invalid JSON schema format');
}
}
// Add security configuration if provided
if (formData.openapi?.securityType && formData.openapi.securityType !== 'none') {
openapi.security = {
type: formData.openapi.securityType,
...(formData.openapi.securityType === 'apiKey' && {
apiKey: {
name: formData.openapi.apiKeyName || '',
in: formData.openapi.apiKeyIn || 'header',
value: formData.openapi.apiKeyValue || ''
}
}),
...(formData.openapi.securityType === 'http' && {
http: {
scheme: formData.openapi.httpScheme || 'bearer',
credentials: formData.openapi.httpCredentials || ''
}
}),
...(formData.openapi.securityType === 'oauth2' && {
oauth2: {
token: formData.openapi.oauth2Token || ''
}
}),
...(formData.openapi.securityType === 'openIdConnect' && {
openIdConnect: {
url: formData.openapi.openIdConnectUrl || '',
token: formData.openapi.openIdConnectToken || ''
}
})
};
}
return openapi;
})(),
...(Object.keys(headers).length > 0 ? { headers } : {})
}
)
: serverType === 'sse' || serverType === 'streamable-http'
? {
url: formData.url,
...(Object.keys(headers).length > 0 ? { headers } : {})
}
: {
command: formData.command,
args: formData.args,
env: Object.keys(env).length > 0 ? env : undefined,
}
),
...(Object.keys(options).length > 0 ? { options } : {})
}
}
@@ -190,25 +332,351 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr
/>
<label htmlFor="streamable-http">Streamable HTTP</label>
</div>
<div>
<input
type="radio"
id="openapi"
name="serverType"
value="openapi"
checked={serverType === 'openapi'}
onChange={() => updateServerType('openapi')}
className="mr-1"
/>
<label htmlFor="openapi">OpenAPI</label>
</div>
</div>
</div>
{serverType === 'sse' || serverType === 'streamable-http' ? (
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="url">
{t('server.url')}
</label>
<input
type="url"
name="url"
id="url"
value={formData.url}
onChange={handleInputChange}
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
placeholder={serverType === 'streamable-http' ? "e.g.: http://localhost:3000/mcp" : "e.g.: http://localhost:3000/sse"}
required={serverType === 'sse' || serverType === 'streamable-http'}
/>
</div>
{serverType === 'openapi' ? (
<>
{/* Input Mode Selection */}
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2">
{t('server.openapi.inputMode')}
</label>
<div className="flex space-x-4">
<div>
<input
type="radio"
id="input-mode-url"
name="inputMode"
value="url"
checked={formData.openapi?.inputMode === 'url'}
onChange={() => setFormData(prev => ({
...prev,
openapi: { ...prev.openapi!, inputMode: 'url' }
}))}
className="mr-1"
/>
<label htmlFor="input-mode-url">{t('server.openapi.inputModeUrl')}</label>
</div>
<div>
<input
type="radio"
id="input-mode-schema"
name="inputMode"
value="schema"
checked={formData.openapi?.inputMode === 'schema'}
onChange={() => setFormData(prev => ({
...prev,
openapi: { ...prev.openapi!, inputMode: 'schema' }
}))}
className="mr-1"
/>
<label htmlFor="input-mode-schema">{t('server.openapi.inputModeSchema')}</label>
</div>
</div>
</div>
{/* URL Input */}
{formData.openapi?.inputMode === 'url' && (
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="openapi-url">
{t('server.openapi.specUrl')}
</label>
<input
type="url"
name="openapi-url"
id="openapi-url"
value={formData.openapi?.url || ''}
onChange={(e) => setFormData(prev => ({
...prev,
openapi: { ...prev.openapi!, url: e.target.value }
}))}
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
placeholder="e.g.: https://api.example.com/openapi.json"
required={serverType === 'openapi' && formData.openapi?.inputMode === 'url'}
/>
</div>
)}
{/* Schema Input */}
{formData.openapi?.inputMode === 'schema' && (
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="openapi-schema">
{t('server.openapi.schema')}
</label>
<textarea
name="openapi-schema"
id="openapi-schema"
rows={10}
value={formData.openapi?.schema || ''}
onChange={(e) => setFormData(prev => ({
...prev,
openapi: { ...prev.openapi!, schema: e.target.value }
}))}
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline font-mono text-sm"
placeholder={`{
"openapi": "3.1.0",
"info": {
"title": "API",
"version": "1.0.0"
},
"servers": [
{
"url": "https://api.example.com"
}
],
"paths": {
...
}
}`}
required={serverType === 'openapi' && formData.openapi?.inputMode === 'schema'}
/>
<p className="text-xs text-gray-500 mt-1">{t('server.openapi.schemaHelp')}</p>
</div>
)}
{/* Security Configuration */}
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2">
{t('server.openapi.security')}
</label>
<select
value={formData.openapi?.securityType || 'none'}
onChange={(e) => setFormData(prev => ({
...prev,
openapi: {
...prev.openapi,
securityType: e.target.value as any,
url: prev.openapi?.url || ''
}
}))}
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
>
<option value="none">{t('server.openapi.securityNone')}</option>
<option value="apiKey">{t('server.openapi.securityApiKey')}</option>
<option value="http">{t('server.openapi.securityHttp')}</option>
<option value="oauth2">{t('server.openapi.securityOAuth2')}</option>
<option value="openIdConnect">{t('server.openapi.securityOpenIdConnect')}</option>
</select>
</div>
{/* API Key Configuration */}
{formData.openapi?.securityType === 'apiKey' && (
<div className="mb-4 p-4 border rounded bg-gray-50">
<h4 className="text-sm font-medium text-gray-700 mb-3">{t('server.openapi.apiKeyConfig')}</h4>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<div>
<label className="block text-xs text-gray-600 mb-1">{t('server.openapi.apiKeyName')}</label>
<input
type="text"
value={formData.openapi?.apiKeyName || ''}
onChange={(e) => setFormData(prev => ({
...prev,
openapi: { ...prev.openapi, apiKeyName: e.target.value, url: prev.openapi?.url || '' }
}))}
className="w-full border rounded px-2 py-1 text-sm"
placeholder="Authorization"
/>
</div>
<div>
<label className="block text-xs text-gray-600 mb-1">{t('server.openapi.apiKeyIn')}</label>
<select
value={formData.openapi?.apiKeyIn || 'header'}
onChange={(e) => setFormData(prev => ({
...prev,
openapi: { ...prev.openapi, apiKeyIn: e.target.value as any, url: prev.openapi?.url || '' }
}))}
className="w-full border rounded px-2 py-1 text-sm"
>
<option value="header">Header</option>
<option value="query">Query</option>
<option value="cookie">Cookie</option>
</select>
</div>
<div>
<label className="block text-xs text-gray-600 mb-1">{t('server.openapi.apiKeyValue')}</label>
<input
type="password"
value={formData.openapi?.apiKeyValue || ''}
onChange={(e) => setFormData(prev => ({
...prev,
openapi: { ...prev.openapi, apiKeyValue: e.target.value, url: prev.openapi?.url || '' }
}))}
className="w-full border rounded px-2 py-1 text-sm"
placeholder="your-api-key"
/>
</div>
</div>
</div>
)}
{/* HTTP Authentication Configuration */}
{formData.openapi?.securityType === 'http' && (
<div className="mb-4 p-4 border rounded bg-gray-50">
<h4 className="text-sm font-medium text-gray-700 mb-3">{t('server.openapi.httpAuthConfig')}</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label className="block text-xs text-gray-600 mb-1">{t('server.openapi.httpScheme')}</label>
<select
value={formData.openapi?.httpScheme || 'bearer'}
onChange={(e) => setFormData(prev => ({
...prev,
openapi: { ...prev.openapi, httpScheme: e.target.value as any, url: prev.openapi?.url || '' }
}))}
className="w-full border rounded px-2 py-1 text-sm"
>
<option value="basic">Basic</option>
<option value="bearer">Bearer</option>
<option value="digest">Digest</option>
</select>
</div>
<div>
<label className="block text-xs text-gray-600 mb-1">{t('server.openapi.httpCredentials')}</label>
<input
type="password"
value={formData.openapi?.httpCredentials || ''}
onChange={(e) => setFormData(prev => ({
...prev,
openapi: { ...prev.openapi, httpCredentials: e.target.value, url: prev.openapi?.url || '' }
}))}
className="w-full border rounded px-2 py-1 text-sm"
placeholder={formData.openapi?.httpScheme === 'basic' ? 'base64-encoded-credentials' : 'bearer-token'}
/>
</div>
</div>
</div>
)}
{/* OAuth2 Configuration */}
{formData.openapi?.securityType === 'oauth2' && (
<div className="mb-4 p-4 border rounded bg-gray-50">
<h4 className="text-sm font-medium text-gray-700 mb-3">{t('server.openapi.oauth2Config')}</h4>
<div className="grid grid-cols-1 gap-3">
<div>
<label className="block text-xs text-gray-600 mb-1">{t('server.openapi.oauth2Token')}</label>
<input
type="password"
value={formData.openapi?.oauth2Token || ''}
onChange={(e) => setFormData(prev => ({
...prev,
openapi: { ...prev.openapi, oauth2Token: e.target.value, url: prev.openapi?.url || '' }
}))}
className="w-full border rounded px-2 py-1 text-sm"
placeholder="access-token"
/>
</div>
</div>
</div>
)}
{/* OpenID Connect Configuration */}
{formData.openapi?.securityType === 'openIdConnect' && (
<div className="mb-4 p-4 border rounded bg-gray-50">
<h4 className="text-sm font-medium text-gray-700 mb-3">{t('server.openapi.openIdConnectConfig')}</h4>
<div className="grid grid-cols-1 gap-3">
<div>
<label className="block text-xs text-gray-600 mb-1">{t('server.openapi.openIdConnectUrl')}</label>
<input
type="url"
value={formData.openapi?.openIdConnectUrl || ''}
onChange={(e) => setFormData(prev => ({
...prev,
openapi: { ...prev.openapi, openIdConnectUrl: e.target.value, url: prev.openapi?.url || '' }
}))}
className="w-full border rounded px-2 py-1 text-sm"
placeholder="https://example.com/.well-known/openid_configuration"
/>
</div>
<div>
<label className="block text-xs text-gray-600 mb-1">{t('server.openapi.openIdConnectToken')}</label>
<input
type="password"
value={formData.openapi?.openIdConnectToken || ''}
onChange={(e) => setFormData(prev => ({
...prev,
openapi: { ...prev.openapi, openIdConnectToken: e.target.value, url: prev.openapi?.url || '' }
}))}
className="w-full border rounded px-2 py-1 text-sm"
placeholder="id-token"
/>
</div>
</div>
</div>
)}
</>
) : serverType === 'sse' || serverType === 'streamable-http' ? (
<>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="url">
{t('server.url')}
</label>
<input
type="url"
name="url"
id="url"
value={formData.url}
onChange={handleInputChange}
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
placeholder={serverType === 'streamable-http' ? "e.g.: http://localhost:3000/mcp" : "e.g.: http://localhost:3000/sse"}
required={serverType === 'sse' || serverType === 'streamable-http'}
/>
</div>
<div className="mb-4">
<div className="flex justify-between items-center mb-2">
<label className="block text-gray-700 text-sm font-bold">
{t('server.headers')}
</label>
<button
type="button"
onClick={addHeaderVar}
className="bg-gray-200 hover:bg-gray-300 text-gray-700 font-medium py-1 px-2 rounded text-sm flex items-center"
>
+ {t('server.add')}
</button>
</div>
{headerVars.map((headerVar, index) => (
<div key={index} className="flex items-center mb-2">
<div className="flex items-center space-x-2 flex-grow">
<input
type="text"
value={headerVar.key}
onChange={(e) => handleHeaderVarChange(index, 'key', e.target.value)}
className="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-1/2"
placeholder="Authorization"
/>
<span className="flex items-center">:</span>
<input
type="text"
value={headerVar.value}
onChange={(e) => handleHeaderVarChange(index, 'value', e.target.value)}
className="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-1/2"
placeholder="Bearer token..."
/>
</div>
<button
type="button"
onClick={() => removeHeaderVar(index)}
className="bg-gray-200 hover:bg-gray-300 text-gray-700 font-medium py-1 px-2 rounded text-sm flex items-center justify-center min-w-[56px] ml-2"
>
- {t('server.remove')}
</button>
</div>
))}
</div>
</>
) : (
<>
<div className="mb-4">
@@ -287,6 +755,77 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr
</>
)}
{/* Request Options Configuration */}
{serverType !== 'openapi' && (
<div className="mb-4">
<div
className="flex items-center justify-between cursor-pointer bg-gray-50 hover:bg-gray-100 p-3 rounded border"
onClick={() => setIsRequestOptionsExpanded(!isRequestOptionsExpanded)}
>
<label className="text-gray-700 text-sm font-bold">
{t('server.requestOptions')}
</label>
<span className="text-gray-500 text-sm">
{isRequestOptionsExpanded ? '▼' : '▶'}
</span>
</div>
{isRequestOptionsExpanded && (
<div className="border rounded-b p-4 bg-gray-50 border-t-0">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-gray-600 text-sm font-medium mb-1" htmlFor="timeout">
{t('server.timeout')}
</label>
<input
type="number"
id="timeout"
value={formData.options?.timeout || 60000}
onChange={(e) => handleOptionsChange('timeout', parseInt(e.target.value) || 60000)}
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
placeholder="30000"
min="1000"
max="300000"
/>
<p className="text-xs text-gray-500 mt-1">{t('server.timeoutDescription')}</p>
</div>
<div>
<label className="block text-gray-600 text-sm font-medium mb-1" htmlFor="maxTotalTimeout">
{t('server.maxTotalTimeout')}
</label>
<input
type="number"
id="maxTotalTimeout"
value={formData.options?.maxTotalTimeout || ''}
onChange={(e) => handleOptionsChange('maxTotalTimeout', e.target.value ? parseInt(e.target.value) : undefined)}
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
placeholder="Optional"
min="1000"
/>
<p className="text-xs text-gray-500 mt-1">{t('server.maxTotalTimeoutDescription')}</p>
</div>
</div>
<div className="mt-3">
<label className="flex items-center">
<input
type="checkbox"
checked={formData.options?.resetTimeoutOnProgress || false}
onChange={(e) => handleOptionsChange('resetTimeoutOnProgress', e.target.checked)}
className="mr-2"
/>
<span className="text-gray-600 text-sm">{t('server.resetTimeoutOnProgress')}</span>
</label>
<p className="text-xs text-gray-500 mt-1 ml-6">
{t('server.resetTimeoutOnProgressDescription')}
</p>
</div>
</div>
)}
</div>
)}
<div className="flex justify-end mt-6">
<button
type="button"

View File

@@ -1,6 +1,38 @@
import { ChevronDown, ChevronRight, Edit, Trash, Copy, Check, User, Settings, LogOut, Info } from 'lucide-react'
import {
ChevronDown,
ChevronRight,
Edit,
Trash,
Copy,
Check,
User,
Settings,
LogOut,
Info,
Play,
Loader,
CheckCircle,
XCircle,
AlertCircle
} from 'lucide-react'
export { ChevronDown, ChevronRight, Edit, Trash, Copy, Check, User, Settings, LogOut, Info }
export {
ChevronDown,
ChevronRight,
Edit,
Trash,
Copy,
Check,
User,
Settings,
LogOut,
Info,
Play,
Loader,
CheckCircle,
XCircle,
AlertCircle
}
const LucideIcons = {
ChevronDown,
@@ -12,7 +44,12 @@ const LucideIcons = {
User,
Settings,
LogOut,
Info
Info,
Play,
Loader,
CheckCircle,
XCircle,
AlertCircle
}
export default LucideIcons

View File

@@ -0,0 +1,723 @@
import React, { useState, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { ToolInputSchema } from '@/types';
interface JsonSchema {
type: string;
properties?: Record<string, JsonSchema>;
required?: string[];
items?: JsonSchema;
enum?: any[];
description?: string;
default?: any;
}
interface DynamicFormProps {
schema: ToolInputSchema;
onSubmit: (values: Record<string, any>) => void;
onCancel: () => void;
loading?: boolean;
storageKey?: string; // Optional key for localStorage persistence
}
const DynamicForm: React.FC<DynamicFormProps> = ({ schema, onSubmit, onCancel, loading = false, storageKey }) => {
const { t } = useTranslation();
const [formValues, setFormValues] = useState<Record<string, any>>({});
const [errors, setErrors] = useState<Record<string, string>>({});
const [isJsonMode, setIsJsonMode] = useState<boolean>(false);
const [jsonText, setJsonText] = useState<string>('');
const [jsonError, setJsonError] = useState<string>('');
// Convert ToolInputSchema to JsonSchema - memoized to prevent infinite re-renders
const jsonSchema = useMemo(() => {
const convertToJsonSchema = (schema: ToolInputSchema): JsonSchema => {
const convertProperty = (prop: unknown): JsonSchema => {
if (typeof prop === 'object' && prop !== null) {
const obj = prop as any;
return {
type: obj.type || 'string',
description: obj.description,
enum: obj.enum,
default: obj.default,
properties: obj.properties ? Object.fromEntries(
Object.entries(obj.properties).map(([key, value]) => [key, convertProperty(value)])
) : undefined,
required: obj.required,
items: obj.items ? convertProperty(obj.items) : undefined,
};
}
return { type: 'string' };
};
return {
type: schema.type,
properties: schema.properties ? Object.fromEntries(
Object.entries(schema.properties).map(([key, value]) => [key, convertProperty(value)])
) : undefined,
required: schema.required,
};
};
return convertToJsonSchema(schema);
}, [schema]);
// Initialize form values with defaults or from localStorage
useEffect(() => {
const initializeValues = (schema: JsonSchema, path: string = ''): Record<string, any> => {
const values: Record<string, any> = {};
if (schema.type === 'object' && schema.properties) {
Object.entries(schema.properties).forEach(([key, propSchema]) => {
const fullPath = path ? `${path}.${key}` : key;
if (propSchema.default !== undefined) {
values[key] = propSchema.default;
} else if (propSchema.type === 'string') {
values[key] = '';
} else if (propSchema.type === 'number' || propSchema.type === 'integer') {
values[key] = 0;
} else if (propSchema.type === 'boolean') {
values[key] = false;
} else if (propSchema.type === 'array') {
values[key] = [];
} else if (propSchema.type === 'object') {
// For objects with properties, recursively initialize
if (propSchema.properties) {
values[key] = initializeValues(propSchema, fullPath);
} else {
// For objects without properties, initialize as empty object
values[key] = {};
}
}
});
}
return values;
};
let initialValues = initializeValues(jsonSchema);
// Try to load saved form data from localStorage
if (storageKey) {
try {
const savedData = localStorage.getItem(storageKey);
if (savedData) {
const parsedData = JSON.parse(savedData);
// Merge saved data with initial values, preserving structure
initialValues = { ...initialValues, ...parsedData };
}
} catch (error) {
console.warn('Failed to load saved form data:', error);
}
}
setFormValues(initialValues);
}, [jsonSchema, storageKey]);
// Sync JSON text with form values when switching modes
useEffect(() => {
if (isJsonMode && Object.keys(formValues).length > 0) {
setJsonText(JSON.stringify(formValues, null, 2));
setJsonError('');
}
}, [isJsonMode, formValues]);
const handleJsonTextChange = (text: string) => {
setJsonText(text);
setJsonError('');
try {
const parsedJson = JSON.parse(text);
setFormValues(parsedJson);
// Save to localStorage if storageKey is provided
if (storageKey) {
try {
localStorage.setItem(storageKey, JSON.stringify(parsedJson));
} catch (error) {
console.warn('Failed to save form data to localStorage:', error);
}
}
} catch (error) {
setJsonError(t('tool.invalidJsonFormat'));
}
};
const switchToJsonMode = () => {
setJsonText(JSON.stringify(formValues, null, 2));
setJsonError('');
setIsJsonMode(true);
};
const switchToFormMode = () => {
// Validate JSON before switching
if (jsonText.trim()) {
try {
const parsedJson = JSON.parse(jsonText);
setFormValues(parsedJson);
setJsonError('');
setIsJsonMode(false);
} catch (error) {
setJsonError(t('tool.fixJsonBeforeSwitching'));
return;
}
} else {
setIsJsonMode(false);
}
};
const handleInputChange = (path: string, value: any) => {
setFormValues(prev => {
const newValues = { ...prev };
const keys = path.split('.');
let current = newValues;
for (let i = 0; i < keys.length - 1; i++) {
if (!current[keys[i]]) {
current[keys[i]] = {};
}
current = current[keys[i]];
}
current[keys[keys.length - 1]] = value;
// Save to localStorage if storageKey is provided
if (storageKey) {
try {
localStorage.setItem(storageKey, JSON.stringify(newValues));
} catch (error) {
console.warn('Failed to save form data to localStorage:', error);
}
}
return newValues;
});
// Clear error for this field
if (errors[path]) {
setErrors(prev => {
const newErrors = { ...prev };
delete newErrors[path];
return newErrors;
});
}
};
const validateForm = (): boolean => {
const newErrors: Record<string, string> = {};
const validateObject = (schema: JsonSchema, values: any, path: string = '') => {
if (schema.type === 'object' && schema.properties) {
Object.entries(schema.properties).forEach(([key, propSchema]) => {
const fullPath = path ? `${path}.${key}` : key;
const value = getNestedValue(values, fullPath);
// Check required fields
if (schema.required?.includes(key) && (value === undefined || value === null || value === '')) {
newErrors[fullPath] = `${key} is required`;
return;
}
// Validate type
if (value !== undefined && value !== null && value !== '') {
if (propSchema.type === 'string' && typeof value !== 'string') {
newErrors[fullPath] = `${key} must be a string`;
} else if (propSchema.type === 'number' && typeof value !== 'number') {
newErrors[fullPath] = `${key} must be a number`;
} else if (propSchema.type === 'integer' && (!Number.isInteger(value) || typeof value !== 'number')) {
newErrors[fullPath] = `${key} must be an integer`;
} else if (propSchema.type === 'boolean' && typeof value !== 'boolean') {
newErrors[fullPath] = `${key} must be a boolean`;
} else if (propSchema.type === 'array' && Array.isArray(value)) {
// Validate array items
if (propSchema.items) {
value.forEach((item: any, index: number) => {
if (propSchema.items?.type === 'object' && propSchema.items.properties) {
validateObject(propSchema.items, item, `${fullPath}.${index}`);
}
});
}
} else if (propSchema.type === 'object' && typeof value === 'object') {
validateObject(propSchema, value, fullPath);
}
}
});
}
};
validateObject(jsonSchema, formValues);
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (validateForm()) {
onSubmit(formValues);
}
};
const getNestedValue = (obj: any, path: string): any => {
return path.split('.').reduce((current, key) => current?.[key], obj);
};
const renderObjectField = (key: string, schema: JsonSchema, currentValue: any, onChange: (value: any) => void): React.ReactNode => {
const value = currentValue?.[key];
if (schema.type === 'string') {
if (schema.enum) {
return (
<select
value={value || ''}
onChange={(e) => onChange(e.target.value)}
className="w-full border rounded-md px-2 py-1 text-sm border-gray-300 focus:outline-none focus:ring-1 focus:ring-blue-500"
>
<option value="">{t('tool.selectOption')}</option>
{schema.enum.map((option: any, idx: number) => (
<option key={idx} value={option}>
{option}
</option>
))}
</select>
);
} else {
return (
<input
type="text"
value={value || ''}
onChange={(e) => onChange(e.target.value)}
className="w-full border rounded-md px-2 py-1 text-sm border-gray-300 focus:outline-none focus:ring-1 focus:ring-blue-500"
placeholder={schema.description || t('tool.enterKey', { key })}
/>
);
}
}
if (schema.type === 'number' || schema.type === 'integer') {
return (
<input
type="number"
step={schema.type === 'integer' ? '1' : 'any'}
value={value || ''}
onChange={(e) => {
const val = e.target.value === '' ? '' : schema.type === 'integer' ? parseInt(e.target.value) : parseFloat(e.target.value);
onChange(val);
}}
className="w-full border rounded-md px-2 py-1 text-sm border-gray-300 focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
);
}
if (schema.type === 'boolean') {
return (
<input
type="checkbox"
checked={value || false}
onChange={(e) => onChange(e.target.checked)}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
);
}
// Default to text input
return (
<input
type="text"
value={value || ''}
onChange={(e) => onChange(e.target.value)}
className="w-full border rounded-md px-2 py-1 text-sm border-gray-300 focus:outline-none focus:ring-1 focus:ring-blue-500"
placeholder={schema.description || t('tool.enterKey', { key })}
/>
);
};
const renderField = (key: string, propSchema: JsonSchema, path: string = ''): React.ReactNode => {
const fullPath = path ? `${path}.${key}` : key;
const value = getNestedValue(formValues, fullPath);
const error = errors[fullPath]; // Handle array type
if (propSchema.type === 'array') {
const arrayValue = getNestedValue(formValues, fullPath) || [];
return (
<div key={fullPath} className="mb-6">
<label className="block text-sm font-medium text-gray-700 mb-1">
{key}
{(path ? getNestedValue(jsonSchema, path)?.required?.includes(key) : jsonSchema.required?.includes(key)) && <span className="text-red-500 ml-1">*</span>}
</label>
{propSchema.description && (
<p className="text-xs text-gray-500 mb-2">{propSchema.description}</p>
)}
<div className="border border-gray-200 rounded-md p-3 bg-gray-50">
{arrayValue.map((item: any, index: number) => (
<div key={index} className="mb-3 p-3 bg-white border rounded-md">
<div className="flex justify-between items-center mb-2">
<span className="text-sm font-medium text-gray-600">{t('tool.item', { index: index + 1 })}</span>
<button
type="button"
onClick={() => {
const newArray = [...arrayValue];
newArray.splice(index, 1);
handleInputChange(fullPath, newArray);
}}
className="text-red-500 hover:text-red-700 text-sm"
>
{t('common.remove')}
</button>
</div>
{propSchema.items?.type === 'string' && propSchema.items.enum ? (
<select
value={item || ''}
onChange={(e) => {
const newArray = [...arrayValue];
newArray[index] = e.target.value;
handleInputChange(fullPath, newArray);
}}
className="w-full border rounded-md px-3 py-2 border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">{t('tool.selectOption')}</option>
{propSchema.items.enum.map((option: any, idx: number) => (
<option key={idx} value={option}>
{option}
</option>
))}
</select>
) : propSchema.items?.type === 'object' && propSchema.items.properties ? (
<div className="space-y-3">
{Object.entries(propSchema.items.properties).map(([objKey, objSchema]) => (
<div key={objKey}>
<label className="block text-xs font-medium text-gray-600 mb-1">
{objKey}
{propSchema.items?.required?.includes(objKey) && <span className="text-red-500 ml-1">*</span>}
</label>
{renderObjectField(objKey, objSchema as JsonSchema, item, (newValue) => {
const newArray = [...arrayValue];
newArray[index] = { ...newArray[index], [objKey]: newValue };
handleInputChange(fullPath, newArray);
})}
</div>
))}
</div>
) : (
<input
type="text"
value={item || ''}
onChange={(e) => {
const newArray = [...arrayValue];
newArray[index] = e.target.value;
handleInputChange(fullPath, newArray);
}}
className="w-full border rounded-md px-3 py-2 border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder={t('tool.enterValue', { type: propSchema.items?.type || 'value' })}
/>
)}
</div>
))}
<button
type="button"
onClick={() => {
const newItem = propSchema.items?.type === 'object' ? {} : '';
handleInputChange(fullPath, [...arrayValue, newItem]);
}}
className="w-full mt-2 px-3 py-2 text-sm text-blue-600 border border-blue-300 rounded-md hover:bg-blue-50"
>
{t('tool.addItem', { key })}
</button>
</div>
{error && <p className="text-red-500 text-xs mt-1">{error}</p>}
</div>
);
} // Handle object type
if (propSchema.type === 'object') {
if (propSchema.properties) {
// Object with defined properties - render as nested form
return (
<div key={fullPath} className="mb-6">
<label className="block text-sm font-medium text-gray-700 mb-1">
{key}
{(path ? getNestedValue(jsonSchema, path)?.required?.includes(key) : jsonSchema.required?.includes(key)) && <span className="text-red-500 ml-1">*</span>}
</label>
{propSchema.description && (
<p className="text-xs text-gray-500 mb-2">{propSchema.description}</p>
)}
<div className="border border-gray-200 rounded-md p-4 bg-gray-50">
{Object.entries(propSchema.properties).map(([objKey, objSchema]) => (
renderField(objKey, objSchema as JsonSchema, fullPath)
))}
</div>
{error && <p className="text-red-500 text-xs mt-1">{error}</p>}
</div>
);
} else {
// Object without defined properties - render as JSON textarea
return (
<div key={fullPath} className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
{key}
{(path ? getNestedValue(jsonSchema, path)?.required?.includes(key) : jsonSchema.required?.includes(key)) && <span className="text-red-500 ml-1">*</span>}
<span className="text-xs text-gray-500 ml-1">(JSON object)</span>
</label>
{propSchema.description && (
<p className="text-xs text-gray-500 mb-2">{propSchema.description}</p>
)}
<textarea
value={typeof value === 'object' ? JSON.stringify(value, null, 2) : value || '{}'}
onChange={(e) => {
try {
const parsedValue = JSON.parse(e.target.value);
handleInputChange(fullPath, parsedValue);
} catch (err) {
// Keep the string value if it's not valid JSON yet
handleInputChange(fullPath, e.target.value);
}
}}
placeholder={`{\n "key": "value"\n}`}
className={`w-full border rounded-md px-3 py-2 font-mono text-sm ${error ? 'border-red-500' : 'border-gray-300'} focus:outline-none focus:ring-2 focus:ring-blue-500`}
rows={4}
/>
{error && <p className="text-red-500 text-xs mt-1">{error}</p>}
</div>
);
}
} if (propSchema.type === 'string') {
if (propSchema.enum) {
return (
<div key={fullPath} className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
{key}
{(path ? false : jsonSchema.required?.includes(key)) && <span className="text-red-500 ml-1">*</span>}
</label>
{propSchema.description && (
<p className="text-xs text-gray-500 mb-2">{propSchema.description}</p>
)}
<select
value={value || ''}
onChange={(e) => handleInputChange(fullPath, e.target.value)}
className={`w-full border rounded-md px-3 py-2 ${error ? 'border-red-500' : 'border-gray-300'} focus:outline-none focus:ring-2 focus:ring-blue-500`}
>
<option value="">{t('tool.selectOption')}</option>
{propSchema.enum.map((option, idx) => (
<option key={idx} value={option}>
{option}
</option>
))}
</select>
{error && <p className="text-red-500 text-xs mt-1">{error}</p>}
</div>
);
} else {
return (
<div key={fullPath} className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
{key}
{(path ? false : jsonSchema.required?.includes(key)) && <span className="text-red-500 ml-1">*</span>}
</label>
{propSchema.description && (
<p className="text-xs text-gray-500 mb-2">{propSchema.description}</p>
)}
<input
type="text"
value={value || ''}
onChange={(e) => handleInputChange(fullPath, e.target.value)}
className={`w-full border rounded-md px-3 py-2 ${error ? 'border-red-500' : 'border-gray-300'} focus:outline-none focus:ring-2 focus:ring-blue-500`}
/>
{error && <p className="text-red-500 text-xs mt-1">{error}</p>}
</div>
);
}
} if (propSchema.type === 'number' || propSchema.type === 'integer') {
return (
<div key={fullPath} className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
{key}
{(path ? false : jsonSchema.required?.includes(key)) && <span className="text-red-500 ml-1">*</span>}
</label>
{propSchema.description && (
<p className="text-xs text-gray-500 mb-2">{propSchema.description}</p>
)}
<input
type="number"
step={propSchema.type === 'integer' ? '1' : 'any'}
value={value || ''}
onChange={(e) => {
const val = e.target.value === '' ? '' : propSchema.type === 'integer' ? parseInt(e.target.value) : parseFloat(e.target.value);
handleInputChange(fullPath, val);
}}
className={`w-full border rounded-md px-3 py-2 ${error ? 'border-red-500' : 'border-gray-300'} focus:outline-none focus:ring-2 focus:ring-blue-500`}
/>
{error && <p className="text-red-500 text-xs mt-1">{error}</p>}
</div>
);
}
if (propSchema.type === 'boolean') {
return (
<div key={fullPath} className="mb-4">
<div className="flex items-center">
<input
type="checkbox"
checked={value || false}
onChange={(e) => handleInputChange(fullPath, e.target.checked)}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label className="ml-2 block text-sm text-gray-700">
{key}
{(path ? false : jsonSchema.required?.includes(key)) && <span className="text-red-500 ml-1">*</span>}
</label>
</div>
{propSchema.description && (
<p className="text-xs text-gray-500 mt-1">{propSchema.description}</p>
)}
{error && <p className="text-red-500 text-xs mt-1">{error}</p>}
</div>
);
} // For other types, show as text input with description
return (
<div key={fullPath} className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
{key}
{(path ? false : jsonSchema.required?.includes(key)) && <span className="text-red-500 ml-1">*</span>}
<span className="text-xs text-gray-500 ml-1">({propSchema.type})</span>
</label>
{propSchema.description && (
<p className="text-xs text-gray-500 mb-2">{propSchema.description}</p>
)}
<input
type="text"
value={value || ''}
onChange={(e) => handleInputChange(fullPath, e.target.value)}
placeholder={t('tool.enterValue', { type: propSchema.type })}
className={`w-full border rounded-md px-3 py-2 ${error ? 'border-red-500' : 'border-gray-300'} focus:outline-none focus:ring-2 focus:ring-blue-500`}
/>
{error && <p className="text-red-500 text-xs mt-1">{error}</p>}
</div>
);
};
if (!jsonSchema.properties) {
return (
<div className="p-4 bg-gray-50 rounded-md">
<p className="text-sm text-gray-600">{t('tool.noParameters')}</p>
<div className="flex justify-end space-x-2 mt-4">
<button
type="button"
onClick={onCancel}
className="px-4 py-2 text-sm text-gray-600 bg-gray-100 rounded-md hover:bg-gray-200"
>
{t('tool.cancel')}
</button>
<button
onClick={() => onSubmit({})}
disabled={loading}
className="px-4 py-2 text-sm text-white bg-blue-600 rounded-md hover:bg-blue-700 disabled:opacity-50"
>
{loading ? t('tool.running') : t('tool.runTool')}
</button>
</div>
</div>
);
}
return (
<div className="space-y-4">
{/* Mode Toggle */}
<div className="flex justify-between items-center border-b pb-3">
<h3 className="text-lg font-medium text-gray-900">{t('tool.parameters')}</h3>
<div className="flex space-x-2">
<button
type="button"
onClick={switchToFormMode}
className={`px-3 py-1 text-sm rounded-md transition-colors ${!isJsonMode
? 'bg-blue-600 text-white'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}`}
>
{t('tool.formMode')}
</button>
<button
type="button"
onClick={switchToJsonMode}
className={`px-3 py-1 text-sm rounded-md transition-colors ${isJsonMode
? 'bg-blue-600 text-white'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}`}
>
{t('tool.jsonMode')}
</button>
</div>
</div>
{/* JSON Mode */}
{isJsonMode ? (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('tool.jsonConfiguration')}
</label>
<textarea
value={jsonText}
onChange={(e) => handleJsonTextChange(e.target.value)}
placeholder={`{\n "key": "value"\n}`}
className={`w-full h-64 border rounded-md px-3 py-2 font-mono text-sm resize-y ${jsonError ? 'border-red-500' : 'border-gray-300'
} focus:outline-none focus:ring-2 focus:ring-blue-500`}
/>
{jsonError && <p className="text-red-500 text-xs mt-1">{jsonError}</p>}
</div>
<div className="flex justify-end space-x-2 pt-4">
<button
type="button"
onClick={onCancel}
className="px-4 py-2 text-sm text-gray-600 bg-gray-100 rounded-md hover:bg-gray-200"
>
{t('tool.cancel')}
</button>
<button
onClick={() => {
try {
const parsedJson = JSON.parse(jsonText);
onSubmit(parsedJson);
} catch (error) {
setJsonError(t('tool.invalidJsonFormat'));
}
}}
disabled={loading || !!jsonError}
className="px-4 py-2 text-sm text-white bg-blue-600 rounded-md hover:bg-blue-700 disabled:opacity-50"
>
{loading ? t('tool.running') : t('tool.runTool')}
</button>
</div>
</div>
) : (
/* Form Mode */
<form onSubmit={handleSubmit} className="space-y-4">
{Object.entries(jsonSchema.properties || {}).map(([key, propSchema]) =>
renderField(key, propSchema)
)}
<div className="flex justify-end space-x-2 pt-4">
<button
type="button"
onClick={onCancel}
className="px-4 py-2 text-sm text-gray-600 bg-gray-100 rounded-md hover:bg-gray-200"
>
{t('tool.cancel')}
</button>
<button
type="submit"
disabled={loading}
className="px-4 py-2 text-sm text-white bg-blue-600 rounded-md hover:bg-blue-700 disabled:opacity-50"
>
{loading ? t('tool.running') : t('tool.runTool')}
</button>
</div>
</form>
)}
</div>
);
};
export default DynamicForm;

View File

@@ -1,34 +1,252 @@
import { useState } from 'react'
import { useState, useCallback, useRef, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { Tool } from '@/types'
import { ChevronDown, ChevronRight } from '@/components/icons/LucideIcons'
import { ChevronDown, ChevronRight, Play, Loader, Edit, Check } from '@/components/icons/LucideIcons'
import { callTool, ToolCallResult, updateToolDescription } from '@/services/toolService'
import { Switch } from './ToggleGroup'
import DynamicForm from './DynamicForm'
import ToolResult from './ToolResult'
interface ToolCardProps {
server: string
tool: Tool
onToggle?: (toolName: string, enabled: boolean) => void
onDescriptionUpdate?: (toolName: string, description: string) => void
}
const ToolCard = ({ tool }: ToolCardProps) => {
const ToolCard = ({ tool, server, onToggle, onDescriptionUpdate }: ToolCardProps) => {
const { t } = useTranslation()
const [isExpanded, setIsExpanded] = useState(false)
const [showRunForm, setShowRunForm] = useState(false)
const [isRunning, setIsRunning] = useState(false)
const [result, setResult] = useState<ToolCallResult | null>(null)
const [isEditingDescription, setIsEditingDescription] = useState(false)
const [customDescription, setCustomDescription] = useState(tool.description || '')
const descriptionInputRef = useRef<HTMLInputElement>(null)
const descriptionTextRef = useRef<HTMLSpanElement>(null)
const [textWidth, setTextWidth] = useState<number>(0)
// Focus the input when editing mode is activated
useEffect(() => {
if (isEditingDescription && descriptionInputRef.current) {
descriptionInputRef.current.focus()
// Set input width to match text width
if (textWidth > 0) {
descriptionInputRef.current.style.width = `${textWidth + 20}px` // Add some padding
}
}
}, [isEditingDescription, textWidth])
// Measure text width when not editing
useEffect(() => {
if (!isEditingDescription && descriptionTextRef.current) {
setTextWidth(descriptionTextRef.current.offsetWidth)
}
}, [isEditingDescription, customDescription])
// Generate a unique key for localStorage based on tool name and server
const getStorageKey = useCallback(() => {
return `mcphub_tool_form_${server ? `${server}_` : ''}${tool.name}`
}, [tool.name, server])
// Clear form data from localStorage
const clearStoredFormData = useCallback(() => {
localStorage.removeItem(getStorageKey())
}, [getStorageKey])
const handleToggle = (enabled: boolean) => {
if (onToggle) {
onToggle(tool.name, enabled)
}
}
const handleDescriptionEdit = () => {
setIsEditingDescription(true)
}
const handleDescriptionSave = async () => {
try {
const result = await updateToolDescription(server, tool.name, customDescription)
if (result.success) {
setIsEditingDescription(false)
if (onDescriptionUpdate) {
onDescriptionUpdate(tool.name, customDescription)
}
} else {
// Revert on error
setCustomDescription(tool.description || '')
console.error('Failed to update tool description:', result.error)
}
} catch (error) {
console.error('Error updating tool description:', error)
setCustomDescription(tool.description || '')
setIsEditingDescription(false)
}
}
const handleDescriptionChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setCustomDescription(e.target.value)
}
const handleDescriptionKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handleDescriptionSave()
} else if (e.key === 'Escape') {
setCustomDescription(tool.description || '')
setIsEditingDescription(false)
}
}
const handleRunTool = async (arguments_: Record<string, any>) => {
setIsRunning(true)
try {
const result = await callTool({
toolName: tool.name,
arguments: arguments_,
}, server)
setResult(result)
// Clear form data on successful submission
// clearStoredFormData()
} catch (error) {
setResult({
success: false,
error: error instanceof Error ? error.message : 'Unknown error occurred',
})
} finally {
setIsRunning(false)
}
}
const handleCancelRun = () => {
setShowRunForm(false)
// Clear form data when cancelled
clearStoredFormData()
setResult(null)
}
const handleCloseResult = () => {
setResult(null)
}
return (
<div className="bg-white shadow rounded-lg p-4 mb-4">
<div className="bg-white border border-gray-300 shadow rounded-lg p-4 mb-4">
<div
className="flex justify-between items-center cursor-pointer"
onClick={() => setIsExpanded(!isExpanded)}
>
<h3 className="text-lg font-medium text-gray-900">{tool.name}</h3>
<button className="text-gray-400 hover:text-gray-600">
{isExpanded ? <ChevronDown size={18} /> : <ChevronRight size={18} />}
</button>
</div>
{isExpanded && (
<div className="mt-4">
<p className="text-gray-600 mb-2">{tool.description || 'No description available'}</p>
<div className="bg-gray-50 rounded p-2">
<h4 className="text-sm font-medium text-gray-900 mb-2">Input Schema:</h4>
<pre className="text-xs text-gray-600 overflow-auto">
{JSON.stringify(tool.inputSchema, null, 2)}
</pre>
<div className="flex-1">
<h3 className="text-lg font-medium text-gray-900">
{tool.name.replace(server + '-', '')}
<span className="ml-2 text-sm font-normal text-gray-600 inline-flex items-center">
{isEditingDescription ? (
<>
<input
ref={descriptionInputRef}
type="text"
className="px-2 py-1 border border-blue-300 rounded bg-white text-sm"
value={customDescription}
onChange={handleDescriptionChange}
onKeyDown={handleDescriptionKeyDown}
onClick={(e) => e.stopPropagation()}
style={{
minWidth: '100px',
width: textWidth > 0 ? `${textWidth + 20}px` : 'auto'
}}
/>
<button
className="ml-2 p-1 text-green-600 hover:text-green-800"
onClick={(e) => {
e.stopPropagation()
handleDescriptionSave()
}}
>
<Check size={16} />
</button>
</>
) : (
<>
<span ref={descriptionTextRef}>{customDescription || t('tool.noDescription')}</span>
<button
className="ml-2 p-1 text-gray-500 hover:text-blue-600 transition-colors"
onClick={(e) => {
e.stopPropagation()
handleDescriptionEdit()
}}
>
<Edit size={14} />
</button>
</>
)}
</span>
</h3>
</div>
<div className="flex items-center space-x-2">
<div
className="flex items-center space-x-2"
onClick={(e) => e.stopPropagation()}
>
<Switch
checked={tool.enabled ?? true}
onCheckedChange={handleToggle}
disabled={isRunning}
/>
</div>
<button
onClick={(e) => {
e.stopPropagation()
setIsExpanded(true) // Ensure card is expanded when showing run form
setShowRunForm(true)
}}
className="flex items-center space-x-1 px-3 py-1 text-sm text-blue-600 bg-blue-50 hover:bg-blue-100 rounded-md transition-colors"
disabled={isRunning || !tool.enabled}
>
{isRunning ? (
<Loader size={14} className="animate-spin" />
) : (
<Play size={14} />
)}
<span>{isRunning ? t('tool.running') : t('tool.run')}</span>
</button>
<button className="text-gray-400 hover:text-gray-600">
{isExpanded ? <ChevronDown size={18} /> : <ChevronRight size={18} />}
</button>
</div>
</div>
{isExpanded && (
<div className="mt-4 space-y-4">
{/* Schema Display */}
{!showRunForm && (
<div className="bg-gray-50 rounded p-3 border border-gray-300">
<h4 className="text-sm font-medium text-gray-900 mb-2">{t('tool.inputSchema')}</h4>
<pre className="text-xs text-gray-600 overflow-auto">
{JSON.stringify(tool.inputSchema, null, 2)}
</pre>
</div>
)}
{/* Run Form */}
{showRunForm && (
<div className="border border-gray-300 rounded-lg p-4 bg-blue-50">
<h4 className="text-sm font-medium text-gray-900 mb-3">{t('tool.runToolWithName', { name: tool.name.replace(server + '-', '') })}</h4>
<DynamicForm
schema={tool.inputSchema || { type: 'object' }}
onSubmit={handleRunTool}
onCancel={handleCancelRun}
loading={isRunning}
storageKey={getStorageKey()}
/>
{/* Tool Result */}
{result && (
<div className="mt-4">
<ToolResult result={result} onClose={handleCloseResult} />
</div>
)}
</div>
)}
</div>
)}
</div>

View File

@@ -0,0 +1,159 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { CheckCircle, XCircle, AlertCircle } from '@/components/icons/LucideIcons';
interface ToolResultProps {
result: {
success: boolean;
content?: Array<{
type: string;
text?: string;
[key: string]: any;
}>;
error?: string;
message?: string;
};
onClose: () => void;
}
const ToolResult: React.FC<ToolResultProps> = ({ result, onClose }) => {
const { t } = useTranslation();
// Extract content from data.content
const content = result.content;
const renderContent = (content: any): React.ReactNode => {
if (Array.isArray(content)) {
return content.map((item, index) => (
<div key={index} className="mb-3 last:mb-0">
{renderContentItem(item)}
</div>
));
}
return renderContentItem(content);
};
const renderContentItem = (item: any): React.ReactNode => {
if (typeof item === 'string') {
return (
<div className="bg-gray-50 rounded-md p-3">
<pre className="whitespace-pre-wrap text-sm text-gray-800 font-mono">{item}</pre>
</div>
);
}
if (typeof item === 'object' && item !== null) {
if (item.type === 'text' && item.text) {
return (
<div className="bg-gray-50 rounded-md p-3">
<pre className="whitespace-pre-wrap text-sm text-gray-800 font-mono">{item.text}</pre>
</div>
);
}
if (item.type === 'image' && item.data) {
return (
<div className="bg-gray-50 rounded-md p-3">
<img
src={`data:${item.mimeType || 'image/png'};base64,${item.data}`}
alt={t('tool.toolResult')}
className="max-w-full h-auto rounded-md"
/>
</div>
);
}
// For other structured content, try to parse as JSON
try {
const jsonString = typeof item === 'string' ? item : JSON.stringify(item, null, 2);
const parsed = typeof item === 'string' ? JSON.parse(item) : item;
return (
<div className="bg-gray-50 rounded-md p-3">
<div className="text-xs text-gray-500 mb-2">{t('tool.jsonResponse')}</div>
<pre className="text-sm text-gray-800 overflow-auto">{JSON.stringify(parsed, null, 2)}</pre>
</div>
);
} catch {
// If not valid JSON, show as string
return (
<div className="bg-gray-50 rounded-md p-3">
<pre className="whitespace-pre-wrap text-sm text-gray-800 font-mono">{String(item)}</pre>
</div>
);
}
}
return (
<div className="bg-gray-50 rounded-md p-3">
<pre className="whitespace-pre-wrap text-sm text-gray-800 font-mono">{String(item)}</pre>
</div>
);
};
return (
<div className="border border-gray-300 rounded-lg bg-white shadow-sm">
<div className="border-b border-gray-300 px-4 py-3 bg-gray-50 rounded-t-lg">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
{result.success ? (
<CheckCircle size={20} className="text-green-500" />
) : (
<XCircle size={20} className="text-red-500" />
)}
<div>
<h4 className="text-sm font-medium text-gray-900">
{t('tool.execution')} {result.success ? t('tool.successful') : t('tool.failed')}
</h4>
</div>
</div>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 text-sm"
>
</button>
</div>
</div>
<div className="p-4">
{result.success ? (
<div>
{result.content && result.content.length > 0 ? (
<div>
<div className="text-sm text-gray-600 mb-3">{t('tool.result')}</div>
{renderContent(result.content)}
</div>
) : (
<div className="text-sm text-gray-500 italic">
{t('tool.noContent')}
</div>
)}
</div>
) : (
<div>
<div className="flex items-center space-x-2 mb-3">
<AlertCircle size={16} className="text-red-500" />
<span className="text-sm font-medium text-red-700">{t('tool.error')}</span>
</div>
{content && content.length > 0 ? (
<div>
<div className="text-sm text-gray-600 mb-3">{t('tool.errorDetails')}</div>
{renderContent(content)}
</div>
) : (
<div className="bg-red-50 border border-red-300 rounded-md p-3">
<pre className="text-sm text-red-800 whitespace-pre-wrap">
{result.error || result.message || t('tool.unknownError')}
</pre>
</div>
)}
</div>
)}
</div>
</div>
);
};
export default ToolResult;

View File

@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { MarketServer, ApiResponse } from '@/types';
import { MarketServer, ApiResponse, ServerConfig } from '@/types';
import { getApiUrl } from '../utils/runtime';
export const useMarketData = () => {
@@ -347,7 +347,7 @@ export const useMarketData = () => {
// Install server to the local environment
const installServer = useCallback(
async (server: MarketServer) => {
async (server: MarketServer, customConfig: ServerConfig) => {
try {
const installType = server.installations?.npm
? 'npm'
@@ -362,14 +362,14 @@ export const useMarketData = () => {
const installation = server.installations[installType];
// Prepare server configuration
// Prepare server configuration, merging with customConfig
const serverConfig = {
name: server.name,
config: {
command: installation.command,
args: installation.args,
env: installation.env || {},
},
config: customConfig.type === 'stdio' ? {
command: customConfig.command || installation.command || '',
args: customConfig.args || installation.args || [],
env: { ...installation.env, ...customConfig.env },
} : customConfig
};
// Call the createServer API

View File

@@ -325,6 +325,51 @@ export const useSettingsData = () => {
}
};
// Update multiple routing configuration fields at once
const updateRoutingConfigBatch = async (updates: Partial<RoutingConfig>) => {
setLoading(true);
setError(null);
try {
const token = localStorage.getItem('mcphub_token');
const response = await fetch(getApiUrl('/system-config'), {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'x-auth-token': token || '',
},
body: JSON.stringify({
routing: updates,
}),
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
if (data.success) {
setRoutingConfig({
...routingConfig,
...updates,
});
showToast(t('settings.systemConfigUpdated'));
return true;
} else {
showToast(t('errors.failedToUpdateRouteConfig'));
return false;
}
} catch (error) {
console.error('Failed to update routing config:', error);
setError(error instanceof Error ? error.message : 'Failed to update routing config');
showToast(t('errors.failedToUpdateRouteConfig'));
return false;
} finally {
setLoading(false);
}
};
// Fetch settings when the component mounts or refreshKey changes
useEffect(() => {
fetchSettings();
@@ -353,5 +398,6 @@ export const useSettingsData = () => {
updateInstallConfig,
updateSmartRoutingConfig,
updateSmartRoutingConfigBatch,
updateRoutingConfigBatch,
};
};

View File

@@ -93,11 +93,19 @@
"command": "Command",
"arguments": "Arguments",
"envVars": "Environment Variables",
"headers": "HTTP Headers",
"key": "key",
"value": "value",
"enabled": "Enabled",
"enable": "Enable",
"disable": "Disable",
"requestOptions": "Configuration",
"timeout": "Request Timeout",
"timeoutDescription": "Timeout for requests to the MCP server (ms)",
"maxTotalTimeout": "Maximum Total Timeout",
"maxTotalTimeoutDescription": "Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications)",
"resetTimeoutOnProgress": "Reset Timeout on Progress",
"resetTimeoutOnProgressDescription": "Reset timeout on progress notifications",
"remove": "Remove",
"toggleError": "Failed to toggle server {{serverName}}",
"alreadyExists": "Server {{serverName}} already exists",
@@ -108,7 +116,33 @@
"commandPlaceholder": "Enter command",
"argumentsPlaceholder": "Enter arguments",
"errorDetails": "Error Details",
"viewErrorDetails": "View error details"
"viewErrorDetails": "View error details",
"openapi": {
"inputMode": "Input Mode",
"inputModeUrl": "Specification URL",
"inputModeSchema": "JSON Schema",
"specUrl": "OpenAPI Specification URL",
"schema": "OpenAPI JSON Schema",
"schemaHelp": "Paste your complete OpenAPI JSON schema here",
"security": "Security Type",
"securityNone": "None",
"securityApiKey": "API Key",
"securityHttp": "HTTP Authentication",
"securityOAuth2": "OAuth 2.0",
"securityOpenIdConnect": "OpenID Connect",
"apiKeyConfig": "API Key Configuration",
"apiKeyName": "Header/Parameter Name",
"apiKeyIn": "Location",
"apiKeyValue": "API Key Value",
"httpAuthConfig": "HTTP Authentication Configuration",
"httpScheme": "Authentication Scheme",
"httpCredentials": "Credentials",
"oauth2Config": "OAuth 2.0 Configuration",
"oauth2Token": "Access Token",
"openIdConnectConfig": "OpenID Connect Configuration",
"openIdConnectUrl": "Discovery URL",
"openIdConnectToken": "ID Token"
}
},
"status": {
"online": "Online",
@@ -136,6 +170,7 @@
"create": "Create",
"submitting": "Submitting...",
"delete": "Delete",
"remove": "Remove",
"copy": "Copy",
"copySuccess": "Copied to clipboard",
"copyFailed": "Copy failed",
@@ -263,6 +298,41 @@
"showing": "Showing {{from}}-{{to}} of {{total}} servers",
"perPage": "Per page"
},
"tool": {
"run": "Run",
"running": "Running...",
"runTool": "Run Tool",
"cancel": "Cancel",
"noDescription": "No description available",
"inputSchema": "Input Schema:",
"runToolWithName": "Run Tool: {{name}}",
"execution": "Tool Execution",
"successful": "Successful",
"failed": "Failed",
"result": "Result:",
"error": "Error",
"errorDetails": "Error Details:",
"noContent": "Tool executed successfully but returned no content.",
"unknownError": "Unknown error occurred",
"jsonResponse": "JSON Response:",
"toolResult": "Tool result",
"noParameters": "This tool does not require any parameters.",
"selectOption": "Select an option",
"enterValue": "Enter {{type}} value",
"enabled": "Enabled",
"enableSuccess": "Tool {{name}} enabled successfully",
"disableSuccess": "Tool {{name}} disabled successfully",
"toggleFailed": "Failed to toggle tool status",
"parameters": "Tool Parameters",
"formMode": "Form Mode",
"jsonMode": "JSON Mode",
"jsonConfiguration": "JSON Configuration",
"invalidJsonFormat": "Invalid JSON format",
"fixJsonBeforeSwitching": "Please fix JSON format before switching to form mode",
"item": "Item {{index}}",
"addItem": "Add {{key}} item",
"enterKey": "Enter {{key}}"
},
"settings": {
"enableGlobalRoute": "Enable Global Route",
"enableGlobalRouteDescription": "Allow connections to /sse endpoint without specifying a group ID",
@@ -283,7 +353,7 @@
"systemConfigUpdated": "System configuration updated successfully",
"enableSmartRouting": "Enable Smart Routing",
"enableSmartRoutingDescription": "Enable smart routing feature to search the most suitable tool based on input (using $smart group name)",
"dbUrl": "PostgreSQL URL (with pgvector support)",
"dbUrl": "PostgreSQL URL (requires pgvector support)",
"dbUrlPlaceholder": "e.g. postgresql://user:password@localhost:5432/dbname",
"openaiApiBaseUrl": "OpenAI API Base URL",
"openaiApiBaseUrlPlaceholder": "https://api.openai.com/v1",

View File

@@ -93,11 +93,19 @@
"command": "命令",
"arguments": "参数",
"envVars": "环境变量",
"headers": "HTTP 请求头",
"key": "键",
"value": "值",
"enabled": "已启用",
"enable": "启用",
"disable": "禁用",
"requestOptions": "配置",
"timeout": "请求超时",
"timeoutDescription": "请求超时时间(毫秒)",
"maxTotalTimeout": "最大总超时",
"maxTotalTimeoutDescription": "无论是否有进度通知的最大总超时时间(毫秒)",
"resetTimeoutOnProgress": "收到进度通知时重置超时",
"resetTimeoutOnProgressDescription": "适用于发送周期性进度更新的长时间运行操作",
"remove": "移除",
"toggleError": "切换服务器 {{serverName}} 状态失败",
"alreadyExists": "服务器 {{serverName}} 已经存在",
@@ -108,7 +116,33 @@
"commandPlaceholder": "请输入命令",
"argumentsPlaceholder": "请输入参数",
"errorDetails": "错误详情",
"viewErrorDetails": "查看错误详情"
"viewErrorDetails": "查看错误详情",
"openapi": {
"inputMode": "输入模式",
"inputModeUrl": "规范 URL",
"inputModeSchema": "JSON 模式",
"specUrl": "OpenAPI 规范 URL",
"schema": "OpenAPI JSON 模式",
"schemaHelp": "请在此处粘贴完整的 OpenAPI JSON 模式",
"security": "安全类型",
"securityNone": "无",
"securityApiKey": "API 密钥",
"securityHttp": "HTTP 认证",
"securityOAuth2": "OAuth 2.0",
"securityOpenIdConnect": "OpenID Connect",
"apiKeyConfig": "API 密钥配置",
"apiKeyName": "请求头/参数名称",
"apiKeyIn": "位置",
"apiKeyValue": "API 密钥值",
"httpAuthConfig": "HTTP 认证配置",
"httpScheme": "认证方案",
"httpCredentials": "凭据",
"oauth2Config": "OAuth 2.0 配置",
"oauth2Token": "访问令牌",
"openIdConnectConfig": "OpenID Connect 配置",
"openIdConnectUrl": "发现 URL",
"openIdConnectToken": "ID 令牌"
}
},
"status": {
"online": "在线",
@@ -137,6 +171,7 @@
"create": "创建",
"submitting": "提交中...",
"delete": "删除",
"remove": "移除",
"copy": "复制",
"copySuccess": "已复制到剪贴板",
"copyFailed": "复制失败",
@@ -264,6 +299,41 @@
"showing": "显示 {{from}}-{{to}}/{{total}} 个服务器",
"perPage": "每页显示"
},
"tool": {
"run": "运行",
"running": "运行中...",
"runTool": "运行工具",
"cancel": "取消",
"noDescription": "无描述信息",
"inputSchema": "输入模式:",
"runToolWithName": "运行工具:{{name}}",
"execution": "工具执行",
"successful": "成功",
"failed": "失败",
"result": "结果:",
"error": "错误",
"errorDetails": "错误详情:",
"noContent": "工具执行成功但未返回内容。",
"unknownError": "发生未知错误",
"jsonResponse": "JSON 响应:",
"toolResult": "工具结果",
"noParameters": "此工具不需要任何参数。",
"selectOption": "选择一个选项",
"enterValue": "输入{{type}}值",
"enabled": "已启用",
"enableSuccess": "工具 {{name}} 启用成功",
"disableSuccess": "工具 {{name}} 禁用成功",
"toggleFailed": "切换工具状态失败",
"parameters": "工具参数",
"formMode": "表单模式",
"jsonMode": "JSON 模式",
"jsonConfiguration": "JSON 配置",
"invalidJsonFormat": "无效的 JSON 格式",
"fixJsonBeforeSwitching": "请修复 JSON 格式后再切换到表单模式",
"item": "项目 {{index}}",
"addItem": "添加 {{key}} 项目",
"enterKey": "输入 {{key}}"
},
"settings": {
"enableGlobalRoute": "启用全局路由",
"enableGlobalRouteDescription": "允许不指定组 ID 就连接到 /sse 端点",
@@ -284,7 +354,7 @@
"systemConfigUpdated": "系统配置更新成功",
"enableSmartRouting": "启用智能路由",
"enableSmartRoutingDescription": "开启智能路由功能,根据输入自动搜索最合适的工具(使用 $smart 分组)",
"dbUrl": "PostgreSQL 连接地址(支持 pgvector",
"dbUrl": "PostgreSQL 连接地址(必须支持 pgvector",
"dbUrlPlaceholder": "例如: postgresql://user:password@localhost:5432/dbname",
"openaiApiBaseUrl": "OpenAI API 基础地址",
"openaiApiBaseUrlPlaceholder": "https://api.openai.com/v1",

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate, useParams, useLocation } from 'react-router-dom';
import { MarketServer } from '@/types';
import { MarketServer, ServerConfig } from '@/types';
import { useMarketData } from '@/hooks/useMarketData';
import { useToast } from '@/contexts/ToastContext';
import MarketServerCard from '@/components/MarketServerCard';
@@ -90,10 +90,11 @@ const MarketPage: React.FC = () => {
navigate('/market');
};
const handleInstall = async (server: MarketServer) => {
const handleInstall = async (server: MarketServer, config: ServerConfig) => {
try {
setInstalling(true);
const success = await installServer(server);
// Pass the server object and the config to the installServer function
const success = await installServer(server, config);
if (success) {
// Show success message using toast instead of alert
showToast(t('market.installSuccess', { serverName: server.display_name }), 'success');
@@ -353,4 +354,4 @@ const MarketPage: React.FC = () => {
);
};
export default MarketPage;
export default MarketPage;

View File

@@ -125,6 +125,7 @@ const ServersPage: React.FC = () => {
onRemove={handleServerRemove}
onEdit={handleEditClick}
onToggle={handleServerToggle}
onRefresh={triggerRefresh}
/>
))}
</div>

View File

@@ -46,6 +46,7 @@ const SettingsPage: React.FC = () => {
smartRoutingConfig,
loading,
updateRoutingConfig,
updateRoutingConfigBatch,
updateInstallConfig,
updateSmartRoutingConfig,
updateSmartRoutingConfigBatch
@@ -85,16 +86,30 @@ const SettingsPage: React.FC = () => {
};
const handleRoutingConfigChange = async (key: 'enableGlobalRoute' | 'enableGroupNameRoute' | 'enableBearerAuth' | 'bearerAuthKey', value: boolean | string) => {
await updateRoutingConfig(key, value);
// If enableBearerAuth is turned on and there's no key, generate one
// If enableBearerAuth is turned on and there's no key, generate one first
if (key === 'enableBearerAuth' && value === true) {
if (!tempRoutingConfig.bearerAuthKey) {
if (!tempRoutingConfig.bearerAuthKey && !routingConfig.bearerAuthKey) {
const newKey = generateRandomKey();
handleBearerAuthKeyChange(newKey);
await updateRoutingConfig('bearerAuthKey', newKey);
// Update both enableBearerAuth and bearerAuthKey in a single call
const success = await updateRoutingConfigBatch({
enableBearerAuth: true,
bearerAuthKey: newKey
});
if (success) {
// Update tempRoutingConfig to reflect the saved values
setTempRoutingConfig(prev => ({
...prev,
bearerAuthKey: newKey
}));
}
return;
}
}
await updateRoutingConfig(key, value);
};
const handleBearerAuthKeyChange = (value: string) => {

View File

@@ -0,0 +1,159 @@
import { getApiUrl } from '../utils/runtime';
import { getToken } from './authService';
export interface ToolCallRequest {
toolName: string;
arguments?: Record<string, any>;
}
export interface ToolCallResult {
success: boolean;
content?: Array<{
type: string;
text?: string;
[key: string]: any;
}>;
error?: string;
message?: string;
}
/**
* Call a MCP tool via the call_tool API
*/
export const callTool = async (
request: ToolCallRequest,
server?: string,
): Promise<ToolCallResult> => {
try {
const token = getToken();
if (!token) {
throw new Error('Authentication token not found. Please log in.');
}
// Construct the URL with optional server parameter
const url = server ? `/tools/call/${server}` : '/tools/call';
const response = await fetch(getApiUrl(url), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-auth-token': token,
Authorization: `Bearer ${token}`, // Add bearer auth for MCP routing
},
body: JSON.stringify({
toolName: request.toolName,
arguments: request.arguments,
}),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (!data.success) {
return {
success: false,
error: data.message || 'Tool call failed',
};
}
return {
success: true,
content: data.data.content || [],
};
} catch (error) {
console.error('Error calling tool:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error occurred',
};
}
};
/**
* Toggle a tool's enabled state for a specific server
*/
export const toggleTool = async (
serverName: string,
toolName: string,
enabled: boolean,
): Promise<{ success: boolean; error?: string }> => {
try {
const token = getToken();
if (!token) {
throw new Error('Authentication token not found. Please log in.');
}
const response = await fetch(getApiUrl(`/servers/${serverName}/tools/${toolName}/toggle`), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-auth-token': token,
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ enabled }),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return {
success: data.success,
error: data.success ? undefined : data.message,
};
} catch (error) {
console.error('Error toggling tool:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error occurred',
};
}
};
/**
* Update a tool's description for a specific server
*/
export const updateToolDescription = async (
serverName: string,
toolName: string,
description: string,
): Promise<{ success: boolean; error?: string }> => {
try {
const token = getToken();
if (!token) {
throw new Error('Authentication token not found. Please log in.');
}
const response = await fetch(
getApiUrl(`/servers/${serverName}/tools/${toolName}/description`),
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'x-auth-token': token,
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ description }),
},
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return {
success: data.success,
error: data.success ? undefined : data.message,
};
} catch (error) {
console.error('Error updating tool description:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error occurred',
};
}
};

View File

@@ -67,16 +67,63 @@ export interface Tool {
name: string;
description: string;
inputSchema: ToolInputSchema;
enabled?: boolean;
}
// Server config types
export interface ServerConfig {
type?: 'stdio' | 'sse' | 'streamable-http';
type?: 'stdio' | 'sse' | 'streamable-http' | 'openapi';
url?: string;
command?: string;
args?: string[];
env?: Record<string, string>;
headers?: Record<string, string>;
enabled?: boolean;
tools?: Record<string, { enabled: boolean; description?: string }>; // Tool-specific configurations with enable/disable state and custom descriptions
options?: {
timeout?: number; // Request timeout in milliseconds
resetTimeoutOnProgress?: boolean; // Reset timeout on progress notifications
maxTotalTimeout?: number; // Maximum total timeout in milliseconds
}; // MCP request options configuration
// OpenAPI specific configuration
openapi?: {
url?: string; // OpenAPI specification URL
schema?: Record<string, any>; // Complete OpenAPI JSON schema
version?: string; // OpenAPI version (default: '3.1.0')
security?: OpenAPISecurityConfig; // Security configuration for API calls
};
}
// OpenAPI Security Configuration
export interface OpenAPISecurityConfig {
type: 'none' | 'apiKey' | 'http' | 'oauth2' | 'openIdConnect';
// API Key authentication
apiKey?: {
name: string; // Header/query/cookie name
in: 'header' | 'query' | 'cookie';
value: string; // The API key value
};
// HTTP authentication (Basic, Bearer, etc.)
http?: {
scheme: 'basic' | 'bearer' | 'digest'; // HTTP auth scheme
bearerFormat?: string; // Bearer token format (e.g., JWT)
credentials?: string; // Base64 encoded credentials for basic auth or bearer token
};
// OAuth2 (simplified - mainly for bearer tokens)
oauth2?: {
tokenUrl?: string; // Token endpoint for client credentials flow
clientId?: string;
clientSecret?: string;
scopes?: string[]; // Required scopes
token?: string; // Pre-obtained access token
};
// OpenID Connect
openIdConnect?: {
url: string; // OpenID Connect discovery URL
clientId?: string;
clientSecret?: string;
token?: string; // Pre-obtained ID token
};
}
// Server types
@@ -110,8 +157,39 @@ export interface ServerFormData {
command: string;
arguments: string;
args?: string[]; // Added explicit args field
type?: 'stdio' | 'sse' | 'streamable-http'; // Added type field
type?: 'stdio' | 'sse' | 'streamable-http' | 'openapi'; // Added type field with openapi support
env: EnvVar[];
headers: EnvVar[];
options?: {
timeout?: number;
resetTimeoutOnProgress?: boolean;
maxTotalTimeout?: number;
};
// OpenAPI specific fields
openapi?: {
url?: string;
schema?: string; // JSON schema as string for form input
inputMode?: 'url' | 'schema'; // Mode to determine input type
version?: string;
securityType?: 'none' | 'apiKey' | 'http' | 'oauth2' | 'openIdConnect';
// API Key fields
apiKeyName?: string;
apiKeyIn?: 'header' | 'query' | 'cookie';
apiKeyValue?: string;
// HTTP auth fields
httpScheme?: 'basic' | 'bearer' | 'digest';
httpCredentials?: string;
// OAuth2 fields
oauth2TokenUrl?: string;
oauth2ClientId?: string;
oauth2ClientSecret?: string;
oauth2Token?: string;
// OpenID Connect fields
openIdConnectUrl?: string;
openIdConnectClientId?: string;
openIdConnectClientSecret?: string;
openIdConnectToken?: string;
};
}
// Group form data types

View File

@@ -41,8 +41,10 @@
"author": "",
"license": "ISC",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.11.1",
"@apidevtools/swagger-parser": "^11.0.1",
"@modelcontextprotocol/sdk": "^1.12.1",
"@types/pg": "^8.15.2",
"axios": "^1.10.0",
"bcryptjs": "^3.0.2",
"dotenv": "^16.3.1",
"dotenv-expand": "^12.0.2",
@@ -50,6 +52,7 @@
"express-validator": "^7.2.1",
"jsonwebtoken": "^9.0.2",
"openai": "^4.103.0",
"openapi-types": "^12.1.3",
"pg": "^8.16.0",
"pgvector": "^0.2.1",
"postgres": "^3.4.7",
@@ -77,7 +80,7 @@
"autoprefixer": "^10.4.21",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"concurrently": "^8.2.2",
"concurrently": "^9.1.2",
"eslint": "^8.50.0",
"i18next": "^24.2.3",
"i18next-browser-languagedetector": "^8.0.4",

1222
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

343
src/clients/openapi.ts Normal file
View File

@@ -0,0 +1,343 @@
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
import SwaggerParser from '@apidevtools/swagger-parser';
import { OpenAPIV3 } from 'openapi-types';
import { ServerConfig, OpenAPISecurityConfig } from '../types/index.js';
export interface OpenAPIToolInfo {
name: string;
description: string;
inputSchema: Record<string, unknown>;
operationId: string;
method: string;
path: string;
parameters?: OpenAPIV3.ParameterObject[];
requestBody?: OpenAPIV3.RequestBodyObject;
responses?: OpenAPIV3.ResponsesObject;
}
export class OpenAPIClient {
private httpClient: AxiosInstance;
private spec: OpenAPIV3.Document | null = null;
private tools: OpenAPIToolInfo[] = [];
private baseUrl: string;
private securityConfig?: OpenAPISecurityConfig;
constructor(private config: ServerConfig) {
if (!config.openapi?.url && !config.openapi?.schema) {
throw new Error('OpenAPI URL or schema is required');
}
// 初始 baseUrl将在 initialize() 中从 OpenAPI servers 字段更新
this.baseUrl = config.openapi?.url ? this.extractBaseUrl(config.openapi.url) : '';
this.securityConfig = config.openapi.security;
this.httpClient = axios.create({
baseURL: this.baseUrl,
timeout: config.options?.timeout || 30000,
headers: {
'Content-Type': 'application/json',
...config.headers,
},
});
this.setupSecurity();
}
private extractBaseUrl(specUrl: string): string {
try {
const url = new URL(specUrl);
return `${url.protocol}//${url.host}`;
} catch {
// If specUrl is a relative path, assume current host
return '';
}
}
private setupSecurity(): void {
if (!this.securityConfig || this.securityConfig.type === 'none') {
return;
}
switch (this.securityConfig.type) {
case 'apiKey':
if (this.securityConfig.apiKey) {
const { name, in: location, value } = this.securityConfig.apiKey;
if (location === 'header') {
this.httpClient.defaults.headers.common[name] = value;
} else if (location === 'query') {
this.httpClient.interceptors.request.use((config: any) => {
config.params = { ...config.params, [name]: value };
return config;
});
}
// Note: Cookie authentication would need additional setup
}
break;
case 'http':
if (this.securityConfig.http) {
const { scheme, credentials } = this.securityConfig.http;
if (scheme === 'bearer' && credentials) {
this.httpClient.defaults.headers.common['Authorization'] = `Bearer ${credentials}`;
} else if (scheme === 'basic' && credentials) {
this.httpClient.defaults.headers.common['Authorization'] = `Basic ${credentials}`;
}
}
break;
case 'oauth2':
if (this.securityConfig.oauth2?.token) {
this.httpClient.defaults.headers.common['Authorization'] =
`Bearer ${this.securityConfig.oauth2.token}`;
}
break;
case 'openIdConnect':
if (this.securityConfig.openIdConnect?.token) {
this.httpClient.defaults.headers.common['Authorization'] =
`Bearer ${this.securityConfig.openIdConnect.token}`;
}
break;
}
}
async initialize(): Promise<void> {
try {
// Parse and dereference the OpenAPI specification
if (this.config.openapi?.url) {
this.spec = (await SwaggerParser.dereference(
this.config.openapi.url,
)) as OpenAPIV3.Document;
} else if (this.config.openapi?.schema) {
// For schema object, we need to pass it as a cloned object
this.spec = (await SwaggerParser.dereference(
JSON.parse(JSON.stringify(this.config.openapi.schema)),
)) as OpenAPIV3.Document;
} else {
throw new Error('Either OpenAPI URL or schema must be provided');
}
// 从 OpenAPI servers 字段更新 baseUrl
this.updateBaseUrlFromServers();
this.extractTools();
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to load OpenAPI specification: ${errorMessage}`);
}
}
private updateBaseUrlFromServers(): void {
if (!this.spec?.servers || this.spec.servers.length === 0) {
return;
}
// 获取第一个 server 的 URL
const serverUrl = this.spec.servers[0].url;
// 如果是相对路径,需要与原始 spec URL 结合
if (serverUrl.startsWith('/')) {
// 相对路径,使用原始 spec URL 的协议和主机
if (this.config.openapi?.url) {
const originalUrl = new URL(this.config.openapi.url);
this.baseUrl = `${originalUrl.protocol}//${originalUrl.host}${serverUrl}`;
}
} else if (serverUrl.startsWith('http://') || serverUrl.startsWith('https://')) {
// 绝对路径
this.baseUrl = serverUrl;
} else {
// 相对路径但不以 / 开头,可能是相对于当前路径
if (this.config.openapi?.url) {
const originalUrl = new URL(this.config.openapi.url);
this.baseUrl = `${originalUrl.protocol}//${originalUrl.host}/${serverUrl}`;
}
}
// 更新 HTTP 客户端的 baseURL
this.httpClient.defaults.baseURL = this.baseUrl;
}
private extractTools(): void {
if (!this.spec?.paths) {
return;
}
this.tools = [];
for (const [path, pathItem] of Object.entries(this.spec.paths)) {
if (!pathItem) continue;
const methods = [
'get',
'post',
'put',
'delete',
'patch',
'head',
'options',
'trace',
] as const;
for (const method of methods) {
const operation = pathItem[method] as OpenAPIV3.OperationObject | undefined;
if (!operation || !operation.operationId) continue;
const tool: OpenAPIToolInfo = {
name: operation.operationId,
description:
operation.summary || operation.description || `${method.toUpperCase()} ${path}`,
inputSchema: this.generateInputSchema(operation, path, method as string),
operationId: operation.operationId,
method: method as string,
path,
parameters: operation.parameters as OpenAPIV3.ParameterObject[],
requestBody: operation.requestBody as OpenAPIV3.RequestBodyObject,
responses: operation.responses,
};
this.tools.push(tool);
}
}
}
private generateInputSchema(
operation: OpenAPIV3.OperationObject,
_path: string,
_method: string,
): Record<string, unknown> {
const schema: Record<string, unknown> = {
type: 'object',
properties: {},
required: [],
};
const properties = schema.properties as Record<string, unknown>;
const required = schema.required as string[];
// Handle path parameters
const pathParams = operation.parameters?.filter(
(p: any) => 'in' in p && p.in === 'path',
) as OpenAPIV3.ParameterObject[];
if (pathParams?.length) {
for (const param of pathParams) {
properties[param.name] = {
type: 'string',
description: param.description || `Path parameter: ${param.name}`,
};
if (param.required) {
required.push(param.name);
}
}
}
// Handle query parameters
const queryParams = operation.parameters?.filter(
(p: any) => 'in' in p && p.in === 'query',
) as OpenAPIV3.ParameterObject[];
if (queryParams?.length) {
for (const param of queryParams) {
properties[param.name] = param.schema || {
type: 'string',
description: param.description || `Query parameter: ${param.name}`,
};
if (param.required) {
required.push(param.name);
}
}
}
// Handle request body
if (operation.requestBody && 'content' in operation.requestBody) {
const requestBody = operation.requestBody as OpenAPIV3.RequestBodyObject;
const jsonContent = requestBody.content?.['application/json'];
if (jsonContent?.schema) {
properties['body'] = jsonContent.schema;
if (requestBody.required) {
required.push('body');
}
}
}
return schema;
}
async callTool(toolName: string, args: Record<string, unknown>): Promise<unknown> {
const tool = this.tools.find((t) => t.name === toolName);
if (!tool) {
throw new Error(`Tool '${toolName}' not found`);
}
try {
// Build the request URL with path parameters
let url = tool.path;
const pathParams = tool.parameters?.filter((p) => p.in === 'path') || [];
for (const param of pathParams) {
const value = args[param.name];
if (value !== undefined) {
url = url.replace(`{${param.name}}`, String(value));
}
}
// Build query parameters
const queryParams: Record<string, unknown> = {};
const queryParamDefs = tool.parameters?.filter((p) => p.in === 'query') || [];
for (const param of queryParamDefs) {
const value = args[param.name];
if (value !== undefined) {
queryParams[param.name] = value;
}
}
// Prepare request configuration
const requestConfig: AxiosRequestConfig = {
method: tool.method as any,
url,
params: queryParams,
};
// Add request body if applicable
if (args.body && ['post', 'put', 'patch'].includes(tool.method)) {
requestConfig.data = args.body;
}
// Add headers if any header parameters are defined
const headerParams = tool.parameters?.filter((p) => p.in === 'header') || [];
if (headerParams.length > 0) {
requestConfig.headers = {};
for (const param of headerParams) {
const value = args[param.name];
if (value !== undefined) {
requestConfig.headers[param.name] = String(value);
}
}
}
const response = await this.httpClient.request(requestConfig);
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(
`API call failed: ${error.response?.status} ${error.response?.statusText} - ${JSON.stringify(error.response?.data)}`,
);
}
throw error;
}
}
getTools(): OpenAPIToolInfo[] {
return this.tools;
}
getSpec(): OpenAPIV3.Document | null {
return this.spec;
}
disconnect(): void {
// No persistent connection to close for OpenAPI
}
}

View File

@@ -15,18 +15,37 @@ const defaultConfig = {
mcpHubVersion: getPackageVersion(),
};
// Settings cache
let settingsCache: McpSettings | null = null;
export const getSettingsPath = (): string => {
return getConfigFilePath('mcp_settings.json', 'Settings');
};
export const loadSettings = (): McpSettings => {
// If cache exists, return cached data directly
if (settingsCache) {
return settingsCache;
}
const settingsPath = getSettingsPath();
try {
const settingsData = fs.readFileSync(settingsPath, 'utf8');
return JSON.parse(settingsData);
const settings = JSON.parse(settingsData);
// Update cache
settingsCache = settings;
console.log(`Loaded settings from ${settingsPath}`);
return settings;
} catch (error) {
console.error(`Failed to load settings from ${settingsPath}:`, error);
return { mcpServers: {}, users: [] };
const defaultSettings = { mcpServers: {}, users: [] };
// Cache default settings
settingsCache = defaultSettings;
return defaultSettings;
}
};
@@ -34,6 +53,10 @@ export const saveSettings = (settings: McpSettings): boolean => {
const settingsPath = getSettingsPath();
try {
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
// Update cache after successful save
settingsCache = settings;
return true;
} catch (error) {
console.error(`Failed to save settings to ${settingsPath}:`, error);
@@ -41,8 +64,43 @@ export const saveSettings = (settings: McpSettings): boolean => {
}
};
/**
* Clear settings cache, force next loadSettings call to re-read from file
*/
export const clearSettingsCache = (): void => {
settingsCache = null;
};
/**
* Get current cache status (for debugging)
*/
export const getSettingsCacheInfo = (): { hasCache: boolean } => {
return {
hasCache: settingsCache !== null,
};
};
export const replaceEnvVars = (env: Record<string, any>): Record<string, any> => {
const res: Record<string, string> = {};
for (const [key, value] of Object.entries(env)) {
if (typeof value === 'string') {
res[key] = expandEnvVars(value);
} else {
res[key] = String(value);
}
}
return res;
};
export const expandEnvVars = (value: string): string => {
return value.replace(/\$\{([^}]+)\}/g, (_, key) => process.env[key] || '');
if (typeof value !== 'string') {
return String(value);
}
// Replace ${VAR} format
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;
};
export default defaultConfig;

View File

@@ -6,6 +6,7 @@ import {
removeServer,
updateMcpServer,
notifyToolChanged,
syncToolEmbedding,
toggleServerStatus,
} from '../services/mcpService.js';
import { loadSettings, saveSettings } from '../config/index.js';
@@ -62,19 +63,25 @@ export const createServer = async (req: Request, res: Response): Promise<void> =
return;
}
if (!config.url && (!config.command || !config.args)) {
if (
!config.url &&
!config.openapi?.url &&
!config.openapi?.schema &&
(!config.command || !config.args)
) {
res.status(400).json({
success: false,
message: 'Server configuration must include either a URL or command with arguments',
message:
'Server configuration must include either a URL, OpenAPI specification URL or schema, or command with arguments',
});
return;
}
// Validate the server type if specified
if (config.type && !['stdio', 'sse', 'streamable-http'].includes(config.type)) {
if (config.type && !['stdio', 'sse', 'streamable-http', 'openapi'].includes(config.type)) {
res.status(400).json({
success: false,
message: 'Server type must be one of: stdio, sse, streamable-http',
message: 'Server type must be one of: stdio, sse, streamable-http, openapi',
});
return;
}
@@ -88,6 +95,38 @@ export const createServer = async (req: Request, res: Response): Promise<void> =
return;
}
// Validate that OpenAPI specification URL or schema is provided for openapi type
if (config.type === 'openapi' && !config.openapi?.url && !config.openapi?.schema) {
res.status(400).json({
success: false,
message: 'OpenAPI specification URL or schema is required for openapi server type',
});
return;
}
// Validate headers if provided
if (config.headers && typeof config.headers !== 'object') {
res.status(400).json({
success: false,
message: 'Headers must be an object',
});
return;
}
// Validate that headers are only used with sse, streamable-http, and openapi types
if (config.headers && config.type === 'stdio') {
res.status(400).json({
success: false,
message: 'Headers are not supported for stdio server type',
});
return;
}
// Set default keep-alive interval for SSE servers if not specified
if ((config.type === 'sse' || (!config.type && config.url)) && !config.keepAliveInterval) {
config.keepAliveInterval = 60000; // Default 60 seconds for SSE servers
}
const result = await addServer(name, config);
if (result.success) {
notifyToolChanged();
@@ -161,19 +200,25 @@ export const updateServer = async (req: Request, res: Response): Promise<void> =
return;
}
if (!config.url && (!config.command || !config.args)) {
if (
!config.url &&
!config.openapi?.url &&
!config.openapi?.schema &&
(!config.command || !config.args)
) {
res.status(400).json({
success: false,
message: 'Server configuration must include either a URL or command with arguments',
message:
'Server configuration must include either a URL, OpenAPI specification URL or schema, or command with arguments',
});
return;
}
// Validate the server type if specified
if (config.type && !['stdio', 'sse', 'streamable-http'].includes(config.type)) {
if (config.type && !['stdio', 'sse', 'streamable-http', 'openapi'].includes(config.type)) {
res.status(400).json({
success: false,
message: 'Server type must be one of: stdio, sse, streamable-http',
message: 'Server type must be one of: stdio, sse, streamable-http, openapi',
});
return;
}
@@ -187,6 +232,38 @@ export const updateServer = async (req: Request, res: Response): Promise<void> =
return;
}
// Validate that OpenAPI specification URL or schema is provided for openapi type
if (config.type === 'openapi' && !config.openapi?.url && !config.openapi?.schema) {
res.status(400).json({
success: false,
message: 'OpenAPI specification URL or schema is required for openapi server type',
});
return;
}
// Validate headers if provided
if (config.headers && typeof config.headers !== 'object') {
res.status(400).json({
success: false,
message: 'Headers must be an object',
});
return;
}
// Validate that headers are only used with sse, streamable-http, and openapi types
if (config.headers && config.type === 'stdio') {
res.status(400).json({
success: false,
message: 'Headers are not supported for stdio server type',
});
return;
}
// Set default keep-alive interval for SSE servers if not specified
if ((config.type === 'sse' || (!config.type && config.url)) && !config.keepAliveInterval) {
config.keepAliveInterval = 60000; // Default 60 seconds for SSE servers
}
const result = await updateMcpServer(name, config);
if (result.success) {
notifyToolChanged();
@@ -282,6 +359,136 @@ export const toggleServer = async (req: Request, res: Response): Promise<void> =
}
};
// Toggle tool status for a specific server
export const toggleTool = async (req: Request, res: Response): Promise<void> => {
try {
const { serverName, toolName } = req.params;
const { enabled } = req.body;
if (!serverName || !toolName) {
res.status(400).json({
success: false,
message: 'Server name and tool name are required',
});
return;
}
if (typeof enabled !== 'boolean') {
res.status(400).json({
success: false,
message: 'Enabled status must be a boolean',
});
return;
}
const settings = loadSettings();
if (!settings.mcpServers[serverName]) {
res.status(404).json({
success: false,
message: 'Server not found',
});
return;
}
// Initialize tools config if it doesn't exist
if (!settings.mcpServers[serverName].tools) {
settings.mcpServers[serverName].tools = {};
}
// Set the tool's enabled state
settings.mcpServers[serverName].tools![toolName] = { enabled };
if (!saveSettings(settings)) {
res.status(500).json({
success: false,
message: 'Failed to save settings',
});
return;
}
// Notify that tools have changed
notifyToolChanged();
res.json({
success: true,
message: `Tool ${toolName} ${enabled ? 'enabled' : 'disabled'} successfully`,
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Internal server error',
});
}
};
// Update tool description for a specific server
export const updateToolDescription = async (req: Request, res: Response): Promise<void> => {
try {
const { serverName, toolName } = req.params;
const { description } = req.body;
if (!serverName || !toolName) {
res.status(400).json({
success: false,
message: 'Server name and tool name are required',
});
return;
}
if (typeof description !== 'string') {
res.status(400).json({
success: false,
message: 'Description must be a string',
});
return;
}
const settings = loadSettings();
if (!settings.mcpServers[serverName]) {
res.status(404).json({
success: false,
message: 'Server not found',
});
return;
}
// Initialize tools config if it doesn't exist
if (!settings.mcpServers[serverName].tools) {
settings.mcpServers[serverName].tools = {};
}
// Set the tool's description
if (!settings.mcpServers[serverName].tools![toolName]) {
settings.mcpServers[serverName].tools![toolName] = { enabled: true };
}
settings.mcpServers[serverName].tools![toolName].description = description;
if (!saveSettings(settings)) {
res.status(500).json({
success: false,
message: 'Failed to save settings',
});
return;
}
// Notify that tools have changed
notifyToolChanged();
syncToolEmbedding(serverName, toolName);
res.json({
success: true,
message: `Tool ${toolName} description updated successfully`,
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Internal server error',
});
}
};
export const updateSystemConfig = (req: Request, res: Response): void => {
try {
const { routing, install, smartRouting } = req.body;

View File

@@ -0,0 +1,86 @@
import { Request, Response } from 'express';
import { ApiResponse } from '../types/index.js';
import { handleCallToolRequest } from '../services/mcpService.js';
/**
* Interface for tool call request
*/
export interface ToolCallRequest {
toolName: string;
arguments?: Record<string, any>;
}
/**
* Interface for tool search request
*/
export interface ToolSearchRequest {
query: string;
limit?: number;
}
/**
* Interface for tool call result
*/
interface ToolCallResult {
content?: Array<{
type: string;
text?: string;
[key: string]: any;
}>;
isError?: boolean;
[key: string]: any;
}
/**
* Call a specific tool with given arguments
*/
export const callTool = async (req: Request, res: Response): Promise<void> => {
try {
const { server } = req.params;
const { toolName, arguments: toolArgs = {} } = req.body as ToolCallRequest;
if (!toolName) {
res.status(400).json({
success: false,
message: 'toolName is required',
});
return;
}
// Create a mock request structure for handleCallToolRequest
const mockRequest = {
params: {
name: 'call_tool',
arguments: {
toolName,
arguments: toolArgs,
},
},
};
const extra = {
sessionId: req.headers['x-session-id'] || 'api-session',
server: server || undefined,
};
const result = (await handleCallToolRequest(mockRequest, extra)) as ToolCallResult;
const response: ApiResponse = {
success: true,
data: {
content: result.content || [],
toolName,
arguments: toolArgs,
},
};
res.json(response);
} catch (error) {
console.error('Error calling tool:', error);
res.status(500).json({
success: false,
message: 'Failed to call tool',
error: error instanceof Error ? error.message : 'Unknown error occurred',
});
}
};

View File

@@ -1,33 +1,32 @@
import 'reflect-metadata'; // Ensure reflect-metadata is imported here too
import { DataSource, DataSourceOptions } from 'typeorm';
import dotenv from 'dotenv';
import dotenvExpand from 'dotenv-expand';
import path from 'path';
import { fileURLToPath } from 'url';
import entities from './entities/index.js';
import { registerPostgresVectorType } from './types/postgresVectorType.js';
import { VectorEmbeddingSubscriber } from './subscribers/VectorEmbeddingSubscriber.js';
import { loadSettings } from '../config/index.js';
import { getSmartRoutingConfig } from '../utils/smartRouting.js';
// Helper function to create required PostgreSQL extensions
const createRequiredExtensions = async (dataSource: DataSource): Promise<void> => {
try {
await dataSource.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";');
console.log('UUID extension created or already exists.');
} catch (err: any) {
console.warn('Failed to create uuid-ossp extension:', err.message);
console.warn('UUID generation functionality may not be available.');
}
try {
await dataSource.query('CREATE EXTENSION IF NOT EXISTS vector;');
console.log('Vector extension created or already exists.');
} catch (err: any) {
console.warn('Failed to create vector extension:', err.message);
console.warn('Vector functionality may not be available.');
}
};
// Get database URL from smart routing config or fallback to environment variable
const getDatabaseUrl = (): string => {
try {
const settings = loadSettings();
const smartRouting = settings.systemConfig?.smartRouting;
// Use smart routing dbUrl if smart routing is enabled and dbUrl is configured
if (smartRouting?.enabled && smartRouting?.dbUrl) {
console.log('Using smart routing database URL');
return smartRouting.dbUrl;
}
} catch (error) {
console.warn(
'Failed to load settings for smart routing database URL, falling back to environment variable:',
error,
);
}
return '';
return getSmartRoutingConfig().dbUrl;
};
// Default database configuration
@@ -40,7 +39,10 @@ const defaultConfig: DataSourceOptions = {
};
// AppDataSource is the TypeORM data source
let AppDataSource = new DataSource(defaultConfig);
let appDataSource = new DataSource(defaultConfig);
// Global promise to track initialization status
let initializationPromise: Promise<DataSource> | null = null;
// Function to create a new DataSource with updated configuration
export const updateDataSourceConfig = (): DataSource => {
@@ -50,31 +52,36 @@ export const updateDataSourceConfig = (): DataSource => {
};
// If the configuration has changed, we need to create a new DataSource
const currentUrl = (AppDataSource.options as any).url;
const currentUrl = (appDataSource.options as any).url;
if (currentUrl !== newConfig.url) {
console.log('Database URL configuration changed, updating DataSource...');
AppDataSource = new DataSource(newConfig);
appDataSource = new DataSource(newConfig);
// Reset initialization promise when configuration changes
initializationPromise = null;
}
return AppDataSource;
return appDataSource;
};
// Get the current AppDataSource instance
export const getAppDataSource = (): DataSource => {
return AppDataSource;
return appDataSource;
};
// Reconnect database with updated configuration
export const reconnectDatabase = async (): Promise<DataSource> => {
try {
// Close existing connection if it exists
if (AppDataSource.isInitialized) {
if (appDataSource.isInitialized) {
console.log('Closing existing database connection...');
await AppDataSource.destroy();
await appDataSource.destroy();
}
// Reset initialization promise to allow fresh initialization
initializationPromise = null;
// Update configuration and reconnect
AppDataSource = updateDataSourceConfig();
appDataSource = updateDataSourceConfig();
return await initializeDatabase();
} catch (error) {
console.error('Error during database reconnection:', error);
@@ -82,32 +89,54 @@ export const reconnectDatabase = async (): Promise<DataSource> => {
}
};
// Initialize database connection
// Initialize database connection with concurrency control
export const initializeDatabase = async (): Promise<DataSource> => {
// If initialization is already in progress, wait for it to complete
if (initializationPromise) {
console.log('Database initialization already in progress, waiting for completion...');
return initializationPromise;
}
// If already initialized, return the existing instance
if (appDataSource.isInitialized) {
console.log('Database already initialized, returning existing instance');
return Promise.resolve(appDataSource);
}
// Create a new initialization promise
initializationPromise = performDatabaseInitialization();
try {
const result = await initializationPromise;
console.log('Database initialization completed successfully');
return result;
} catch (error) {
// Reset the promise on error so initialization can be retried
initializationPromise = null;
console.error('Database initialization failed:', error);
throw error;
}
};
// Internal function to perform the actual database initialization
const performDatabaseInitialization = async (): Promise<DataSource> => {
try {
// Update configuration before initializing
AppDataSource = updateDataSourceConfig();
appDataSource = updateDataSourceConfig();
if (!AppDataSource.isInitialized) {
if (!appDataSource.isInitialized) {
console.log('Initializing database connection...');
await AppDataSource.initialize();
// Register the vector type with TypeORM
registerPostgresVectorType(AppDataSource);
await appDataSource.initialize();
registerPostgresVectorType(appDataSource);
// Create pgvector extension if it doesn't exist
await AppDataSource.query('CREATE EXTENSION IF NOT EXISTS vector;').catch((err) => {
console.warn('Failed to create vector extension:', err.message);
console.warn('Vector functionality may not be available.');
});
// Create required PostgreSQL extensions
await createRequiredExtensions(appDataSource);
// Set up vector column and index with a more direct approach
try {
// First, create the extension
await AppDataSource.query(`CREATE EXTENSION IF NOT EXISTS vector;`);
// Check if table exists first
const tableExists = await AppDataSource.query(`
const tableExists = await appDataSource.query(`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
@@ -121,7 +150,7 @@ export const initializeDatabase = async (): Promise<DataSource> => {
// Step 1: Drop any existing index on the column
try {
await AppDataSource.query(`DROP INDEX IF EXISTS idx_vector_embeddings_embedding;`);
await appDataSource.query(`DROP INDEX IF EXISTS idx_vector_embeddings_embedding;`);
} catch (dropError: any) {
console.warn('Note: Could not drop existing index:', dropError.message);
}
@@ -129,14 +158,14 @@ export const initializeDatabase = async (): Promise<DataSource> => {
// Step 2: Alter column type to vector (if it's not already)
try {
// Check column type first
const columnType = await AppDataSource.query(`
const columnType = await appDataSource.query(`
SELECT data_type FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'vector_embeddings'
AND column_name = 'embedding';
`);
if (columnType.length > 0 && columnType[0].data_type !== 'vector') {
await AppDataSource.query(`
await appDataSource.query(`
ALTER TABLE vector_embeddings
ALTER COLUMN embedding TYPE vector USING embedding::vector;
`);
@@ -150,7 +179,7 @@ export const initializeDatabase = async (): Promise<DataSource> => {
// Step 3: Try to create appropriate indices
try {
// First, let's check if there are any records to determine the dimensions
const records = await AppDataSource.query(`
const records = await appDataSource.query(`
SELECT dimensions FROM vector_embeddings LIMIT 1;
`);
@@ -164,13 +193,13 @@ export const initializeDatabase = async (): Promise<DataSource> => {
// Set the vector dimensions explicitly only if table has data
if (records && records.length > 0) {
await AppDataSource.query(`
await appDataSource.query(`
ALTER TABLE vector_embeddings
ALTER COLUMN embedding TYPE vector(${dimensions});
`);
// Now try to create the index
await AppDataSource.query(`
await appDataSource.query(`
CREATE INDEX IF NOT EXISTS idx_vector_embeddings_embedding
ON vector_embeddings USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
`);
@@ -186,7 +215,7 @@ export const initializeDatabase = async (): Promise<DataSource> => {
try {
// Try HNSW index instead
await AppDataSource.query(`
await appDataSource.query(`
CREATE INDEX IF NOT EXISTS idx_vector_embeddings_embedding
ON vector_embeddings USING hnsw (embedding vector_cosine_ops);
`);
@@ -197,7 +226,7 @@ export const initializeDatabase = async (): Promise<DataSource> => {
try {
// Create a basic GIN index as last resort
await AppDataSource.query(`
await appDataSource.query(`
CREATE INDEX IF NOT EXISTS idx_vector_embeddings_embedding
ON vector_embeddings USING gin (embedding);
`);
@@ -222,12 +251,11 @@ export const initializeDatabase = async (): Promise<DataSource> => {
// Run one final setup check after schema synchronization is done
if (defaultConfig.synchronize) {
setTimeout(async () => {
try {
console.log('Running final vector configuration check...');
try {
console.log('Running final vector configuration check...');
// Try setup again with the same code from above
const tableExists = await AppDataSource.query(`
// Try setup again with the same code from above
const tableExists = await appDataSource.query(`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
@@ -235,64 +263,60 @@ export const initializeDatabase = async (): Promise<DataSource> => {
);
`);
if (tableExists[0].exists) {
console.log('Vector embeddings table found, checking configuration...');
if (tableExists[0].exists) {
console.log('Vector embeddings table found, checking configuration...');
// Get the dimension size first
try {
// Try to get dimensions from an existing record
const records = await AppDataSource.query(`
// Get the dimension size first
try {
// Try to get dimensions from an existing record
const records = await appDataSource.query(`
SELECT dimensions FROM vector_embeddings LIMIT 1;
`);
// Only proceed if we have existing data, otherwise let vector service handle it
if (records && records.length > 0 && records[0].dimensions) {
const dimensions = records[0].dimensions;
console.log(`Found vector dimension from database: ${dimensions}`);
// Only proceed if we have existing data, otherwise let vector service handle it
if (records && records.length > 0 && records[0].dimensions) {
const dimensions = records[0].dimensions;
console.log(`Found vector dimension from database: ${dimensions}`);
// Ensure column type is vector with explicit dimensions
await AppDataSource.query(`
// Ensure column type is vector with explicit dimensions
await appDataSource.query(`
ALTER TABLE vector_embeddings
ALTER COLUMN embedding TYPE vector(${dimensions});
`);
console.log('Vector embedding column type updated in final check.');
console.log('Vector embedding column type updated in final check.');
// One more attempt at creating the index with dimensions
try {
// Drop existing index if any
await AppDataSource.query(`
// One more attempt at creating the index with dimensions
try {
// Drop existing index if any
await appDataSource.query(`
DROP INDEX IF EXISTS idx_vector_embeddings_embedding;
`);
// Create new index with proper dimensions
await AppDataSource.query(`
// Create new index with proper dimensions
await appDataSource.query(`
CREATE INDEX idx_vector_embeddings_embedding
ON vector_embeddings USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
`);
console.log('Created IVFFlat index in final check.');
} catch (indexError: any) {
console.warn(
'Final index creation attempt did not succeed:',
indexError.message,
);
console.warn('Using basic lookup without vector index.');
}
} else {
console.log(
'No existing vector data found, vector dimensions will be configured by vector service.',
);
console.log('Created IVFFlat index in final check.');
} catch (indexError: any) {
console.warn('Final index creation attempt did not succeed:', indexError.message);
console.warn('Using basic lookup without vector index.');
}
} catch (setupError: any) {
console.warn('Vector setup in final check failed:', setupError.message);
} else {
console.log(
'No existing vector data found, vector dimensions will be configured by vector service.',
);
}
} catch (setupError: any) {
console.warn('Vector setup in final check failed:', setupError.message);
}
} catch (error: any) {
console.warn('Post-initialization vector setup failed:', error.message);
}
}, 3000); // Give synchronize some time to complete
} catch (error: any) {
console.warn('Post-initialization vector setup failed:', error.message);
}
}
}
return AppDataSource;
return appDataSource;
} catch (error) {
console.error('Error during database initialization:', error);
throw error;
@@ -301,18 +325,18 @@ export const initializeDatabase = async (): Promise<DataSource> => {
// Get database connection status
export const isDatabaseConnected = (): boolean => {
return AppDataSource.isInitialized;
return appDataSource.isInitialized;
};
// Close database connection
export const closeDatabase = async (): Promise<void> => {
if (AppDataSource.isInitialized) {
await AppDataSource.destroy();
if (appDataSource.isInitialized) {
await appDataSource.destroy();
console.log('Database connection closed.');
}
};
// Export AppDataSource for backward compatibility
export { AppDataSource };
export const AppDataSource = appDataSource;
export default getAppDataSource;

View File

@@ -1,37 +1,8 @@
import express, { Request, Response, NextFunction } from 'express';
import path from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import fs from 'fs';
import { auth } from './auth.js';
import { initializeDefaultUser } from '../models/User.js';
import config from '../config/index.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 = (
err: Error,
_req: Request,
@@ -52,6 +23,7 @@ export const initMiddlewares = (app: express.Application): void => {
app.use((req, res, next) => {
const basePath = config.basePath;
// Only apply JSON parsing for API and auth routes, not for SSE or message endpoints
// TODO exclude sse responses by mcp endpoint
if (
req.path !== `${basePath}/sse` &&
!req.path.startsWith(`${basePath}/sse/`) &&

View File

@@ -8,6 +8,8 @@ import {
updateServer,
deleteServer,
toggleServer,
toggleTool,
updateToolDescription,
updateSystemConfig,
} from '../controllers/serverController.js';
import {
@@ -33,6 +35,7 @@ import {
import { login, register, getCurrentUser, changePassword } from '../controllers/authController.js';
import { getAllLogs, clearLogs, streamLogs } from '../controllers/logController.js';
import { getRuntimeConfig } from '../controllers/configController.js';
import { callTool } from '../controllers/toolController.js';
import { auth } from '../middlewares/auth.js';
const router = express.Router();
@@ -45,6 +48,8 @@ export const initRoutes = (app: express.Application): void => {
router.put('/servers/:name', updateServer);
router.delete('/servers/:name', deleteServer);
router.post('/servers/:name/toggle', toggleServer);
router.post('/servers/:serverName/tools/:toolName/toggle', toggleTool);
router.put('/servers/:serverName/tools/:toolName/description', updateToolDescription);
router.put('/system-config', updateSystemConfig);
// Group management routes
@@ -59,6 +64,9 @@ export const initRoutes = (app: express.Application): void => {
// New route for batch updating servers in a group
router.put('/groups/:id/servers/batch', updateGroupServersBatch);
// Tool management routes
router.post('/tools/call/:server', callTool);
// Market routes
router.get('/market/servers', getAllMarketServers);
router.get('/market/servers/search', searchMarketServersByQuery);

View File

@@ -4,15 +4,48 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import { ServerInfo, ServerConfig } from '../types/index.js';
import { loadSettings, saveSettings, expandEnvVars } from '../config/index.js';
import { ServerInfo, ServerConfig, ToolInfo } from '../types/index.js';
import { loadSettings, saveSettings, expandEnvVars, replaceEnvVars } from '../config/index.js';
import config from '../config/index.js';
import { getGroup } from './sseService.js';
import { getServersInGroup } from './groupService.js';
import { saveToolsAsVectorEmbeddings, searchToolsByVector } from './vectorSearchService.js';
import { OpenAPIClient } from '../clients/openapi.js';
const servers: { [sessionId: string]: Server } = {};
// Helper function to set up keep-alive ping for SSE connections
const setupKeepAlive = (serverInfo: ServerInfo, serverConfig: ServerConfig): void => {
// Only set up keep-alive for SSE connections
if (!(serverInfo.transport instanceof SSEClientTransport)) {
return;
}
// Clear any existing interval first
if (serverInfo.keepAliveIntervalId) {
clearInterval(serverInfo.keepAliveIntervalId);
}
// Use configured interval or default to 60 seconds for SSE
const interval = serverConfig.keepAliveInterval || 60000;
serverInfo.keepAliveIntervalId = setInterval(async () => {
try {
if (serverInfo.client && serverInfo.status === 'connected') {
await serverInfo.client.ping();
console.log(`Keep-alive ping successful for server: ${serverInfo.name}`);
}
} catch (error) {
console.warn(`Keep-alive ping failed for server ${serverInfo.name}:`, error);
// TODO Consider handling reconnection logic here if needed
}
}, interval);
console.log(
`Keep-alive ping set up for server ${serverInfo.name} with interval ${interval / 1000} seconds`,
);
};
export const initUpstreamServers = async (): Promise<void> => {
await registerAllTools(true);
};
@@ -50,11 +83,26 @@ export const notifyToolChanged = async () => {
});
};
export const syncToolEmbedding = async (serverName: string, toolName: string) => {
const serverInfo = getServerByName(serverName);
if (!serverInfo) {
console.warn(`Server not found: ${serverName}`);
return;
}
const tool = serverInfo.tools.find((t) => t.name === toolName);
if (!tool) {
console.warn(`Tool not found: ${toolName} on server: ${serverName}`);
return;
}
// Save tool as vector embedding for search
saveToolsAsVectorEmbeddings(serverName, [tool]);
};
// Store all server information
let serverInfos: ServerInfo[] = [];
// Initialize MCP server clients
export const initializeClientsFromSettings = (isInit: boolean): ServerInfo[] => {
export const initializeClientsFromSettings = async (isInit: boolean): Promise<ServerInfo[]> => {
const settings = loadSettings();
const existingServerInfos = serverInfos;
serverInfos = [];
@@ -88,14 +136,110 @@ export const initializeClientsFromSettings = (isInit: boolean): ServerInfo[] =>
}
let transport;
if (conf.type === 'streamable-http') {
transport = new StreamableHTTPClientTransport(new URL(conf.url || ''));
let openApiClient;
if (conf.type === 'openapi') {
// Handle OpenAPI type servers
if (!conf.openapi?.url && !conf.openapi?.schema) {
console.warn(
`Skipping OpenAPI server '${name}': missing OpenAPI specification URL or schema`,
);
serverInfos.push({
name,
status: 'disconnected',
error: 'Missing OpenAPI specification URL or schema',
tools: [],
createTime: Date.now(),
});
continue;
}
try {
// Create OpenAPI client instance
openApiClient = new OpenAPIClient(conf);
// Add server with connecting status first
const serverInfo: ServerInfo = {
name,
status: 'connecting',
error: null,
tools: [],
createTime: Date.now(),
enabled: conf.enabled === undefined ? true : conf.enabled,
};
serverInfos.push(serverInfo);
console.log(`Initializing OpenAPI server: ${name}...`);
// Perform async initialization
await openApiClient.initialize();
// Convert OpenAPI tools to MCP tool format
const openApiTools = openApiClient.getTools();
const mcpTools: ToolInfo[] = openApiTools.map((tool) => ({
name: `${name}-${tool.name}`,
description: tool.description,
inputSchema: tool.inputSchema,
}));
// Update server info with successful initialization
serverInfo.status = 'connected';
serverInfo.tools = mcpTools;
serverInfo.openApiClient = openApiClient;
console.log(
`Successfully initialized OpenAPI server: ${name} with ${mcpTools.length} tools`,
);
// Save tools as vector embeddings for search
saveToolsAsVectorEmbeddings(name, mcpTools);
continue;
} catch (error) {
console.error(`Failed to initialize OpenAPI server ${name}:`, error);
// Find and update the server info if it was already added
const existingServerIndex = serverInfos.findIndex((s) => s.name === name);
if (existingServerIndex !== -1) {
serverInfos[existingServerIndex].status = 'disconnected';
serverInfos[existingServerIndex].error = `Failed to initialize OpenAPI server: ${error}`;
} else {
// Add new server info with error status
serverInfos.push({
name,
status: 'disconnected',
error: `Failed to initialize OpenAPI server: ${error}`,
tools: [],
createTime: Date.now(),
});
}
continue;
}
} else if (conf.type === 'streamable-http') {
const options: any = {};
if (conf.headers && Object.keys(conf.headers).length > 0) {
options.requestInit = {
headers: conf.headers,
};
}
transport = new StreamableHTTPClientTransport(new URL(conf.url || ''), options);
} else if (conf.url) {
// Default to SSE only when 'conf.type' is not specified and 'conf.url' is available
transport = new SSEClientTransport(new URL(conf.url));
const options: any = {};
if (conf.headers && Object.keys(conf.headers).length > 0) {
options.eventSourceInit = {
headers: conf.headers,
};
options.requestInit = {
headers: conf.headers,
};
}
transport = new SSEClientTransport(new URL(conf.url), options);
} else if (conf.command && conf.args) {
// If type is stdio or if command and args are provided without type
const env: Record<string, string> = conf.env || {};
const env: Record<string, string> = {
...(process.env as Record<string, string>), // Inherit all environment variables from parent process
...replaceEnvVars(conf.env || {}), // Override with configured env vars
};
env['PATH'] = expandEnvVars(process.env.PATH as string) || '';
// Add UV_DEFAULT_INDEX from settings if available (for Python packages)
@@ -153,14 +297,27 @@ export const initializeClientsFromSettings = (isInit: boolean): ServerInfo[] =>
},
},
);
const timeout = isInit ? Number(config.initTimeout) : Number(config.timeout);
const initRequestOptions = isInit
? {
timeout: Number(config.initTimeout) || 60000,
}
: undefined;
// Get request options from server configuration, with fallbacks
const serverRequestOptions = conf.options || {};
const requestOptions = {
timeout: serverRequestOptions.timeout || 60000,
resetTimeoutOnProgress: serverRequestOptions.resetTimeoutOnProgress || false,
maxTotalTimeout: serverRequestOptions.maxTotalTimeout,
};
client
.connect(transport, { timeout: timeout })
.connect(transport, initRequestOptions || requestOptions)
.then(() => {
console.log(`Successfully connected client for server: ${name}`);
client
.listTools({}, { timeout: timeout })
.listTools({}, initRequestOptions || requestOptions)
.then((tools) => {
console.log(`Successfully listed ${tools.tools.length} tools for server: ${name}`);
const serverInfo = getServerByName(name);
@@ -170,28 +327,18 @@ export const initializeClientsFromSettings = (isInit: boolean): ServerInfo[] =>
}
serverInfo.tools = tools.tools.map((tool) => ({
name: tool.name,
name: `${name}-${tool.name}`,
description: tool.description || '',
inputSchema: tool.inputSchema || {},
}));
serverInfo.status = 'connected';
serverInfo.error = null;
// Save tools as vector embeddings for search (only when smart routing is enabled)
if (serverInfo.tools.length > 0) {
try {
const settings = loadSettings();
const smartRoutingEnabled = settings.systemConfig?.smartRouting?.enabled || false;
if (smartRoutingEnabled) {
console.log(
`Smart routing enabled - saving vector embeddings for server ${name}`,
);
saveToolsAsVectorEmbeddings(name, serverInfo.tools);
}
} catch (vectorError) {
console.warn(`Failed to save vector embeddings for server ${name}:`, vectorError);
}
}
// Set up keep-alive ping for SSE connections
setupKeepAlive(serverInfo, conf);
// Save tools as vector embeddings for search
saveToolsAsVectorEmbeddings(name, serverInfo.tools);
})
.catch((error) => {
console.error(
@@ -221,6 +368,7 @@ export const initializeClientsFromSettings = (isInit: boolean): ServerInfo[] =>
tools: [],
client,
transport,
options: requestOptions,
createTime: Date.now(),
});
console.log(`Initialized client for server: ${name}`);
@@ -231,7 +379,7 @@ export const initializeClientsFromSettings = (isInit: boolean): ServerInfo[] =>
// Register all MCP tools
export const registerAllTools = async (isInit: boolean): Promise<void> => {
initializeClientsFromSettings(isInit);
await initializeClientsFromSettings(isInit);
};
// Get all server information
@@ -240,11 +388,22 @@ export const getServersInfo = (): Omit<ServerInfo, 'client' | 'transport'>[] =>
const infos = serverInfos.map(({ name, status, tools, createTime, error }) => {
const serverConfig = settings.mcpServers[name];
const enabled = serverConfig ? serverConfig.enabled !== false : true;
// Add enabled status and custom description to each tool
const toolsWithEnabled = tools.map((tool) => {
const toolConfig = serverConfig?.tools?.[tool.name];
return {
...tool,
description: toolConfig?.description || tool.description, // Use custom description if available
enabled: toolConfig?.enabled !== false, // Default to true if not explicitly disabled
};
});
return {
name,
status,
error,
tools,
tools: toolsWithEnabled,
createTime,
enabled,
};
@@ -261,6 +420,23 @@ const getServerByName = (name: string): ServerInfo | undefined => {
return serverInfos.find((serverInfo) => serverInfo.name === name);
};
// Filter tools by server configuration
const filterToolsByConfig = (serverName: string, tools: ToolInfo[]): ToolInfo[] => {
const settings = loadSettings();
const serverConfig = settings.mcpServers[serverName];
if (!serverConfig || !serverConfig.tools) {
// If no tool configuration exists, all tools are enabled by default
return tools;
}
return tools.filter((tool) => {
const toolConfig = serverConfig.tools?.[tool.name];
// If tool is not in config, it's enabled by default
return toolConfig?.enabled !== false;
});
};
// Get server by tool name
const getServerByTool = (toolName: string): ServerInfo | undefined => {
return serverInfos.find((serverInfo) => serverInfo.tools.some((tool) => tool.name === toolName));
@@ -341,6 +517,13 @@ export const updateMcpServer = async (
function closeServer(name: string) {
const serverInfo = serverInfos.find((serverInfo) => serverInfo.name === name);
if (serverInfo && serverInfo.client && serverInfo.transport) {
// Clear keep-alive interval if exists
if (serverInfo.keepAliveIntervalId) {
clearInterval(serverInfo.keepAliveIntervalId);
serverInfo.keepAliveIntervalId = undefined;
console.log(`Cleared keep-alive interval for server: ${serverInfo.name}`);
}
serverInfo.client.close();
serverInfo.transport.close();
console.log(`Closed client and transport for server: ${serverInfo.name}`);
@@ -388,7 +571,7 @@ export const toggleServerStatus = async (
}
};
const handleListToolsRequest = async (_: any, extra: any) => {
export const handleListToolsRequest = async (_: any, extra: any) => {
const sessionId = extra.sessionId || '';
const group = getGroup(sessionId);
console.log(`Handling ListToolsRequest for group: ${group}`);
@@ -471,7 +654,21 @@ Available servers: ${serversList}`;
const allTools = [];
for (const serverInfo of allServerInfos) {
if (serverInfo.tools && serverInfo.tools.length > 0) {
allTools.push(...serverInfo.tools);
// Filter tools based on server configuration and apply custom descriptions
const enabledTools = filterToolsByConfig(serverInfo.name, serverInfo.tools);
// Apply custom descriptions from configuration
const settings = loadSettings();
const serverConfig = settings.mcpServers[serverInfo.name];
const toolsWithCustomDescriptions = enabledTools.map((tool) => {
const toolConfig = serverConfig?.tools?.[tool.name];
return {
...tool,
description: toolConfig?.description || tool.description, // Use custom description if available
};
});
allTools.push(...toolsWithCustomDescriptions);
}
}
@@ -480,8 +677,8 @@ Available servers: ${serversList}`;
};
};
const handleCallToolRequest = async (request: any, extra: any) => {
console.log(`Handling CallToolRequest for tool: ${request.params.name}`);
export const handleCallToolRequest = async (request: any, extra: any) => {
console.log(`Handling CallToolRequest for tool: ${JSON.stringify(request.params)}`);
try {
// Special handling for agent group tools
if (request.params.name === 'search_tools') {
@@ -512,30 +709,54 @@ const handleCallToolRequest = async (request: any, extra: any) => {
const searchResults = await searchToolsByVector(query, limitNum, thresholdNum, servers);
console.log(`Search results: ${JSON.stringify(searchResults)}`);
// Find actual tool information from serverInfos by serverName and toolName
const tools = searchResults.map((result) => {
// Find the server in serverInfos
const server = serverInfos.find(
(serverInfo) =>
serverInfo.name === result.serverName &&
serverInfo.status === 'connected' &&
serverInfo.enabled !== false,
);
if (server && server.tools && server.tools.length > 0) {
// Find the tool in server.tools
const actualTool = server.tools.find((tool) => tool.name === result.toolName);
if (actualTool) {
// Return the actual tool info from serverInfos
return actualTool;
}
}
const tools = searchResults
.map((result) => {
// Find the server in serverInfos
const server = serverInfos.find(
(serverInfo) =>
serverInfo.name === result.serverName &&
serverInfo.status === 'connected' &&
serverInfo.enabled !== false,
);
if (server && server.tools && server.tools.length > 0) {
// Find the tool in server.tools
const actualTool = server.tools.find((tool) => tool.name === result.toolName);
if (actualTool) {
// Check if the tool is enabled in configuration
const enabledTools = filterToolsByConfig(server.name, [actualTool]);
if (enabledTools.length > 0) {
// Apply custom description from configuration
const settings = loadSettings();
const serverConfig = settings.mcpServers[server.name];
const toolConfig = serverConfig?.tools?.[actualTool.name];
// Fallback to search result if server or tool not found
return {
name: result.toolName,
description: result.description || '',
inputSchema: result.inputSchema || {},
};
});
// Return the actual tool info from serverInfos with custom description
return {
...actualTool,
description: toolConfig?.description || actualTool.description,
};
}
}
}
// Fallback to search result if server or tool not found or disabled
return {
name: result.toolName,
description: result.description || '',
inputSchema: result.inputSchema || {},
};
})
.filter((tool) => {
// Additional filter to remove tools that are disabled
if (tool.name) {
const serverName = searchResults.find((r) => r.toolName === tool.name)?.serverName;
if (serverName) {
const enabledTools = filterToolsByConfig(serverName, [tool as ToolInfo]);
return enabledTools.length > 0;
}
}
return true; // Keep fallback results
});
// Add usage guidance to the response
const response = {
@@ -568,23 +789,24 @@ const handleCallToolRequest = async (request: any, extra: any) => {
// Special handling for call_tool
if (request.params.name === 'call_tool') {
const { toolName, arguments: toolArgs = {} } = request.params.arguments || {};
let { toolName } = request.params.arguments || {};
if (!toolName) {
throw new Error('toolName parameter is required');
}
// arguments parameter is now optional
const { arguments: toolArgs = {} } = request.params.arguments || {};
let targetServerInfo: ServerInfo | undefined;
// Find the first server that has this tool
targetServerInfo = serverInfos.find(
(serverInfo) =>
serverInfo.status === 'connected' &&
serverInfo.enabled !== false &&
serverInfo.tools.some((tool) => tool.name === toolName),
);
if (extra && extra.server) {
targetServerInfo = getServerByName(extra.server);
} else {
// Find the first server that has this tool
targetServerInfo = serverInfos.find(
(serverInfo) =>
serverInfo.status === 'connected' &&
serverInfo.enabled !== false &&
serverInfo.tools.some((tool) => tool.name === toolName),
);
}
if (!targetServerInfo) {
throw new Error(`No available servers found with tool: ${toolName}`);
@@ -596,7 +818,38 @@ const handleCallToolRequest = async (request: any, extra: any) => {
throw new Error(`Tool '${toolName}' not found on server '${targetServerInfo.name}'`);
}
// Call the tool on the target server
// Handle OpenAPI servers differently
if (targetServerInfo.openApiClient) {
// For OpenAPI servers, use the OpenAPI client
const openApiClient = targetServerInfo.openApiClient;
// Use toolArgs if it has properties, otherwise fallback to request.params.arguments
const finalArgs =
toolArgs && Object.keys(toolArgs).length > 0 ? toolArgs : request.params.arguments || {};
console.log(
`Invoking OpenAPI tool '${toolName}' on server '${targetServerInfo.name}' with arguments: ${JSON.stringify(finalArgs)}`,
);
// Remove server prefix from tool name if present
const cleanToolName = toolName.startsWith(`${targetServerInfo.name}-`)
? toolName.replace(`${targetServerInfo.name}-`, '')
: toolName;
const result = await openApiClient.callTool(cleanToolName, finalArgs);
console.log(`OpenAPI tool invocation result: ${JSON.stringify(result)}`);
return {
content: [
{
type: 'text',
text: JSON.stringify(result),
},
],
};
}
// Call the tool on the target server (MCP servers)
const client = targetServerInfo.client;
if (!client) {
throw new Error(`Client not found for server: ${targetServerInfo.name}`);
@@ -610,10 +863,17 @@ const handleCallToolRequest = async (request: any, extra: any) => {
`Invoking tool '${toolName}' on server '${targetServerInfo.name}' with arguments: ${JSON.stringify(finalArgs)}`,
);
const result = await client.callTool({
name: toolName,
arguments: finalArgs,
});
toolName = toolName.startsWith(`${targetServerInfo.name}-`)
? toolName.replace(`${targetServerInfo.name}-`, '')
: toolName;
const result = await client.callTool(
{
name: toolName,
arguments: finalArgs,
},
undefined,
targetServerInfo.options || {},
);
console.log(`Tool invocation result: ${JSON.stringify(result)}`);
return result;
@@ -624,11 +884,44 @@ const handleCallToolRequest = async (request: any, extra: any) => {
if (!serverInfo) {
throw new Error(`Server not found: ${request.params.name}`);
}
// Handle OpenAPI servers differently
if (serverInfo.openApiClient) {
// For OpenAPI servers, use the OpenAPI client
const openApiClient = serverInfo.openApiClient;
// Remove server prefix from tool name if present
const cleanToolName = request.params.name.startsWith(`${serverInfo.name}-`)
? request.params.name.replace(`${serverInfo.name}-`, '')
: request.params.name;
console.log(
`Invoking OpenAPI tool '${cleanToolName}' on server '${serverInfo.name}' with arguments: ${JSON.stringify(request.params.arguments)}`,
);
const result = await openApiClient.callTool(cleanToolName, request.params.arguments || {});
console.log(`OpenAPI tool invocation result: ${JSON.stringify(result)}`);
return {
content: [
{
type: 'text',
text: JSON.stringify(result),
},
],
};
}
// Handle MCP servers
const client = serverInfo.client;
if (!client) {
throw new Error(`Client not found for server: ${request.params.name}`);
throw new Error(`Client not found for server: ${serverInfo.name}`);
}
const result = await client.callTool(request.params);
request.params.name = request.params.name.startsWith(`${serverInfo.name}-`)
? request.params.name.replace(`${serverInfo.name}-`, '')
: request.params.name;
const result = await client.callTool(request.params, undefined, serverInfo.options || {});
console.log(`Tool call result: ${JSON.stringify(result)}`);
return result;
} catch (error) {

View File

@@ -6,6 +6,7 @@ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
import { deleteMcpServer, getMcpServer } from './mcpService.js';
import { loadSettings } from '../config/index.js';
import config from '../config/index.js';
const transports: { [sessionId: string]: { transport: Transport; group: string } } = {};
@@ -58,7 +59,7 @@ export const handleSseConnection = async (req: Request, res: Response): Promise<
return;
}
const transport = new SSEServerTransport('/messages', res);
const transport = new SSEServerTransport(`${config.basePath}/messages`, res);
transports[transport.sessionId] = { transport, group: group };
res.on('close', () => {
@@ -93,7 +94,7 @@ export const handleSseMessage = async (req: Request, res: Response): Promise<voi
const transportData = transports[sessionId];
if (!transportData) {
console.warn(`No transport found for sessionId: ${sessionId}`);
res.status(400).send('No transport found for sessionId');
res.status(404).send('No transport found for sessionId');
return;
}
@@ -108,7 +109,10 @@ export const handleSseMessage = async (req: Request, res: Response): Promise<voi
export const handleMcpPostRequest = async (req: Request, res: Response): Promise<void> => {
const sessionId = req.headers['mcp-session-id'] as string | undefined;
const group = req.params.group;
console.log(`Handling MCP post request for sessionId: ${sessionId} and group: ${group}`);
const body = req.body;
console.log(
`Handling MCP post request for sessionId: ${sessionId} and group: ${group} with body: ${JSON.stringify(body)}`,
);
// Check bearer auth
if (!validateBearerAuth(req)) {
res.status(401).send('Bearer authentication required or invalid token');

View File

@@ -2,45 +2,17 @@ import { getRepositoryFactory } from '../db/index.js';
import { VectorEmbeddingRepository } from '../db/repositories/index.js';
import { ToolInfo } from '../types/index.js';
import { getAppDataSource, initializeDatabase } from '../db/connection.js';
import { loadSettings } from '../config/index.js';
import { getSmartRoutingConfig } from '../utils/smartRouting.js';
import OpenAI from 'openai';
// Get OpenAI configuration from smartRouting settings or fallback to environment variables
const getOpenAIConfig = () => {
try {
const settings = loadSettings();
const smartRouting = settings.systemConfig?.smartRouting;
return {
apiKey: smartRouting?.openaiApiKey || process.env.OPENAI_API_KEY,
baseURL:
smartRouting?.openaiApiBaseUrl ||
process.env.OPENAI_API_BASE_URL ||
'https://api.openai.com/v1',
embeddingModel:
smartRouting?.openaiApiEmbeddingModel ||
process.env.OPENAI_API_EMBEDDING_MODEL ||
'text-embedding-3-small',
};
} catch (error) {
console.warn(
'Failed to load smartRouting settings, falling back to environment variables:',
error,
);
return {
apiKey: '',
baseURL: 'https://api.openai.com/v1',
embeddingModel: 'text-embedding-3-small',
};
}
};
// Environment variables for embedding configuration
const EMBEDDING_ENV = {
// The embedding model to use - default to OpenAI but allow BAAI/BGE models
MODEL: process.env.EMBEDDING_MODEL || getOpenAIConfig().embeddingModel,
// Detect if using a BGE model from the environment variable
IS_BGE_MODEL: !!(process.env.EMBEDDING_MODEL && process.env.EMBEDDING_MODEL.includes('bge')),
const smartRoutingConfig = getSmartRoutingConfig();
return {
apiKey: smartRoutingConfig.openaiApiKey,
baseURL: smartRoutingConfig.openaiApiBaseUrl,
embeddingModel: smartRoutingConfig.openaiApiEmbeddingModel,
};
};
// Constants for embedding models
@@ -221,6 +193,16 @@ export const saveToolsAsVectorEmbeddings = async (
tools: ToolInfo[],
): Promise<void> => {
try {
if (tools.length === 0) {
console.warn(`No tools to save for server: ${serverName}`);
return;
}
const smartRoutingConfig = getSmartRoutingConfig();
if (!smartRoutingConfig.enabled) {
return;
}
const config = getOpenAIConfig();
const vectorRepository = getRepositoryFactory(
'vectorEmbeddings',

View File

@@ -2,6 +2,8 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import { RequestOptions } from '@modelcontextprotocol/sdk/shared/protocol.js';
import { SmartRoutingConfig } from '../utils/smartRouting.js';
// User interface
export interface IUser {
@@ -90,25 +92,62 @@ export interface McpSettings {
pythonIndexUrl?: string; // Python package repository URL (UV_DEFAULT_INDEX)
npmRegistry?: string; // NPM registry URL (npm_config_registry)
};
smartRouting?: {
enabled?: boolean; // Controls whether smart routing is enabled
dbUrl?: string; // Database URL for smart routing
openaiApiBaseUrl?: string; // OpenAI API base URL
openaiApiKey?: string; // OpenAI API key
openaiApiEmbeddingModel?: string; // OpenAI API embedding model
};
smartRouting?: SmartRoutingConfig;
// Add other system configuration sections here in the future
};
}
// Configuration details for an individual server
export interface ServerConfig {
type?: 'stdio' | 'sse' | 'streamable-http'; // Type of server
type?: 'stdio' | 'sse' | 'streamable-http' | 'openapi'; // Type of server
url?: string; // URL for SSE or streamable HTTP servers
command?: string; // Command to execute for stdio-based servers
args?: string[]; // Arguments for the command
env?: Record<string, string>; // Environment variables
headers?: Record<string, string>; // HTTP headers for SSE/streamable-http/openapi servers
enabled?: boolean; // Flag to enable/disable the server
keepAliveInterval?: number; // Keep-alive ping interval in milliseconds (default: 60000ms for SSE servers)
tools?: Record<string, { enabled: boolean; description?: string }>; // Tool-specific configurations with enable/disable state and custom descriptions
options?: Partial<Pick<RequestOptions, 'timeout' | 'resetTimeoutOnProgress' | 'maxTotalTimeout'>>; // MCP request options configuration
// OpenAPI specific configuration
openapi?: {
url?: string; // OpenAPI specification URL
schema?: Record<string, any>; // Complete OpenAPI JSON schema
version?: string; // OpenAPI version (default: '3.1.0')
security?: OpenAPISecurityConfig; // Security configuration for API calls
};
}
// OpenAPI Security Configuration
export interface OpenAPISecurityConfig {
type: 'none' | 'apiKey' | 'http' | 'oauth2' | 'openIdConnect';
// API Key authentication
apiKey?: {
name: string; // Header/query/cookie name
in: 'header' | 'query' | 'cookie';
value: string; // The API key value
};
// HTTP authentication (Basic, Bearer, etc.)
http?: {
scheme: 'basic' | 'bearer' | 'digest'; // HTTP auth scheme
bearerFormat?: string; // Bearer token format (e.g., JWT)
credentials?: string; // Base64 encoded credentials for basic auth or bearer token
};
// OAuth2 (simplified - mainly for bearer tokens)
oauth2?: {
tokenUrl?: string; // Token endpoint for client credentials flow
clientId?: string;
clientSecret?: string;
scopes?: string[]; // Required scopes
token?: string; // Pre-obtained access token
};
// OpenID Connect
openIdConnect?: {
url: string; // OpenID Connect discovery URL
clientId?: string;
clientSecret?: string;
token?: string; // Pre-obtained ID token
};
}
// Information about a server's status and tools
@@ -117,10 +156,13 @@ export interface ServerInfo {
status: 'connected' | 'connecting' | 'disconnected'; // Current connection status
error: string | null; // Error message if any
tools: ToolInfo[]; // List of tools available on the server
client?: Client; // Client instance for communication
client?: Client; // Client instance for communication (MCP clients)
transport?: SSEClientTransport | StdioClientTransport | StreamableHTTPClientTransport; // Transport mechanism used
openApiClient?: any; // OpenAPI client instance for openapi type servers
options?: RequestOptions; // Options for requests
createTime: number; // Timestamp of when the server was created
enabled?: boolean; // Flag to indicate if the server is enabled
keepAliveIntervalId?: NodeJS.Timeout; // Timer ID for keep-alive ping interval
}
// Details about a tool available on the server
@@ -128,6 +170,7 @@ export interface ToolInfo {
name: string; // Name of the tool
description: string; // Brief description of the tool
inputSchema: Record<string, unknown>; // Input schema for the tool
enabled?: boolean; // Whether the tool is enabled (optional, defaults to true)
}
// Standardized API response structure

View File

@@ -16,8 +16,9 @@ const rootDir = dirname(dirname(__dirname));
* @returns The path to the file
*/
export const getConfigFilePath = (filename: string, description = 'Configuration'): string => {
// Try to find the correct path to the file
const envPath = process.env.MCPHUB_SETTING_PATH;
const potentialPaths = [
...(envPath ? [envPath] : []),
// Prioritize process.cwd() as the first location to check
path.resolve(process.cwd(), filename),
// Use path relative to the root directory
@@ -25,13 +26,13 @@ export const getConfigFilePath = (filename: string, description = 'Configuration
// 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

143
src/utils/smartRouting.ts Normal file
View File

@@ -0,0 +1,143 @@
import { loadSettings, expandEnvVars } from '../config/index.js';
/**
* Smart routing configuration interface
*/
export interface SmartRoutingConfig {
enabled: boolean;
dbUrl: string;
openaiApiBaseUrl: string;
openaiApiKey: string;
openaiApiEmbeddingModel: string;
}
/**
* Gets the complete smart routing configuration from environment variables and settings.
*
* Priority order for each setting:
* 1. Specific environment variables (ENABLE_SMART_ROUTING, SMART_ROUTING_ENABLED, etc.)
* 2. Generic environment variables (OPENAI_API_KEY, DATABASE_URL, etc.)
* 3. Settings configuration (systemConfig.smartRouting)
* 4. Default values
*
* @returns {SmartRoutingConfig} Complete smart routing configuration
*/
export function getSmartRoutingConfig(): SmartRoutingConfig {
let settings = loadSettings();
const smartRoutingSettings: Partial<SmartRoutingConfig> =
settings.systemConfig?.smartRouting || {};
return {
// Enabled status - check multiple environment variables
enabled: getConfigValue(
[process.env.SMART_ROUTING_ENABLED],
smartRoutingSettings.enabled,
false,
parseBooleanEnvVar,
),
// Database configuration
dbUrl: getConfigValue([process.env.DB_URL], smartRoutingSettings.dbUrl, '', expandEnvVars),
// OpenAI API configuration
openaiApiBaseUrl: getConfigValue(
[process.env.OPENAI_API_BASE_URL],
smartRoutingSettings.openaiApiBaseUrl,
'https://api.openai.com/v1',
expandEnvVars,
),
openaiApiKey: getConfigValue(
[process.env.OPENAI_API_KEY],
smartRoutingSettings.openaiApiKey,
'',
expandEnvVars,
),
openaiApiEmbeddingModel: getConfigValue(
[process.env.OPENAI_API_EMBEDDING_MODEL],
smartRoutingSettings.openaiApiEmbeddingModel,
'text-embedding-3-small',
expandEnvVars,
),
};
}
/**
* Gets a configuration value with priority order: environment variables > settings > default.
*
* @param {(string | undefined)[]} envVars - Array of environment variable names to check in order
* @param {any} settingsValue - Value from settings configuration
* @param {any} defaultValue - Default value to use if no other value is found
* @param {Function} transformer - Function to transform the final value to the correct type
* @returns {any} The configuration value with the appropriate transformation applied
*/
function getConfigValue<T>(
envVars: (string | undefined)[],
settingsValue: any,
defaultValue: T,
transformer: (value: any) => T,
): T {
// Check environment variables in order
for (const envVar of envVars) {
if (envVar !== undefined && envVar !== null && envVar !== '') {
try {
return transformer(envVar);
} catch (error) {
console.warn(`Failed to transform environment variable "${envVar}":`, error);
continue;
}
}
}
// Check settings value
if (settingsValue !== undefined && settingsValue !== null) {
try {
return transformer(settingsValue);
} catch (error) {
console.warn('Failed to transform settings value:', error);
}
}
// Return default value
return defaultValue;
}
/**
* Parses a string environment variable value to a boolean.
* Supports common boolean representations: true/false, 1/0, yes/no, on/off
*
* @param {string} value - The environment variable value to parse
* @returns {boolean} The parsed boolean value
*/
function parseBooleanEnvVar(value: string): boolean {
if (typeof value === 'boolean') {
return value;
}
if (typeof value !== 'string') {
return false;
}
const normalized = value.toLowerCase().trim();
// Handle common truthy values
if (normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on') {
return true;
}
// Handle common falsy values
if (
normalized === 'false' ||
normalized === '0' ||
normalized === 'no' ||
normalized === 'off' ||
normalized === ''
) {
return false;
}
// Default to false for unrecognized values
console.warn(`Unrecognized boolean value for smart routing: "${value}", defaulting to false`);
return false;
}

177
test-integration.ts Normal file
View File

@@ -0,0 +1,177 @@
// Comprehensive test for OpenAPI server support in MCPHub
// This test verifies the complete integration including types, client, and service
import { OpenAPIClient } from './src/clients/openapi.js';
import { addServer, removeServer, getServersInfo } from './src/services/mcpService.js';
import type { ServerConfig } from './src/types/index.js';
async function testOpenAPIIntegration() {
console.log('🧪 Testing OpenAPI Integration in MCPHub\n');
// Test 1: OpenAPI Type System
console.log('1⃣ Testing OpenAPI Type System...');
const openAPIConfig: ServerConfig = {
type: 'openapi',
openapi: {
url: 'https://petstore3.swagger.io/api/v3/openapi.json',
version: '3.1.0',
security: {
type: 'none',
},
},
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
};
const apiKeyConfig: ServerConfig = {
type: 'openapi',
openapi: {
url: 'https://api.example.com/v1/openapi.json',
version: '3.1.0',
security: {
type: 'apiKey',
apiKey: {
name: 'X-API-Key',
in: 'header',
value: 'test-api-key',
},
},
},
};
const httpAuthConfig: ServerConfig = {
type: 'openapi',
openapi: {
url: 'https://api.example.com/v1/openapi.json',
version: '3.1.0',
security: {
type: 'http',
http: {
scheme: 'bearer',
credentials: 'test-token',
},
},
},
};
console.log('✅ OpenAPI type definitions are working correctly');
console.log(` - Basic config: ${openAPIConfig.type}`);
console.log(` - API Key config: ${apiKeyConfig.openapi?.security?.type}`);
console.log(` - HTTP Auth config: ${httpAuthConfig.openapi?.security?.type}`);
// Test 2: OpenAPI Client Direct
console.log('\n2⃣ Testing OpenAPI Client...');
try {
const client = new OpenAPIClient(openAPIConfig);
await client.initialize();
const tools = client.getTools();
console.log(`✅ OpenAPI client loaded ${tools.length} tools`);
// Show some example tools
const sampleTools = tools.slice(0, 3);
sampleTools.forEach((tool) => {
console.log(` - ${tool.name} (${tool.method.toUpperCase()} ${tool.path})`);
});
} catch (error) {
console.error('❌ OpenAPI client test failed:', (error as Error).message);
}
// Test 3: MCP Service Integration
console.log('\n3⃣ Testing MCP Service Integration...');
try {
// Test server registration
const serverName = 'test-openapi-server';
await addServer(serverName, openAPIConfig);
console.log(`✅ Successfully registered OpenAPI server: ${serverName}`);
// Test server retrieval
const servers = getServersInfo();
const openAPIServer = servers.find((s) => s.name === serverName);
if (openAPIServer) {
console.log(`✅ Server configuration retrieved correctly`);
console.log(` - Name: ${openAPIServer.name}`);
console.log(` - Status: ${openAPIServer.status}`);
}
// Clean up
removeServer(serverName);
console.log(`✅ Server cleanup completed`);
} catch (error) {
console.error('❌ MCP Service integration test failed:', (error as Error).message);
}
// Test 4: Security Configuration Variants
console.log('\n4⃣ Testing Security Configuration Variants...');
const securityConfigs = [
{ name: 'None', config: { type: 'none' as const } },
{
name: 'API Key (Header)',
config: {
type: 'apiKey' as const,
apiKey: { name: 'X-API-Key', in: 'header' as const, value: 'test' },
},
},
{
name: 'API Key (Query)',
config: {
type: 'apiKey' as const,
apiKey: { name: 'api_key', in: 'query' as const, value: 'test' },
},
},
{
name: 'HTTP Bearer',
config: {
type: 'http' as const,
http: { scheme: 'bearer' as const, credentials: 'token' },
},
},
{
name: 'HTTP Basic',
config: {
type: 'http' as const,
http: { scheme: 'basic' as const, credentials: 'user:pass' },
},
},
];
securityConfigs.forEach(({ name, config }) => {
const _testConfig: ServerConfig = {
type: 'openapi',
openapi: {
url: 'https://api.example.com/openapi.json',
version: '3.1.0',
security: config,
},
};
console.log(`${name} security configuration is valid`);
});
console.log('\n🎉 OpenAPI Integration Test Completed!');
console.log('\n📊 Summary:');
console.log(' ✅ Type system supports all OpenAPI configuration variants');
console.log(' ✅ OpenAPI client can load and parse specifications');
console.log(' ✅ MCP service can register and manage OpenAPI servers');
console.log(' ✅ Security configurations are properly typed and validated');
console.log('\n🚀 OpenAPI support is ready for production use!');
}
// Handle uncaught errors gracefully
process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled rejection at:', promise, 'reason:', reason);
process.exit(1);
});
// Run the comprehensive test
testOpenAPIIntegration().catch(console.error);

216
test-openapi-schema.ts Normal file
View File

@@ -0,0 +1,216 @@
// Test script to verify OpenAPI schema support
// Run this in the MCPHub project directory with: tsx test-openapi-schema.ts
import { OpenAPIClient } from './src/clients/openapi.js';
import type { ServerConfig } from './src/types/index.js';
async function testOpenAPISchemaSupport() {
console.log('🧪 Testing OpenAPI Schema Support...\n');
// Test 1: Schema-based OpenAPI client
console.log('1⃣ Testing OpenAPI client with JSON schema...');
const sampleSchema = {
openapi: '3.1.0',
info: {
title: 'Test API',
version: '1.0.0',
},
servers: [
{
url: 'https://api.example.com',
},
],
paths: {
'/users': {
get: {
operationId: 'getUsers',
summary: 'Get all users',
responses: {
'200': {
description: 'List of users',
content: {
'application/json': {
schema: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
},
},
},
},
},
},
},
},
},
'/users/{id}': {
get: {
operationId: 'getUserById',
summary: 'Get user by ID',
parameters: [
{
name: 'id',
in: 'path',
required: true,
schema: { type: 'integer' },
},
],
responses: {
'200': {
description: 'User details',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
email: { type: 'string' },
},
},
},
},
},
},
},
},
},
};
try {
const schemaConfig: ServerConfig = {
type: 'openapi',
openapi: {
schema: sampleSchema,
version: '3.1.0',
security: {
type: 'apiKey',
apiKey: {
name: 'X-API-Key',
in: 'header',
value: 'test-key',
},
},
},
};
console.log(' Creating OpenAPI client with schema...');
const client = new OpenAPIClient(schemaConfig);
console.log(' Initializing client...');
await client.initialize();
console.log(' Getting available tools...');
const tools = client.getTools();
console.log(` ✅ Schema-based client initialized successfully!`);
console.log(` 📋 Found ${tools.length} tools:`);
tools.forEach((tool) => {
console.log(` - ${tool.name}: ${tool.description}`);
});
// Test 2: Compare with URL-based client (if available)
console.log('\n2⃣ Testing configuration validation...');
// Valid configurations
const validConfigs = [
{
name: 'URL-based config',
config: {
type: 'openapi' as const,
openapi: {
url: 'https://api.example.com/openapi.json',
},
},
},
{
name: 'Schema-based config',
config: {
type: 'openapi' as const,
openapi: {
schema: sampleSchema,
},
},
},
{
name: 'Both URL and schema (should prefer schema)',
config: {
type: 'openapi' as const,
openapi: {
url: 'https://api.example.com/openapi.json',
schema: sampleSchema,
},
},
},
];
validConfigs.forEach(({ name, config }) => {
try {
const _client = new OpenAPIClient(config);
console.log(`${name}: Valid configuration`);
} catch (error) {
console.log(`${name}: Invalid configuration - ${error}`);
}
});
// Invalid configurations
console.log('\n3⃣ Testing invalid configurations...');
const invalidConfigs = [
{
name: 'No URL or schema',
config: {
type: 'openapi' as const,
openapi: {
version: '3.1.0',
},
},
},
{
name: 'Empty openapi object',
config: {
type: 'openapi' as const,
openapi: {},
},
},
];
invalidConfigs.forEach(({ name, config }) => {
try {
const _client = new OpenAPIClient(config);
console.log(`${name}: Should have failed but didn't`);
} catch (error) {
console.log(`${name}: Correctly rejected - ${(error as Error).message}`);
}
});
console.log('\n🎉 All tests completed successfully!');
console.log('\n📝 Summary:');
console.log(' ✅ OpenAPI client supports JSON schema input');
console.log(' ✅ Schema parsing and tool extraction works');
console.log(' ✅ Configuration validation works correctly');
console.log(' ✅ Both URL and schema modes are supported');
} catch (error) {
console.error('❌ Test failed:', (error as Error).message);
console.error(' Stack trace:', (error as Error).stack);
}
}
// Handle uncaught errors gracefully
process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled rejection at:', promise, 'reason:', reason);
process.exit(1);
});
// Run the test
testOpenAPISchemaSupport().catch(console.error);

64
test-openapi.ts Normal file
View File

@@ -0,0 +1,64 @@
// Test script to verify OpenAPI server functionality
// Run this in the MCPHub project directory with: tsx test-openapi.ts
import { OpenAPIClient } from './src/clients/openapi.js';
import type { ServerConfig } from './src/types/index.js';
async function testOpenAPIClient() {
console.log('Testing OpenAPI client...');
// Test configuration
const testConfig: ServerConfig = {
type: 'openapi',
openapi: {
url: 'https://petstore3.swagger.io/api/v3/openapi.json', // Public Swagger Petstore API
version: '3.1.0',
security: {
type: 'none',
},
},
headers: {
'Content-Type': 'application/json',
},
};
try {
// Initialize the OpenAPI client
const client = new OpenAPIClient(testConfig);
await client.initialize();
console.log('✅ OpenAPI client initialized successfully');
// Get available tools
const tools = client.getTools();
console.log(`✅ Found ${tools.length} tools:`);
tools.slice(0, 5).forEach((tool) => {
console.log(` - ${tool.name}: ${tool.description}`);
});
// Test a simple GET operation if available
const getTool = tools.find(
(tool) => tool.method === 'get' && tool.path.includes('/pet') && !tool.path.includes('{'),
);
if (getTool) {
console.log(`\n🔧 Testing tool: ${getTool.name}`);
try {
const result = await client.callTool(getTool.name, {});
console.log('✅ Tool call successful');
console.log('Result type:', typeof result);
} catch (error) {
console.log('⚠️ Tool call failed (expected for demo API):', (error as Error).message);
}
}
console.log('\n🎉 OpenAPI integration test completed!');
} catch (error) {
console.error('❌ Test failed:', error);
process.exit(1);
}
}
// Run the test
testOpenAPIClient().catch(console.error);