Skip to content

Commit f543795

Browse files
review comments to address
Signed-off-by: Prashant <prashant.rakheja@sap.com>
1 parent e3d3c31 commit f543795

5 files changed

Lines changed: 38 additions & 35 deletions

File tree

src/sap_cloud_sdk/agentgateway/_customer.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,6 @@
3535
# Default credential path for Kyma production deployments
3636
_CREDENTIALS_DEFAULT_PATH = "/etc/ums/credentials/credentials"
3737

38-
# HTTP timeout for token requests and MCP server calls (seconds)
39-
_HTTP_TIMEOUT = 60.0
40-
4138
# Resource URN for Agent Gateway token scope (hardcoded - production value)
4239
_AGW_RESOURCE_URN = "urn:sap:identity:application:provider:name:agent-gateway"
4340

@@ -213,6 +210,7 @@ def _create_ssl_context(certificate: str, private_key: str) -> ssl.SSLContext:
213210
def _request_token_mtls(
214211
credentials: CustomerCredentials,
215212
grant_type: str,
213+
timeout: float,
216214
app_tid: str | None = None,
217215
extra_data: dict | None = None,
218216
) -> str:
@@ -255,7 +253,7 @@ def _request_token_mtls(
255253
try:
256254
with httpx.Client(
257255
verify=ssl_context,
258-
timeout=_HTTP_TIMEOUT,
256+
timeout=timeout,
259257
) as client:
260258
response = client.post(
261259
credentials.token_service_url,
@@ -293,6 +291,7 @@ def _request_token_mtls(
293291

294292
def get_system_token_mtls(
295293
credentials: CustomerCredentials,
294+
timeout: float,
296295
app_tid: str | None = None,
297296
) -> str:
298297
"""Get system-scoped token using mTLS client credentials flow.
@@ -301,6 +300,7 @@ def get_system_token_mtls(
301300
302301
Args:
303302
credentials: Customer credentials.
303+
timeout: HTTP timeout in seconds.
304304
app_tid: BTP Application Tenant ID of subscriber (optional).
305305
306306
Returns:
@@ -310,6 +310,7 @@ def get_system_token_mtls(
310310
return _request_token_mtls(
311311
credentials,
312312
grant_type=_GRANT_TYPE_CLIENT_CREDENTIALS,
313+
timeout=timeout,
313314
app_tid=app_tid,
314315
extra_data={"response_type": "token"},
315316
)
@@ -318,6 +319,7 @@ def get_system_token_mtls(
318319
def exchange_user_token(
319320
credentials: CustomerCredentials,
320321
user_token: str,
322+
timeout: float,
321323
app_tid: str | None = None,
322324
) -> str:
323325
"""Exchange user token for AGW-scoped token using jwt-bearer grant.
@@ -328,6 +330,7 @@ def exchange_user_token(
328330
Args:
329331
credentials: Customer credentials.
330332
user_token: User's JWT token to exchange.
333+
timeout: HTTP timeout in seconds.
331334
app_tid: BTP Application Tenant ID of subscriber (optional).
332335
333336
Returns:
@@ -337,6 +340,7 @@ def exchange_user_token(
337340
return _request_token_mtls(
338341
credentials,
339342
grant_type=_GRANT_TYPE_JWT_BEARER,
343+
timeout=timeout,
340344
app_tid=app_tid,
341345
extra_data={
342346
"assertion": user_token,
@@ -371,7 +375,7 @@ async def _list_server_tools(
371375
url: str,
372376
auth_token: str,
373377
dependency: IntegrationDependency,
374-
timeout: float = _HTTP_TIMEOUT,
378+
timeout: float,
375379
) -> list[MCPTool]:
376380
"""List tools from a single MCP server.
377381
@@ -428,8 +432,8 @@ async def _list_server_tools(
428432

429433
async def get_mcp_tools_customer(
430434
credentials: CustomerCredentials,
435+
timeout: float,
431436
app_tid: str | None = None,
432-
timeout: float = _HTTP_TIMEOUT,
433437
) -> list[MCPTool]:
434438
"""List all MCP tools from servers defined in credentials.
435439
@@ -458,7 +462,7 @@ async def get_mcp_tools_customer(
458462
# Get system token for discovery
459463
loop = asyncio.get_running_loop()
460464
system_token = await loop.run_in_executor(
461-
None, get_system_token_mtls, credentials, app_tid
465+
None, get_system_token_mtls, credentials, timeout, app_tid
462466
)
463467

464468
tools: list[MCPTool] = []
@@ -489,8 +493,8 @@ async def call_mcp_tool_customer(
489493
credentials: CustomerCredentials,
490494
tool: MCPTool,
491495
user_token: str | None,
496+
timeout: float,
492497
app_tid: str | None = None,
493-
timeout: float = _HTTP_TIMEOUT,
494498
**kwargs,
495499
) -> str:
496500
"""Invoke an MCP tool using customer flow.
@@ -516,7 +520,7 @@ async def call_mcp_tool_customer(
516520
if user_token:
517521
# Exchange user token for AGW-scoped token (with principal propagation)
518522
agw_token = await loop.run_in_executor(
519-
None, exchange_user_token, credentials, user_token, app_tid
523+
None, exchange_user_token, credentials, user_token, timeout, app_tid
520524
)
521525
else:
522526
# TODO: IBD workaround - use system token when user_token is not available.
@@ -527,7 +531,7 @@ async def call_mcp_tool_customer(
527531
"Principal propagation will NOT work."
528532
)
529533
agw_token = await loop.run_in_executor(
530-
None, get_system_token_mtls, credentials, app_tid
534+
None, get_system_token_mtls, credentials, timeout, app_tid
531535
)
532536

533537
async with httpx.AsyncClient(

src/sap_cloud_sdk/agentgateway/_lob.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@
3636

3737
_DESTINATION_INSTANCE = "default"
3838

39-
# HTTP timeout for MCP server requests (seconds)
40-
_HTTP_TIMEOUT = 60.0
41-
4239

4340
def _ias_dest_name() -> str:
4441
"""Get IAS destination name based on landscape.
@@ -227,7 +224,7 @@ def _fetch_user_auth_sync():
227224

228225

229226
async def list_server_tools(
230-
dest_url: str, system_auth: str, fragment_name: str, timeout: float = _HTTP_TIMEOUT
227+
dest_url: str, system_auth: str, fragment_name: str, timeout: float
231228
) -> list[MCPTool]:
232229
"""List tools from a single MCP server.
233230
@@ -273,7 +270,7 @@ async def list_server_tools(
273270

274271
async def get_mcp_tools_lob(
275272
tenant_subdomain: str,
276-
timeout: float = _HTTP_TIMEOUT,
273+
timeout: float,
277274
) -> list[MCPTool]:
278275
"""List all MCP tools using LoB flow (destination-based).
279276
@@ -333,7 +330,7 @@ async def call_mcp_tool_lob(
333330
tool: MCPTool,
334331
user_token: str,
335332
tenant_subdomain: str,
336-
timeout: float = _HTTP_TIMEOUT,
333+
timeout: float,
337334
**kwargs,
338335
) -> str:
339336
"""Invoke an MCP tool using LoB flow (destination-based).

src/sap_cloud_sdk/agentgateway/agw_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ async def list_mcp_tools(
158158
)
159159
credentials = load_customer_credentials(credentials_path)
160160
return await get_mcp_tools_customer(
161-
credentials, app_tid, self._config.timeout
161+
credentials, self._config.timeout, app_tid
162162
)
163163

164164
# LoB flow - requires tenant_subdomain
@@ -250,8 +250,8 @@ async def call_mcp_tool(
250250
credentials,
251251
tool,
252252
resolved_user_token,
253-
app_tid,
254253
self._config.timeout,
254+
app_tid,
255255
**kwargs,
256256
)
257257

tests/agentgateway/unit/test_customer.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ def test_requests_client_credentials_token(self, credentials):
305305
mock_client.post.return_value = mock_response
306306
mock_client_class.return_value = mock_client
307307

308-
result = get_system_token_mtls(credentials)
308+
result = get_system_token_mtls(credentials, timeout=60.0)
309309

310310
assert result == "system-token-123"
311311
mock_client.post.assert_called_once()
@@ -332,7 +332,7 @@ def test_raises_on_failed_request(self, credentials):
332332
mock_client_class.return_value = mock_client
333333

334334
with pytest.raises(AgentGatewaySDKError, match="Token request failed"):
335-
get_system_token_mtls(credentials)
335+
get_system_token_mtls(credentials, timeout=60.0)
336336

337337

338338
# ============================================================
@@ -374,7 +374,7 @@ def test_exchanges_user_token_with_jwt_bearer(self, credentials):
374374
mock_client.post.return_value = mock_response
375375
mock_client_class.return_value = mock_client
376376

377-
result = exchange_user_token(credentials, "user-jwt-token")
377+
result = exchange_user_token(credentials, "user-jwt-token", timeout=60.0)
378378

379379
assert result == "exchanged-token-123"
380380
call_args = mock_client.post.call_args
@@ -402,7 +402,9 @@ def test_passes_app_tid_when_provided(self, credentials):
402402
mock_client.post.return_value = mock_response
403403
mock_client_class.return_value = mock_client
404404

405-
result = exchange_user_token(credentials, "user-jwt", app_tid="test-tid")
405+
result = exchange_user_token(
406+
credentials, "user-jwt", timeout=60.0, app_tid="test-tid"
407+
)
406408

407409
assert result == "token-with-tid"
408410
call_args = mock_client.post.call_args
@@ -449,7 +451,7 @@ async def test_raises_when_empty_dependencies(self):
449451
with pytest.raises(
450452
AgentGatewaySDKError, match="integrationDependencies is empty"
451453
):
452-
await get_mcp_tools_customer(credentials)
454+
await get_mcp_tools_customer(credentials, timeout=60.0)
453455

454456
@pytest.mark.asyncio
455457
async def test_discovers_tools_from_credentials(self, credentials):
@@ -475,7 +477,7 @@ async def test_discovers_tools_from_credentials(self, credentials):
475477
return_value=mock_tools,
476478
) as mock_list,
477479
):
478-
result = await get_mcp_tools_customer(credentials)
480+
result = await get_mcp_tools_customer(credentials, timeout=60.0)
479481

480482
assert len(result) == 1
481483
assert result[0].name == "list_cost_centers"
@@ -523,7 +525,7 @@ async def mock_list_tools(*args, **kwargs):
523525
side_effect=mock_list_tools,
524526
),
525527
):
526-
result = await get_mcp_tools_customer(credentials)
528+
result = await get_mcp_tools_customer(credentials, timeout=60.0)
527529

528530
# Should still return tools from server2
529531
assert len(result) == 1
@@ -613,11 +615,11 @@ async def test_exchanges_user_token_before_call(self, credentials, mock_tool):
613615
mock_session_class.return_value = mock_session_ctx
614616

615617
result = await call_mcp_tool_customer(
616-
credentials, mock_tool, "user-jwt", order_id="12345"
618+
credentials, mock_tool, "user-jwt", 60.0, order_id="12345"
617619
)
618620

619621
assert result == "Order created successfully"
620-
mock_exchange.assert_called_once_with(credentials, "user-jwt", None)
622+
mock_exchange.assert_called_once_with(credentials, "user-jwt", 60.0, None)
621623

622624
@pytest.mark.asyncio
623625
async def test_uses_system_token_when_user_token_not_provided(
@@ -669,10 +671,10 @@ async def test_uses_system_token_when_user_token_not_provided(
669671

670672
# Call without user_token (None)
671673
result = await call_mcp_tool_customer(
672-
credentials, mock_tool, None, order_id="12345"
674+
credentials, mock_tool, None, 60.0, order_id="12345"
673675
)
674676

675677
assert result == "Result with system token"
676678
# Should use system token, not exchange
677-
mock_system_token.assert_called_once_with(credentials, None)
679+
mock_system_token.assert_called_once_with(credentials, 60.0, None)
678680
mock_exchange.assert_not_called()

tests/agentgateway/unit/test_lob.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ async def test_returns_empty_when_no_fragments(self):
323323
with patch("sap_cloud_sdk.agentgateway._lob.list_mcp_fragments") as mock_list:
324324
mock_list.return_value = []
325325

326-
result = await get_mcp_tools_lob("tenant-sub")
326+
result = await get_mcp_tools_lob("tenant-sub", 60.0)
327327

328328
assert result == []
329329

@@ -337,7 +337,7 @@ async def test_skips_fragments_without_url(self):
337337
with patch("sap_cloud_sdk.agentgateway._lob.list_mcp_fragments") as mock_list:
338338
mock_list.return_value = [fragment]
339339

340-
result = await get_mcp_tools_lob("tenant-sub")
340+
result = await get_mcp_tools_lob("tenant-sub", 60.0)
341341

342342
assert result == []
343343

@@ -372,7 +372,7 @@ async def test_uses_fragment_name_directly(self):
372372
mock_auth.return_value = "Bearer token"
373373
mock_tools.return_value = [mock_tool]
374374

375-
await get_mcp_tools_lob("tenant-sub")
375+
await get_mcp_tools_lob("tenant-sub", 60.0)
376376

377377
# Verify get_system_auth called with just tenant_subdomain
378378
mock_auth.assert_called_once_with("tenant-sub")
@@ -418,7 +418,7 @@ async def test_handles_exception_for_single_fragment(self):
418418
mock_auth.side_effect = [Exception("Auth failed"), "Bearer token"]
419419
mock_tools.return_value = [mock_tool]
420420

421-
result = await get_mcp_tools_lob("tenant-sub")
421+
result = await get_mcp_tools_lob("tenant-sub", 60.0)
422422

423423
# Should still get tools from second fragment
424424
assert len(result) == 1
@@ -477,7 +477,7 @@ async def test_calls_tool_with_user_auth(self):
477477
mock_session.return_value.__aenter__.return_value = mock_session_instance
478478

479479
result = await call_mcp_tool_lob(
480-
tool, "user-jwt", "tenant-sub", param1="value1"
480+
tool, "user-jwt", "tenant-sub", 60.0, param1="value1"
481481
)
482482

483483
assert result == "Tool result"
@@ -527,6 +527,6 @@ async def test_returns_empty_string_when_no_content(self):
527527
mock_session_instance.call_tool = AsyncMock(return_value=mock_result)
528528
mock_session.return_value.__aenter__.return_value = mock_session_instance
529529

530-
result = await call_mcp_tool_lob(tool, "user-jwt", "tenant-sub")
530+
result = await call_mcp_tool_lob(tool, "user-jwt", "tenant-sub", 60.0)
531531

532532
assert result == ""

0 commit comments

Comments
 (0)