Skip to content

Commit 917f0ef

Browse files
committed
Add listing of sanatized integrations.
1 parent 47bc4e3 commit 917f0ef

4 files changed

Lines changed: 371 additions & 0 deletions

File tree

src/sap_cloud_sdk/agentgateway/_fragments.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
- Label constants for managed-runtime fragment types
55
- Fragment listing by label (MCP, A2A, IAS)
66
- IAS fragment name lookup for auth flows
7+
- Active integration listing for tenant context
78
"""
89

910
import logging
1011
from enum import Enum
12+
from typing import Optional
1113

1214
from sap_cloud_sdk.destination import (
1315
create_fragment_client,
@@ -25,6 +27,9 @@
2527

2628
_DESTINATION_INSTANCE = "default"
2729

30+
# URL mode path segments used by system integration fragments
31+
_INTEGRATION_URL_MODES = ("mcp", "a2a")
32+
2833

2934
class FragmentLabel(str, Enum):
3035
"""Label values for the sap-managed-runtime-type fragment label key."""
@@ -118,3 +123,91 @@ def get_ias_user_fragment_name(tenant_subdomain: str) -> str:
118123
f"for tenant '{tenant_subdomain}'"
119124
)
120125
return fragments[0].name
126+
127+
128+
def list_active_integrations(tenant_subdomain: str) -> list[dict]:
129+
"""List all active backend system integrations for the given tenant.
130+
131+
Reads Destination Service instance fragments written by the Destinations
132+
Facilitator during UCL Formation assignment (SPII flow). Each fragment
133+
represents a connected backend system (e.g. SAP PCE, SAP S/4HANA).
134+
135+
Extracts integration details from the fragment URL, which always has the form:
136+
{agw_base_url}/v1/mcp/{ord_id}/{gtid} (MCP integrations)
137+
{agw_base_url}/v1/a2a/{ord_id}/{gtid} (A2A integrations)
138+
139+
Args:
140+
tenant_subdomain: Subscriber tenant subdomain.
141+
142+
Returns:
143+
List of dicts, each with keys:
144+
- global_tenant_id: GTID of the connected partner system.
145+
- system_type: Application namespace of the partner (e.g. "sap.pce").
146+
- integration_dependency: ORD ID of the integration dependency fulfilled.
147+
Returns empty list if no active integrations exist.
148+
"""
149+
client = create_fragment_client(
150+
instance=_DESTINATION_INSTANCE,
151+
_telemetry_source=Module.AGENTGATEWAY,
152+
)
153+
fragments = client.list_instance_fragments(
154+
filter=ListOptions(
155+
filter_labels=[
156+
Label(
157+
key=LABEL_KEY,
158+
values=[FragmentLabel.MCP.value, FragmentLabel.A2A.value],
159+
)
160+
]
161+
),
162+
tenant=tenant_subdomain,
163+
)
164+
165+
result = []
166+
for fragment in fragments:
167+
url = fragment.properties.get("URL", "")
168+
entry = _parse_integration_from_url(url)
169+
if entry is not None:
170+
result.append(entry)
171+
return result
172+
173+
174+
def _parse_integration_from_url(url: str) -> Optional[dict]:
175+
"""Extract integration metadata from a system fragment URL.
176+
177+
Fragment URLs have the form:
178+
{base}/v1/{mode}/{ord_id}/{gtid}
179+
where mode is "mcp" or "a2a", ord_id may contain colons and slashes,
180+
and gtid is the last path segment.
181+
182+
Args:
183+
url: The fragment URL property value.
184+
185+
Returns:
186+
Dict with global_tenant_id, system_type, integration_dependency,
187+
or None if the URL does not match the expected pattern.
188+
"""
189+
parts = url.rstrip("/").split("/")
190+
191+
mode_idx = None
192+
for i, part in enumerate(parts):
193+
if i > 0 and parts[i - 1] == "v1" and part in _INTEGRATION_URL_MODES:
194+
mode_idx = i
195+
break
196+
197+
if mode_idx is None or mode_idx + 2 > len(parts) - 1:
198+
logger.debug("Skipping fragment with unexpected URL pattern: %s", url)
199+
return None
200+
201+
gtid = parts[-1]
202+
ord_id = "/".join(parts[mode_idx + 1 : -1])
203+
system_type = ord_id.split(":")[0]
204+
205+
if not gtid or not ord_id:
206+
logger.debug("Skipping fragment with empty gtid or ord_id in URL: %s", url)
207+
return None
208+
209+
return {
210+
"global_tenant_id": gtid,
211+
"system_type": system_type,
212+
"integration_dependency": ord_id,
213+
}

src/sap_cloud_sdk/agentgateway/agw_client.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
)
3939
from sap_cloud_sdk.agentgateway._token_cache import _GatewayUrlCache, _TokenCache
4040
from sap_cloud_sdk.agentgateway.exceptions import AgentGatewaySDKError
41+
from sap_cloud_sdk.agentgateway import _fragments
4142
from sap_cloud_sdk.core.telemetry import Module, Operation, record_metrics
4243

4344
logger = logging.getLogger(__name__)
@@ -498,6 +499,38 @@ async def list_agent_cards(
498499
logger.exception("Unexpected error during agent card discovery")
499500
raise AgentGatewaySDKError(f"Agent card discovery failed: {e}") from e
500501

502+
@record_metrics(Module.AGENTGATEWAY, Operation.AGENTGATEWAY_LIST_ACTIVE_INTEGRATIONS)
503+
def list_active_integrations(self) -> list[dict]:
504+
"""List all active backend system integrations for the current tenant.
505+
506+
Returns the connected backend systems (e.g. SAP PCE, SAP S/4HANA) that
507+
were wired up via UCL Formations and are currently in READY state. Use
508+
this to determine which systems are connected and which GTIDs to pass
509+
when loading MCP tools.
510+
511+
Only available for LoB agents (requires tenant_subdomain configured on
512+
the client).
513+
514+
Returns:
515+
List of dicts, each with:
516+
- global_tenant_id: GTID of the connected partner system.
517+
- system_type: Application namespace (e.g. "sap.pce", "sap.s4").
518+
- integration_dependency: ORD ID fulfilled by this integration.
519+
Returns empty list if no active integrations exist.
520+
521+
Raises:
522+
AgentGatewaySDKError: If tenant_subdomain is not configured.
523+
524+
Example:
525+
```python
526+
integrations = agw_client.list_active_integrations()
527+
for i in integrations:
528+
print(i["system_type"], i["global_tenant_id"])
529+
```
530+
"""
531+
tenant = self._resolve_tenant_subdomain()
532+
return _fragments.list_active_integrations(tenant)
533+
501534
@record_metrics(Module.AGENTGATEWAY, Operation.AGENTGATEWAY_CALL_MCP_TOOL)
502535
async def call_mcp_tool(
503536
self,

src/sap_cloud_sdk/core/telemetry/operation.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ class Operation(str, Enum):
193193
AGENTGATEWAY_GET_USER_AUTH = "get_user_auth"
194194
AGENTGATEWAY_LIST_AGENT_CARDS = "list_agent_cards"
195195
AGENTGATEWAY_GET_IAS_CLIENT_ID = "get_ias_client_id"
196+
AGENTGATEWAY_LIST_ACTIVE_INTEGRATIONS = "list_active_integrations"
196197

197198
# Agent Memory Operations
198199
AGENT_MEMORY_ADD_MEMORY = "add_memory"

0 commit comments

Comments
 (0)