Skip to content
Open
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
4 changes: 2 additions & 2 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"files": "(?x)( package-lock\\.json$ |Cargo\\.lock$ |uv\\.lock$ |go\\.sum$ |mcpgateway/sri_hashes\\.json$ )|^.secrets.baseline$",
"lines": null
},
"generated_at": "2026-06-05T12:31:44Z",
"generated_at": "2026-06-05T14:10:37Z",
"plugins_used": [
{
"name": "AWSKeyDetector"
Expand Down Expand Up @@ -4336,7 +4336,7 @@
"hashed_secret": "c377074d6473f35a91001981355da793dc808ffd",
"is_secret": false,
"is_verified": false,
"line_number": 4323,
"line_number": 4397,
"type": "Hex High Entropy String",
"verified_result": null
}
Expand Down
128 changes: 122 additions & 6 deletions mcpgateway/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -2857,19 +2857,52 @@ class GatewayCreate(BaseModelWithConfigDict):
auth_query_param_key: Optional[str] = Field(
None,
description="Query parameter name for authentication (e.g., 'api_key', 'tavilyApiKey')",
pattern=r"^[a-zA-Z_][a-zA-Z0-9_\-]*$",
)
auth_query_param_value: Optional[SecretStr] = Field(
None,
description="Query parameter value (API key). Stored encrypted.",
)

@field_validator("auth_query_param_key")
@classmethod
def validate_auth_query_param_key(cls, v: Optional[str]) -> Optional[str]:
"""Validate query param key format only if provided and non-empty.

Args:
v: Query parameter key to validate

Returns:
The validated query parameter key

Raises:
ValueError: If the key format is invalid
"""
if v is not None and v != "":
if not re.match(r"^[a-zA-Z_][a-zA-Z0-9_\-]*$", v):
raise ValueError("Query parameter key must start with a letter or underscore, " "followed by letters, numbers, underscores, or hyphens")
return v

# Adding `auth_value` as an alias for better access post-validation
auth_value: Optional[str] = Field(None, validate_default=True)

# One time auth - do not store the auth in gateway flag
one_time_auth: Optional[bool] = Field(default=False, description="The authentication should be used only once and not stored in the gateway")

@field_validator("auth_type", mode="before")
@classmethod
def normalize_auth_type(cls, v: Any) -> Optional[str]:
"""Normalize auth_type: convert string 'none' or 'None' to None.

Args:
v: The auth_type value (may be string "none" or "None")

Returns:
None if v is "none" or "None", otherwise returns v unchanged
"""
if isinstance(v, str) and v.lower() == "none":
return None
return v

tags: Optional[List[Union[str, Dict[str, str]]]] = Field(default_factory=list, description="Tags for categorizing the gateway")

# Team scoping fields for resource organization
Expand Down Expand Up @@ -3143,7 +3176,11 @@ def _process_auth_fields(info: ValidationInfo) -> Optional[str]:
# Validation is handled by model_validator
return None

raise ValueError("Invalid 'auth_type'. Must be one of: basic, bearer, oauth, authheaders, or query_param.")
# Handle no authentication (None or already normalized from "none")
if auth_type is None:
return None

raise ValueError("Invalid 'auth_type'. Must be one of: basic, bearer, oauth, authheaders, query_param, or none.")

@model_validator(mode="after")
def validate_query_param_auth(self) -> "GatewayCreate":
Expand Down Expand Up @@ -3207,6 +3244,21 @@ class GatewayUpdate(BaseModelWithConfigDict):
# Adding `auth_value` as an alias for better access post-validation
auth_value: Optional[str] = Field(None, validate_default=True)

@field_validator("auth_type", mode="before")
@classmethod
def normalize_auth_type(cls, v: Any) -> Optional[str]:
"""Normalize auth_type: convert string 'none' or 'None' to None.

Args:
v: The auth_type value (may be string "none" or "None")

Returns:
None if v is "none" or "None", otherwise returns v unchanged
"""
if isinstance(v, str) and v.lower() == "none":
return None
return v

# OAuth 2.0 configuration
oauth_config: Optional[Dict[str, Any]] = Field(
None, description="OAuth 2.0 configuration including grant_type, client_id, encrypted client_secret, URLs, scopes, audience (for Atlassian/Auth0), and resource (RFC 8707)"
Expand All @@ -3216,13 +3268,31 @@ class GatewayUpdate(BaseModelWithConfigDict):
auth_query_param_key: Optional[str] = Field(
None,
description="Query parameter name for authentication",
pattern=r"^[a-zA-Z_][a-zA-Z0-9_\-]*$",
)
auth_query_param_value: Optional[SecretStr] = Field(
None,
description="Query parameter value (API key)",
)

@field_validator("auth_query_param_key")
@classmethod
def validate_auth_query_param_key(cls, v: Optional[str]) -> Optional[str]:
"""Validate query param key format only if provided and non-empty.

Args:
v: Query parameter key to validate

Returns:
The validated query parameter key

Raises:
ValueError: If the key format is invalid
"""
if v is not None and v != "":
if not re.match(r"^[a-zA-Z_][a-zA-Z0-9_\-]*$", v):
raise ValueError("Query parameter key must start with a letter or underscore, " "followed by letters, numbers, underscores, or hyphens")
return v

# One time auth - do not store the auth in gateway flag
one_time_auth: Optional[bool] = Field(default=False, description="The authentication should be used only once and not stored in the gateway")

Expand Down Expand Up @@ -3460,7 +3530,11 @@ def _process_auth_fields(info: ValidationInfo) -> Optional[str]:
# Validation is handled by model_validator
return None

raise ValueError("Invalid 'auth_type'. Must be one of: basic, bearer, oauth, authheaders, or query_param.")
# Handle no authentication (None or already normalized from "none")
if auth_type is None:
return None

raise ValueError("Invalid 'auth_type'. Must be one of: basic, bearer, oauth, authheaders, query_param, or none.")

@model_validator(mode="after")
def validate_query_param_auth(self) -> "GatewayUpdate":
Expand Down Expand Up @@ -4661,6 +4735,22 @@ class A2AAgentCreate(BaseModel):

# Adding `auth_value` as an alias for better access post-validation
auth_value: Optional[str] = Field(None, validate_default=True)

@field_validator("auth_type", mode="before")
@classmethod
def normalize_auth_type(cls, v: Any) -> Optional[str]:
"""Normalize auth_type: convert string 'none' or 'None' to None.

Args:
v: The auth_type value (may be string "none" or "None")

Returns:
None if v is "none" or "None", otherwise returns v unchanged
"""
if isinstance(v, str) and v.lower() == "none":
return None
return v

tags: List[str] = Field(default_factory=list, description="Tags for categorizing the agent")

# Team scoping fields
Expand Down Expand Up @@ -4909,7 +4999,11 @@ def _process_auth_fields(info: ValidationInfo) -> Optional[str]:
# Validation is handled by model_validator
return None

raise ValueError("Invalid 'auth_type'. Must be one of: basic, bearer, oauth, authheaders, or query_param.")
# Handle no authentication (None or already normalized from "none")
if auth_type is None:
return None

raise ValueError("Invalid 'auth_type'. Must be one of: basic, bearer, oauth, authheaders, query_param, or none.")

@model_validator(mode="after")
def validate_query_param_auth(self) -> "A2AAgentCreate":
Expand Down Expand Up @@ -5002,6 +5096,24 @@ class A2AAgentUpdate(BaseModelWithConfigDict):
version: Optional[str] = Field(default=None, description="Agent version for UAID generation")
uaid_native_id_override: Optional[str] = Field(None, description="Override nativeId in UAID for cross-gateway routing (defaults to endpoint_url if not provided)")

@field_validator("auth_type")
@classmethod
def normalize_auth_type(cls, v: Any) -> Optional[str]:
"""Normalize auth_type by converting string 'none' or 'None' to Python None.

This allows the UI to send "none" as a string value which gets converted
to Python None for proper handling throughout the system.

Args:
v: The auth_type value (can be str or None)

Returns:
The normalized auth_type (None if input was "none"/"None", otherwise unchanged)
"""
if isinstance(v, str) and v.lower() == "none":
return None
return v

@field_validator("tags")
@classmethod
def validate_tags(cls, v: Optional[List[str]]) -> Optional[List[str]]:
Expand Down Expand Up @@ -5239,7 +5351,11 @@ def _process_auth_fields(info: ValidationInfo) -> Optional[str]:
# Validation is handled by model_validator
return None

raise ValueError("Invalid 'auth_type'. Must be one of: basic, bearer, oauth, authheaders, or query_param.")
# Handle no authentication (None or already normalized from "none")
if auth_type is None:
return None

raise ValueError("Invalid 'auth_type'. Must be one of: basic, bearer, oauth, authheaders, query_param, or none.")

@model_validator(mode="after")
def validate_query_param_auth(self) -> "A2AAgentUpdate":
Expand Down
12 changes: 6 additions & 6 deletions mcpgateway/templates/admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -3896,7 +3896,7 @@ <h3 class="text-lg font-bold dark:text-gray-200">
id="auth-type"
class="mt-1 px-3 py-2 block w-full rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-900 dark:placeholder-gray-300 dark:text-gray-300"
>
<option value="">None</option>
<option value="none">None</option>
<option value="basic">Basic</option>
<option value="bearer">Bearer Token</option>
<option value="authheaders">Custom Headers</option>
Expand Down Expand Up @@ -5444,7 +5444,7 @@ <h3 class="text-lg font-bold mb-4 dark:text-gray-200">
id="auth-type-gw"
class="mt-1 px-3 py-2 block w-full rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-900 dark:placeholder-gray-300 dark:text-gray-300"
>
<option value="">None</option>
<option value="none">None</option>
<option value="basic">Basic</option>
<option value="bearer">Bearer Token</option>
<option value="authheaders">Custom Headers</option>
Expand Down Expand Up @@ -6886,7 +6886,7 @@ <h3 class="text-lg font-medium text-gray-900 dark:text-gray-200 mb-4">
name="auth_type"
class="mt-1 px-3 py-2 block w-full rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-900 dark:text-gray-300"
>
<option value="">None</option>
<option value="none">None</option>
<option value="basic">Basic</option>
<option value="bearer">Bearer Token</option>
<option value="authheaders">Custom Headers</option>
Expand Down Expand Up @@ -8795,7 +8795,7 @@ <h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">
id="edit-auth-type"
class="mt-1 px-3 py-2 block w-full rounded-md border border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-900 dark:placeholder-gray-300 dark:text-gray-300"
>
<option value="">None</option>
<option value="none">None</option>
<option value="basic">Basic</option>
<option value="bearer">Bearer Token</option>
<option value="authheaders">Custom Headers</option>
Expand Down Expand Up @@ -9980,7 +9980,7 @@ <h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">
id="auth-type-gw-edit"
class="mt-1 px-3 py-2 block w-full rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-900 dark:placeholder-gray-300 dark:text-gray-300"
>
<option value="">None</option>
<option value="none">None</option>
<option value="basic">Basic</option>
<option value="bearer">Bearer Token</option>
<option value="authheaders">Custom Headers</option>
Expand Down Expand Up @@ -10487,7 +10487,7 @@ <h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">
class="mt-1 px-3 py-2 block w-full rounded-md border border-gray-300 dark:border-gray-700 shadow-sm
focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-900 dark:text-gray-300"
>
<option value="">None</option>
<option value="none">None</option>
<option value="basic">Basic</option>
<option value="bearer">Bearer Token</option>
<option value="authheaders">Custom Headers</option>
Expand Down
Loading
Loading