feat: Add HTTP header support to /mcp load command for SSE servers (#350)

Adds --header/-H flag support to `/mcp load` for authenticated MCP servers
like Hack The Box. Handles long Bearer tokens that wrap across terminal lines.

Changes:
- Added --header/-H flag parsing in mcp.py handle_load() method
- Parse "Key: Value" format headers and strip quotes
- Pass headers to MCPServerSseParams
- Fix CLI command parsing to handle newlines from terminal wrapping
- Use shlex.split() for proper quote handling in pasted commands

Example usage:
/mcp load https://mcp.ai.hackthebox.com/v1/ctf/sse htb --header "Authorization: Bearer TOKEN"

Tested with HTB MCP server - successfully connects with 13 tools.

Developed by Claude Sonnet 4 with guidance from @AgeOfAlgorithms

Co-authored-by: AgeOfAlgorithms <AgeOfAlgorithms@pm.me>
This commit is contained in:
AgeOfAlgorithms 2025-11-24 11:30:54 -04:00 committed by GitHub
parent 4dc4f2e96e
commit efd15f43ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 77 additions and 16 deletions

View File

@ -141,6 +141,7 @@ if os.getenv("CAI_DEBUG", "1") != "2":
import asyncio
import logging
import shlex
import time
# Configure comprehensive error filtering
@ -1295,7 +1296,19 @@ def run_cai_cli(
# Handle special commands
if user_input.startswith("/") or user_input.startswith("$"):
parts = user_input.strip().split()
# Remove newlines from pasted input
cleaned_input = user_input.strip().replace('\n', '').replace('\r', '')
try:
# Parse with shell-like quoting support
parts = shlex.split(cleaned_input)
except ValueError:
# Fallback to simple split on error
parts = cleaned_input.split()
if not parts:
continue
command = parts[0]
args = parts[1:] if len(parts) > 1 else None

View File

@ -10,27 +10,31 @@ USAGE EXAMPLES:
1. Load an SSE (Server-Sent Events) MCP server:
/mcp load http://localhost:9876/sse burp
2. Load a STDIO MCP server:
2. Load an SSE server with authentication headers:
/mcp load https://mcp.ai.hackthebox.com/v1/ctf/sse htb --header "Authorization: Bearer YOUR_TOKEN"
/mcp load https://api.example.com/mcp myapi -H "X-API-Key: secret" -H "Custom-Header: value"
3. Load a STDIO MCP server:
/mcp load stdio myserver python mcp_server.py
/mcp load stdio myserver node server.js --port 8080
3. List all active MCP connections:
4. List all active MCP connections:
/mcp list
4. Add MCP tools to an agent:
5. Add MCP tools to an agent:
/mcp add burp redteam_agent # Add by agent name
/mcp add burp 13 # Add by agent number
5. List tools from a specific server:
6. List tools from a specific server:
/mcp tools burp
6. Check server connection status:
7. Check server connection status:
/mcp status
7. Remove a server connection:
8. Remove a server connection:
/mcp remove burp
8. Show help:
9. Show help:
/mcp help
NOTES:
@ -466,10 +470,21 @@ The MCP command allows you to manage Model Context Protocol servers and integrat
**SSE (Server-Sent Events) Server:**
```
/mcp load <url> <name>
/mcp load <url> <name> [--header "Key: Value" | -H "Key: Value"]
```
Example: `/mcp load http://localhost:9876/sse burp`
**SSE Server with Authentication:**
```
/mcp load <url> <name> --header "Authorization: Bearer TOKEN"
```
Example: `/mcp load https://mcp.ai.hackthebox.com/v1/ctf/sse htb --header "Authorization: Bearer eyJ0..."`
You can specify multiple headers by repeating the flag:
```
/mcp load <url> <name> -H "Header1: Value1" -H "Header2: Value2"
```
**STDIO Server:**
```
/mcp load stdio <name> <command> [args...]
@ -598,8 +613,8 @@ Example: `/mcp add burp 13`
"""Handle /mcp load command.
Usage:
/mcp load <url> <name> - Load SSE server
/mcp load stdio <name> <command> [args...] - Load stdio server
/mcp load <url> <name> [--header "Key: Value"] - Load SSE server with optional auth headers
/mcp load stdio <name> <command> [args...] - Load stdio server
Args:
args: List of command arguments
@ -610,8 +625,8 @@ Example: `/mcp add burp 13`
if not args or len(args) < 2:
console.print("[red]Error: Invalid arguments[/red]")
console.print("Usage:")
console.print(" /mcp load <url> <name> - For SSE servers")
console.print(" /mcp load stdio <name> <command> [args...]")
console.print(" /mcp load <url> <name> [--header \"Key: Value\"] - For SSE servers")
console.print(" /mcp load stdio <name> <command> [args...] - For STDIO servers")
return False
# Check if it's a stdio server
@ -630,14 +645,40 @@ Example: `/mcp add burp 13`
url = args[0]
name = args[1]
return self._load_sse_server(url, name)
# Parse headers from remaining arguments
headers = {}
i = 2
while i < len(args):
if args[i] in ["--header", "-H"]:
if i + 1 >= len(args):
console.print("[red]Error: --header requires a value[/red]")
return False
def _load_sse_server(self, url: str, name: str) -> bool:
# Parse header in format "Key: Value"
header_str = args[i + 1]
if ":" not in header_str:
console.print(f"[red]Error: Invalid header format '{header_str}'. Use 'Key: Value'[/red]")
return False
key, value = header_str.split(":", 1)
# Strip quotes and whitespace from key and value
key = key.strip().strip('"').strip("'")
value = value.strip().strip('"').strip("'")
headers[key] = value
i += 2
else:
console.print(f"[yellow]Warning: Unknown argument '{args[i]}' ignored[/yellow]")
i += 1
return self._load_sse_server(url, name, headers if headers else None)
def _load_sse_server(self, url: str, name: str, headers: Optional[Dict[str, str]] = None) -> bool:
"""Load an SSE MCP server.
Args:
url: URL of the SSE server
name: Name to identify the server
headers: Optional HTTP headers for authentication (e.g., {"Authorization": "Bearer token"})
Returns:
True if successful
@ -647,7 +688,10 @@ Example: `/mcp add burp 13`
console.print(f"[dim]Use '/mcp remove {name}' first if you want to reload it.[/dim]")
return True
console.print(f"Connecting to SSE server at {url}...")
if headers:
console.print(f"Connecting to SSE server at {url} with authentication headers...")
else:
console.print(f"Connecting to SSE server at {url}...")
async def connect_and_test():
params: MCPServerSseParams = {
@ -655,6 +699,10 @@ Example: `/mcp add burp 13`
"timeout": 10, # Connection timeout
"sse_read_timeout": 300 # 5 minutes for SSE reads
}
# Add headers if provided
if headers:
params["headers"] = headers
server = MCPServerSse(params, name=name, cache_tools_list=True)
# Connect to the server with retry logic