Skip to content

Commit 256f204

Browse files
committed
Merge remote-tracking branch 'upstream/main'
2 parents 255d8a8 + 5103bc8 commit 256f204

43 files changed

Lines changed: 1873 additions & 74 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "sap-cloud-sdk"
3-
version = "0.46.0"
3+
version = "0.49.0"
44
description = "SAP Cloud SDK for Python"
55
readme = "README.md"
66
license = "Apache-2.0"

‎src/sap_cloud_sdk/__init__.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@
22

33
from sap_cloud_sdk.core.bootstrap import bootstrap, TelemetryConfig
44

5-
__all__ = ["bootstrap", "TelemetryConfig"]
5+
__all__ = [
6+
"bootstrap",
7+
"TelemetryConfig",
8+
]

‎src/sap_cloud_sdk/agentgateway/_customer.py‎

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@
2323
import httpx
2424
from mcp import ClientSession
2525
from mcp.client.streamable_http import streamable_http_client
26+
from mcp.shared.exceptions import McpError
2627

2728
from sap_cloud_sdk.agentgateway._dependencies_resolver import (
2829
EnvironmentDependenciesResolver,
2930
IntegrationDependenciesResolver,
3031
)
3132
from sap_cloud_sdk.agentgateway._models import (
33+
JsonRpcError,
3234
CustomerCredentials,
3335
IntegrationDependency,
3436
MCPTool,
@@ -680,15 +682,42 @@ def _log_mcp_server_error(ord_id: str, exc: BaseException) -> None:
680682
_log_mcp_server_error(ord_id, inner)
681683
return
682684
if isinstance(exc, httpx.HTTPStatusError):
685+
try:
686+
body = exc.response.text
687+
except httpx.ResponseNotRead:
688+
body = None
689+
rpc_error = JsonRpcError.parse(body) if body else None
690+
if rpc_error:
691+
logger.error(
692+
"Failed to load tools from %s — %s returned HTTP %d [JSON-RPC %d]: %s",
693+
ord_id,
694+
exc.request.url,
695+
exc.response.status_code,
696+
rpc_error.code,
697+
rpc_error.message,
698+
)
699+
else:
700+
logger.error(
701+
"Failed to load tools from %s — %s returned HTTP %d: %s",
702+
ord_id,
703+
exc.request.url,
704+
exc.response.status_code,
705+
body[:500] if body else "(response body not available)",
706+
)
707+
elif isinstance(exc, McpError):
683708
logger.error(
684-
"Failed to load tools from %s (HTTP %d): %s",
709+
"Failed to load tools from %s — JSON-RPC %d: %s",
685710
ord_id,
686-
exc.response.status_code,
687-
exc.response.text[:500],
711+
exc.error.code,
712+
exc.error.message,
688713
)
689714
else:
690-
logger.exception(
691-
"Failed to load tools from %s — skipping", ord_id, exc_info=exc
715+
logger.error(
716+
"Failed to load tools from %s — %s: %s",
717+
ord_id,
718+
type(exc).__name__,
719+
exc,
720+
exc_info=exc,
692721
)
693722

694723

@@ -796,13 +825,20 @@ async def call_mcp_tool_customer(
796825
result = await session.call_tool(tool.name, kwargs)
797826

798827
if not result.content:
799-
logger.warning("Tool '%s' returned empty content", tool.name)
828+
logger.warning(
829+
"Tool '%s' on '%s' returned empty content", tool.name, tool.url
830+
)
800831
return ""
801832

802833
first = result.content[0]
803834
text = str(getattr(first, "text", ""))
804835

805836
if result.isError:
806-
logger.error("Tool '%s' returned an error: %s", tool.name, text)
837+
logger.error(
838+
"Tool '%s' on '%s' returned an error: %s",
839+
tool.name,
840+
tool.url,
841+
text,
842+
)
807843

808844
return text

‎src/sap_cloud_sdk/agentgateway/_lob.py‎

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import httpx
1414
from mcp import ClientSession
1515
from mcp.client.streamable_http import streamable_http_client
16+
from mcp.shared.exceptions import McpError
1617
from sap_cloud_sdk.destination import (
1718
create_client as create_destination_client,
1819
ConsumptionLevel,
@@ -29,6 +30,7 @@
2930
list_a2a_fragments,
3031
)
3132
from sap_cloud_sdk.agentgateway._models import (
33+
JsonRpcError,
3234
Agent,
3335
AgentCard,
3436
AgentCardFilter,
@@ -307,16 +309,41 @@ def _log_mcp_server_error(fragment_name: str, exc: BaseException) -> None:
307309
_log_mcp_server_error(fragment_name, inner)
308310
return
309311
if isinstance(exc, httpx.HTTPStatusError):
312+
try:
313+
body = exc.response.text
314+
except httpx.ResponseNotRead:
315+
body = None
316+
rpc_error = JsonRpcError.parse(body) if body else None
317+
if rpc_error:
318+
logger.error(
319+
"Failed to load tools from fragment '%s' — %s returned HTTP %d [JSON-RPC %d]: %s",
320+
fragment_name,
321+
exc.request.url,
322+
exc.response.status_code,
323+
rpc_error.code,
324+
rpc_error.message,
325+
)
326+
else:
327+
logger.error(
328+
"Failed to load tools from fragment '%s' — %s returned HTTP %d: %s",
329+
fragment_name,
330+
exc.request.url,
331+
exc.response.status_code,
332+
body[:500] if body else "(response body not available)",
333+
)
334+
elif isinstance(exc, McpError):
310335
logger.error(
311-
"Failed to load tools from fragment '%s' (HTTP %d): %s",
336+
"Failed to load tools from fragment '%s' — JSON-RPC %d: %s",
312337
fragment_name,
313-
exc.response.status_code,
314-
exc.response.text[:500],
338+
exc.error.code,
339+
exc.error.message,
315340
)
316341
else:
317-
logger.exception(
318-
"Failed to load tools from fragment '%s' — skipping",
342+
logger.error(
343+
"Failed to load tools from fragment '%s' — %s: %s",
319344
fragment_name,
345+
type(exc).__name__,
346+
exc,
320347
exc_info=exc,
321348
)
322349

@@ -482,13 +509,20 @@ async def call_mcp_tool_lob(
482509
await session.initialize()
483510
result = await session.call_tool(tool.name, kwargs)
484511
if not result.content:
485-
logger.warning("Tool '%s' returned empty content", tool.name)
512+
logger.warning(
513+
"Tool '%s' on '%s' returned empty content", tool.name, tool.url
514+
)
486515
return ""
487516
first = result.content[0]
488517
text = str(getattr(first, "text", ""))
489518

490519
if result.isError:
491-
logger.error("Tool '%s' returned an error: %s", tool.name, text)
520+
logger.error(
521+
"Tool '%s' on '%s' returned an error: %s",
522+
tool.name,
523+
tool.url,
524+
text,
525+
)
492526

493527
return text
494528

‎src/sap_cloud_sdk/agentgateway/_models.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Data models for Agent Gateway MCP tools."""
22

3+
import json
34
from dataclasses import dataclass, field
45
from typing import Any
56

@@ -96,6 +97,33 @@ class CustomerCredentials:
9697
private_key: str | None = None
9798

9899

100+
@dataclass
101+
class JsonRpcError:
102+
"""Parsed JSON-RPC error from an Agent Gateway response.
103+
104+
AGW returns HTTP 200 with a JSON-RPC error body when the request is
105+
structurally valid but the server encountered an error.
106+
107+
Example: {"jsonrpc": "2.0", "error": {"code": -32603, "message": "Internal Server Error"}}
108+
109+
Attributes:
110+
code: JSON-RPC error code.
111+
message: Human-readable error message from AGW.
112+
"""
113+
114+
code: int
115+
message: str
116+
117+
@classmethod
118+
def parse(cls, text: str) -> "JsonRpcError | None":
119+
try:
120+
data = json.loads(text)
121+
error = data.get("error", {})
122+
return cls(code=error["code"], message=error["message"])
123+
except Exception:
124+
return None
125+
126+
99127
@dataclass
100128
class AgentCard:
101129
"""Agent Card as returned by the A2A well-known endpoint.

‎src/sap_cloud_sdk/agentgateway/agw_client.py‎

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -284,10 +284,14 @@ async def get_user_auth(
284284
self._config.timeout,
285285
self._token_cache,
286286
)
287-
return AuthResult(
287+
result = AuthResult(
288288
access_token=token,
289289
gateway_url=credentials.gateway_url,
290290
)
291+
logger.info(
292+
"User auth token obtained — gateway: '%s'", result.gateway_url
293+
)
294+
return result
291295

292296
# Check for transparent mode
293297
if detect_transparent_credentials():
@@ -302,10 +306,14 @@ async def get_user_auth(
302306
self._config.timeout,
303307
self._token_cache,
304308
)
305-
return AuthResult(
309+
result = AuthResult(
306310
access_token=token,
307311
gateway_url=credentials.gateway_url,
308312
)
313+
logger.info(
314+
"User auth token obtained — gateway: '%s'", result.gateway_url
315+
)
316+
return result
309317

310318
tenant = self._resolve_tenant_subdomain()
311319
token, gateway_url = await fetch_user_auth(
@@ -314,7 +322,9 @@ async def get_user_auth(
314322
token_cache=self._token_cache,
315323
gateway_url_cache=self._gateway_url_cache,
316324
)
317-
return AuthResult(access_token=token, gateway_url=gateway_url)
325+
result = AuthResult(access_token=token, gateway_url=gateway_url)
326+
logger.info("User auth token obtained — gateway: '%s'", result.gateway_url)
327+
return result
318328

319329
except AgentGatewaySDKError:
320330
raise
@@ -444,10 +454,6 @@ async def list_mcp_tools(
444454

445455
# LoB flow - requires tenant_subdomain
446456
tenant = self._resolve_tenant_subdomain()
447-
if user_token:
448-
auth = await self.get_user_auth(user_token)
449-
else:
450-
auth = await self.get_system_auth()
451457
return await get_mcp_tools_lob(
452458
tenant,
453459
auth.access_token,
@@ -597,7 +603,10 @@ async def call_mcp_tool(
597603
tool, auth.access_token, self._config.timeout, **kwargs
598604
)
599605

600-
auth = await self.get_user_auth(user_token)
606+
if not user_token:
607+
raise AgentGatewaySDKError(
608+
"user_token is required for LoB tool invocation."
609+
)
601610
return await call_mcp_tool_lob(
602611
tool, auth.access_token, self._config.timeout, **kwargs
603612
)

0 commit comments

Comments
 (0)