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
2 changes: 2 additions & 0 deletions mcpgateway/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6783,6 +6783,7 @@ async def admin_add_gateway(request: Request, db: Session = Depends(get_db), use
auth_header_value=str(form.get("auth_header_value", "")),
auth_headers=auth_headers if auth_headers else None,
oauth_config=oauth_config,
one_time_auth=form.get("one_time_auth", False),
passthrough_headers=passthrough_headers,
visibility=visibility,
ca_certificate=ca_certificate,
Expand Down Expand Up @@ -7091,6 +7092,7 @@ async def admin_edit_gateway(
auth_header_value=str(form.get("auth_header_value", "")),
auth_value=str(form.get("auth_value", "")),
auth_headers=auth_headers if auth_headers else None,
one_time_auth=form.get("one_time_auth", False),
passthrough_headers=passthrough_headers,
oauth_config=oauth_config,
visibility=visibility,
Expand Down
32 changes: 30 additions & 2 deletions mcpgateway/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ def assemble_auth(cls, values: Dict[str, Any]) -> Dict[str, Any]:
)

auth_type = values.get("auth_type")
if auth_type:
if auth_type and auth_type.lower() != "one_time_auth":
if auth_type.lower() == "basic":
creds = base64.b64encode(f"{values.get('auth_username', '')}:{values.get('auth_password', '')}".encode("utf-8")).decode()
encoded_auth = encode_auth({"Authorization": f"Basic {creds}"})
Expand Down Expand Up @@ -1032,7 +1032,7 @@ def assemble_auth(cls, values: Dict[str, Any]) -> Dict[str, Any]:
)

auth_type = values.get("auth_type")
if auth_type:
if auth_type and auth_type.lower() != "one_time_auth":
if auth_type.lower() == "basic":
creds = base64.b64encode(f"{values.get('auth_username', '')}:{values.get('auth_password', '')}".encode("utf-8")).decode()
encoded_auth = encode_auth({"Authorization": f"Basic {creds}"})
Expand Down Expand Up @@ -2413,6 +2413,10 @@ class GatewayCreate(BaseModel):

# 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")

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

# Team scoping fields for resource organization
Expand Down Expand Up @@ -2646,6 +2650,9 @@ def _process_auth_fields(info: ValidationInfo) -> Optional[str]:

return encode_auth({header_key: header_value})

if auth_type == "one_time_auth":
return None # No auth_value needed for one-time auth

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


Expand Down Expand Up @@ -2677,6 +2684,9 @@ class GatewayUpdate(BaseModelWithConfigDict):
# 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, and scopes")

# 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")

tags: Optional[List[str]] = Field(None, description="Tags for categorizing the gateway")

# Team scoping fields for resource organization
Expand Down Expand Up @@ -2879,6 +2889,9 @@ def _process_auth_fields(info: ValidationInfo) -> Optional[str]:

return encode_auth({header_key: header_value})

if auth_type == "one_time_auth":
return None # No auth_value needed for one-time auth

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


Expand Down Expand Up @@ -3037,6 +3050,10 @@ def _populate_auth(self) -> Self:
# They use oauth_config instead
return self

if auth_type == "one_time_auth":
# One-time auth gateways don't store auth_value
return self

# If no encoded value is present, nothing to populate
if not auth_value_encoded:
return self
Expand Down Expand Up @@ -4141,6 +4158,10 @@ def _process_auth_fields(info: ValidationInfo) -> Optional[str]:

return encode_auth({header_key: header_value})

if auth_type == "one_time_auth":
# One-time auth does not require encoding here
return None

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


Expand Down Expand Up @@ -4425,6 +4446,10 @@ def _process_auth_fields(info: ValidationInfo) -> Optional[str]:

return encode_auth({header_key: header_value})

if auth_type == "one_time_auth":
# One-time auth does not require encoding here
return None

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


Expand Down Expand Up @@ -4575,6 +4600,9 @@ def _populate_auth(self) -> Self:
# They use oauth_config instead
return self

if auth_type == "one_time_auth":
return self

