Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ MCPGATEWAY_ADMIN_API_ENABLED=true
# Enable bulk import endpoint for tools (true/false)
MCPGATEWAY_BULK_IMPORT_ENABLED=true

# Maximum number of tools allowed per bulk import request
MCPGATEWAY_BULK_IMPORT_MAX_TOOLS=200

# Rate limiting for bulk import endpoint (requests per minute)
MCPGATEWAY_BULK_IMPORT_RATE_LIMIT=10

#####################################
# Header Passthrough Configuration
#####################################
Expand Down
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ AUTH_REQUIRED=true
MCPGATEWAY_UI_ENABLED=true
MCPGATEWAY_ADMIN_API_ENABLED=true

# Bulk Import (Admin UI feature)
MCPGATEWAY_BULK_IMPORT_ENABLED=true # Enable/disable bulk import endpoint
MCPGATEWAY_BULK_IMPORT_MAX_TOOLS=200 # Maximum tools per import batch
MCPGATEWAY_BULK_IMPORT_RATE_LIMIT=10 # Requests per minute limit

# Federation
MCPGATEWAY_ENABLE_MDNS_DISCOVERY=true
MCPGATEWAY_ENABLE_FEDERATION=true
Expand Down
67 changes: 61 additions & 6 deletions docs/docs/manage/bulk-import.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,89 @@

The MCP Gateway provides a bulk import endpoint for efficiently loading multiple tools in a single request, perfect for migrations, environment setup, and team onboarding.

!!! info "Feature Flag Required"
This feature is controlled by the `MCPGATEWAY_BULK_IMPORT_ENABLED` environment variable.
Default: `true` (enabled). Set to `false` to disable this endpoint.
!!! info "Configuration Options"
This feature is controlled by several environment variables:

- `MCPGATEWAY_BULK_IMPORT_ENABLED=true` - Enable/disable the endpoint (default: true)
- `MCPGATEWAY_BULK_IMPORT_MAX_TOOLS=200` - Maximum tools per batch (default: 200)
- `MCPGATEWAY_BULK_IMPORT_RATE_LIMIT=10` - Requests per minute limit (default: 10)

---

## 🚀 Overview

The `/admin/tools/import` endpoint allows you to register multiple tools at once, providing:
The bulk import feature allows you to register multiple tools at once through both the Admin UI and API, providing:

- **Per-item validation** - One invalid tool won't fail the entire batch
- **Detailed reporting** - Know exactly which tools succeeded or failed
- **Rate limiting** - Protected against abuse (10 requests/minute)
- **Batch size limits** - Maximum 200 tools per request
- **Multiple input formats** - JSON payload or form data
- **Multiple input formats** - JSON payload, form data, or file upload
- **User-friendly UI** - Modal dialog with drag-and-drop file support

---

## 🎨 Admin UI Usage

### Accessing the Bulk Import Modal

1. **Navigate to Admin UI** - Open your gateway's admin interface at `http://localhost:4444/admin`
2. **Go to Tools Tab** - Click on the "Tools" tab in the main navigation
3. **Open Bulk Import** - Click the "+ Bulk Import Tools" button next to "Add New Tool"

### Using the Modal

The bulk import modal provides two ways to input tool data:

#### Option 1: JSON Textarea
1. **Paste JSON directly** into the text area
2. **Validate format** - The modal will check JSON syntax before submission
3. **Click Import Tools** to process

#### Option 2: File Upload
1. **Prepare a JSON file** with your tools array
2. **Click "Choose File"** and select your `.json` file
3. **Click Import Tools** to process

### UI Features

- **Real-time validation** - JSON syntax checking before submission
- **Loading indicators** - Progress spinner during import
- **Detailed results** - Success/failure counts with error details
- **Auto-refresh** - Page reloads automatically after successful import
- **Modal controls** - Close with button, backdrop click, or ESC key

---

## 📡 API Endpoint

### Request
### Request Methods

#### Method 1: JSON Body
```http
POST /admin/tools/import
Authorization: Bearer <jwt_token>
Content-Type: application/json
```

#### Method 2: Form Data (JSON String)
```http
POST /admin/tools/import
Authorization: Bearer <jwt_token>
Content-Type: multipart/form-data

Form field: tools_json=<json_string>
```

#### Method 3: File Upload
```http
POST /admin/tools/import
Authorization: Bearer <jwt_token>
Content-Type: multipart/form-data

Form field: tools_file=<uploaded_json_file>
```

### Payload Structure

