Skip to main content

Playbook: One Mandate, Two Systems

The scenario​

A group buys through two systems. Plant 1000 raises purchase requisitions in SAP. The Munich plant raises requests in ServiceNow, where approving a requested item releases it for ordering. One procurement agent works both queues, under one procurement lead, with one budget.

The risk​

Two systems usually mean two credentials and two permission models. The agent holds both credentials, and each system's rules are written in its own terms: a SAP release strategy here, a ServiceNow approval rule there. The two drift apart, and neither knows what the agent did in the other, so a daily budget is enforced in neither.

The mandate design​

A mandate names the business action and the sites, never a tool or a vendor. Every tool of procurement.requisition.release reads the same facts under the same names, the plant and the amount among them, so one mandate governs both systems unchanged:

FieldValueWhy
Actionprocurement.requisition.releaseReleasing in SAP and approving in ServiceNow are the same business action
Resourcesprocurement:plant:1000/requisition:* and procurement:plant:c7dae03a431069aecf016c0a0474d40a/requisition:*Plant 1000 in SAP, and the Munich plant in ServiceNow
max_amountEUR 100,000One cap, in either system
amount_aboveEUR 10,000One approval threshold, in either system
max_totalEUR 50,000 a dayOne budget across both systems

ServiceNow identifies a location by its sys_id, so the Munich plant is c7dae03a431069aecf016c0a0474d40a in every ServiceNow sandbox. Mandaitor reads the location from the requested item, and never goes by its name, which anyone allowed to rename a location could change.

Set it up​

As a tenant administrator:

  1. Two sandboxes and their tools. On the Connectors page, create a SAP S/4HANA demo sandbox and a ServiceNow demo sandbox. Under Registered tools, enable sap.purchase_requisition.release on the SAP sandbox's connector, and servicenow.requested_item.approve on the ServiceNow sandbox's.
  2. One agent. Register it once, with the public half of a key only it holds.
  3. One mandate, bound to the agent:
const mandate = await adminClient.post("/mandates", {
principal: { type: "NATURAL_PERSON", subject_id: "cognito:a1b2c3d4-e5f6", display_name: "Procurement lead" },
delegate: { type: "AGENT", subject_id: agent.did, display_name: "Procurement agent" },
scope: {
actions: ["procurement.requisition.release"],
resources: [
"procurement:plant:1000/requisition:*", // SAP, plant 1000
"procurement:plant:c7dae03a431069aecf016c0a0474d40a/requisition:*", // ServiceNow, Munich plant
],
effect: "ALLOW",
},
constraints: {
transaction_limits: {
max_amount: { currency: "EUR", value: "100000.00" },
max_total: [{ amount: { currency: "EUR", value: "50000.00" }, window: "P1D" }],
},
escalation_rules: { amount_above: { currency: "EUR", value: "10000.00" } },
},
});
await adminClient.post(`/agents/${agent.agent_id}/bind`, { mandate_id: mandate.mandate_id });

Let the agent ask​

tools/list now shows both tools. The agent names the record in each system's own terms:

// SAP: a purchase requisition, by its number.
await mcp.callTool({
name: "sap_purchase_requisition_release",
arguments: { purchase_requisition: "10000123" },
});

// ServiceNow: a requested item, by its number.
await mcp.callTool({
name: "servicenow_requested_item_approve",
arguments: { requested_item: "RITM0042002" },
});

Each call is decided against the same mandate, on facts read from its own system: the plant and the amount from the SAP requisition, the location and the price, with its own currency, from the ServiceNow item.

What must be refused​

Run these against the two sandboxes' seeded records:

RequestExpectedWhy
10000123: SAP, plant 1000, EUR 4,850.00SUCCEEDEDWithin the mandate
RITM0042002: ServiceNow, Munich plant, EUR 2,900.00SUCCEEDEDWithin the same mandate
RITM0042003: ServiceNow, Munich plant, EUR 12,000.00AWAITING_APPROVALAbove the threshold, as in SAP
RITM0042004: ServiceNow, Frankfurt office, EUR 480.00DENIED, NO_COVERING_MANDATEA location the mandate does not name
10000126: SAP, plant 2000, EUR 3,200.00DENIED, NO_COVERING_MANDATEA plant the mandate does not name
RITM0042002 again, once it is approvedDENIED, PRECONDITION_FAILEDOnly an item waiting for approval is approved

To see the shared budget, write the mandate with a max_total of EUR 5,000 a day instead. Release 10000123 (EUR 4,850.00) in SAP, then ask for RITM0042001 (EUR 349.00) in ServiceNow: it is DENIED with CUMULATIVE_LIMIT_EXCEEDED, because the mandate's day already holds EUR 4,850.00.

Mandaitor never approves over a person's decision in ServiceNow: an item a person rejected or cancelled is DENIED, with FACT_UNAVAILABLE. Reset demo data on the Connectors page restores either sandbox's seed.

Evidence and review​

  • The receipts say which system was written: target.system is sap-s4hana or servicenow, and target.record the requisition or the item.
  • The Executions page lists both systems' executions side by side, under the one mandate.
  • The evidence pack holds one audit chain for the mandate, whichever system each write went to.

Adapt it​

  • More sites. Add each plant's pattern, in SAP's plant numbers or ServiceNow's location sys_ids.
  • Different limits per site. Write one mandate per site; each counts its own limits.
  • A test landscape. Register the agent once more in a test environment, with both tools enabled there on test connectors. See Environments.