# If no encoded value is present, nothing to populate
if not auth_value_encoded:
return self
Expand Down
16 changes: 16 additions & 0 deletions mcpgateway/services/gateway_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,12 @@ async def register_gateway(
ca_certificate = getattr(gateway, "ca_certificate", None)
capabilities, tools, resources, prompts = await self._initialize_gateway(normalized_url, authentication_headers, gateway.transport, auth_type, oauth_config, ca_certificate)

if gateway.one_time_auth:
# For one-time auth, clear auth_type and auth_value after initialization
auth_type = "one_time_auth"
auth_value = None
oauth_config = None

tools = [
DbTool(
original_name=tool.name,
Expand Down Expand Up @@ -1262,6 +1268,12 @@ async def update_gateway(
new_resource_uris = [resource.uri for resource in resources]
new_prompt_names = [prompt.name for prompt in prompts]

if gateway_update.one_time_auth:
# For one-time auth, clear auth_type and auth_value after initialization
gateway.auth_type = "one_time_auth"
gateway.auth_value = None
gateway.oauth_config = None

# Update tools using helper method
tools_to_add = self._update_or_create_tools(db, tools, gateway, "update")

Expand Down Expand Up @@ -2037,6 +2049,10 @@ async def check_health_of_gateways(self, db: Session, gateways: List[DbGateway],
# Create trace span for health check batch
with create_span("gateway.health_check_batch", {"gateway.count": len(gateways), "check.type": "health"}) as batch_span:
for gateway in gateways:

if gateway.auth_type == "one_time_auth":
continue # Skip health check for one-time auth gateways as these are authenticated with passthrough headers only

# Create span for individual gateway health check
with create_span(
"gateway.health_check",
Expand Down
1 change: 1 addition & 0 deletions mcpgateway/services/tool_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -1405,6 +1405,7 @@ async def connect_to_streamablehttp_server(server_url: str, headers: dict = head
tool_call_result = await connect_to_sse_server(tool_gateway.url, headers=headers)
elif transport == "streamablehttp":
tool_call_result = await connect_to_streamablehttp_server(tool_gateway.url, headers=headers)

content = tool_call_result.model_dump(by_alias=True).get("content", [])

filtered_response = extract_using_jq(content, tool.jsonpath_filter)
Expand Down
50 changes: 50 additions & 0 deletions mcpgateway/templates/admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -4559,6 +4559,32 @@ <h3 class="text-lg font-bold mb-4 dark:text-gray-200">
</div>
</div>
</div>

<div class="mt-4">
<label class="flex items-center">
<input
type="checkbox"
name="one_time_auth"
id="single-use-auth-gw"
class="form-checkbox h-5 w-5 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 dark:bg-gray-800 dark:border-gray-600"
x-tooltip="'💡This will not store the server credentials, Some features may not work.'"
onchange="document.getElementById('single-use-auth-hint-gw').style.display = this.checked ? 'block' : 'none'"
/>
<span
class="ml-2 text-sm font-medium text-gray-700 dark:text-gray-300"
>
Use one-time authentication
</span>
</label>
<p
id="single-use-auth-hint-gw"
style="display: none"
class="mt-2 text-sm text-gray-700 dark:text-gray-300 bg-indigo-50 dark:bg-indigo-900/30 border border-indigo-200 dark:border-indigo-700 rounded-md p-2"
>
⚠️ NOTE: Authentication won't be stored in the gateway. Configure Passthrough headers for reuse. Health checks will be disabled.
</p>
</div>

<div>
<label
class="block text-sm font-medium text-gray-700 dark:text-gray-400"
Expand Down Expand Up @@ -8046,6 +8072,30 @@ <h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">
</div>
</div>
</div>
<div class="mt-4">
<label class="flex items-center">
<input
type="checkbox"
name="one_time_auth"
id="single-use-auth-gw-edit"
class="form-checkbox h-5 w-5 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 dark:bg-gray-800 dark:border-gray-600"
x-tooltip="'💡This will not store the server credentials, Some features may not work.'"
onchange="document.getElementById('single-use-auth-hint-gw-edit').style.display = this.checked ? 'block' : 'none'"
/>
<span
class="ml-2 text-sm font-medium text-gray-700 dark:text-gray-300"
>
Use one-time authentication
</span>
</label>
<p
id="single-use-auth-hint-gw-edit"
style="display: none"
class="mt-2 text-sm text-gray-700 dark:text-gray-300 bg-indigo-50 dark:bg-indigo-900/30 border border-indigo-200 dark:border-indigo-700 rounded-md p-2"
>
⚠️ NOTE: Authentication won't be stored in the gateway. Configure Passthrough headers for reuse. Health checks will be disabled.
</p>
</div>
<div>
<label
class="block text-sm font-medium text-gray-700 dark:text-gray-400"
Expand Down
Loading