Skip to content

Commit 3be2628

Browse files
Fix/multi header auth 526 (#691)
* feat(makefile): add comprehensive file-specific linting support - Add support for linting specific files and directories via arguments - Implement smart file type detection (Python, YAML, JSON, Markdown, TOML) - Add convenience targets: lint-quick, lint-fix, lint-smart - Add git integration: lint-changed, lint-staged, pre-commit hooks - Fix TARGET variable parsing for multiple directories - Remove duplicate lint-changed target definitions - Add dummy targets to prevent 'No rule to make target' errors Usage: - make lint filename.py (single file) - make lint dirname/ (directory) - make lint-quick filename.py (fast linters only) - make lint-changed (git changed files) Resolves: 'mcpgateway tests is not a Python file or directory' error Fixes: Duplicate target override warnings Implements: All requirements from file-specific linting chore * fix: resolve multiple authentication headers and validation issues - Fix gateway initialization failure with multi-header authentication by properly encoding header dictionaries before passing to _initialize_gateway() - Fix error formatter IndexError when validation errors have empty location tuples - Fix GatewayRead model to support multiple authentication headers instead of requiring exactly one Resolves issue #526 - multi-header authentication support * feat: support multiple custom authentication headers for MCP gateways - Add dynamic multi-header UI with add/remove functionality - Update backend validation to accept auth_headers array - Maintain backward compatibility with single header format - Add comprehensive tests and documentation - Handle edge cases (duplicates, validation, limits) Fixes #526 * fix: updated test assertion to match the updated validation when auth_headers is an empty list * updated files with linter formatting Signed-off-by: Manav Gupta <[email protected]> * Rebase, lint and revert Makefile changes Signed-off-by: Mihai Criveti <[email protected]> * Rebase, lint and revert Makefile changes Signed-off-by: Mihai Criveti <[email protected]> * Rebase, lint and revert Makefile changes Signed-off-by: Mihai Criveti <[email protected]> --------- Signed-off-by: Manav Gupta <[email protected]> Signed-off-by: Mihai Criveti <[email protected]> Co-authored-by: Mihai Criveti <[email protected]>
1 parent 2a0637e commit 3be2628

34 files changed

+1277
-768
lines changed

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ pytest -m "not slow"
228228
# To test everything:
229229

230230
make autoflake isort black pre-commit
231-
make doctest test smoketest lint-web flake8 pylint
231+
make interrogate doctest test smoketest lint-web flake8 bandit pylint
232232

233233
# Rules
234234
- When using git commit always add a -s to sign commits

docs/docs/using/multi-auth-headers.md

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
# Multiple Authentication Headers
2+
3+
## Overview
4+
5+
MCP Gateway now supports multiple custom authentication headers for gateway connections. This feature allows you to configure multiple header key-value pairs that will be sent with every request to your MCP servers.
6+
7+
## Use Cases
8+
9+
Multiple authentication headers are useful when:
10+
- Your MCP server requires multiple API keys or tokens
11+
- You need to send client identification along with authentication
12+
- Your service uses region-specific or version-specific headers
13+
- You're integrating with services that require complex header-based authentication
14+
15+
## Configuration
16+
17+
### Via Admin UI
18+
19+
1. Navigate to the Admin Panel at `http://localhost:8000/admin/`
20+
2. Click on the "Gateways" tab
21+
3. When adding or editing a gateway:
22+
- Select "Custom Headers" as the Authentication Type
23+
- Click "Add Header" to add multiple header pairs
24+
- Enter the header key (e.g., `X-API-Key`) and value for each header
25+
- Click "Remove" next to any header to delete it
26+
- Submit the form to save your configuration
27+
28+
### Via API
29+
30+
Send a POST request to `/admin/gateways` with the `auth_headers` field as a JSON array:
31+
32+
```json
33+
{
34+
"name": "My Gateway",
35+
"url": "http://mcp-server.example.com",
36+
"auth_type": "authheaders",
37+
"auth_headers": [
38+
{"key": "X-API-Key", "value": "secret-key-123"},
39+
{"key": "X-Client-ID", "value": "client-456"},
40+
{"key": "X-Region", "value": "us-east-1"}
41+
]
42+
}
43+
```
44+
45+
### Via Python SDK
46+
47+
```python
48+
from mcpgateway.schemas import GatewayCreate
49+
50+
gateway = GatewayCreate(
51+
name="My Gateway",
52+
url="http://mcp-server.example.com",
53+
auth_type="authheaders",
54+
auth_headers=[
55+
{"key": "X-API-Key", "value": "secret-key-123"},
56+
{"key": "X-Client-ID", "value": "client-456"},
57+
{"key": "X-Region", "value": "us-east-1"}
58+
]
59+
)
60+
```
61+
62+
## Backward Compatibility
63+
64+
The gateway still supports the legacy single-header format for backward compatibility:
65+
66+
```json
67+
{
68+
"name": "My Gateway",
69+
"url": "http://mcp-server.example.com",
70+
"auth_type": "authheaders",
71+
"auth_header_key": "X-API-Key",
72+
"auth_header_value": "secret-key-123"
73+
}
74+
```
75+
76+
If both `auth_headers` (multi) and `auth_header_key`/`auth_header_value` (single) are provided, the multi-header format takes precedence.
77+
78+
## Security Considerations
79+
80+
### Encryption
81+
All authentication headers are encrypted before being stored in the database using AES-256-GCM encryption. The encryption key is derived from the `AUTH_ENCRYPTION_SECRET` environment variable.
82+
83+
### Header Validation
84+
- Empty header keys are ignored
85+
- Duplicate header keys will use the last provided value
86+
- Header values can be empty strings if required by your authentication scheme
87+
- Special characters in header keys and values are supported
88+
89+
### Best Practices
90+
1. **Use HTTPS**: Always use HTTPS URLs for your MCP servers to prevent header interception
91+
2. **Rotate Keys**: Regularly rotate your API keys and update them in the gateway configuration
92+
3. **Minimal Headers**: Only include headers that are strictly necessary for authentication
93+
4. **Environment Variables**: Store sensitive values in environment variables when deploying
94+
95+
## Common Patterns
96+
97+
### Multiple API Keys
98+
```json
99+
{
100+
"auth_headers": [
101+
{"key": "X-Primary-Key", "value": "primary-secret"},
102+
{"key": "X-Secondary-Key", "value": "secondary-secret"}
103+
]
104+
}
105+
```
106+
107+
### API Key with Client Identification
108+
```json
109+
{
110+
"auth_headers": [
111+
{"key": "X-API-Key", "value": "api-secret"},
112+
{"key": "X-Client-ID", "value": "client-123"},
113+
{"key": "X-Client-Secret", "value": "client-secret"}
114+
]
115+
}
116+
```
117+
118+
### Regional Configuration
119+
```json
120+
{
121+
"auth_headers": [
122+
{"key": "X-API-Key", "value": "api-secret"},
123+
{"key": "X-Region", "value": "eu-west-1"},
124+
{"key": "X-Environment", "value": "production"}
125+
]
126+
}
127+
```
128+
129+
## Troubleshooting
130+
131+
### Headers Not Being Sent
132+
1. Check that your gateway is using `auth_type: "authheaders"`
133+
2. Verify headers are properly formatted in the JSON array
134+
3. Ensure the gateway is enabled and reachable
135+
4. Check server logs to confirm headers are being received
136+
137+
### Case Sensitivity
138+
HTTP headers are case-insensitive by specification. Some HTTP clients or servers may normalize header names to lowercase. Your MCP server should handle headers in a case-insensitive manner.
139+
140+
### Validation Errors
141+
If you receive validation errors when saving:
142+
- Ensure at least one header is provided when using "Custom Headers" authentication
143+
- Check that your JSON is properly formatted if using the API
144+
- Verify that header keys don't contain invalid characters
145+
146+
### Testing Your Configuration
147+
Use the "Test" button in the Admin UI to verify your gateway connection with the configured headers. The test will attempt to connect to your MCP server and validate that authentication is working correctly.
148+
149+
## Migration from Single Headers
150+
151+
If you have existing gateways using single header authentication, they will continue to work without modification. To migrate to multi-headers:
152+
153+
1. Edit your gateway in the Admin UI
154+
2. Your existing single header will be displayed
155+
3. Add additional headers as needed
156+
4. Save the configuration
157+
158+
The system will automatically convert your configuration to the multi-header format while preserving your existing authentication.
159+
160+
## API Reference
161+
162+
### GatewayCreate Schema
163+
```python
164+
{
165+
"name": str,
166+
"url": str,
167+
"auth_type": "authheaders",
168+
"auth_headers": [
169+
{"key": str, "value": str},
170+
...
171+
]
172+
}
173+
```
174+
175+
### GatewayUpdate Schema
176+
```python
177+
{
178+
"auth_type": "authheaders",
179+
"auth_headers": [
180+
{"key": str, "value": str},
181+
...
182+
]
183+
}
184+
```
185+
186+
## Related Documentation
187+
- [Gateway Authentication](./authentication.md)
188+
- [Security Best Practices](../security/best-practices.md)
189+
- [API Documentation](../api/gateways.md)

mcpgateway/admin.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2585,6 +2585,26 @@ async def admin_add_gateway(request: Request, db: Session = Depends(get_db), use
25852585
tags_str = form.get("tags", "")
25862586
tags = [tag.strip() for tag in tags_str.split(",") if tag.strip()] if tags_str else []
25872587

2588+
# Parse auth_headers JSON if present
2589+
auth_headers_json = form.get("auth_headers")
2590+
auth_headers = []
2591+
if auth_headers_json:
2592+
try:
2593+
auth_headers = json.loads(auth_headers_json)
2594+
except (json.JSONDecodeError, ValueError):
2595+
auth_headers = []
2596+
2597+
# Handle passthrough_headers
2598+
passthrough_headers = form.get("passthrough_headers")
2599+
if passthrough_headers and passthrough_headers.strip():
2600+
try:
2601+
passthrough_headers = json.loads(passthrough_headers)
2602+
except (json.JSONDecodeError, ValueError):
2603+
# Fallback to comma-separated parsing
2604+
passthrough_headers = [h.strip() for h in passthrough_headers.split(",") if h.strip()]
2605+
else:
2606+
passthrough_headers = None
2607+
25882608
gateway = GatewayCreate(
25892609
name=form["name"],
25902610
url=form["url"],
@@ -2597,6 +2617,8 @@ async def admin_add_gateway(request: Request, db: Session = Depends(get_db), use
25972617
auth_token=form.get("auth_token", ""),
25982618
auth_header_key=form.get("auth_header_key", ""),
25992619
auth_header_value=form.get("auth_header_value", ""),
2620+
auth_headers=auth_headers if auth_headers else None,
2621+
passthrough_headers=passthrough_headers,
26002622
)
26012623
except KeyError as e:
26022624
# Convert KeyError to ValidationError-like response
@@ -2741,6 +2763,26 @@ async def admin_edit_gateway(
27412763
tags_str = form.get("tags", "")
27422764
tags = [tag.strip() for tag in tags_str.split(",") if tag.strip()] if tags_str else []
27432765

2766+
# Parse auth_headers JSON if present
2767+
auth_headers_json = form.get("auth_headers")
2768+
auth_headers = []
2769+
if auth_headers_json:
2770+
try:
2771+
auth_headers = json.loads(auth_headers_json)
2772+
except (json.JSONDecodeError, ValueError):
2773+
auth_headers = []
2774+
2775+
# Handle passthrough_headers
2776+
passthrough_headers = form.get("passthrough_headers")
2777+
if passthrough_headers and passthrough_headers.strip():
2778+
try:
2779+
passthrough_headers = json.loads(passthrough_headers)
2780+
except (json.JSONDecodeError, ValueError):
2781+
# Fallback to comma-separated parsing
2782+
passthrough_headers = [h.strip() for h in passthrough_headers.split(",") if h.strip()]
2783+
else:
2784+
passthrough_headers = None
2785+
27442786
gateway = GatewayUpdate( # Pydantic validation happens here
27452787
name=form.get("name"),
27462788
url=form["url"],
@@ -2753,6 +2795,8 @@ async def admin_edit_gateway(
27532795
auth_token=form.get("auth_token", None),
27542796
auth_header_key=form.get("auth_header_key", None),
27552797
auth_header_value=form.get("auth_header_value", None),
2798+
auth_headers=auth_headers if auth_headers else None,
2799+
passthrough_headers=passthrough_headers,
27562800
)
27572801
await gateway_service.update_gateway(db, gateway_id, gateway)
27582802
return JSONResponse(

mcpgateway/handlers/sampling.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ async def create_message(self, db: Session, request: Dict[str, Any]) -> CreateMe
218218
if not self._validate_message(msg):
219219
raise SamplingError(f"Invalid message format: {msg}")
220220

221-
# TODO: Sample from selected model
221+
# FIXME: Implement actual model sampling - currently returns mock response
222222
# For now return mock response
223223
response = self._mock_sample(messages=messages)
224224

@@ -357,7 +357,7 @@ async def _add_context(self, _db: Session, messages: List[Dict[str, Any]], _cont
357357
>>> len(result)
358358
2
359359
"""
360-
# TODO: Implement context gathering based on type
360+
# FIXME: Implement context gathering based on type - currently no-op
361361
# For now return original messages
362362
return messages
363363

0 commit comments

Comments
 (0)