mirror of
https://github.com/samanhappy/mcphub.git
synced 2025-12-30 21:49:13 -05:00
Compare commits
5 Commits
copilot/fi
...
copilot/ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee301a893f | ||
|
|
a8852f7807 | ||
|
|
d8e127d911 | ||
|
|
f782f69251 | ||
|
|
1c0473183f |
@@ -57,15 +57,6 @@ Créez un fichier `mcp_settings.json` pour personnaliser les paramètres de votr
|
||||
}
|
||||
```
|
||||
|
||||
#### Exemples de Configuration
|
||||
|
||||
Pour des configurations spécifiques de serveurs MCP, consultez le répertoire [examples](./examples/) :
|
||||
|
||||
- **[Démarrage rapide Jira Cloud](./examples/QUICK_START_JIRA.md)** - Guide de configuration en 5 minutes pour Jira Cloud
|
||||
- **[Guide complet Atlassian/Jira](./examples/README_ATLASSIAN_JIRA.md)** - Configuration détaillée pour Jira et Confluence
|
||||
- **[Variables d'environnement](./examples/mcp_settings_with_env_vars.json)** - Utilisation de variables d'environnement dans la configuration
|
||||
- **[OpenAPI Schema](./examples/openapi-schema-config.json)** - Serveurs MCP basés sur OpenAPI
|
||||
|
||||
### Déploiement avec Docker
|
||||
|
||||
**Recommandé** : Montez votre configuration personnalisée :
|
||||
|
||||
@@ -59,15 +59,6 @@ Create a `mcp_settings.json` file to customize your server settings:
|
||||
}
|
||||
```
|
||||
|
||||
#### Configuration Examples
|
||||
|
||||
For specific MCP server configurations, see the [examples](./examples/) directory:
|
||||
|
||||
- **[Jira Cloud Quick Start](./examples/QUICK_START_JIRA.md)** - 5-minute setup guide for Jira Cloud
|
||||
- **[Atlassian/Jira Complete Guide](./examples/README_ATLASSIAN_JIRA.md)** - Detailed setup for Jira and Confluence
|
||||
- **[Environment Variables](./examples/mcp_settings_with_env_vars.json)** - Using environment variables in configuration
|
||||
- **[OpenAPI Schema](./examples/openapi-schema-config.json)** - OpenAPI-based MCP servers
|
||||
|
||||
#### OAuth Configuration (Optional)
|
||||
|
||||
MCPHub supports OAuth 2.0 for authenticating with upstream MCP servers. See the [OAuth feature guide](docs/features/oauth.mdx) for a full walkthrough. In practice you will run into two configuration patterns:
|
||||
|
||||
@@ -57,15 +57,6 @@ MCPHub 通过将多个 MCP(Model Context Protocol)服务器组织为灵活
|
||||
}
|
||||
```
|
||||
|
||||
#### 配置示例
|
||||
|
||||
有关特定 MCP 服务器配置,请参阅 [examples](./examples/) 目录:
|
||||
|
||||
- **[Jira Cloud 快速入门](./examples/QUICK_START_JIRA.md)** - Jira Cloud 5 分钟配置指南
|
||||
- **[Atlassian/Jira 完整指南](./examples/README_ATLASSIAN_JIRA.md)** - Jira 和 Confluence 详细设置
|
||||
- **[环境变量](./examples/mcp_settings_with_env_vars.json)** - 在配置中使用环境变量
|
||||
- **[OpenAPI Schema](./examples/openapi-schema-config.json)** - 基于 OpenAPI 的 MCP 服务器
|
||||
|
||||
#### OAuth 配置(可选)
|
||||
|
||||
MCPHub 支持通过 OAuth 2.0 访问上游 MCP 服务器。完整说明请参阅[《OAuth 功能指南》](docs/zh/features/oauth.mdx)。实际使用中通常会遇到两类配置:
|
||||
|
||||
@@ -207,55 +207,6 @@ MCPHub uses several configuration files:
|
||||
}
|
||||
```
|
||||
|
||||
### Productivity and Project Management
|
||||
|
||||
#### Atlassian (Jira & Confluence) Server
|
||||
|
||||
```json
|
||||
{
|
||||
"atlassian": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"mcp-atlassian",
|
||||
"--jira-url=${JIRA_URL}",
|
||||
"--jira-username=${JIRA_USERNAME}",
|
||||
"--jira-token=${JIRA_TOKEN}",
|
||||
"--confluence-url=${CONFLUENCE_URL}",
|
||||
"--confluence-username=${CONFLUENCE_USERNAME}",
|
||||
"--confluence-token=${CONFLUENCE_TOKEN}"
|
||||
],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Jira Cloud Only Configuration:**
|
||||
|
||||
```json
|
||||
{
|
||||
"jira": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"mcp-atlassian",
|
||||
"--jira-url=${JIRA_URL}",
|
||||
"--jira-username=${JIRA_USERNAME}",
|
||||
"--jira-token=${JIRA_TOKEN}"
|
||||
],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Required Environment Variables:**
|
||||
- `JIRA_URL`: Your Jira Cloud URL (e.g., `https://your-company.atlassian.net`)
|
||||
- `JIRA_USERNAME`: Your Atlassian account email
|
||||
- `JIRA_TOKEN`: API token from [Atlassian API Tokens](https://id.atlassian.com/manage-profile/security/api-tokens)
|
||||
- `CONFLUENCE_URL`: Your Confluence URL (e.g., `https://your-company.atlassian.net/wiki`)
|
||||
- `CONFLUENCE_USERNAME`: Your Confluence account email (often same as Jira)
|
||||
- `CONFLUENCE_TOKEN`: Confluence API token (can be same as Jira token for Cloud)
|
||||
|
||||
**Setup Guide:** See [examples/README_ATLASSIAN_JIRA.md](../../examples/README_ATLASSIAN_JIRA.md) for detailed setup instructions.
|
||||
|
||||
### Map and Location Services
|
||||
|
||||
#### Amap (高德地图) Server
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# Atlassian Jira Cloud Configuration Example
|
||||
# Copy this to your .env file and fill in your actual values
|
||||
|
||||
# Jira Configuration (Required)
|
||||
JIRA_URL=https://your-company.atlassian.net
|
||||
JIRA_USERNAME=your.email@company.com
|
||||
JIRA_TOKEN=your_jira_api_token_here
|
||||
|
||||
# Confluence Configuration (Optional - only if you want to use Confluence)
|
||||
CONFLUENCE_URL=https://your-company.atlassian.net/wiki
|
||||
CONFLUENCE_USERNAME=your.email@company.com
|
||||
CONFLUENCE_TOKEN=your_confluence_api_token_here
|
||||
|
||||
# Notes:
|
||||
# 1. Get your API token from: https://id.atlassian.com/manage-profile/security/api-tokens
|
||||
# 2. For Atlassian Cloud, you can often use the same API token for both Jira and Confluence
|
||||
# 3. The username should be your Atlassian account email address
|
||||
# 4. Never commit your .env file to version control
|
||||
@@ -1,264 +0,0 @@
|
||||
# Atlassian/Jira Configuration Screenshot Guide
|
||||
|
||||
This guide shows what your configuration should look like at each step.
|
||||
|
||||
## 📋 Configuration File Structure
|
||||
|
||||
Your `mcp_settings.json` should look like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"jira": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"mcp-atlassian",
|
||||
"--jira-url=${JIRA_URL}",
|
||||
"--jira-username=${JIRA_USERNAME}",
|
||||
"--jira-token=${JIRA_TOKEN}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"users": [
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "${ADMIN_PASSWORD_HASH}",
|
||||
"isAdmin": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Security Note:** The `password` field must contain a bcrypt hash, not plain text.
|
||||
|
||||
**To generate a bcrypt hash:**
|
||||
```bash
|
||||
node -e "console.log(require('bcrypt').hashSync('your-password', 10))"
|
||||
```
|
||||
|
||||
**⚠️ CRITICAL SECURITY:**
|
||||
- Never use default credentials in production
|
||||
- Always change the admin password before deploying
|
||||
- Store password hashes, never plain text passwords
|
||||
|
||||
## 📁 File Structure
|
||||
|
||||
Your project should have these files:
|
||||
|
||||
```
|
||||
mcphub/
|
||||
├── mcp_settings.json ← Your configuration file
|
||||
├── .env ← Your environment variables (DO NOT COMMIT!)
|
||||
├── data/ ← Database directory (auto-created)
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 🔐 Environment Variables (.env file)
|
||||
|
||||
```env
|
||||
# .env file content
|
||||
JIRA_URL=https://mycompany.atlassian.net
|
||||
JIRA_USERNAME=myemail@company.com
|
||||
JIRA_TOKEN=ATBBxxxxxxxxxxxxxxxxxxx
|
||||
```
|
||||
|
||||
## 🎯 Expected Dashboard View
|
||||
|
||||
After starting MCPHub, you should see:
|
||||
|
||||
### 1. Server List View
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ MCP Servers │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ✅ jira │
|
||||
│ Status: Connected │
|
||||
│ Type: stdio │
|
||||
│ Command: uvx mcp-atlassian │
|
||||
│ Tools: 15 available │
|
||||
│ │
|
||||
│ [View Details] [Restart] [Stop] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2. Server Details View
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Server: jira │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Status: ✅ Connected │
|
||||
│ Type: stdio │
|
||||
│ Command: uvx │
|
||||
│ │
|
||||
│ Available Tools: │
|
||||
│ • jira_search_issues │
|
||||
│ • jira_get_issue │
|
||||
│ • jira_list_projects │
|
||||
│ • jira_get_project │
|
||||
│ • ... and 11 more │
|
||||
│ │
|
||||
│ Logs: │
|
||||
│ [INFO] Successfully connected to Jira │
|
||||
│ [INFO] Loaded 15 tools │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3. Connection Endpoints
|
||||
|
||||
Once connected, your Jira server is available at:
|
||||
|
||||
| Endpoint | URL | Description |
|
||||
|----------|-----|-------------|
|
||||
| All Servers | `http://localhost:3000/mcp` | Access all configured MCP servers |
|
||||
| Jira Only | `http://localhost:3000/mcp/jira` | Direct access to Jira server |
|
||||
| SSE (Legacy) | `http://localhost:3000/sse/jira` | SSE endpoint for Jira |
|
||||
|
||||
## ✅ Success Indicators
|
||||
|
||||
You'll know the configuration is working when you see:
|
||||
|
||||
1. **✅ Green status indicator** next to the server name
|
||||
2. **"Connected" status** in the server details
|
||||
3. **Tool count showing** (e.g., "15 tools available")
|
||||
4. **No error messages** in the logs
|
||||
5. **Server responds** to health check requests
|
||||
|
||||
## ❌ Common Error Indicators
|
||||
|
||||
### Connection Failed
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ ❌ jira │
|
||||
│ Status: Disconnected │
|
||||
│ Error: Failed to start server │
|
||||
│ Last error: 401 Unauthorized │
|
||||
│ │
|
||||
│ Possible causes: │
|
||||
│ • Invalid API token │
|
||||
│ • Wrong username/email │
|
||||
│ • Incorrect Jira URL │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### UVX Not Found
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ ❌ jira │
|
||||
│ Status: Error │
|
||||
│ Error: Command not found: uvx │
|
||||
│ │
|
||||
│ Solution: Install UV │
|
||||
│ curl -LsSf https://astral.sh/uv/install.sh | sh │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Environment Variable Not Set
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ ⚠️ jira │
|
||||
│ Status: Configuration Error │
|
||||
│ Error: Environment variable JIRA_TOKEN not found │
|
||||
│ │
|
||||
│ Solution: Check your .env file │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 🧪 Testing Your Configuration
|
||||
|
||||
### Test 1: Health Check
|
||||
```bash
|
||||
curl http://localhost:3000/api/health
|
||||
```
|
||||
|
||||
Expected response:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"servers": {
|
||||
"jira": "connected"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Test 2: List Servers
|
||||
```bash
|
||||
curl http://localhost:3000/api/servers
|
||||
```
|
||||
|
||||
Expected response:
|
||||
```json
|
||||
{
|
||||
"servers": [
|
||||
{
|
||||
"name": "jira",
|
||||
"status": "connected",
|
||||
"type": "stdio",
|
||||
"toolCount": 15
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Test 3: MCP Endpoint
|
||||
```bash
|
||||
curl http://localhost:3000/mcp/jira \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"method": "tools/list",
|
||||
"params": {}
|
||||
}'
|
||||
```
|
||||
|
||||
Expected response: List of available Jira tools
|
||||
|
||||
## 📊 Log Messages Explained
|
||||
|
||||
### Successful Startup
|
||||
```
|
||||
[INFO] Loading configuration from mcp_settings.json
|
||||
[INFO] Found 1 MCP server(s) to initialize
|
||||
[INFO] Starting server: jira
|
||||
[INFO] Executing: uvx mcp-atlassian --jira-url=https://...
|
||||
[INFO] Successfully connected client for server: jira
|
||||
[INFO] Successfully listed 15 tools for server: jira
|
||||
[INFO] ✅ Server jira is ready
|
||||
```
|
||||
|
||||
### Connection Issues
|
||||
```
|
||||
[ERROR] Failed to start server: jira
|
||||
[ERROR] Error: spawn uvx ENOENT
|
||||
[WARN] Server jira will retry in 5 seconds
|
||||
```
|
||||
|
||||
### Authentication Issues
|
||||
```
|
||||
[ERROR] Failed to connect to Jira
|
||||
[ERROR] HTTP 401: Unauthorized
|
||||
[ERROR] Please check your API token and credentials
|
||||
```
|
||||
|
||||
## 🔍 Debugging Steps
|
||||
|
||||
If your server shows as disconnected:
|
||||
|
||||
1. **Check logs** in the dashboard or console
|
||||
2. **Verify environment variables** are set correctly
|
||||
3. **Test manually** with uvx:
|
||||
```bash
|
||||
uvx mcp-atlassian --jira-url=https://your-company.atlassian.net --jira-username=your@email.com --jira-token=your_token
|
||||
```
|
||||
4. **Check network connectivity** to Jira
|
||||
5. **Verify API token** is still valid
|
||||
6. **Restart MCPHub** after making changes
|
||||
|
||||
## 📚 Additional Resources
|
||||
|
||||
- [Quick Start Guide](./QUICK_START_JIRA.md)
|
||||
- [Complete Setup Guide](./README_ATLASSIAN_JIRA.md)
|
||||
- [MCPHub Documentation](https://docs.mcphubx.com/)
|
||||
- [Atlassian API Tokens](https://id.atlassian.com/manage-profile/security/api-tokens)
|
||||
@@ -1,134 +0,0 @@
|
||||
# Quick Start: Jira Cloud Integration
|
||||
|
||||
This is a quick 5-minute setup guide for connecting MCPHub to Jira Cloud.
|
||||
|
||||
## ⚡ Quick Setup (5 minutes)
|
||||
|
||||
### Step 1: Get Your Jira API Token (2 minutes)
|
||||
|
||||
1. Go to https://id.atlassian.com/manage-profile/security/api-tokens
|
||||
2. Click **"Create API token"**
|
||||
3. Label it "MCPHub Integration"
|
||||
4. **Copy the token** (you can't see it again!)
|
||||
|
||||
### Step 2: Find Your Jira URL (30 seconds)
|
||||
|
||||
Your Jira URL is what you see in your browser:
|
||||
- Example: `https://mycompany.atlassian.net`
|
||||
- ✅ Include: `https://` protocol
|
||||
- ❌ Don't include: trailing `/` or `/jira`
|
||||
|
||||
### Step 3: Create .env File (1 minute)
|
||||
|
||||
Create a `.env` file in your MCPHub root directory:
|
||||
|
||||
```bash
|
||||
JIRA_URL=https://mycompany.atlassian.net
|
||||
JIRA_USERNAME=myemail@company.com
|
||||
JIRA_TOKEN=paste_your_token_here
|
||||
```
|
||||
|
||||
Replace with your actual values!
|
||||
|
||||
### Step 4: Update mcp_settings.json (1 minute)
|
||||
|
||||
Add this to your `mcp_settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"jira": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"mcp-atlassian",
|
||||
"--jira-url=${JIRA_URL}",
|
||||
"--jira-username=${JIRA_USERNAME}",
|
||||
"--jira-token=${JIRA_TOKEN}"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Install UV & Start MCPHub (1 minute)
|
||||
|
||||
#### Install UV (if not already installed):
|
||||
|
||||
**macOS/Linux:**
|
||||
```bash
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
```powershell
|
||||
irm https://astral.sh/uv/install.ps1 | iex
|
||||
```
|
||||
|
||||
#### Start MCPHub:
|
||||
|
||||
**With Docker:**
|
||||
```bash
|
||||
docker run -p 3000:3000 \
|
||||
--env-file .env \
|
||||
-v ./mcp_settings.json:/app/mcp_settings.json \
|
||||
samanhappy/mcphub
|
||||
```
|
||||
|
||||
**Without Docker:**
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Step 6: Verify Connection (30 seconds)
|
||||
|
||||
1. Open http://localhost:3000
|
||||
2. Login with default credentials (see [README_ATLASSIAN_JIRA.md](./README_ATLASSIAN_JIRA.md#verification) for credentials)
|
||||
|
||||
**⚠️ CRITICAL:** Immediately change the admin password through dashboard Settings → Users
|
||||
|
||||
3. Check dashboard - you should see "jira" server as "Connected" ✅
|
||||
|
||||
## 🎉 That's It!
|
||||
|
||||
You can now use Jira through MCPHub at:
|
||||
- All servers: `http://localhost:3000/mcp`
|
||||
- Jira only: `http://localhost:3000/mcp/jira`
|
||||
|
||||
## 🐛 Common Issues
|
||||
|
||||
### "uvx command not found"
|
||||
```bash
|
||||
# Install UV first (see Step 5)
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
```
|
||||
|
||||
### "401 Unauthorized"
|
||||
- Double-check your API token
|
||||
- Make sure username is your email
|
||||
- Try regenerating the API token
|
||||
|
||||
### Server shows "Disconnected"
|
||||
- Check logs for specific errors
|
||||
- Verify .env file is in the correct location
|
||||
- Ensure no trailing slashes in JIRA_URL
|
||||
|
||||
### "Downloading cryptography" errors
|
||||
- This is usually temporary
|
||||
- Wait and restart MCPHub
|
||||
- Check internet connection
|
||||
|
||||
## 📚 Need More Help?
|
||||
|
||||
See [README_ATLASSIAN_JIRA.md](./README_ATLASSIAN_JIRA.md) for the complete guide with:
|
||||
- Both Jira + Confluence setup
|
||||
- Detailed troubleshooting
|
||||
- Security best practices
|
||||
- Example use cases
|
||||
|
||||
## 🔒 Security Reminder
|
||||
|
||||
- ✅ Never commit `.env` to git
|
||||
- ✅ Keep API tokens secret
|
||||
- ✅ Rotate tokens regularly
|
||||
- ✅ Use different tokens for dev/prod
|
||||
@@ -1,233 +0,0 @@
|
||||
# MCPHub Configuration Examples
|
||||
|
||||
This directory contains example configurations for various MCP servers and use cases.
|
||||
|
||||
## 📁 Directory Contents
|
||||
|
||||
### Atlassian/Jira Configuration
|
||||
|
||||
| File | Description | Best For |
|
||||
|------|-------------|----------|
|
||||
| [QUICK_START_JIRA.md](./QUICK_START_JIRA.md) | 5-minute quick start guide | Getting started fast with Jira Cloud |
|
||||
| [README_ATLASSIAN_JIRA.md](./README_ATLASSIAN_JIRA.md) | Complete setup guide | Comprehensive setup with troubleshooting |
|
||||
| [CONFIGURATION_SCREENSHOT_GUIDE.md](./CONFIGURATION_SCREENSHOT_GUIDE.md) | Visual configuration guide | Understanding the dashboard and logs |
|
||||
| [mcp_settings_atlassian_jira.json](./mcp_settings_atlassian_jira.json) | Basic Jira configuration | Copy-paste configuration template |
|
||||
| [.env.atlassian.example](./.env.atlassian.example) | Environment variables template | Setting up credentials securely |
|
||||
|
||||
### General Configuration Examples
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| [mcp_settings_with_env_vars.json](./mcp_settings_with_env_vars.json) | Environment variable examples for various server types (SSE, HTTP, stdio, OpenAPI) |
|
||||
| [openapi-schema-config.json](./openapi-schema-config.json) | OpenAPI-based MCP server configuration examples |
|
||||
|
||||
## 🚀 Quick Start Guides
|
||||
|
||||
### For Jira Cloud Users
|
||||
|
||||
**New to MCPHub?** Start here: [QUICK_START_JIRA.md](./QUICK_START_JIRA.md)
|
||||
|
||||
This 5-minute guide covers:
|
||||
- ✅ Getting your API token
|
||||
- ✅ Basic configuration
|
||||
- ✅ Starting MCPHub
|
||||
- ✅ Verifying connection
|
||||
|
||||
### For Experienced Users
|
||||
|
||||
**Need detailed setup?** See: [README_ATLASSIAN_JIRA.md](./README_ATLASSIAN_JIRA.md)
|
||||
|
||||
This comprehensive guide includes:
|
||||
- 📋 Both Jira and Confluence configuration
|
||||
- 🔧 Multiple installation methods (uvx, python, docker)
|
||||
- 🐛 Extensive troubleshooting section
|
||||
- 🔒 Security best practices
|
||||
- 💡 Example use cases
|
||||
|
||||
### Need Visual Guidance?
|
||||
|
||||
**Want to see what to expect?** Check: [CONFIGURATION_SCREENSHOT_GUIDE.md](./CONFIGURATION_SCREENSHOT_GUIDE.md)
|
||||
|
||||
This visual guide shows:
|
||||
- 📊 Expected dashboard views
|
||||
- ✅ Success indicators
|
||||
- ❌ Common error messages
|
||||
- 🧪 Test commands and expected outputs
|
||||
|
||||
## 📝 Configuration Templates
|
||||
|
||||
### Jira Cloud Only
|
||||
|
||||
Minimal configuration for Jira Cloud:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"jira": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"mcp-atlassian",
|
||||
"--jira-url=${JIRA_URL}",
|
||||
"--jira-username=${JIRA_USERNAME}",
|
||||
"--jira-token=${JIRA_TOKEN}"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Jira + Confluence
|
||||
|
||||
Combined configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"atlassian": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"mcp-atlassian",
|
||||
"--jira-url=${JIRA_URL}",
|
||||
"--jira-username=${JIRA_USERNAME}",
|
||||
"--jira-token=${JIRA_TOKEN}",
|
||||
"--confluence-url=${CONFLUENCE_URL}",
|
||||
"--confluence-username=${CONFLUENCE_USERNAME}",
|
||||
"--confluence-token=${CONFLUENCE_TOKEN}"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Create a `.env` file based on [.env.atlassian.example](./.env.atlassian.example):
|
||||
|
||||
```env
|
||||
JIRA_URL=https://your-company.atlassian.net
|
||||
JIRA_USERNAME=your.email@company.com
|
||||
JIRA_TOKEN=your_api_token_here
|
||||
```
|
||||
|
||||
## 🔐 Security Best Practices
|
||||
|
||||
1. **Never commit sensitive data**
|
||||
- ✅ Use `.env` files for credentials
|
||||
- ✅ Add `.env` to `.gitignore`
|
||||
- ✅ Use environment variable substitution: `${VAR_NAME}`
|
||||
|
||||
2. **Protect your API tokens**
|
||||
- ✅ Rotate tokens regularly
|
||||
- ✅ Use different tokens for dev/staging/prod
|
||||
- ✅ Revoke unused tokens immediately
|
||||
|
||||
3. **Secure your configuration**
|
||||
- ✅ Restrict file permissions on `.env` files
|
||||
- ✅ Use secrets management in production
|
||||
- ✅ Audit token usage regularly
|
||||
|
||||
## 🛠️ Common Use Cases
|
||||
|
||||
### Case 1: Development Environment
|
||||
|
||||
**Scenario**: Testing Jira integration locally
|
||||
|
||||
**Files needed**:
|
||||
- `mcp_settings_atlassian_jira.json` → Copy to `mcp_settings.json`
|
||||
- `.env.atlassian.example` → Copy to `.env` and fill in values
|
||||
|
||||
**Steps**:
|
||||
1. Copy template files
|
||||
2. Fill in your credentials
|
||||
3. Run `pnpm dev`
|
||||
|
||||
### Case 2: Production Deployment
|
||||
|
||||
**Scenario**: Deploying MCPHub with Jira to production
|
||||
|
||||
**Approach**:
|
||||
- Use environment variables in configuration
|
||||
- Store secrets in your deployment platform's secrets manager
|
||||
- Use Docker with environment file: `docker run --env-file .env ...`
|
||||
|
||||
### Case 3: Multiple Environments
|
||||
|
||||
**Scenario**: Separate dev, staging, prod configurations
|
||||
|
||||
**Structure**:
|
||||
```
|
||||
.env.development
|
||||
.env.staging
|
||||
.env.production
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```bash
|
||||
# Development
|
||||
docker run --env-file .env.development ...
|
||||
|
||||
# Staging
|
||||
docker run --env-file .env.staging ...
|
||||
|
||||
# Production
|
||||
docker run --env-file .env.production ...
|
||||
```
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Quick Diagnostics
|
||||
|
||||
| Symptom | Likely Cause | Quick Fix |
|
||||
|---------|--------------|-----------|
|
||||
| "uvx command not found" | UV not installed | Install UV: `curl -LsSf https://astral.sh/uv/install.sh | sh` |
|
||||
| "401 Unauthorized" | Wrong API token | Regenerate token at Atlassian settings |
|
||||
| Server "Disconnected" | Missing env vars | Check `.env` file exists and has values |
|
||||
| "Downloading cryptography" errors | Network/Python issue | Wait and retry, check internet connection |
|
||||
|
||||
### Detailed Troubleshooting
|
||||
|
||||
For comprehensive troubleshooting steps, see:
|
||||
- [README_ATLASSIAN_JIRA.md - Troubleshooting Section](./README_ATLASSIAN_JIRA.md#troubleshooting)
|
||||
- [CONFIGURATION_SCREENSHOT_GUIDE.md - Error Indicators](./CONFIGURATION_SCREENSHOT_GUIDE.md#-common-error-indicators)
|
||||
|
||||
## 📚 Additional Resources
|
||||
|
||||
### Official Documentation
|
||||
|
||||
- [MCPHub Documentation](https://docs.mcphubx.com/)
|
||||
- [MCPHub GitHub Repository](https://github.com/samanhappy/mcphub)
|
||||
- [MCP Protocol Specification](https://modelcontextprotocol.io/)
|
||||
|
||||
### Atlassian Resources
|
||||
|
||||
- [Atlassian API Tokens](https://id.atlassian.com/manage-profile/security/api-tokens)
|
||||
- [Jira Cloud REST API](https://developer.atlassian.com/cloud/jira/platform/rest/v3/)
|
||||
- [Confluence Cloud REST API](https://developer.atlassian.com/cloud/confluence/rest/v2/)
|
||||
- [MCP Atlassian Server](https://github.com/sooperset/mcp-atlassian)
|
||||
|
||||
### Community Support
|
||||
|
||||
- [MCPHub Discord Community](https://discord.gg/qMKNsn5Q)
|
||||
- [GitHub Issues](https://github.com/samanhappy/mcphub/issues)
|
||||
- [GitHub Discussions](https://github.com/samanhappy/mcphub/discussions)
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Have a useful configuration example? We'd love to include it!
|
||||
|
||||
1. Create your example configuration
|
||||
2. Add documentation explaining the setup
|
||||
3. Submit a pull request to the repository
|
||||
|
||||
Example contributions:
|
||||
- Configuration for other MCP servers
|
||||
- Multi-server setup examples
|
||||
- Docker Compose configurations
|
||||
- Kubernetes deployment examples
|
||||
- CI/CD integration examples
|
||||
|
||||
## 📄 License
|
||||
|
||||
All examples in this directory are provided under the same license as MCPHub (Apache 2.0).
|
||||
|
||||
Feel free to use, modify, and distribute these examples as needed for your projects.
|
||||
@@ -1,319 +0,0 @@
|
||||
# Atlassian Jira Cloud MCP Server Configuration
|
||||
|
||||
This guide provides detailed instructions for configuring the MCP Atlassian server to connect to Jira Cloud in MCPHub.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Jira Cloud Account**: You need access to a Jira Cloud instance
|
||||
2. **API Token**: Generate an API token from your Atlassian account
|
||||
3. **Python/UV**: The mcp-atlassian server requires Python and `uvx` (UV package manager)
|
||||
|
||||
## Step 1: Generate Jira API Token
|
||||
|
||||
1. Go to [Atlassian Account Settings](https://id.atlassian.com/manage-profile/security/api-tokens)
|
||||
2. Click **"Create API token"**
|
||||
3. Give it a label (e.g., "MCPHub Integration")
|
||||
4. Copy the generated token (you won't be able to see it again!)
|
||||
5. Save it securely
|
||||
|
||||
## Step 2: Get Your Jira Information
|
||||
|
||||
You'll need the following information:
|
||||
|
||||
- **JIRA_URL**: Your Jira Cloud URL (e.g., `https://your-company.atlassian.net`)
|
||||
- **JIRA_USERNAME**: Your Atlassian account email (e.g., `your.email@company.com`)
|
||||
- **JIRA_TOKEN**: The API token you generated in Step 1
|
||||
|
||||
## Step 3: Set Environment Variables
|
||||
|
||||
Create or update your `.env` file in the MCPHub root directory:
|
||||
|
||||
```bash
|
||||
# Jira Configuration
|
||||
JIRA_URL=https://your-company.atlassian.net
|
||||
JIRA_USERNAME=your.email@company.com
|
||||
JIRA_TOKEN=your_api_token_here
|
||||
```
|
||||
|
||||
**Important Security Note**: Never commit your `.env` file to version control. It should be listed in `.gitignore`.
|
||||
|
||||
## Step 4: Configure MCPHub
|
||||
|
||||
### Option 1: Using Environment Variables (Recommended)
|
||||
|
||||
Update your `mcp_settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"atlassian": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"mcp-atlassian",
|
||||
"--jira-url=${JIRA_URL}",
|
||||
"--jira-username=${JIRA_USERNAME}",
|
||||
"--jira-token=${JIRA_TOKEN}"
|
||||
],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Option 2: Direct Configuration (Not Recommended)
|
||||
|
||||
If you prefer not to use environment variables (less secure):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"atlassian": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"mcp-atlassian",
|
||||
"--jira-url=https://your-company.atlassian.net",
|
||||
"--jira-username=your.email@company.com",
|
||||
"--jira-token=your_api_token_here"
|
||||
],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Option 3: Jira Only (Without Confluence)
|
||||
|
||||
If you only want to use Jira and not Confluence:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"jira": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"mcp-atlassian",
|
||||
"--jira-url=${JIRA_URL}",
|
||||
"--jira-username=${JIRA_USERNAME}",
|
||||
"--jira-token=${JIRA_TOKEN}"
|
||||
],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Option 4: Both Jira and Confluence
|
||||
|
||||
To use both Jira and Confluence, you'll need API tokens for both:
|
||||
|
||||
```bash
|
||||
# .env file
|
||||
JIRA_URL=https://your-company.atlassian.net
|
||||
JIRA_USERNAME=your.email@company.com
|
||||
JIRA_TOKEN=your_jira_api_token
|
||||
|
||||
CONFLUENCE_URL=https://your-company.atlassian.net/wiki
|
||||
CONFLUENCE_USERNAME=your.email@company.com
|
||||
CONFLUENCE_TOKEN=your_confluence_api_token
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"atlassian": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"mcp-atlassian",
|
||||
"--confluence-url=${CONFLUENCE_URL}",
|
||||
"--confluence-username=${CONFLUENCE_USERNAME}",
|
||||
"--confluence-token=${CONFLUENCE_TOKEN}",
|
||||
"--jira-url=${JIRA_URL}",
|
||||
"--jira-username=${JIRA_USERNAME}",
|
||||
"--jira-token=${JIRA_TOKEN}"
|
||||
],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: For Atlassian Cloud, you can often use the same API token for both Jira and Confluence.
|
||||
|
||||
## Step 5: Install UV (if not already installed)
|
||||
|
||||
The mcp-atlassian server uses `uvx` to run. Install UV if you haven't already:
|
||||
|
||||
### On macOS/Linux:
|
||||
```bash
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
```
|
||||
|
||||
### On Windows:
|
||||
```powershell
|
||||
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
|
||||
```
|
||||
|
||||
### Using pip:
|
||||
```bash
|
||||
pip install uv
|
||||
```
|
||||
|
||||
## Step 6: Start MCPHub
|
||||
|
||||
### Using Docker:
|
||||
```bash
|
||||
docker run -p 3000:3000 \
|
||||
-v ./mcp_settings.json:/app/mcp_settings.json \
|
||||
-v ./data:/app/data \
|
||||
-e JIRA_URL="${JIRA_URL}" \
|
||||
-e JIRA_USERNAME="${JIRA_USERNAME}" \
|
||||
-e JIRA_TOKEN="${JIRA_TOKEN}" \
|
||||
samanhappy/mcphub
|
||||
```
|
||||
|
||||
### Using Development Mode:
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Using Production Mode:
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm build
|
||||
pnpm start
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
After starting MCPHub:
|
||||
|
||||
1. Open `http://localhost:3000` in your browser
|
||||
2. Log in with default credentials: `admin` / `admin123`
|
||||
|
||||
**⚠️ SECURITY WARNING:** Change the default admin password immediately in production!
|
||||
|
||||
**To change the password:**
|
||||
- Option 1: Use the dashboard after logging in (Settings → Users → Change Password)
|
||||
- Option 2: Generate a bcrypt hash and update `mcp_settings.json`:
|
||||
```bash
|
||||
node -e "console.log(require('bcrypt').hashSync('your-new-password', 10))"
|
||||
```
|
||||
|
||||
3. Check the dashboard to see if the Atlassian server is connected
|
||||
4. Look for the server status - it should show as "Connected" or "Running"
|
||||
5. Check the logs for any connection errors
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Error: "uvx command not found"
|
||||
|
||||
**Solution**: Install UV as described in Step 5 above.
|
||||
|
||||
### Error: "Traceback (most recent call last): File ... mcp-atlassian"
|
||||
|
||||
This error usually indicates:
|
||||
1. Missing or incorrect API credentials
|
||||
2. Network connectivity issues
|
||||
3. Python dependency issues
|
||||
|
||||
**Solutions**:
|
||||
- Verify your API token is correct
|
||||
- Ensure your Jira URL doesn't have trailing slashes
|
||||
- Check that your username is the email address you use for Atlassian
|
||||
- Verify network connectivity to your Jira instance
|
||||
- Try regenerating your API token
|
||||
|
||||
### Error: "401 Unauthorized"
|
||||
|
||||
**Solution**:
|
||||
- Double-check your API token is correct
|
||||
- Ensure you're using the email address associated with your Atlassian account
|
||||
- Regenerate your API token if needed
|
||||
|
||||
### Error: "403 Forbidden"
|
||||
|
||||
**Solution**:
|
||||
- Check that your account has appropriate permissions in Jira
|
||||
- Verify your Jira administrator hasn't restricted API access
|
||||
|
||||
### Error: Downloading cryptography errors
|
||||
|
||||
**Solution**:
|
||||
- This is usually a transient network or Python package installation issue
|
||||
- Wait a moment and try restarting MCPHub
|
||||
- Ensure you have a stable internet connection
|
||||
- If the issue persists, try installing mcp-atlassian manually:
|
||||
```bash
|
||||
uvx mcp-atlassian --help
|
||||
```
|
||||
|
||||
### Server shows as "Disconnected"
|
||||
|
||||
**Solution**:
|
||||
1. Check MCPHub logs for specific error messages
|
||||
2. Verify all environment variables are set correctly
|
||||
3. Test the connection manually:
|
||||
```bash
|
||||
uvx mcp-atlassian \
|
||||
--jira-url=https://your-company.atlassian.net \
|
||||
--jira-username=your.email@company.com \
|
||||
--jira-token=your_token
|
||||
```
|
||||
|
||||
## Using the Jira MCP Server
|
||||
|
||||
Once connected, you can use the Jira MCP server to:
|
||||
|
||||
- **Search Issues**: Query Jira issues using JQL
|
||||
- **Read Issues**: Get detailed information about specific issues
|
||||
- **Access Projects**: List and retrieve project metadata
|
||||
- **View Comments**: Read issue comments and discussions
|
||||
- **Get Transitions**: Check available status transitions for issues
|
||||
|
||||
Access the server through:
|
||||
- **All servers**: `http://localhost:3000/mcp`
|
||||
- **Specific server**: `http://localhost:3000/mcp/atlassian`
|
||||
- **Server groups**: `http://localhost:3000/mcp/{group}` (if configured)
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [MCP Atlassian GitHub Repository](https://github.com/sooperset/mcp-atlassian)
|
||||
- [Atlassian API Token Documentation](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/)
|
||||
- [Jira Cloud REST API](https://developer.atlassian.com/cloud/jira/platform/rest/v3/)
|
||||
- [MCPHub Documentation](https://docs.mcphubx.com/)
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. ✅ **Always use environment variables** for sensitive credentials
|
||||
2. ✅ **Never commit `.env` files** to version control
|
||||
3. ✅ **Rotate API tokens** regularly
|
||||
4. ✅ **Use separate tokens** for different environments (dev, staging, prod)
|
||||
5. ✅ **Restrict API token permissions** to only what's needed
|
||||
6. ✅ **Monitor token usage** in Atlassian account settings
|
||||
7. ✅ **Revoke unused tokens** immediately
|
||||
|
||||
## Example Use Cases
|
||||
|
||||
### Example 1: Search for Issues
|
||||
Query: "List all open bugs assigned to me"
|
||||
- Tool: `jira_search_issues`
|
||||
- JQL: `project = MYPROJECT AND status = Open AND assignee = currentUser() AND type = Bug`
|
||||
|
||||
### Example 2: Get Issue Details
|
||||
Query: "Show me details of issue PROJ-123"
|
||||
- Tool: `jira_get_issue`
|
||||
- Issue Key: `PROJ-123`
|
||||
|
||||
### Example 3: List Projects
|
||||
Query: "What Jira projects do I have access to?"
|
||||
- Tool: `jira_list_projects`
|
||||
|
||||
## Need Help?
|
||||
|
||||
If you're still experiencing issues:
|
||||
|
||||
1. Check the [MCPHub Discord community](https://discord.gg/qMKNsn5Q)
|
||||
2. Review [MCPHub GitHub Issues](https://github.com/samanhappy/mcphub/issues)
|
||||
3. Check [mcp-atlassian Issues](https://github.com/sooperset/mcp-atlassian/issues)
|
||||
4. Contact your Jira administrator for API access questions
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"atlassian": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"mcp-atlassian",
|
||||
"--jira-url=${JIRA_URL}",
|
||||
"--jira-username=${JIRA_USERNAME}",
|
||||
"--jira-token=${JIRA_TOKEN}"
|
||||
],
|
||||
"env": {}
|
||||
}
|
||||
},
|
||||
"users": [
|
||||
{
|
||||
"username": "admin",
|
||||
"_comment": "Password must be a bcrypt hash. Generate with: node -e \"console.log(require('bcrypt').hashSync('your-password', 10))\"",
|
||||
"password": "${ADMIN_PASSWORD_HASH}",
|
||||
"isAdmin": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Group, Server, IGroupServerConfig } from '@/types'
|
||||
import { Edit, Trash, Copy, Check, Link, FileCode, DropdownIcon, Wrench } from '@/components/icons/LucideIcons'
|
||||
import { Edit, Trash, Copy, Check, Link, FileCode, DropdownIcon, Wrench, Download } from '@/components/icons/LucideIcons'
|
||||
import DeleteDialog from '@/components/ui/DeleteDialog'
|
||||
import { useToast } from '@/contexts/ToastContext'
|
||||
import { useSettingsData } from '@/hooks/useSettingsData'
|
||||
import InstallToClientDialog from '@/components/InstallToClientDialog'
|
||||
|
||||
interface GroupCardProps {
|
||||
group: Group
|
||||
@@ -26,6 +27,7 @@ const GroupCard = ({
|
||||
const [copied, setCopied] = useState(false)
|
||||
const [showCopyDropdown, setShowCopyDropdown] = useState(false)
|
||||
const [expandedServer, setExpandedServer] = useState<string | null>(null)
|
||||
const [showInstallDialog, setShowInstallDialog] = useState(false)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
@@ -50,6 +52,10 @@ const GroupCard = ({
|
||||
setShowDeleteDialog(true)
|
||||
}
|
||||
|
||||
const handleInstall = () => {
|
||||
setShowInstallDialog(true)
|
||||
}
|
||||
|
||||
const handleConfirmDelete = () => {
|
||||
onDelete(group.id)
|
||||
setShowDeleteDialog(false)
|
||||
@@ -183,6 +189,13 @@ const GroupCard = ({
|
||||
<div className="bg-blue-50 text-blue-700 px-3 py-1 rounded-full text-sm btn-secondary">
|
||||
{t('groups.serverCount', { count: group.servers.length })}
|
||||
</div>
|
||||
<button
|
||||
onClick={handleInstall}
|
||||
className="text-purple-500 hover:text-purple-700"
|
||||
title={t('install.installButton')}
|
||||
>
|
||||
<Download size={18} />
|
||||
</button>
|
||||
<button
|
||||
onClick={handleEdit}
|
||||
className="text-gray-500 hover:text-gray-700"
|
||||
@@ -277,6 +290,20 @@ const GroupCard = ({
|
||||
serverName={group.name}
|
||||
isGroup={true}
|
||||
/>
|
||||
{showInstallDialog && installConfig && (
|
||||
<InstallToClientDialog
|
||||
groupId={group.id}
|
||||
groupName={group.name}
|
||||
config={{
|
||||
type: 'streamable-http',
|
||||
url: `${installConfig.protocol}://${installConfig.baseUrl}${installConfig.basePath}/mcp/${group.id}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${installConfig.token}`
|
||||
}
|
||||
}}
|
||||
onClose={() => setShowInstallDialog(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
219
frontend/src/components/InstallToClientDialog.tsx
Normal file
219
frontend/src/components/InstallToClientDialog.tsx
Normal file
@@ -0,0 +1,219 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Copy, Check } from 'lucide-react';
|
||||
|
||||
interface InstallToClientDialogProps {
|
||||
serverName?: string;
|
||||
groupId?: string;
|
||||
groupName?: string;
|
||||
config: any;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const InstallToClientDialog: React.FC<InstallToClientDialogProps> = ({
|
||||
serverName,
|
||||
groupId,
|
||||
groupName,
|
||||
config,
|
||||
onClose,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [activeTab, setActiveTab] = useState<'cursor' | 'claude-code' | 'claude-desktop'>('cursor');
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
// Generate configuration based on the active tab
|
||||
const generateConfig = () => {
|
||||
if (groupId) {
|
||||
// For groups, generate group-based configuration
|
||||
return {
|
||||
mcpServers: {
|
||||
[`mcphub-${groupId}`]: config,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// For individual servers
|
||||
return {
|
||||
mcpServers: {
|
||||
[serverName || 'mcp-server']: config,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const configJson = JSON.stringify(generateConfig(), null, 2);
|
||||
|
||||
const handleCopyConfig = () => {
|
||||
navigator.clipboard.writeText(configJson).then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
});
|
||||
};
|
||||
|
||||
// Generate deep link for Cursor (if supported in the future)
|
||||
const handleInstallToCursor = () => {
|
||||
// For now, just copy the config since deep linking may not be widely supported
|
||||
handleCopyConfig();
|
||||
// In the future, this could be:
|
||||
// const deepLink = `cursor://install-mcp?config=${encodeURIComponent(configJson)}`;
|
||||
// window.open(deepLink, '_blank');
|
||||
};
|
||||
|
||||
const getStepsList = () => {
|
||||
const displayName = groupName || serverName || 'MCP server';
|
||||
|
||||
switch (activeTab) {
|
||||
case 'cursor':
|
||||
return [
|
||||
t('install.step1Cursor'),
|
||||
t('install.step2Cursor'),
|
||||
t('install.step3Cursor'),
|
||||
t('install.step4Cursor', { name: displayName }),
|
||||
];
|
||||
case 'claude-code':
|
||||
return [
|
||||
t('install.step1ClaudeCode'),
|
||||
t('install.step2ClaudeCode'),
|
||||
t('install.step3ClaudeCode'),
|
||||
t('install.step4ClaudeCode', { name: displayName }),
|
||||
];
|
||||
case 'claude-desktop':
|
||||
return [
|
||||
t('install.step1ClaudeDesktop'),
|
||||
t('install.step2ClaudeDesktop'),
|
||||
t('install.step3ClaudeDesktop'),
|
||||
t('install.step4ClaudeDesktop', { name: displayName }),
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-hidden">
|
||||
<div className="flex justify-between items-center p-6 border-b">
|
||||
<h2 className="text-2xl font-bold text-gray-900">
|
||||
{groupId
|
||||
? t('install.installGroupTitle', { name: groupName })
|
||||
: t('install.installServerTitle', { name: serverName })}
|
||||
</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-gray-500 hover:text-gray-700 transition-colors duration-200"
|
||||
aria-label={t('common.close')}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="overflow-y-auto max-h-[calc(90vh-140px)]">
|
||||
{/* Tab Navigation */}
|
||||
<div className="border-b border-gray-200 px-6 pt-4">
|
||||
<nav className="-mb-px flex space-x-4">
|
||||
<button
|
||||
onClick={() => setActiveTab('cursor')}
|
||||
className={`py-2 px-1 border-b-2 font-medium text-sm transition-colors duration-200 ${
|
||||
activeTab === 'cursor'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
Cursor
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('claude-code')}
|
||||
className={`py-2 px-1 border-b-2 font-medium text-sm transition-colors duration-200 ${
|
||||
activeTab === 'claude-code'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
Claude Code
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('claude-desktop')}
|
||||
className={`py-2 px-1 border-b-2 font-medium text-sm transition-colors duration-200 ${
|
||||
activeTab === 'claude-desktop'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
Claude Desktop
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Configuration Display */}
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<h3 className="text-sm font-medium text-gray-700">{t('install.configCode')}</h3>
|
||||
<button
|
||||
onClick={handleCopyConfig}
|
||||
className="flex items-center space-x-2 px-3 py-1.5 bg-blue-100 text-blue-800 rounded hover:bg-blue-200 transition-colors duration-200 text-sm"
|
||||
>
|
||||
{copied ? <Check size={16} /> : <Copy size={16} />}
|
||||
<span>{copied ? t('common.copied') : t('install.copyConfig')}</span>
|
||||
</button>
|
||||
</div>
|
||||
<pre className="bg-white border border-gray-200 rounded p-4 text-xs overflow-x-auto">
|
||||
<code>{configJson}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
{/* Installation Steps */}
|
||||
<div className="bg-blue-50 rounded-lg p-4">
|
||||
<h3 className="text-sm font-semibold text-blue-900 mb-3">{t('install.steps')}</h3>
|
||||
<ol className="space-y-3">
|
||||
{getStepsList().map((step, index) => (
|
||||
<li key={index} className="flex items-start space-x-3">
|
||||
<span className="flex-shrink-0 w-6 h-6 bg-blue-500 text-white rounded-full flex items-center justify-center text-xs font-medium">
|
||||
{index + 1}
|
||||
</span>
|
||||
<span className="text-sm text-blue-900 pt-0.5">{step}</span>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex justify-between items-center p-6 border-t bg-gray-50">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 border border-gray-300 text-gray-700 rounded hover:bg-gray-100 transition-colors duration-200"
|
||||
>
|
||||
{t('common.close')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleInstallToCursor}
|
||||
className="px-6 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors duration-200 flex items-center space-x-2"
|
||||
>
|
||||
<Copy size={16} />
|
||||
<span>
|
||||
{activeTab === 'cursor' && t('install.installToCursor', { name: groupName || serverName })}
|
||||
{activeTab === 'claude-code' && t('install.installToClaudeCode', { name: groupName || serverName })}
|
||||
{activeTab === 'claude-desktop' && t('install.installToClaudeDesktop', { name: groupName || serverName })}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InstallToClientDialog;
|
||||
@@ -1,13 +1,14 @@
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Server } from '@/types';
|
||||
import { ChevronDown, ChevronRight, AlertCircle, Copy, Check } from 'lucide-react';
|
||||
import { ChevronDown, ChevronRight, AlertCircle, Copy, Check, Download } from 'lucide-react';
|
||||
import { StatusBadge } from '@/components/ui/Badge';
|
||||
import ToolCard from '@/components/ui/ToolCard';
|
||||
import PromptCard from '@/components/ui/PromptCard';
|
||||
import DeleteDialog from '@/components/ui/DeleteDialog';
|
||||
import { useToast } from '@/contexts/ToastContext';
|
||||
import { useSettingsData } from '@/hooks/useSettingsData';
|
||||
import InstallToClientDialog from '@/components/InstallToClientDialog';
|
||||
|
||||
interface ServerCardProps {
|
||||
server: Server;
|
||||
@@ -25,6 +26,7 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle, onRefresh }: ServerCar
|
||||
const [isToggling, setIsToggling] = useState(false);
|
||||
const [showErrorPopover, setShowErrorPopover] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [showInstallDialog, setShowInstallDialog] = useState(false);
|
||||
const errorPopoverRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -52,6 +54,11 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle, onRefresh }: ServerCar
|
||||
onEdit(server);
|
||||
};
|
||||
|
||||
const handleInstall = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
setShowInstallDialog(true);
|
||||
};
|
||||
|
||||
const handleToggle = async (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (isToggling || !onToggle) return;
|
||||
@@ -310,6 +317,13 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle, onRefresh }: ServerCar
|
||||
<button onClick={handleCopyServerConfig} className={`px-3 py-1 btn-secondary`}>
|
||||
{t('server.copy')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleInstall}
|
||||
className="px-3 py-1 bg-purple-100 text-purple-800 rounded hover:bg-purple-200 text-sm btn-primary flex items-center space-x-1"
|
||||
>
|
||||
<Download size={14} />
|
||||
<span>{t('install.installButton')}</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleEdit}
|
||||
className="px-3 py-1 bg-blue-100 text-blue-800 rounded hover:bg-blue-200 text-sm btn-primary"
|
||||
@@ -398,6 +412,13 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle, onRefresh }: ServerCar
|
||||
onConfirm={handleConfirmDelete}
|
||||
serverName={server.name}
|
||||
/>
|
||||
{showInstallDialog && server.config && (
|
||||
<InstallToClientDialog
|
||||
serverName={server.name}
|
||||
config={server.config}
|
||||
onClose={() => setShowInstallDialog(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,7 +17,8 @@ import {
|
||||
Link,
|
||||
FileCode,
|
||||
ChevronDown as DropdownIcon,
|
||||
Wrench
|
||||
Wrench,
|
||||
Download
|
||||
} from 'lucide-react'
|
||||
|
||||
export {
|
||||
@@ -39,7 +40,8 @@ export {
|
||||
Link,
|
||||
FileCode,
|
||||
DropdownIcon,
|
||||
Wrench
|
||||
Wrench,
|
||||
Download
|
||||
}
|
||||
|
||||
const LucideIcons = {
|
||||
|
||||
@@ -8,6 +8,7 @@ import EditServerForm from '@/components/EditServerForm';
|
||||
import { useServerData } from '@/hooks/useServerData';
|
||||
import DxtUploadForm from '@/components/DxtUploadForm';
|
||||
import JSONImportForm from '@/components/JSONImportForm';
|
||||
import { apiGet } from '@/utils/fetchInterceptor';
|
||||
|
||||
const ServersPage: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -27,6 +28,10 @@ const ServersPage: React.FC = () => {
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const [showDxtUpload, setShowDxtUpload] = useState(false);
|
||||
const [showJsonImport, setShowJsonImport] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [similarityThreshold, setSimilarityThreshold] = useState(0.65);
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const [searchResults, setSearchResults] = useState<Server[] | null>(null);
|
||||
|
||||
const handleEditClick = async (server: Server) => {
|
||||
const fullServerData = await handleServerEdit(server);
|
||||
@@ -63,6 +68,31 @@ const ServersPage: React.FC = () => {
|
||||
triggerRefresh();
|
||||
};
|
||||
|
||||
const handleSemanticSearch = async () => {
|
||||
if (!searchQuery.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSearching(true);
|
||||
try {
|
||||
const result = await apiGet(`/servers/search?query=${encodeURIComponent(searchQuery)}&threshold=${similarityThreshold}`);
|
||||
if (result.success && result.data) {
|
||||
setSearchResults(result.data.servers);
|
||||
} else {
|
||||
setError(result.message || 'Search failed');
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Search failed');
|
||||
} finally {
|
||||
setIsSearching(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClearSearch = () => {
|
||||
setSearchQuery('');
|
||||
setSearchResults(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
@@ -116,6 +146,72 @@ const ServersPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Semantic Search Section */}
|
||||
<div className="bg-white shadow rounded-lg p-6 mb-6 page-card">
|
||||
<div className="space-y-4">
|
||||
<div className="flex space-x-4">
|
||||
<div className="flex-grow">
|
||||
<input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && handleSemanticSearch()}
|
||||
placeholder={t('pages.servers.semanticSearchPlaceholder')}
|
||||
className="shadow appearance-none border border-gray-200 rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline form-input"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleSemanticSearch}
|
||||
disabled={isSearching || !searchQuery.trim()}
|
||||
className="px-6 py-2 bg-blue-100 text-blue-800 rounded hover:bg-blue-200 flex items-center btn-primary transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isSearching ? (
|
||||
<svg className="animate-spin h-4 w-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
) : (
|
||||
<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="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clipRule="evenodd" />
|
||||
</svg>
|
||||
)}
|
||||
{t('pages.servers.searchButton')}
|
||||
</button>
|
||||
{searchResults && (
|
||||
<button
|
||||
onClick={handleClearSearch}
|
||||
className="border border-gray-300 text-gray-700 font-medium py-2 px-4 rounded hover:bg-gray-50 btn-secondary transition-all duration-200"
|
||||
>
|
||||
{t('pages.servers.clearSearch')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<label className="text-sm text-gray-700 font-medium min-w-max">{t('pages.servers.similarityThreshold')}: {similarityThreshold.toFixed(2)}</label>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.05"
|
||||
value={similarityThreshold}
|
||||
onChange={(e) => setSimilarityThreshold(parseFloat(e.target.value))}
|
||||
className="flex-grow h-2 bg-blue-200 rounded-lg appearance-none cursor-pointer"
|
||||
/>
|
||||
<span className="text-xs text-gray-500">{t('pages.servers.similarityThresholdHelp')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{searchResults && (
|
||||
<div className="mb-4 bg-blue-50 border-l-4 border-blue-500 p-4 rounded">
|
||||
<p className="text-blue-800">
|
||||
{searchResults.length > 0
|
||||
? t('pages.servers.searchResults', { count: searchResults.length })
|
||||
: t('pages.servers.noSearchResults')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="mb-6 bg-red-50 border-l-4 border-red-500 p-4 rounded shadow-sm error-box">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -145,13 +241,13 @@ const ServersPage: React.FC = () => {
|
||||
<p className="text-gray-600">{t('app.loading')}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : servers.length === 0 ? (
|
||||
) : (searchResults ? searchResults : servers).length === 0 ? (
|
||||
<div className="bg-white shadow rounded-lg p-6 empty-state">
|
||||
<p className="text-gray-600">{t('app.noServers')}</p>
|
||||
<p className="text-gray-600">{searchResults ? t('pages.servers.noSearchResults') : t('app.noServers')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{servers.map((server, index) => (
|
||||
{(searchResults || servers).map((server, index) => (
|
||||
<ServerCard
|
||||
key={index}
|
||||
server={server}
|
||||
|
||||
@@ -268,7 +268,15 @@
|
||||
"recentServers": "Recent Servers"
|
||||
},
|
||||
"servers": {
|
||||
"title": "Servers Management"
|
||||
"title": "Servers Management",
|
||||
"semanticSearch": "Intelligent search for tools...",
|
||||
"semanticSearchPlaceholder": "Describe the functionality you need, e.g.: maps, weather, file processing",
|
||||
"similarityThreshold": "Similarity Threshold",
|
||||
"similarityThresholdHelp": "Higher values return more precise results, lower values return broader matches",
|
||||
"searchButton": "Search",
|
||||
"clearSearch": "Clear Search",
|
||||
"searchResults": "Found {{count}} matching server(s)",
|
||||
"noSearchResults": "No matching servers found"
|
||||
},
|
||||
"groups": {
|
||||
"title": "Group Management"
|
||||
@@ -743,5 +751,28 @@
|
||||
"internalError": "Internal Error",
|
||||
"internalErrorMessage": "An unexpected error occurred while processing the OAuth callback.",
|
||||
"closeWindow": "Close Window"
|
||||
},
|
||||
"install": {
|
||||
"installServerTitle": "Install Server to {{name}}",
|
||||
"installGroupTitle": "Install Group {{name}}",
|
||||
"configCode": "Configuration Code",
|
||||
"copyConfig": "Copy Configuration",
|
||||
"steps": "Installation Steps",
|
||||
"step1Cursor": "Copy the configuration code above",
|
||||
"step2Cursor": "Open Cursor, go to Settings > Features > MCP",
|
||||
"step3Cursor": "Click 'Add New MCP Server' to add a new server",
|
||||
"step4Cursor": "Paste the configuration in the appropriate location and restart Cursor",
|
||||
"step1ClaudeCode": "Copy the configuration code above",
|
||||
"step2ClaudeCode": "Open Claude Code, go to Settings > Features > MCP",
|
||||
"step3ClaudeCode": "Click 'Add New MCP Server' to add a new server",
|
||||
"step4ClaudeCode": "Paste the configuration in the appropriate location and restart Claude Code",
|
||||
"step1ClaudeDesktop": "Copy the configuration code above",
|
||||
"step2ClaudeDesktop": "Open Claude Desktop, go to Settings > Developer",
|
||||
"step3ClaudeDesktop": "Click 'Edit Config' to edit the configuration file",
|
||||
"step4ClaudeDesktop": "Paste the configuration in the mcpServers section and restart Claude Desktop",
|
||||
"installToCursor": "Add {{name}} MCP server to Cursor",
|
||||
"installToClaudeCode": "Add {{name}} MCP server to Claude Code",
|
||||
"installToClaudeDesktop": "Add {{name}} MCP server to Claude Desktop",
|
||||
"installButton": "Install"
|
||||
}
|
||||
}
|
||||
@@ -268,7 +268,15 @@
|
||||
"recentServers": "Serveurs récents"
|
||||
},
|
||||
"servers": {
|
||||
"title": "Gestion des serveurs"
|
||||
"title": "Gestion des serveurs",
|
||||
"semanticSearch": "Recherche intelligente d'outils...",
|
||||
"semanticSearchPlaceholder": "Décrivez la fonctionnalité dont vous avez besoin, par ex. : cartes, météo, traitement de fichiers",
|
||||
"similarityThreshold": "Seuil de similarité",
|
||||
"similarityThresholdHelp": "Des valeurs plus élevées renvoient des résultats plus précis, des valeurs plus faibles des correspondances plus larges",
|
||||
"searchButton": "Rechercher",
|
||||
"clearSearch": "Effacer la recherche",
|
||||
"searchResults": "{{count}} serveur(s) correspondant(s) trouvé(s)",
|
||||
"noSearchResults": "Aucun serveur correspondant trouvé"
|
||||
},
|
||||
"groups": {
|
||||
"title": "Gestion des groupes"
|
||||
@@ -743,5 +751,28 @@
|
||||
"internalError": "Erreur interne",
|
||||
"internalErrorMessage": "Une erreur inattendue s'est produite lors du traitement du callback OAuth.",
|
||||
"closeWindow": "Fermer la fenêtre"
|
||||
},
|
||||
"install": {
|
||||
"installServerTitle": "Installer le serveur sur {{name}}",
|
||||
"installGroupTitle": "Installer le groupe {{name}}",
|
||||
"configCode": "Code de configuration",
|
||||
"copyConfig": "Copier la configuration",
|
||||
"steps": "Étapes d'installation",
|
||||
"step1Cursor": "Copiez le code de configuration ci-dessus",
|
||||
"step2Cursor": "Ouvrez Cursor, allez dans Paramètres > Features > MCP",
|
||||
"step3Cursor": "Cliquez sur 'Add New MCP Server' pour ajouter un nouveau serveur",
|
||||
"step4Cursor": "Collez la configuration à l'emplacement approprié et redémarrez Cursor",
|
||||
"step1ClaudeCode": "Copiez le code de configuration ci-dessus",
|
||||
"step2ClaudeCode": "Ouvrez Claude Code, allez dans Paramètres > Features > MCP",
|
||||
"step3ClaudeCode": "Cliquez sur 'Add New MCP Server' pour ajouter un nouveau serveur",
|
||||
"step4ClaudeCode": "Collez la configuration à l'emplacement approprié et redémarrez Claude Code",
|
||||
"step1ClaudeDesktop": "Copiez le code de configuration ci-dessus",
|
||||
"step2ClaudeDesktop": "Ouvrez Claude Desktop, allez dans Paramètres > Développeur",
|
||||
"step3ClaudeDesktop": "Cliquez sur 'Edit Config' pour modifier le fichier de configuration",
|
||||
"step4ClaudeDesktop": "Collez la configuration dans la section mcpServers et redémarrez Claude Desktop",
|
||||
"installToCursor": "Ajouter le serveur MCP {{name}} à Cursor",
|
||||
"installToClaudeCode": "Ajouter le serveur MCP {{name}} à Claude Code",
|
||||
"installToClaudeDesktop": "Ajouter le serveur MCP {{name}} à Claude Desktop",
|
||||
"installButton": "Installer"
|
||||
}
|
||||
}
|
||||
@@ -269,7 +269,15 @@
|
||||
"recentServers": "最近的服务器"
|
||||
},
|
||||
"servers": {
|
||||
"title": "服务器管理"
|
||||
"title": "服务器管理",
|
||||
"semanticSearch": "智能搜索工具...",
|
||||
"semanticSearchPlaceholder": "描述您需要的功能,例如:地图、天气、文件处理",
|
||||
"similarityThreshold": "相似度阈值",
|
||||
"similarityThresholdHelp": "较高值返回更精确结果,较低值返回更广泛匹配",
|
||||
"searchButton": "搜索",
|
||||
"clearSearch": "清除搜索",
|
||||
"searchResults": "找到 {{count}} 个匹配的服务器",
|
||||
"noSearchResults": "未找到匹配的服务器"
|
||||
},
|
||||
"settings": {
|
||||
"title": "设置",
|
||||
@@ -745,5 +753,28 @@
|
||||
"internalError": "内部错误",
|
||||
"internalErrorMessage": "处理 OAuth 回调时发生意外错误。",
|
||||
"closeWindow": "关闭窗口"
|
||||
},
|
||||
"install": {
|
||||
"installServerTitle": "安装服务器到 {{name}}",
|
||||
"installGroupTitle": "安装分组 {{name}}",
|
||||
"configCode": "配置代码",
|
||||
"copyConfig": "复制配置",
|
||||
"steps": "安装步骤",
|
||||
"step1Cursor": "复制上面的配置代码",
|
||||
"step2Cursor": "打开 Cursor,进入设置 > Features > MCP",
|
||||
"step3Cursor": "点击 'Add New MCP Server' 添加新服务器",
|
||||
"step4Cursor": "将配置粘贴到相应位置并重启 Cursor",
|
||||
"step1ClaudeCode": "复制上面的配置代码",
|
||||
"step2ClaudeCode": "打开 Claude Code,进入设置 > Features > MCP",
|
||||
"step3ClaudeCode": "点击 'Add New MCP Server' 添加新服务器",
|
||||
"step4ClaudeCode": "将配置粘贴到相应位置并重启 Claude Code",
|
||||
"step1ClaudeDesktop": "复制上面的配置代码",
|
||||
"step2ClaudeDesktop": "打开 Claude Desktop,进入设置 > Developer",
|
||||
"step3ClaudeDesktop": "点击 'Edit Config' 编辑配置文件",
|
||||
"step4ClaudeDesktop": "将配置粘贴到 mcpServers 部分并重启 Claude Desktop",
|
||||
"installToCursor": "添加 {{name}} MCP 服务器到 Cursor",
|
||||
"installToClaudeCode": "添加 {{name}} MCP 服务器到 Claude Code",
|
||||
"installToClaudeDesktop": "添加 {{name}} MCP 服务器到 Claude Desktop",
|
||||
"installButton": "安装"
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
|
||||
import SwaggerParser from '@apidevtools/swagger-parser';
|
||||
import { OpenAPIV3 } from 'openapi-types';
|
||||
import { ServerConfig, OpenAPISecurityConfig } from '../types/index.js';
|
||||
import { createSafeJSON } from '../utils/serialization.js';
|
||||
|
||||
export interface OpenAPIToolInfo {
|
||||
name: string;
|
||||
@@ -299,6 +300,31 @@ export class OpenAPIClient {
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands parameters that may have been stringified due to circular reference handling
|
||||
* This reverses the '[Circular Reference]' placeholder back to proper values when possible
|
||||
*/
|
||||
private expandParameter(value: unknown): unknown {
|
||||
if (typeof value === 'string' && value === '[Circular Reference]') {
|
||||
// Return undefined for circular references to avoid sending invalid data
|
||||
return undefined;
|
||||
}
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => this.expandParameter(item));
|
||||
}
|
||||
const result: Record<string, unknown> = {};
|
||||
for (const [key, val] of Object.entries(value)) {
|
||||
const expanded = this.expandParameter(val);
|
||||
if (expanded !== undefined) {
|
||||
result[key] = expanded;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
async callTool(
|
||||
toolName: string,
|
||||
args: Record<string, unknown>,
|
||||
@@ -310,12 +336,15 @@ export class OpenAPIClient {
|
||||
}
|
||||
|
||||
try {
|
||||
// Expand any circular reference placeholders in arguments
|
||||
const expandedArgs = this.expandParameter(args) as Record<string, unknown>;
|
||||
|
||||
// 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];
|
||||
const value = expandedArgs[param.name];
|
||||
if (value !== undefined) {
|
||||
url = url.replace(`{${param.name}}`, String(value));
|
||||
}
|
||||
@@ -326,7 +355,7 @@ export class OpenAPIClient {
|
||||
const queryParamDefs = tool.parameters?.filter((p) => p.in === 'query') || [];
|
||||
|
||||
for (const param of queryParamDefs) {
|
||||
const value = args[param.name];
|
||||
const value = expandedArgs[param.name];
|
||||
if (value !== undefined) {
|
||||
queryParams[param.name] = value;
|
||||
}
|
||||
@@ -340,8 +369,8 @@ export class OpenAPIClient {
|
||||
};
|
||||
|
||||
// Add request body if applicable
|
||||
if (args.body && ['post', 'put', 'patch'].includes(tool.method)) {
|
||||
requestConfig.data = args.body;
|
||||
if (expandedArgs.body && ['post', 'put', 'patch'].includes(tool.method)) {
|
||||
requestConfig.data = expandedArgs.body;
|
||||
}
|
||||
|
||||
// Collect all headers to be sent
|
||||
@@ -350,7 +379,7 @@ export class OpenAPIClient {
|
||||
// Add headers if any header parameters are defined
|
||||
const headerParams = tool.parameters?.filter((p) => p.in === 'header') || [];
|
||||
for (const param of headerParams) {
|
||||
const value = args[param.name];
|
||||
const value = expandedArgs[param.name];
|
||||
if (value !== undefined) {
|
||||
allHeaders[param.name] = String(value);
|
||||
}
|
||||
@@ -383,7 +412,8 @@ export class OpenAPIClient {
|
||||
}
|
||||
|
||||
getTools(): OpenAPIToolInfo[] {
|
||||
return this.tools;
|
||||
// Return a safe copy to avoid circular reference issues
|
||||
return createSafeJSON(this.tools);
|
||||
}
|
||||
|
||||
getSpec(): OpenAPIV3.Document | null {
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
toggleServerStatus,
|
||||
} from '../services/mcpService.js';
|
||||
import { loadSettings, saveSettings } from '../config/index.js';
|
||||
import { syncAllServerToolsEmbeddings } from '../services/vectorSearchService.js';
|
||||
import { syncAllServerToolsEmbeddings, searchToolsByVector } from '../services/vectorSearchService.js';
|
||||
import { createSafeJSON } from '../utils/serialization.js';
|
||||
|
||||
export const getAllServers = async (_: Request, res: Response): Promise<void> => {
|
||||
@@ -879,3 +879,74 @@ export const updatePromptDescription = async (req: Request, res: Response): Prom
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Search servers by semantic query using vector embeddings
|
||||
* This searches through server tools and returns servers that match the query
|
||||
*/
|
||||
export const searchServers = async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { query, limit = 10, threshold = 0.65 } = req.query;
|
||||
|
||||
if (!query || typeof query !== 'string') {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'Search query is required',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse limit and threshold
|
||||
const limitNum = typeof limit === 'string' ? parseInt(limit, 10) : Number(limit);
|
||||
const thresholdNum = typeof threshold === 'string' ? parseFloat(threshold) : Number(threshold);
|
||||
|
||||
// Validate limit and threshold
|
||||
if (isNaN(limitNum) || limitNum < 1 || limitNum > 100) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'Limit must be between 1 and 100',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNaN(thresholdNum) || thresholdNum < 0 || thresholdNum > 1) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'Threshold must be between 0 and 1',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Search for tools that match the query
|
||||
const searchResults = await searchToolsByVector(query, limitNum, thresholdNum);
|
||||
|
||||
// Extract unique server names from search results
|
||||
const serverNames = Array.from(new Set(searchResults.map((result) => result.serverName)));
|
||||
|
||||
// Get full server information for the matching servers
|
||||
const allServers = await getServersInfo();
|
||||
const matchingServers = allServers.filter((server) => serverNames.includes(server.name));
|
||||
|
||||
const response: ApiResponse = {
|
||||
success: true,
|
||||
data: {
|
||||
servers: createSafeJSON(matchingServers),
|
||||
matches: searchResults.map((result) => ({
|
||||
serverName: result.serverName,
|
||||
toolName: result.toolName,
|
||||
similarity: result.similarity,
|
||||
})),
|
||||
query,
|
||||
threshold: thresholdNum,
|
||||
},
|
||||
};
|
||||
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
console.error('Failed to search servers:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to search servers',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
togglePrompt,
|
||||
updatePromptDescription,
|
||||
updateSystemConfig,
|
||||
searchServers,
|
||||
} from '../controllers/serverController.js';
|
||||
import {
|
||||
getGroups,
|
||||
@@ -93,6 +94,7 @@ export const initRoutes = (app: express.Application): void => {
|
||||
|
||||
// API routes protected by auth middleware in middlewares/index.ts
|
||||
router.get('/servers', getAllServers);
|
||||
router.get('/servers/search', searchServers);
|
||||
router.get('/settings', getAllSettings);
|
||||
router.post('/servers', createServer);
|
||||
router.put('/servers/:name', updateServer);
|
||||
|
||||
Reference in New Issue
Block a user