Skip to content

Commit 4dc4b4e

Browse files
feat(extensibility): migrate n8n hook execution to per-workflow MCP tool invocation (#324)
1 parent 6ad20d7 commit 4dc4b4e

12 files changed

Lines changed: 305 additions & 521 deletions

File tree

‎pyproject.toml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ dependencies = [
4242
]
4343

4444
[project.optional-dependencies]
45-
extensibility = ["a2a-sdk>=0.2.0,<2"]
45+
extensibility = ["a2a-sdk>=0.2.0,<1.0"]
4646
starlette = ["starlette>=0.40.0,<1"]
4747
fastapi = ["fastapi>=0.100.0,<1"]
4848
aiohttp = ["aiohttp>=3.9.0,<4"]
@@ -76,7 +76,7 @@ dev = [
7676
"sqlalchemy>=2.0.0",
7777
"django>=4.0",
7878
"flask>=3.0",
79-
"a2a-sdk>=0.2.0",
79+
"a2a-sdk>=0.2.0,<1.0",
8080
"langchain-core>=1.2.7",
8181
"langgraph>=0.2.0",
8282
"langchain-community>=0.3.0",

‎src/sap_cloud_sdk/extensibility/_models.py‎

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,12 +236,21 @@ class N8nWorkflowConfig:
236236
"""n8n workflow configuration embedded in a hook.
237237
238238
Attributes:
239-
workflow_id: n8n workflow ID.
239+
ord_id: ORD ID of the API resource for this workflow.
240+
card_ord_id: MCP translation card ORD ID (per ADR-021).
241+
tool_name: MCP tool name (apiName segment of ordId, per ADR-021).
242+
global_tenant_id: Global tenant ID of the n8n workflow tenant (per ADR-022).
240243
method: HTTP method for the n8n webhook call.
244+
workflow_id: n8n workflow ID. Retained for backward compatibility; not
245+
populated by the current backend (defaults to "").
241246
"""
242247

243-
workflow_id: str
248+
ord_id: str
249+
card_ord_id: str
250+
tool_name: str
251+
global_tenant_id: str
244252
method: HTTPMethod
253+
workflow_id: str = ""
245254

246255
@classmethod
247256
def from_dict(cls, obj: Dict[str, Any]) -> N8nWorkflowConfig:
@@ -250,7 +259,10 @@ def from_dict(cls, obj: Dict[str, Any]) -> N8nWorkflowConfig:
250259
Expected JSON shape::
251260
252261
{
253-
"workflowId": "unique_n8n_workflow_id",
262+
"ordId": "sap.n8nwfrt:apiResource:invoice-po-solution_my-workflow.postInvoice:v1",
263+
"cardOrdId": "sap.n8nwfrt:apiResource:invoice-po-solution_my-workflow.postInvoice_mcp:v1",
264+
"toolName": "postInvoice",
265+
"globalTenantId": "tenant-abc",
254266
"method": "POST"
255267
}
256268
@@ -262,8 +274,12 @@ def from_dict(cls, obj: Dict[str, Any]) -> N8nWorkflowConfig:
262274
"""
263275
method = _parse_http_method(obj.get("method", "POST")) or HTTPMethod.POST
264276
return cls(
265-
workflow_id=obj.get("workflowId", ""),
277+
ord_id=obj.get("ordId", ""),
278+
card_ord_id=obj.get("cardOrdId", ""),
279+
tool_name=obj.get("toolName", ""),
280+
global_tenant_id=obj.get("globalTenantId", ""),
266281
method=method,
282+
workflow_id=obj.get("workflowId", ""),
267283
)
268284

269285

@@ -338,7 +354,10 @@ def from_dict(cls, obj: Dict[str, Any]) -> Hook:
338354
"order": 1,
339355
"canShortCircuit": true,
340356
"n8nWorkflowConfig": {
341-
"workflowId": "unique_n8n_workflow_id",
357+
"ordId": "sap.n8nwfrt:apiResource:invoice-po-solution_my-workflow.postInvoice:v1",
358+
"cardOrdId": "sap.n8nwfrt:apiResource:invoice-po-solution_my-workflow.postInvoice_mcp:v1",
359+
"toolName": "postInvoice",
360+
"globalTenantId": "tenant-abc",
342361
"method": "POST"
343362
}
344363
}
@@ -698,7 +717,10 @@ def from_dict(cls, obj: Dict[str, Any]) -> ExtensionCapabilityImplementation:
698717
"order": 1,
699718
"canShortCircuit": true,
700719
"n8nWorkflowConfig": {
701-
"workflowId": "unique_n8n_workflow_id",
720+
"ordId": "sap.n8nwfrt:apiResource:invoice-po-solution_my-workflow.postInvoice:v1",
721+
"cardOrdId": "sap.n8nwfrt:apiResource:invoice-po-solution_my-workflow.postInvoice_mcp:v1",
722+
"toolName": "postInvoice",
723+
"globalTenantId": "tenant-abc",
702724
"method": "POST"
703725
}
704726
}

‎src/sap_cloud_sdk/extensibility/_ums_transport.py‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
additions { type mcpConfig { globalTenantId ordId toolNames } }
7575
}
7676
hooks { id hookId type name onFailure timeout deploymentType canShortCircuit
77-
n8nWorkflowConfig { workflowId method }
77+
n8nWorkflowConfig { ordId cardOrdId toolName globalTenantId method }
7878
}
7979
}
8080
}
@@ -280,10 +280,11 @@ def _build_hook(raw: Dict[str, Any]) -> Optional[Hook]:
280280
return None
281281

282282
n8n_config = raw.get("n8nWorkflowConfig") or {}
283-
workflow_id = n8n_config.get("workflowId", "")
284-
if not workflow_id:
283+
tool_name = n8n_config.get("toolName", "")
284+
card_ord_id = n8n_config.get("cardOrdId", "")
285+
if not tool_name or not card_ord_id:
285286
logger.warning(
286-
"Skipping hook with missing workflowId (hookId=%s)",
287+
"Skipping hook with missing toolName or cardOrdId (hookId=%s)",
287288
raw.get("hookId"),
288289
)
289290
return None
@@ -295,7 +296,13 @@ def _build_hook(raw: Dict[str, Any]) -> Optional[Hook]:
295296
return Hook(
296297
id=raw.get("id", ""),
297298
hook_id=raw.get("hookId", ""),
298-
n8n_workflow_config=N8nWorkflowConfig(workflow_id=workflow_id, method=method),
299+
n8n_workflow_config=N8nWorkflowConfig(
300+
ord_id=n8n_config.get("ordId", ""),
301+
card_ord_id=n8n_config.get("cardOrdId", ""),
302+
tool_name=tool_name,
303+
global_tenant_id=n8n_config.get("globalTenantId", ""),
304+
method=method,
305+
),
299306
name=raw.get("name", ""),
300307
type=hook_type,
301308
deployment_type=deployment_type,

0 commit comments

Comments
 (0)