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: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "sap-cloud-sdk"
version = "0.35.0"
version = "0.35.3"
description = "SAP Cloud SDK for Python"
readme = "README.md"
license = "Apache-2.0"
Expand Down
9 changes: 9 additions & 0 deletions src/sap_cloud_sdk/agentgateway/_customer.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,15 @@ async def _list_server_tools(
server_name = init_result.serverInfo.name
result = await session.list_tools()

if result is None or result.tools is None:
logger.warning(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why review OTel instrumentation?

"list_tools() returned no tools (response=%r); dependency %r skipped — "
"check MCP server health",
result,
dependency.ord_id,
)
return []

return [
MCPTool(
name=t.name,
Expand Down
31 changes: 31 additions & 0 deletions src/sap_cloud_sdk/agentgateway/_lob.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,14 @@ async def list_server_tools(
else fragment_name
)
result = await session.list_tools()
if result is None or result.tools is None:
logger.warning(
"list_tools() returned no tools (response=%r); fragment %r skipped — "
"check MCP server health and OpenTelemetry MCP instrumentation",
result,
fragment_name,
)
return []
return [
MCPTool(
name=t.name,
Expand Down Expand Up @@ -388,12 +396,35 @@ async def get_mcp_tools_lob(
len(server_tools),
fragment_name,
)
# intentional: fragment failure must not abort remaining fragments
# (HTTPStatusError: 403 → warning + continue; other status → exception log + skip)
except httpx.HTTPStatusError as exc:
if exc.response.status_code == 403:
logger.warning(
"HTTP 403 listing tools from fragment '%s' with system token — "
"pass user_token to list_mcp_tools() for user-scoped tool discovery",
fragment_name,
)
continue
logger.exception(
"Failed to load tools from fragment '%s' — skipping",
fragment_name,
)
# intentional: fragment failure must not abort remaining fragments
# (unexpected errors → exception log + skip)
except Exception:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should still be executed if all fragments fail to load. And who requested this behaviour?

logger.exception(
"Failed to load tools from fragment '%s' — skipping",
fragment_name,
)

if fragments and not tools:
logger.warning(
"No MCP tools loaded from %d fragment(s) for tenant '%s'",
len(fragments),
tenant_subdomain,
)

logger.info("Loaded %d MCP tool(s) from %d fragment(s)", len(tools), len(fragments))
return tools

Expand Down
16 changes: 16 additions & 0 deletions src/sap_cloud_sdk/agentgateway/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,3 +270,19 @@ class MCPTool:
url: str
fragment_name: str | None
```

## Troubleshooting (LoB MCP)

### Empty or null tool lists

If during `list_mcp_tools()` discovery the MCP session returns no tool list (`session.list_tools()` is `None` or `tools` is `None`), the SDK logs a warning and skips that destination fragment instead of raising an error. Discovery continues with remaining fragments. This often happens when OpenTelemetry MCP instrumentation swallows errors. SDK 0.35.3+ treats null responses defensively; see the telemetry and OpenTelemetry sections below for optional agent-side workarounds.

### HTTP 403 during discovery

Many LoB landscapes require a **user-scoped token** for MCP tool listing. Pass `user_token` on **every** `list_mcp_tools()` call that must see user-scoped tools. If one code path calls `list_mcp_tools()` without `user_token` while another passes it, you may get HTTP 403 on some fragments, zero tools from that path, and misleading errors such as “no tool for role” even when ordId mapping is correct.

### OpenTelemetry and MCP

Call `auto_instrument()` from `sap_cloud_sdk.core.telemetry` before importing MCP or AI libraries. The SDK does **not** remove OpenTelemetry MCP wrappers during `auto_instrument()` — instrumentation should stay enabled.

If you still see null tool lists during `list_mcp_tools()` with MCP OTel enabled, you can apply an agent-side unwrap after `auto_instrument()` and before MCP imports (for example `opentelemetry.instrumentation.utils.unwrap` on `BaseSession.send_request` and both `streamablehttp_client` / `streamable_http_client` in `mcp.client.streamable_http`) until `opentelemetry-instrumentation-mcp` is fixed upstream. Prefer the SDK None guard so discovery continues without disabling telemetry.
3 changes: 3 additions & 0 deletions src/sap_cloud_sdk/core/telemetry/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ from litellm import completion
# LLM calls are now automatically traced
```

If OpenTelemetry MCP instrumentation (`opentelemetry-instrumentation-mcp`) swallows protocol errors during `list_mcp_tools()` discovery (the underlying MCP `session.list_tools()` may return `None`), the Agent Gateway SDK logs a warning and skips that fragment instead of crashing (SDK 0.35.3+). Removing instrumentation is not recommended. Until upstream fixes land, agents may keep an application-side unwrap after `auto_instrument()` (see Agent Gateway user guide — OpenTelemetry and MCP).


### 2. Add business context with a parent span

Wrap your LLM calls to add the context autoinstrumentation can't provide:
Expand Down
Loading
Loading