```json
Expand Down
69 changes: 51 additions & 18 deletions mcpgateway/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1568,6 +1568,7 @@ async def admin_ui(
"root_path": root_path,
"max_name_length": max_name_length,
"gateway_tool_name_separator": settings.gateway_tool_name_separator,
"bulk_import_max_tools": settings.mcpgateway_bulk_import_max_tools,
},
)

Expand Down Expand Up @@ -4375,7 +4376,7 @@ async def admin_list_tags(

@admin_router.post("/tools/import/")
@admin_router.post("/tools/import")
@rate_limit(requests_per_minute=10)
@rate_limit(requests_per_minute=settings.mcpgateway_bulk_import_rate_limit)
async def admin_import_tools(
request: Request,
db: Session = Depends(get_db),
Expand Down Expand Up @@ -4418,19 +4419,33 @@ async def admin_import_tools(
except Exception as ex:
LOGGER.exception("Invalid form body")
return JSONResponse({"success": False, "message": f"Invalid form data: {ex}"}, status_code=422)
raw = form.get("tools_json") or form.get("json") or form.get("payload")
if not raw:
return JSONResponse({"success": False, "message": "Missing tools_json/json/payload form field."}, status_code=422)
try:
payload = json.loads(raw)
except Exception as ex:
LOGGER.exception("Invalid JSON in form field")
return JSONResponse({"success": False, "message": f"Invalid JSON: {ex}"}, status_code=422)
# Check for file upload first
if "tools_file" in form:
file = form["tools_file"]
if hasattr(file, "file"):
content = await file.read()
try:
payload = json.loads(content.decode("utf-8"))
except (json.JSONDecodeError, UnicodeDecodeError) as ex:
LOGGER.exception("Invalid JSON file")
return JSONResponse({"success": False, "message": f"Invalid JSON file: {ex}"}, status_code=422)
else:
return JSONResponse({"success": False, "message": "Invalid file upload"}, status_code=422)
else:
# Check for JSON in form fields
raw = form.get("tools") or form.get("tools_json") or form.get("json") or form.get("payload")
if not raw:
return JSONResponse({"success": False, "message": "Missing tools/tools_json/json/payload form field."}, status_code=422)
try:
payload = json.loads(raw)
except Exception as ex:
LOGGER.exception("Invalid JSON in form field")
return JSONResponse({"success": False, "message": f"Invalid JSON: {ex}"}, status_code=422)

if not isinstance(payload, list):
return JSONResponse({"success": False, "message": "Payload must be a JSON array of tools."}, status_code=422)

max_batch = 200
max_batch = settings.mcpgateway_bulk_import_max_tools
if len(payload) > max_batch:
return JSONResponse({"success": False, "message": f"Too many tools ({len(payload)}). Max {max_batch}."}, status_code=413)

Expand Down Expand Up @@ -4463,15 +4478,33 @@ async def admin_import_tools(
LOGGER.exception("Unexpected error importing tool %r at index %d", name, i)
errors.append({"index": i, "name": name, "error": {"message": str(ex)}})

return JSONResponse(
{
"success": len(errors) == 0,
"created_count": len(created),
"failed_count": len(errors),
"created": created,
"errors": errors,
# Format response to match both frontend and test expectations
response_data = {
"success": len(errors) == 0,
# New format for frontend
"imported": len(created),
"failed": len(errors),
"total": len(payload),
# Original format for tests
"created_count": len(created),
"failed_count": len(errors),
"created": created,
"errors": errors,
# Detailed format for frontend
"details": {
"success": [item["name"] for item in created if item.get("name")],
"failed": [{"name": item["name"], "error": item["error"].get("message", str(item["error"]))} for item in errors],
},
status_code=200,
}

if len(errors) == 0:
response_data["message"] = f"Successfully imported all {len(created)} tools"
else:
response_data["message"] = f"Imported {len(created)} of {len(payload)} tools. {len(errors)} failed."

return JSONResponse(
response_data,
status_code=200, # Always return 200, success field indicates if all succeeded
)

except HTTPException:
Expand Down
2 changes: 2 additions & 0 deletions mcpgateway/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ class Settings(BaseSettings):
mcpgateway_ui_enabled: bool = False
mcpgateway_admin_api_enabled: bool = False
mcpgateway_bulk_import_enabled: bool = True
mcpgateway_bulk_import_max_tools: int = 200
mcpgateway_bulk_import_rate_limit: int = 10

# Security
skip_ssl_verify: bool = False
Expand Down
Loading
Loading