Skip to content

Commit b88ea13

Browse files
Merge branch 'main' into implement-token-caching
2 parents fda1c72 + 00247c3 commit b88ea13

20 files changed

Lines changed: 164 additions & 54 deletions

File tree

.github/workflows/check-version-bump.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ on:
55
branches:
66
- main
77

8+
permissions:
9+
contents: read
10+
811
jobs:
912
check-version-bump:
1013
name: Enforce version bump when src/ is modified

.github/workflows/commit-validation.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ on:
88
branches:
99
- main
1010

11+
permissions:
12+
contents: read
13+
1114
jobs:
1215
commit-validation:
1316
runs-on: ${{ contains(github.server_url, 'github.com') && 'ubuntu-latest' || fromJSON('["self-hosted"]') }}

.github/workflows/proto-verify.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ on:
1515
- 'src/sap_cloud_sdk/core/auditlog_ng/proto/**'
1616
- 'Makefile'
1717

18+
permissions:
19+
contents: read
20+
1821
jobs:
1922
verify-proto:
2023
name: Verify generated proto code is up-to-date

.github/workflows/release-internal.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ on:
66
- main
77
types: [opened, synchronize, reopened]
88

9+
permissions:
10+
contents: read
11+
912
jobs:
1013
publish:
1114
name: Publish to Artifactory

.github/workflows/reuse.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ on:
88
branches:
99
- "main"
1010

11+
permissions:
12+
contents: read
13+
1114
jobs:
1215
check-reuse-compliance:
1316
runs-on: ${{ contains(github.server_url, 'github.com') && 'ubuntu-latest' || fromJSON('["self-hosted"]') }}

.github/workflows/sync.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ on:
55
- cron: '0 */3 * * *'
66
workflow_dispatch:
77

8+
permissions:
9+
contents: write
10+
811
jobs:
912
sync:
1013
runs-on: ["self-hosted"]

README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ The Python SDK offers a clean, type-safe API following Python best practices whi
1111
### Key Features
1212

1313
- **Agent Decorators**
14+
- **Agent Gateway**
15+
- **Agent Memory**
1416
- **AI Core Integration**
1517
- **Audit Log Service**
16-
- **Agent Memory Service**
18+
- **Audit Log NG**
1719
- **Destination Service**
1820
- **Document Management Service**
21+
- **Extensibility**
22+
- **IAS (Identity and Access Service)**
1923
- **ObjectStore Service**
2024
- **Secret Resolver**
2125
- **Telemetry & Observability**
@@ -60,10 +64,15 @@ The SDK automatically resolves configuration from multiple sources with the foll
6064
Each module has comprehensive usage guides:
6165

6266
- [Agent Decorators](src/sap_cloud_sdk/agent_decorators/user-guide.md)
63-
- [AuditLog](src/sap_cloud_sdk/core/auditlog/user-guide.md)
67+
- [Agent Gateway](src/sap_cloud_sdk/agentgateway/user-guide.md)
6468
- [Agent Memory](src/sap_cloud_sdk/agent_memory/user-guide.md)
69+
- [AI Core](src/sap_cloud_sdk/aicore/user-guide.md)
70+
- [AuditLog](src/sap_cloud_sdk/core/auditlog/user-guide.md)
71+
- [AuditLog NG](src/sap_cloud_sdk/core/auditlog_ng/user-guide.md)
6572
- [Destination](src/sap_cloud_sdk/destination/user-guide.md)
6673
- [DMS](src/sap_cloud_sdk/dms/user-guide.md)
74+
- [Extensibility](src/sap_cloud_sdk/extensibility/user-guide.md)
75+
- [IAS](src/sap_cloud_sdk/ias/user-guide.md)
6776
- [ObjectStore](src/sap_cloud_sdk/objectstore/user-guide.md)
6877
- [Secret Resolver](src/sap_cloud_sdk/core/secret_resolver/user-guide.md)
6978
- [Telemetry](src/sap_cloud_sdk/core/telemetry/user-guide.md)

