Why MCP, and why a gateway
The Model Context Protocol (MCP) gives models and agents a standard way to discover and call tools. Inside an enterprise, the protocol alone is not enough: someone must decide who may call which tool, with which data, and leave a record. This example puts an enterprise MCP gateway in front of every AP (Accounts Payable) system. See ADR-003.
Topology
The agent and the DAG (Directed Acyclic Graph) workers are separate identities with separate scopes. The agent can read and resolve; only the DAG's posting identity can post, and only after the approval record exists.
The tool catalogue
| Tool | Kind | Scope | Purpose |
|---|---|---|---|
| vendor.lookup | Read | ap:vendor:read | Vendor status, bank details hash, payment terms, approval flags. |
| po.get | Read | ap:po:read | Purchase order header and lines for a PO (Purchase Order) number. |
| grn.search | Read | ap:receipt:read | Goods receipts and service entries for a PO, with quantities accepted. |
| ledger.query | Read (replica) | ap:ledger:read | Read-only query against the finance read replica, row-limited. |
| invoice.create_draft | Write, not posted | ap:invoice:draft | Creates a draft AP invoice that a person or the DAG can review. |
| approval.request | Write | ap:approval:request | Opens an approval task for a named role with the evidence attached. |
| dispute.open | Write | ap:dispute:write | Records an open query against a vendor for a disputed amount. |
| invoice.post | Write, posts to ERP | ap:invoice:post | Posts an approved invoice. Held by the DAG service identity only, never by the agent. |
Each tool has a JSON (JavaScript Object Notation) schema for inputs and outputs, an owner, a risk rating and a rate limit. Tool definitions are versioned in the repository and reviewed like code.
A tool definition
Tools are listed with the standard MCP fields: name, description and an input schema. Here is invoice.post, with the constraints the gateway enforces:
{
"name": "invoice.post",
"description": "Post an approved AP invoice to the ERP. Requires a decision record from approval.request.",
"inputSchema": {
"type": "object",
"required": ["invoice_id", "approval_id", "idempotency_key"],
"properties": {
"invoice_id": { "type": "string", "pattern": "^INV-[0-9]{4}-[0-9]{5}$" },
"approval_id": { "type": "string" },
"idempotency_key": { "type": "string", "minLength": 32 }
},
"additionalProperties": false
},
"annotations": { "risk": "high", "requiresApproval": true },
"gateway": { "scope": "ap:invoice:post", "callers": ["svc-ap-dag"], "rateLimit": "60/min" }
}A call through the gateway
The exception agent looks up the receipts for a disputed PO. The request and response are JSON-RPC (JavaScript Object Notation Remote Procedure Call), as MCP specifies. Results are filtered for instruction-like text before the model sees them.
--> {"jsonrpc":"2.0","id":7,"method":"tools/call",
"params":{"name":"grn.search","arguments":{"po_number":"PO-88213"}}}
<-- {"jsonrpc":"2.0","id":7,"result":{"content":[{"type":"text","text":
"{\"po_number\":\"PO-88213\",\"receipts\":[{\"grn\":\"GRN-70211\",\"line\":2,\"qty_received\":36}]}"}],
"isError":false}}
audit: trace=trc_7f3a9c caller=svc-ap-agent tool=grn.search scope=ap:receipt:read decision=allow ms=41Controls the gateway enforces
Identity
Every caller authenticates with SSO (Single Sign-On) (OIDC) or a workload identity. No shared accounts.
Scopes
A tool call is refused unless the caller holds the tool's scope. Scopes are reviewed quarterly.
Policy
Rules such as vendor-level limits and blocked vendors are evaluated per call.
Data limits
Row and field limits on queries; personal data masked unless the scope requires it.
Output safety
Tool output is checked for instruction-like text that could hijack the agent (prompt injection).
Audit
Append-only log of every call: caller, tool, arguments hash, decision, result size and trace ID (identifier).
Threats we designed against
- A poisoned supplier PDF (Portable Document Format) that tells the agent to approve. Mitigation: the agent cannot post, and the approval band is rule-based.
- A compromised agent calling write tools. Mitigation: scopes deny posting; write tools need an approval record.
- Credential exposure. Mitigation: no backend credential leaves the gateway; agents hold only short-lived tokens.
- Runaway cost or loops. Mitigation: per-caller rate limits and the DAG's per-node timeouts.
Build this for your business
This example uses a generic invoicing system, but the same pattern — orchestrated steps, governed tools, grounded knowledge and a documented lifecycle — fits most back-office processes. We can scope a pilot on your own documents and systems.
Book a discovery call