Skip to content

Commit ec581fd

Browse files
fix: #1370 Custom Headers now are visible when editing saved servers (#1371)
* fix: #1370 Custom Headers now are visible when editing saved servers. Signed-off-by: Aakash <aakash.howlader@gmail.com> * chore: apply pre-commit formatting fixes - Fix whitespace and line endings - Apply black formatting to schemas.py - Normalize indentation in go.mod Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> --------- Signed-off-by: Aakash <aakash.howlader@gmail.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
1 parent 1786dcd commit ec581fd

File tree

8 files changed

+348
-45
lines changed

8 files changed

+348
-45
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,9 @@ This release delivers **REST API Passthrough Capabilities**, **API & UI Paginati
234234
* **Non-root Container Users** (#1231) - Added non-root user to scratch Go containers
235235
* **Container Runtime Detection** - Improved Docker/Podman detection in Makefile
236236

237+
#### **💻 Admin UI Fixes** (#1370)
238+
* **Saved custom headers not visible** (#1370) - Fixed custom headers not visible to Admins when editing a MCP server using custom headers for auth.
239+
237240
### Changed
238241

239242
#### **🗄️ Database Schema & Multi-Tenancy Enhancements** (#1246, #1273)

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2758,11 +2758,11 @@ endif
27582758

27592759
# Profile detection (for platform-specific services)
27602760
ifeq ($(PLATFORM),linux/amd64)
2761-
PROFILE = --profile with-fast-time
2761+
PROFILE = --profile with-fast-time
27622762
endif
27632763

27642764
define COMPOSE
2765-
$(COMPOSE_CMD) -f $(COMPOSE_FILE) $(PROFILE)
2765+
$(COMPOSE_CMD) -f $(COMPOSE_FILE) $(PROFILE)
27662766
endef
27672767

27682768
.PHONY: compose-up compose-restart compose-build compose-pull \

docs/docs/architecture/roadmap.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494

9595
???+ info "✨ Features - Remaining (38)"
9696

97-
- ⏳ [**#262**](https://github.com/IBM/mcp-context-forge/issues/262) - [Feature Request]: Sample Agent - LangChain Integration (OpenAI & A2A Endpoints)
97+
- ⏳ [**#262**](https://github.com/IBM/mcp-context-forge/issues/262) - [Feature Request]: Sample Agent - LangChain Integration (OpenAI & A2A Endpoints)
9898
- ⏳ [**#263**](https://github.com/IBM/mcp-context-forge/issues/263) - [Feature Request]: Sample Agent - CrewAI Integration (OpenAI & A2A Endpoints)
9999
- ⏳ [**#268**](https://github.com/IBM/mcp-context-forge/issues/268) - [Feature Request]: Sample MCP Server - Haskell Implementation ("pandoc-server") (html, docx, pptx, latex conversion)
100100
- ⏳ [**#269**](https://github.com/IBM/mcp-context-forge/issues/269) - [Feature Request]: MCP Server - Go Implementation (LaTeX Service)
@@ -180,7 +180,7 @@
180180
- ⏳ [**#266**](https://github.com/IBM/mcp-context-forge/issues/266) - [Feature Request]: Sample MCP Server - Rust Implementation ("filesystem-server")
181181
- ⏳ [**#267**](https://github.com/IBM/mcp-context-forge/issues/267) - [Feature Request]: Sample MCP Server – Java Implementation ("plantuml-server")
182182
- ⏳ [**#272**](https://github.com/IBM/mcp-context-forge/issues/272) - [Feature Request]: Observability - Pre-built Grafana Dashboards & Loki Log Export
183-
- ⏳ [**#273**](https://github.com/IBM/mcp-context-forge/issues/273) - [Feature Request]: Terraform Module - mcp-gateway-aws supporting both EKS and ECS Fargate targets
183+
- ⏳ [**#273**](https://github.com/IBM/mcp-context-forge/issues/273) - [Feature Request]: Terraform Module - "mcp-gateway-aws" supporting both EKS and ECS Fargate targets
184184
- ⏳ [**#274**](https://github.com/IBM/mcp-context-forge/issues/274) - [Feature Request]: Terraform Module - "mcp-gateway-azure" supporting AKS and ACA
185185
- ⏳ [**#275**](https://github.com/IBM/mcp-context-forge/issues/275) - [Feature Request]: Terraform Module - "mcp-gateway-gcp" supporting GKE and Cloud Run
186186
- ⏳ [**#276**](https://github.com/IBM/mcp-context-forge/issues/276) - [Feature Request]: Terraform Module – "mcp-gateway-ibm-cloud" supporting IKS, ROKS, Code Engine targets
@@ -859,4 +859,4 @@
859859
-**Completed** - Issue has been resolved and closed
860860

861861
!!! tip "Contributing"
862-
Want to contribute to any of these features? Check out the individual GitHub issues for more details and discussion!
862+
Want to contribute to any of these features? Check out the individual GitHub issues for more details and discussion!

mcp-servers/go/fast-time-server/go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ toolchain go1.23.10
77
require github.com/mark3labs/mcp-go v0.32.0 // MCP server/runtime
88

99
require (
10-
github.com/google/uuid v1.6.0 // indirect
11-
github.com/spf13/cast v1.7.1 // indirect
12-
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
10+
github.com/google/uuid v1.6.0 // indirect
11+
github.com/spf13/cast v1.7.1 // indirect
12+
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
1313
)

mcpgateway/schemas.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2916,6 +2916,8 @@ class GatewayRead(BaseModelWithConfigDict):
29162916
# Authorizations
29172917
auth_type: Optional[str] = Field(None, description="auth_type: basic, bearer, headers, oauth, or None")
29182918
auth_value: Optional[str] = Field(None, description="auth value: username/password or token or custom headers")
2919+
auth_headers: Optional[List[Dict[str, str]]] = Field(default=None, description="List of custom headers for authentication")
2920+
auth_headers_unmasked: Optional[List[Dict[str, str]]] = Field(default=None, description="Unmasked custom headers for administrative views")
29192921

29202922
# OAuth 2.0 configuration
29212923
oauth_config: Optional[Dict[str, Any]] = Field(None, description="OAuth 2.0 configuration including grant_type, client_id, encrypted client_secret, URLs, and scopes")
@@ -2928,6 +2930,10 @@ class GatewayRead(BaseModelWithConfigDict):
29282930
auth_header_value: Optional[str] = Field(None, description="vallue for custom headers authentication")
29292931
tags: List[str] = Field(default_factory=list, description="Tags for categorizing the gateway")
29302932

2933+
auth_password_unmasked: Optional[str] = Field(default=None, description="Unmasked password for basic authentication")
2934+
auth_token_unmasked: Optional[str] = Field(default=None, description="Unmasked bearer token for authentication")
2935+
auth_header_value_unmasked: Optional[str] = Field(default=None, description="Unmasked single custom header value")
2936+
29312937
# Team scoping fields for resource organization
29322938
team_id: Optional[str] = Field(None, description="Team ID this gateway belongs to")
29332939
team: Optional[str] = Field(None, description="Name of the team that owns this resource")
@@ -3040,19 +3046,24 @@ def _populate_auth(self) -> Self:
30403046
if not u or not p:
30413047
raise ValueError("basic auth requires both username and password")
30423048
self.auth_username, self.auth_password = u, p
3049+
self.auth_password_unmasked = p
30433050

30443051
elif auth_type == "bearer":
30453052
auth = auth_value.get("Authorization")
30463053
if not (isinstance(auth, str) and auth.startswith("Bearer ")):
30473054
raise ValueError("bearer auth requires an Authorization header of the form 'Bearer <token>'")
30483055
self.auth_token = auth.removeprefix("Bearer ")
3056+
self.auth_token_unmasked = self.auth_token
30493057

30503058
elif auth_type == "authheaders":
30513059
# For backward compatibility, populate first header in key/value fields
3052-
if len(auth_value) == 0:
3060+
if not isinstance(auth_value, dict) or len(auth_value) == 0:
30533061
raise ValueError("authheaders requires at least one key/value pair")
3062+
self.auth_headers = [{"key": str(key), "value": "" if value is None else str(value)} for key, value in auth_value.items()]
3063+
self.auth_headers_unmasked = [{"key": str(key), "value": "" if value is None else str(value)} for key, value in auth_value.items()]
30543064
k, v = next(iter(auth_value.items()))
30553065
self.auth_header_key, self.auth_header_value = k, v
3066+
self.auth_header_value_unmasked = v
30563067

30573068
return self
30583069

@@ -3087,7 +3098,19 @@ def masked(self) -> "GatewayRead":
30873098
masked_data["auth_password"] = settings.masked_auth_value if masked_data.get("auth_password") else None
30883099
masked_data["auth_token"] = settings.masked_auth_value if masked_data.get("auth_token") else None
30893100
masked_data["auth_header_value"] = settings.masked_auth_value if masked_data.get("auth_header_value") else None
3090-
3101+
if masked_data.get("auth_headers"):
3102+
masked_data["auth_headers"] = [
3103+
{
3104+
"key": header.get("key"),
3105+
"value": settings.masked_auth_value if header.get("value") else header.get("value"),
3106+
}
3107+
for header in masked_data["auth_headers"]
3108+
]
3109+
3110+
masked_data["auth_password_unmasked"] = self.auth_password_unmasked
3111+
masked_data["auth_token_unmasked"] = self.auth_token_unmasked
3112+
masked_data["auth_header_value_unmasked"] = self.auth_header_value_unmasked
3113+
masked_data["auth_headers_unmasked"] = [header.copy() for header in self.auth_headers_unmasked] if self.auth_headers_unmasked else None
30913114
return GatewayRead.model_validate(masked_data)
30923115

30933116

mcpgateway/services/gateway_service.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1192,7 +1192,27 @@ async def update_gateway(
11921192

11931193
# Support multiple custom headers on update
11941194
if hasattr(gateway_update, "auth_headers") and gateway_update.auth_headers:
1195-
header_dict = {h["key"]: h["value"] for h in gateway_update.auth_headers if h.get("key")}
1195+
existing_auth_raw = getattr(gateway, "auth_value", {}) or {}
1196+
if isinstance(existing_auth_raw, str):
1197+
try:
1198+
existing_auth = decode_auth(existing_auth_raw)
1199+
except Exception:
1200+
existing_auth = {}
1201+
elif isinstance(existing_auth_raw, dict):
1202+
existing_auth = existing_auth_raw
1203+
else:
1204+
existing_auth = {}
1205+
1206+
header_dict: Dict[str, str] = {}
1207+
for header in gateway_update.auth_headers:
1208+
key = header.get("key")
1209+
if not key:
1210+
continue
1211+
value = header.get("value", "")
1212+
if value == settings.masked_auth_value and key in existing_auth:
1213+
header_dict[key] = existing_auth[key]
1214+
else:
1215+
header_dict[key] = value
11961216
gateway.auth_value = header_dict # Store as dict for DB JSON field
11971217
elif settings.masked_auth_value not in (token, password, header_value):
11981218
# Check if values differ from existing ones or if setting for first time

0 commit comments

Comments
 (0)