mirror of
https://github.com/samanhappy/mcphub.git
synced 2025-12-24 18:59:30 -05:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
298d96d593 | ||
|
|
d44886b81b | ||
|
|
bbe44fc540 | ||
|
|
c60b98e3d6 | ||
|
|
a447fe5b41 | ||
|
|
94d51fa03a | ||
|
|
b88a7240c6 | ||
|
|
3c875590ce |
29
.github/ISSUE_TEMPLATE/bug-report---bug-报告.md
vendored
Normal file
29
.github/ISSUE_TEMPLATE/bug-report---bug-报告.md
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Bug Report / Bug 报告
|
||||
about: Create a report to help us improve / 报告问题以帮助改进
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Bug Description / 问题描述**
|
||||
What happened? / 发生了什么?
|
||||
|
||||
**Steps to Reproduce / 复现步骤**
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Expected Behavior / 预期行为**
|
||||
What should happen? / 应该发生什么?
|
||||
|
||||
**Environment / 运行环境**
|
||||
- Running on / 运行方式: [docker/npx/local / docker/npx/本地]
|
||||
- Version / 版本: [e.g. 1.0.0]
|
||||
|
||||
**Screenshots / 截图**
|
||||
If relevant, add screenshots / 如果有帮助的话,请添加截图
|
||||
|
||||
**Additional Info / 补充信息**
|
||||
Any other details? / 还有其他信息吗?
|
||||
20
.github/ISSUE_TEMPLATE/feature-request---功能请求.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature-request---功能请求.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request / 功能请求
|
||||
about: Suggest an idea for this project / 为项目提出新想法
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Current Problem / 当前问题**
|
||||
What problem are you trying to solve? / 您想要解决什么问题?
|
||||
|
||||
**Proposed Solution / 建议方案**
|
||||
How would you like this to work? / 您期望的解决方案是什么?
|
||||
|
||||
**Alternatives / 替代方案**
|
||||
Have you considered any alternatives? / 您是否考虑过其他解决方案?
|
||||
|
||||
**Additional Context / 补充说明**
|
||||
Any screenshots, mockups, or relevant information? / 有任何截图、设计图或相关信息吗?
|
||||
28
.github/workflows/release.yml
vendored
28
.github/workflows/release.yml
vendored
@@ -13,34 +13,6 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Update package.json version
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
# Extract version from tag (remove 'v' prefix)
|
||||
TAG_VERSION=${GITHUB_REF#refs/tags/v}
|
||||
echo "Updating package.json version to $TAG_VERSION"
|
||||
|
||||
# Update package.json version field
|
||||
sed -i "s/\"version\": \".*\"/\"version\": \"$TAG_VERSION\"/" package.json
|
||||
|
||||
# Commit the change
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
git add package.json
|
||||
git commit -m "chore: update version to $TAG_VERSION [skip ci]"
|
||||
|
||||
# Push using GITHUB_TOKEN for authentication
|
||||
git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git
|
||||
git push origin HEAD:${GITHUB_REF#refs/tags/}
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
|
||||
20
README.md
20
README.md
@@ -68,11 +68,13 @@ Create a `mcp_settings.json` file to customize your server settings:
|
||||
### Docker Deployment
|
||||
|
||||
**Recommended**: Mount your custom config:
|
||||
|
||||
```bash
|
||||
docker run -p 3000:3000 -v $(pwd)/mcp_settings.json:/app/mcp_settings.json samanhappy/mcphub
|
||||
```
|
||||
|
||||
or run with default settings:
|
||||
|
||||
```bash
|
||||
docker run -p 3000:3000 samanhappy/mcphub
|
||||
```
|
||||
@@ -80,22 +82,28 @@ docker run -p 3000:3000 samanhappy/mcphub
|
||||
### Access the Dashboard
|
||||
|
||||
Open `http://localhost:3000` and log in with your credentials.
|
||||
|
||||
> **Note**: Default credentials are `admin` / `admin123`.
|
||||
|
||||
**Dashboard Overview**:
|
||||
|
||||
- Live status of all MCP servers
|
||||
- Enable/disable or reconfigure servers
|
||||
- Group management for organizing servers
|
||||
- User administration for access control
|
||||
|
||||
### Streamable HTTP Endpoint
|
||||
|
||||
> As of now, support for streaming HTTP endpoints varies across different AI clients. If you encounter issues, you can use the SSE endpoint or wait for future updates.
|
||||
|
||||
Connect AI clients (e.g., Claude Desktop, Cursor, DeepChat, etc.) via:
|
||||
|
||||
```
|
||||
http://localhost:3000/mcp
|
||||
```
|
||||
|
||||
This endpoint provides a unified streamable HTTP interface for all your MCP servers. It allows you to:
|
||||
|
||||
- Send requests to any configured MCP server
|
||||
- Receive responses in real-time
|
||||
- Easily integrate with various AI clients and tools
|
||||
@@ -106,20 +114,24 @@ This endpoint provides a unified streamable HTTP interface for all your MCP serv
|
||||

|
||||
|
||||
For targeted access to specific server groups, use the group-based HTTP endpoint:
|
||||
|
||||
```
|
||||
http://localhost:3000/mcp/{group}
|
||||
```
|
||||
|
||||
Where `{group}` is the ID or name of the group you created in the dashboard. This allows you to:
|
||||
|
||||
- Connect to a specific subset of MCP servers organized by use case
|
||||
- Isolate different AI tools to access only relevant servers
|
||||
- Implement more granular access control for different environments or teams
|
||||
|
||||
**Server-Specific Endpoints**:
|
||||
For direct access to individual servers, use the server-specific HTTP endpoint:
|
||||
|
||||
```
|
||||
http://localhost:3000/mcp/{server}
|
||||
```
|
||||
|
||||
Where `{server}` is the name of the server you want to connect to. This allows you to access a specific MCP server directly.
|
||||
|
||||
> **Note**: If the server name and group name are the same, the group name will take precedence.
|
||||
@@ -127,16 +139,19 @@ Where `{server}` is the name of the server you want to connect to. This allows y
|
||||
### SSE Endpoint (Deprecated in Future)
|
||||
|
||||
Connect AI clients (e.g., Claude Desktop, Cursor, DeepChat, etc.) via:
|
||||
|
||||
```
|
||||
http://localhost:3000/sse
|
||||
```
|
||||
|
||||
For targeted access to specific server groups, use the group-based SSE endpoint:
|
||||
|
||||
```
|
||||
http://localhost:3000/sse/{group}
|
||||
```
|
||||
|
||||
For direct access to individual servers, use the server-specific SSE endpoint:
|
||||
|
||||
```
|
||||
http://localhost:3000/sse/{server}
|
||||
```
|
||||
@@ -157,6 +172,7 @@ This starts both frontend and backend in development mode with hot-reloading.
|
||||
## 🛠️ Common Issues
|
||||
|
||||
### Using Nginx as a Reverse Proxy
|
||||
|
||||
If you are using Nginx to reverse proxy MCPHub, please make sure to add the following configuration in your Nginx setup:
|
||||
|
||||
```nginx
|
||||
@@ -172,13 +188,15 @@ proxy_buffering off
|
||||
|
||||
## 👥 Contributing
|
||||
|
||||
Contributions are welcome!
|
||||
Contributions of any kind are welcome!
|
||||
|
||||
- New features & optimizations
|
||||
- Documentation improvements
|
||||
- Bug reports & fixes
|
||||
- Translations & suggestions
|
||||
|
||||
Welcome to join our [Discord community](https://discord.gg/qMKNsn5Q) for discussions and support.
|
||||
|
||||
## ❤️ Sponsor
|
||||
|
||||
If you like this project, maybe you can consider:
|
||||
|
||||
BIN
frontend/public/assets/reward.png
Normal file
BIN
frontend/public/assets/reward.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
BIN
frontend/public/assets/wexin.png
Normal file
BIN
frontend/public/assets/wexin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 92 KiB |
20
frontend/src/components/icons/DiscordIcon.tsx
Normal file
20
frontend/src/components/icons/DiscordIcon.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
export const DiscordIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
|
||||
return (
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<title>Discord</title>
|
||||
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default DiscordIcon;
|
||||
21
frontend/src/components/icons/GitHubIcon.tsx
Normal file
21
frontend/src/components/icons/GitHubIcon.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
|
||||
export const GitHubIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
|
||||
return (
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<title>GitHub</title>
|
||||
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default GitHubIcon;
|
||||
20
frontend/src/components/icons/SponsorIcon.tsx
Normal file
20
frontend/src/components/icons/SponsorIcon.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
export const SponsorIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
|
||||
return (
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<title>Sponsor</title>
|
||||
<path d="M17.625 1.499c-2.32 0-4.354 1.203-5.625 3.03-1.271-1.827-3.305-3.03-5.625-3.03C3.129 1.499 0 4.253 0 8.249c0 4.275 3.068 7.847 5.828 10.227a33.14 33.14 0 0 0 5.616 3.876l.028.017.008.003-.001.003c.163.085.342.126.521.125.179.001.358-.041.521-.125l-.001-.003.008-.003.028-.017a33.14 33.14 0 0 0 5.616-3.876C20.932 16.096 24 12.524 24 8.249c0-3.996-3.129-6.75-6.375-6.75zm-.919 15.275a30.766 30.766 0 0 1-4.703 3.316l-.004-.002-.004.002a30.955 30.955 0 0 1-4.703-3.316c-2.677-2.307-5.047-5.298-5.047-8.523 0-2.754 2.121-4.5 4.125-4.5 2.06 0 3.914 1.479 4.544 3.684.143.495.596.797 1.086.796.49.001.943-.302 1.085-.796.63-2.205 2.484-3.684 4.544-3.684 2.004 0 4.125 1.746 4.125 4.5 0 3.225-2.37 6.216-5.048 8.523z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default SponsorIcon;
|
||||
20
frontend/src/components/icons/WeChatIcon.tsx
Normal file
20
frontend/src/components/icons/WeChatIcon.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
export const WeChatIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
|
||||
return (
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<title>WeChat</title>
|
||||
<path d="M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.596-6.348zM5.785 5.991c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178A1.17 1.17 0 0 1 4.623 7.17c0-.651.52-1.18 1.162-1.18zm5.813 0c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178 1.17 1.17 0 0 1-1.162-1.178c0-.651.52-1.18 1.162-1.18zm5.34 2.867c-1.797-.052-3.746.512-5.28 1.786-1.72 1.428-2.687 3.72-1.78 6.22.942 2.453 3.666 4.229 6.884 4.229.826 0 1.622-.12 2.361-.336a.722.722 0 0 1 .598.082l1.584.926a.272.272 0 0 0 .14.047c.134 0 .24-.111.24-.247 0-.06-.023-.12-.038-.177l-.327-1.233a.582.582 0 0 1-.023-.156.49.49 0 0 1 .201-.398C23.024 18.48 24 16.82 24 14.98c0-3.21-2.931-5.837-6.656-6.088V8.89c-.135-.01-.27-.027-.407-.03zm-2.53 3.274c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.97-.982zm4.844 0c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.969-.982z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default WeChatIcon;
|
||||
1
frontend/src/components/icons/discord.svg
Normal file
1
frontend/src/components/icons/discord.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Discord</title><path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
frontend/src/components/icons/github.svg
Normal file
1
frontend/src/components/icons/github.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
|
||||
|
After Width: | Height: | Size: 822 B |
1
frontend/src/components/icons/sponsor.svg
Normal file
1
frontend/src/components/icons/sponsor.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub Sponsors</title><path d="M17.625 1.499c-2.32 0-4.354 1.203-5.625 3.03-1.271-1.827-3.305-3.03-5.625-3.03C3.129 1.499 0 4.253 0 8.249c0 4.275 3.068 7.847 5.828 10.227a33.14 33.14 0 0 0 5.616 3.876l.028.017.008.003-.001.003c.163.085.342.126.521.125.179.001.358-.041.521-.125l-.001-.003.008-.003.028-.017a33.14 33.14 0 0 0 5.616-3.876C20.932 16.096 24 12.524 24 8.249c0-3.996-3.129-6.75-6.375-6.75zm-.919 15.275a30.766 30.766 0 0 1-4.703 3.316l-.004-.002-.004.002a30.955 30.955 0 0 1-4.703-3.316c-2.677-2.307-5.047-5.298-5.047-8.523 0-2.754 2.121-4.5 4.125-4.5 2.06 0 3.914 1.479 4.544 3.684.143.495.596.797 1.086.796.49.001.943-.302 1.085-.796.63-2.205 2.484-3.684 4.544-3.684 2.004 0 4.125 1.746 4.125 4.5 0 3.225-2.37 6.216-5.048 8.523z"/></svg>
|
||||
|
After Width: | Height: | Size: 829 B |
1
frontend/src/components/icons/wechat.svg
Normal file
1
frontend/src/components/icons/wechat.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>WeChat</title><path d="M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.596-6.348zM5.785 5.991c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178A1.17 1.17 0 0 1 4.623 7.17c0-.651.52-1.18 1.162-1.18zm5.813 0c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178 1.17 1.17 0 0 1-1.162-1.178c0-.651.52-1.18 1.162-1.18zm5.34 2.867c-1.797-.052-3.746.512-5.28 1.786-1.72 1.428-2.687 3.72-1.78 6.22.942 2.453 3.666 4.229 6.884 4.229.826 0 1.622-.12 2.361-.336a.722.722 0 0 1 .598.082l1.584.926a.272.272 0 0 0 .14.047c.134 0 .24-.111.24-.247 0-.06-.023-.12-.038-.177l-.327-1.233a.582.582 0 0 1-.023-.156.49.49 0 0 1 .201-.398C23.024 18.48 24 16.82 24 14.98c0-3.21-2.931-5.837-6.656-6.088V8.89c-.135-.01-.27-.027-.407-.03zm-2.53 3.274c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.97-.982zm4.844 0c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.969-.982z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -1,15 +1,23 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
import ThemeSwitch from '@/components/ui/ThemeSwitch';
|
||||
import GitHubIcon from '@/components/icons/GitHubIcon';
|
||||
import SponsorIcon from '@/components/icons/SponsorIcon';
|
||||
import WeChatIcon from '@/components/icons/WeChatIcon';
|
||||
import DiscordIcon from '@/components/icons/DiscordIcon';
|
||||
import SponsorDialog from '@/components/ui/SponsorDialog';
|
||||
import WeChatDialog from '@/components/ui/WeChatDialog';
|
||||
|
||||
interface HeaderProps {
|
||||
onToggleSidebar: () => void;
|
||||
}
|
||||
|
||||
const Header: React.FC<HeaderProps> = ({ onToggleSidebar }) => {
|
||||
const { t } = useTranslation();
|
||||
const { t, i18n } = useTranslation();
|
||||
const { auth } = useAuth();
|
||||
const [sponsorDialogOpen, setSponsorDialogOpen] = useState(false);
|
||||
const [wechatDialogOpen, setWechatDialogOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<header className="bg-white dark:bg-gray-800 shadow-sm z-10">
|
||||
@@ -30,12 +38,49 @@ const Header: React.FC<HeaderProps> = ({ onToggleSidebar }) => {
|
||||
<h1 className="ml-4 text-xl font-bold text-gray-900 dark:text-white">{t('app.title')}</h1>
|
||||
</div>
|
||||
|
||||
{/* Theme Switch */}
|
||||
{/* Theme Switch and Version */}
|
||||
<div className="flex items-center space-x-4">
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">{import.meta.env.PACKAGE_VERSION}</span>
|
||||
<a
|
||||
href="https://github.com/samanhappy/mcphub"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
|
||||
aria-label="GitHub Repository"
|
||||
>
|
||||
<GitHubIcon className="h-5 w-5" />
|
||||
</a>
|
||||
{i18n.language === 'zh' ? (
|
||||
<button
|
||||
onClick={() => setWechatDialogOpen(true)}
|
||||
className="text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 focus:outline-none"
|
||||
aria-label={t('wechat.label')}
|
||||
>
|
||||
<WeChatIcon className="h-5 w-5" />
|
||||
</button>
|
||||
) : (
|
||||
<a
|
||||
href="https://discord.gg/qMKNsn5Q"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"
|
||||
aria-label={t('discord.label')}
|
||||
>
|
||||
<DiscordIcon className="h-5 w-5" />
|
||||
</a>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setSponsorDialogOpen(true)}
|
||||
className="text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 focus:outline-none"
|
||||
aria-label={t('sponsor.label')}
|
||||
>
|
||||
<SponsorIcon className="h-5 w-5" />
|
||||
</button>
|
||||
<ThemeSwitch />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<SponsorDialog open={sponsorDialogOpen} onOpenChange={setSponsorDialogOpen} />
|
||||
<WeChatDialog open={wechatDialogOpen} onOpenChange={setWechatDialogOpen} />
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
60
frontend/src/components/ui/SponsorDialog.tsx
Normal file
60
frontend/src/components/ui/SponsorDialog.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
interface SponsorDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
const SponsorDialog: React.FC<SponsorDialogProps> = ({ open, onOpenChange }) => {
|
||||
const { i18n, t } = useTranslation();
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg max-w-md w-full">
|
||||
<div className="p-6 relative">
|
||||
{/* Close button (X) in the top-right corner */}
|
||||
<button
|
||||
onClick={() => onOpenChange(false)}
|
||||
className="absolute top-4 right-4 text-gray-400 hover:text-gray-500 dark:text-gray-500 dark:hover:text-gray-400"
|
||||
aria-label={t('common.close')}
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
|
||||
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
|
||||
{t('sponsor.title')}
|
||||
</h3>
|
||||
|
||||
<div className="flex flex-col items-center justify-center py-4">
|
||||
{i18n.language === 'zh' ? (
|
||||
<img
|
||||
src="/assets/reward.png"
|
||||
alt={t('sponsor.rewardAlt')}
|
||||
className="max-w-full h-auto"
|
||||
style={{ maxHeight: '400px' }}
|
||||
/>
|
||||
) : (
|
||||
<div className="text-center">
|
||||
<p className="mb-4 text-gray-700 dark:text-gray-300">{t('sponsor.supportMessage')}</p>
|
||||
<a
|
||||
href="https://ko-fi.com/samanhappy"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center justify-center bg-[#13C3FF] text-white px-4 py-2 rounded-md hover:bg-[#00A5E5] transition-colors"
|
||||
>
|
||||
{t('sponsor.supportButton')}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SponsorDialog;
|
||||
@@ -104,7 +104,6 @@ const UserProfileMenu: React.FC<UserProfileMenuProps> = ({ collapsed, version })
|
||||
>
|
||||
<Info className="h-4 w-4 mr-2" />
|
||||
{t('about.title')}
|
||||
({version})
|
||||
{showNewVersionInfo && (
|
||||
<span className="absolute top-2 right-4 block w-2 h-2 bg-red-500 rounded-full"></span>
|
||||
)}
|
||||
|
||||
49
frontend/src/components/ui/WeChatDialog.tsx
Normal file
49
frontend/src/components/ui/WeChatDialog.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
interface WeChatDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
const WeChatDialog: React.FC<WeChatDialogProps> = ({ open, onOpenChange }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg max-w-md w-full">
|
||||
<div className="p-6 relative">
|
||||
{/* Close button (X) in the top-right corner */}
|
||||
<button
|
||||
onClick={() => onOpenChange(false)}
|
||||
className="absolute top-4 right-4 text-gray-400 hover:text-gray-500 dark:text-gray-500 dark:hover:text-gray-400"
|
||||
aria-label={t('common.close')}
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
|
||||
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
|
||||
{t('wechat.title')}
|
||||
</h3>
|
||||
|
||||
<div className="flex flex-col items-center justify-center py-4">
|
||||
<img
|
||||
src="/assets/wexin.png"
|
||||
alt={t('wechat.qrCodeAlt')}
|
||||
className="max-w-full h-auto"
|
||||
style={{ maxHeight: '400px' }}
|
||||
/>
|
||||
<p className="mt-4 text-center text-gray-700 dark:text-gray-300">
|
||||
{t('wechat.scanMessage')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WeChatDialog;
|
||||
@@ -26,6 +26,24 @@
|
||||
"viewProfile": "View profile",
|
||||
"userCenter": "User Center"
|
||||
},
|
||||
"sponsor": {
|
||||
"label": "Sponsor",
|
||||
"title": "Support the Project",
|
||||
"rewardAlt": "Reward QR Code",
|
||||
"supportMessage": "Support the development of MCP Hub by buying me a coffee!",
|
||||
"supportButton": "Support on Ko-fi"
|
||||
},
|
||||
"wechat": {
|
||||
"label": "WeChat",
|
||||
"title": "Connect via WeChat",
|
||||
"qrCodeAlt": "WeChat QR Code",
|
||||
"scanMessage": "Scan this QR code to connect with us on WeChat"
|
||||
},
|
||||
"discord": {
|
||||
"label": "Discord",
|
||||
"title": "Join our Discord server",
|
||||
"community": "Join our growing community on Discord for support, discussions, and updates!"
|
||||
},
|
||||
"theme": {
|
||||
"title": "Theme",
|
||||
"light": "Light",
|
||||
|
||||
@@ -26,6 +26,24 @@
|
||||
"viewProfile": "查看个人中心",
|
||||
"userCenter": "个人中心"
|
||||
},
|
||||
"sponsor": {
|
||||
"label": "赞助",
|
||||
"title": "支持项目",
|
||||
"rewardAlt": "赞赏码",
|
||||
"supportMessage": "通过捐赠支持 MCP Hub 的开发!",
|
||||
"supportButton": "在 Ko-fi 上支持"
|
||||
},
|
||||
"wechat": {
|
||||
"label": "微信",
|
||||
"title": "微信联系",
|
||||
"qrCodeAlt": "微信二维码",
|
||||
"scanMessage": "扫描二维码添加微信"
|
||||
},
|
||||
"discord": {
|
||||
"label": "Discord",
|
||||
"title": "加入我们的 Discord 服务器",
|
||||
"community": "加入我们不断壮大的 Discord 社区,获取支持、参与讨论并了解最新动态!"
|
||||
},
|
||||
"theme": {
|
||||
"title": "主题",
|
||||
"light": "浅色",
|
||||
|
||||
@@ -16,6 +16,7 @@ export const checkLatestVersion = async (): Promise<string | null> => {
|
||||
};
|
||||
|
||||
export const compareVersions = (current: string, latest: string): number => {
|
||||
if (current === 'dev') return -1;
|
||||
const currentParts = current.split('.').map(Number);
|
||||
const latestParts = latest.split('.').map(Number);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@samanhappy/mcphub",
|
||||
"version": "0.5.4",
|
||||
"version": "dev",
|
||||
"description": "A hub server for mcp servers",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -16,10 +16,16 @@ export const initMcpServer = async (name: string, version: string): Promise<void
|
||||
await registerAllTools(true);
|
||||
};
|
||||
|
||||
export const getMcpServer = (sessionId: string): Server => {
|
||||
export const getMcpServer = (sessionId?: string): Server => {
|
||||
if (!sessionId) {
|
||||
return createMcpServer(config.mcpHubName, config.mcpHubVersion);
|
||||
}
|
||||
|
||||
if (!servers[sessionId]) {
|
||||
const server = createMcpServer(config.mcpHubName, config.mcpHubVersion);
|
||||
servers[sessionId] = server;
|
||||
} else {
|
||||
console.log(`MCP server already exists for sessionId: ${sessionId}`);
|
||||
}
|
||||
return servers[sessionId];
|
||||
};
|
||||
|
||||
@@ -126,6 +126,7 @@ export const handleMcpPostRequest = async (req: Request, res: Response): Promise
|
||||
});
|
||||
|
||||
transport.onclose = () => {
|
||||
console.log(`Transport closed: ${transport.sessionId}`);
|
||||
if (transport.sessionId) {
|
||||
delete transports[transport.sessionId];
|
||||
deleteMcpServer(transport.sessionId);
|
||||
@@ -134,7 +135,7 @@ export const handleMcpPostRequest = async (req: Request, res: Response): Promise
|
||||
};
|
||||
|
||||
console.log(`MCP connection established: ${transport.sessionId}`);
|
||||
await getMcpServer(transport.sessionId || 'mcp').connect(transport);
|
||||
await getMcpServer(transport.sessionId).connect(transport);
|
||||
} else {
|
||||
res.status(400).json({
|
||||
jsonrpc: '2.0',
|
||||
|
||||
Reference in New Issue
Block a user