Compare commits

..

5 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
ee301a893f Add one-click installation dialog for servers and groups
Co-authored-by: samanhappy <2755122+samanhappy@users.noreply.github.com>
2025-10-31 15:25:01 +00:00
copilot-swe-agent[bot]
a8852f7807 Add semantic search UI to servers management page
Co-authored-by: samanhappy <2755122+samanhappy@users.noreply.github.com>
2025-10-31 15:16:34 +00:00
copilot-swe-agent[bot]
d8e127d911 Add semantic search API endpoint for servers
Co-authored-by: samanhappy <2755122+samanhappy@users.noreply.github.com>
2025-10-31 15:12:13 +00:00
copilot-swe-agent[bot]
f782f69251 Fix circular reference issue in OpenAPI tool parameters
Co-authored-by: samanhappy <2755122+samanhappy@users.noreply.github.com>
2025-10-31 15:09:30 +00:00
copilot-swe-agent[bot]
1c0473183f Initial plan 2025-10-31 15:01:56 +00:00
21 changed files with 578 additions and 1083 deletions

View File

@@ -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 :

View File

@@ -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:

View File

@@ -57,15 +57,6 @@ MCPHub 通过将多个 MCPModel 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)。实际使用中通常会遇到两类配置:

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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
}
]
}

View File

@@ -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>
)
}

View 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;

View File

@@ -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)}
/>
)}
</>
);
};

View File

@@ -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 = {

View File

@@ -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}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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": "安装"
}
}

View File

@@ -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 {

View File

@@ -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',
});
}
};

View File

@@ -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);