src/sap_cloud_sdk/agentgateway/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"""
5454

5555
from sap_cloud_sdk.agentgateway._models import MCPTool
56+
from sap_cloud_sdk.agentgateway.config import ClientConfig
5657
from sap_cloud_sdk.agentgateway.agw_client import create_client, AgentGatewayClient
5758
from sap_cloud_sdk.agentgateway.exceptions import (
5859
AgentGatewaySDKError,
@@ -65,6 +66,8 @@
6566
"create_client",
6667
# Client class
6768
"AgentGatewayClient",
69+
# Configuration
70+
"ClientConfig",
6871
# Data models
6972
"MCPTool",
7073
# Exceptions

src/sap_cloud_sdk/agentgateway/_customer.py

Lines changed: 17 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 = 30.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,6 +375,7 @@ async def _list_server_tools(
371375
url: str,
372376
auth_token: str,
373377
dependency: IntegrationDependency,
378+
timeout: float,
374379
) -> list[MCPTool]:
375380
"""List tools from a single MCP server.
376381
@@ -390,7 +395,7 @@ async def _list_server_tools(
390395
"Authorization": f"Bearer {auth_token}",
391396
"x-correlation-id": str(uuid.uuid4()),
392397
},
393-
timeout=_HTTP_TIMEOUT,
398+
timeout=timeout,
394399
) as http_client:
395400
async with streamable_http_client(url, http_client=http_client) as (
396401
read,
@@ -427,6 +432,7 @@ async def _list_server_tools(
427432

428433
async def get_mcp_tools_customer(
429434
credentials: CustomerCredentials,
435+
timeout: float,
430436
app_tid: str | None = None,
431437
) -> list[MCPTool]:
432438
"""List all MCP tools from servers defined in credentials.
@@ -456,7 +462,7 @@ async def get_mcp_tools_customer(
456462
# Get system token for discovery
457463
loop = asyncio.get_running_loop()
458464
system_token = await loop.run_in_executor(
459-
None, get_system_token_mtls, credentials, app_tid
465+
None, get_system_token_mtls, credentials, timeout, app_tid
460466
)
461467

462468
tools: list[MCPTool] = []
@@ -471,7 +477,7 @@ async def get_mcp_tools_customer(
471477
)
472478

473479
try:
474-
server_tools = await _list_server_tools(url, system_token, dep)
480+
server_tools = await _list_server_tools(url, system_token, dep, timeout)
475481
tools.extend(server_tools)
476482
logger.debug("Loaded %d tool(s) from %s", len(server_tools), dep.ord_id)
477483
except Exception:
@@ -487,6 +493,7 @@ async def call_mcp_tool_customer(
487493
credentials: CustomerCredentials,
488494
tool: MCPTool,
489495
user_token: str | None,
496+
timeout: float,
490497
app_tid: str | None = None,
491498
**kwargs,
492499
) -> str:
@@ -513,7 +520,7 @@ async def call_mcp_tool_customer(
513520
if user_token:
514521
# Exchange user token for AGW-scoped token (with principal propagation)
515522
agw_token = await loop.run_in_executor(
516-
None, exchange_user_token, credentials, user_token, app_tid
523+
None, exchange_user_token, credentials, user_token, timeout, app_tid
517524
)
518525
else:
519526
# TODO: IBD workaround - use system token when user_token is not available.
@@ -524,15 +531,15 @@ async def call_mcp_tool_customer(
524531
"Principal propagation will NOT work."
525532
)
526533
agw_token = await loop.run_in_executor(
527-
None, get_system_token_mtls, credentials, app_tid
534+
None, get_system_token_mtls, credentials, timeout, app_tid
528535
)
529536

530537
async with httpx.AsyncClient(
531538
headers={
532539
"Authorization": f"Bearer {agw_token}",
533540
"x-correlation-id": str(uuid.uuid4()),
534541
},
535-
timeout=_HTTP_TIMEOUT,
542+
timeout=timeout,
536543
) as http_client:
537544
async with streamable_http_client(tool.url, http_client=http_client) as (
538545
read,

src/sap_cloud_sdk/agentgateway/_lob.py

Lines changed: 8 additions & 7 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 = 30.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
227+
dest_url: str, system_auth: str, fragment_name: str, timeout: float
231228
) -> list[MCPTool]:
232229
"""List tools from a single MCP server.
233230
@@ -241,7 +238,7 @@ async def list_server_tools(
241238
"""
242239
async with httpx.AsyncClient(
243240
headers={"Authorization": system_auth, "x-correlation-id": str(uuid.uuid4())},
244-
timeout=_HTTP_TIMEOUT,
241+
timeout=timeout,
245242
) as http_client:
246243
async with streamable_http_client(dest_url, http_client=http_client) as (
247244
read,
@@ -273,6 +270,7 @@ async def list_server_tools(
273270

274271
async def get_mcp_tools_lob(
275272
tenant_subdomain: str,
273+
timeout: float,
276274
) -> list[MCPTool]:
277275
"""List all MCP tools using LoB flow (destination-based).
278276
@@ -309,7 +307,9 @@ async def get_mcp_tools_lob(
309307

310308
try:
311309
system_auth = await get_system_auth(tenant_subdomain)
312-
server_tools = await list_server_tools(mcp_url, system_auth, fragment_name)
310+
server_tools = await list_server_tools(
311+
mcp_url, system_auth, fragment_name, timeout
312+
)
313313
tools.extend(server_tools)
314314
logger.debug(
315315
"Loaded %d tool(s) from fragment '%s'",
@@ -330,6 +330,7 @@ async def call_mcp_tool_lob(
330330
tool: MCPTool,
331331
user_token: str,
332332
tenant_subdomain: str,
333+
timeout: float,
333334
**kwargs,
334335
) -> str:
335336
"""Invoke an MCP tool using LoB flow (destination-based).
@@ -357,7 +358,7 @@ async def call_mcp_tool_lob(
357358

358359
async with httpx.AsyncClient(
359360
headers={"Authorization": user_auth, "x-correlation-id": str(uuid.uuid4())},
360-
timeout=_HTTP_TIMEOUT,
361+
timeout=timeout,
361362
) as http_client:
362363
async with streamable_http_client(tool.url, http_client=http_client) as (
363364
read,

0 commit comments

Comments
 (0)