mirror of https://github.com/aliasrobotics/cai.git
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:
parent
4dc4f2e96e
commit
efd15f43ba
|
|
@ -141,6 +141,7 @@ if os.getenv("CAI_DEBUG", "1") != "2":
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import shlex
|
||||||
import time
|
import time
|
||||||
|
|
||||||
# Configure comprehensive error filtering
|
# Configure comprehensive error filtering
|
||||||
|
|
@ -1295,7 +1296,19 @@ def run_cai_cli(
|
||||||
|
|
||||||
# Handle special commands
|
# Handle special commands
|
||||||
if user_input.startswith("/") or user_input.startswith("$"):
|
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]
|
command = parts[0]
|
||||||
args = parts[1:] if len(parts) > 1 else None
|
args = parts[1:] if len(parts) > 1 else None
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,27 +10,31 @@ USAGE EXAMPLES:
|
||||||
1. Load an SSE (Server-Sent Events) MCP server:
|
1. Load an SSE (Server-Sent Events) MCP server:
|
||||||
/mcp load http://localhost:9876/sse burp
|
/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 python mcp_server.py
|
||||||
/mcp load stdio myserver node server.js --port 8080
|
/mcp load stdio myserver node server.js --port 8080
|
||||||
|
|
||||||
3. List all active MCP connections:
|
4. List all active MCP connections:
|
||||||
/mcp list
|
/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 redteam_agent # Add by agent name
|
||||||
/mcp add burp 13 # Add by agent number
|
/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
|
/mcp tools burp
|
||||||
|
|
||||||
6. Check server connection status:
|
7. Check server connection status:
|
||||||
/mcp status
|
/mcp status
|
||||||
|
|
||||||
7. Remove a server connection:
|
8. Remove a server connection:
|
||||||
/mcp remove burp
|
/mcp remove burp
|
||||||
|
|
||||||
8. Show help:
|
9. Show help:
|
||||||
/mcp help
|
/mcp help
|
||||||
|
|
||||||
NOTES:
|
NOTES:
|
||||||
|
|
@ -466,10 +470,21 @@ The MCP command allows you to manage Model Context Protocol servers and integrat
|
||||||
|
|
||||||
**SSE (Server-Sent Events) Server:**
|
**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`
|
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:**
|
**STDIO Server:**
|
||||||
```
|
```
|
||||||
/mcp load stdio <name> <command> [args...]
|
/mcp load stdio <name> <command> [args...]
|
||||||
|
|
@ -598,8 +613,8 @@ Example: `/mcp add burp 13`
|
||||||
"""Handle /mcp load command.
|
"""Handle /mcp load command.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
/mcp load <url> <name> - Load SSE server
|
/mcp load <url> <name> [--header "Key: Value"] - Load SSE server with optional auth headers
|
||||||
/mcp load stdio <name> <command> [args...] - Load stdio server
|
/mcp load stdio <name> <command> [args...] - Load stdio server
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
args: List of command arguments
|
args: List of command arguments
|
||||||
|
|
@ -610,8 +625,8 @@ Example: `/mcp add burp 13`
|
||||||
if not args or len(args) < 2:
|
if not args or len(args) < 2:
|
||||||
console.print("[red]Error: Invalid arguments[/red]")
|
console.print("[red]Error: Invalid arguments[/red]")
|
||||||
console.print("Usage:")
|
console.print("Usage:")
|
||||||
console.print(" /mcp load <url> <name> - For SSE servers")
|
console.print(" /mcp load <url> <name> [--header \"Key: Value\"] - For SSE servers")
|
||||||
console.print(" /mcp load stdio <name> <command> [args...]")
|
console.print(" /mcp load stdio <name> <command> [args...] - For STDIO servers")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check if it's a stdio server
|
# Check if it's a stdio server
|
||||||
|
|
@ -630,14 +645,40 @@ Example: `/mcp add burp 13`
|
||||||
url = args[0]
|
url = args[0]
|
||||||
name = args[1]
|
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.
|
"""Load an SSE MCP server.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
url: URL of the SSE server
|
url: URL of the SSE server
|
||||||
name: Name to identify the server
|
name: Name to identify the server
|
||||||
|
headers: Optional HTTP headers for authentication (e.g., {"Authorization": "Bearer token"})
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if successful
|
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]")
|
console.print(f"[dim]Use '/mcp remove {name}' first if you want to reload it.[/dim]")
|
||||||
return True
|
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():
|
async def connect_and_test():
|
||||||
params: MCPServerSseParams = {
|
params: MCPServerSseParams = {
|
||||||
|
|
@ -655,6 +699,10 @@ Example: `/mcp add burp 13`
|
||||||
"timeout": 10, # Connection timeout
|
"timeout": 10, # Connection timeout
|
||||||
"sse_read_timeout": 300 # 5 minutes for SSE reads
|
"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)
|
server = MCPServerSse(params, name=name, cache_tools_list=True)
|
||||||
|
|
||||||
# Connect to the server with retry logic
|
# Connect to the server with retry logic
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue