SAP BTP · Model Context Protocol

Architecture of the procurement mailer integration

SAP Joule Agent procurement-mailer-mcp Exchange / MS Graph

How the pieces fit together — the runtime call path an email prompt takes, the component makeup of the caller / bridge / data-source, and the procurement data domain the agent reasons over. The companion setup runbook covers how to build each stage.

← Back to the setup runbook
00
Framing

Two planes

The solution splits cleanly into two planes. The call plane is the request/response path — a user prompt travels through Joule, into this MCP server, out to Microsoft Graph, and back as a rendered answer. The data plane is the procurement domain the agent operates against: products, stock levels, purchase orders, allocations. This server is the bridge between the two — it turns an agent's intent into a concrete mailbox action.

These diagrams are native recreationsEverything below is drawn in HTML/CSS/SVG — no image files — so it stays crisp at any zoom and matches the runbook's theme. The sections map to the three source diagrams: runtime call flow, setup & architecture, and the procurement apps schema.
01
The call plane · runtime

Runtime call flow

One email prompt, nine steps. Invocation runs left-to-right; data flows back right-to-left as dashed returns. The MCP server holds no session — every call rebuilds a fresh server + transport pair.

User SAP Joule Agent procurement-mailer-mcp Exchange / Graph 1 · Procurement prompt 2 · Plan intent, select MCP tool 3 · Tool call · Streamable HTTP /mcp BTP destination resolves the route — no trailing /mcp 4 · OAuth2 client-credentials token 5 · Access token 6 · Graph API call (list / read / sendMail) 7 · Mail data / send result 8 · MCP tool result (JSON) 9 · Rendered procurement response

Steps 4–5 are the OAuth2 client-credentials handshake the SAP Cloud SDK performs transparently; steps 6–7 are the actual Graph call. The server never stores a token — the graph-mail destination handles it per request.

02
The call plane · static view

Component architecture

The same three stages, decomposed into the parts you configure. The caller and data source are configuration on either side; the bridge in the middle is this repository.

1 · SAP Joule Agent
caller
BTP destination — route URL WITHOUT a trailing /mcp
Destination property sap-joule-studio-mcp-server = true
Joule Studio — MCP registered as a tool source
Capability bound to skill · per-member deploy in Control Tower env
Runtime: user prompt → plan intent → select MCP tool
2 · procurement-mailer-mcp
bridge · this repo
Node.js + TypeScript · @modelcontextprotocol/sdk
SAP Cloud SDK — BTP service access
Streamable HTTP transport exposed at /mcp
Tools: ping · list_messages · read_message · send_mail
Cloud Foundry · procurement-demo space · us10 region
3 service bindings rebound + restaged → VCAP_SERVICES
Entra app creds: client ID · tenant ID · client secret
3 · Microsoft Exchange / Graph
data source
Entra ID app registration (client ID, tenant ID, secret)
Graph app permissions: Mail.Read · Mail.Send + admin consent
Exchange Online app access policy → scope to demo mailbox
OAuth2 client-credentials → token endpoint → access token
Graph endpoints: list messages / read message / sendMail
→ call path   1 · tool call /mcp  ·  2 · OAuth2 token + Graph API
← return path   3 · mail data / result  ·  4 · tool result → render
03
The data plane · domain

Procurement data model

The domain the Joule agent reasons over — the CAP backend behind the procurement service. PK marks a primary key, FK a foreign key. Product and Location are the hubs almost everything else references.

ProcurementRequest
request_idPKint
requested_qtyint
statusvarchar
raised_byvarchar
created_atdatetime
product_idFKint
location_idFKint
Product
product_idPKint
namevarchar
categoryvarchar
safety_stock_leveldouble
current_stock_leveldouble
StockLevel
stock_level_idPKint
quantityOnHandint
reorderPointint
safetyStockint
location_idFKint
product_idFKint
Location
location_idPKint
namevarchar
typeenum
cityvarchar
PurchaseOrder
po_idPKint
po_numbervarchar
statusvarchar
expected_delivery_datedatetime
received_datedatetime
disruption_reasonenum
product_quantityint
po_totaldouble
allocation_idFKint
product_idFKint
shipToLocation_idFKint
ProcurementIssue
issue_idPKint
statusvarchar
descriptiontext
created_atdatetime
resolved_byvarchar
notestext
purchase_order_idFKint
AllocationResult
allocation_idPKint
allocated_quantityint
total_effective_costdouble
rationale_summarytext
created_atdatetime
suggested_by_agentboolean
product_idFKint
location_idFKint
⋔ crow's foot = many  ·  │ bar = one  ·  each line runs parent → child along the FK
Relationships
  • Product 1 — ∗ StockLevel · ProcurementRequest · PurchaseOrder · AllocationResult  (via product_id)
  • Location 1 — ∗ StockLevel · ProcurementRequest · AllocationResult, and PurchaseOrder (via shipToLocation_id)
  • AllocationResult 1 — ∗ PurchaseOrder  (a costed allocation drives one or more purchase orders)
  • PurchaseOrder 1 — ∗ ProcurementIssue  (disruptions logged against an order)

The mailer MCP server doesn't read these tables directly — this is the business context the agent works in. When the agent sends or reads mail about a purchase order or a stock shortfall, these are the entities it's reasoning about.