MCP Tool Drift Detection (via A²D)
A Mulesoft Flex / Omni Gateway custom policy that detects MCP tool
drift — runtime tools/list responses that have diverged from the
spec approved in A²D — and (optionally) strips the drifted tools from
the response before they reach the LLM client.
What it catches
The policy intercepts every tools/list response, computes a
canonical SHA-256 hash per tool descriptor (name, description,
inputSchema, outputSchema, annotations), and compares it to the
hash stored in A²D at the moment the MCP server was approved. Any
mismatch is a drift event.
The classifier reports which field changed — description_changed,
input_schema_changed, output_schema_changed,
annotation_changed — so an A²D reviewer immediately sees whether the
change is cosmetic or structurally meaningful.
Decision sources
The interesting axis of this policy is where the decision comes
from. Three sources are supported, configured via decision.source:
| Source | What it does | Latency cost | Freshness |
|---|---|---|---|
cache | Decides locally from a refreshed spec cache (last-known-good). | ~0 ms | Bounded by refreshIntervalSec (default 5 min). |
remote-pdp | Calls A²D’s PDP per request (/api/platform/{assetId}/mcp/validate). | One round-trip; capped at pdpTimeoutMs (default 250 ms). | Real-time. |
hybrid | Decides locally, also calls the PDP asynchronously for a sampled fraction of requests. Divergence raises pdp_disagreement evidence. | ~0 ms on the hot path. | LKG on the hot path; sampled real-time audit. |
hybrid is the recommended default: latency of cache with the
auditability of remote-pdp. The sample rate is deterministic per
request via an FNV-1a hash of method + path so a hot tool doesn’t
repeatedly tax the PDP.
Decision modes
Orthogonal to source — what to do once the verdict is known.
enforce— strip the drifted tool from the response.warn— pass through with anx-mcp-drift-warningheader.observe— emit evidence only.
Configuration
| Path | Type | Default | Notes |
|---|---|---|---|
a2d.baseUrl | string | https://a2d-ai.com | A²D platform base URL. |
a2d.assetId | string | required | A²D MCP asset id. |
a2d.apiKeySecretRef | string | required | Per-instance API key (Flex secrets entry). |
a2d.refreshIntervalSec | int 30–86400 | 300 | Cache mode spec refresh. |
a2d.pdpTimeoutMs | int 25–5000 | 250 | Per-request PDP timeout. |
decision.source | enum | cache | cache / remote-pdp / hybrid. |
decision.hybridSampleRate | float 0–1 | 0.1 | Hybrid PDP audit rate. |
enforce.exactMatch | bool | true | Strict hash equality. |
enforce.allowAddedTools | bool | false | Unpinned tools blocked by default. |
enforce.allowRemovedTools | bool | true | Deprecation allowed. |
evidence.reportToA2d | bool | true | POST every event to A²D. |
evidence.logLocally | bool | true | Emit JSON log lines. |
mode | enum | enforce | enforce / observe / warn. |
failOpen.onSpecUnavailable | bool | false | Allow traffic when cache is empty. |
failOpen.onPdpUnavailable | bool | true | Fall back to cache when PDP is down. |
Evidence
Every decision lands as a JSON log line and (when reportToA2d=true)
POSTs to {baseUrl}/api/platform/{assetId}/mcp/evidence.
{
"class": "descriptor_drift",
"severity": "critical",
"decision": "stripped",
"source": "hybrid",
"asset_id": "demo-mcp-asset",
"asset_version": "1.4.2",
"tool_name": "lookup_account",
"local_verdict": "descriptor_drift",
"pdp_verdict": "descriptor_drift"
}class ∈ descriptor_drift | unpinned_tool | removed_tool | spec_unavailable | spec_stale | pdp_unavailable | pdp_disagreement.
Failure modes
- PDP slow / down (remote-pdp). Times out at
pdpTimeoutMs; iffailOpen.onPdpUnavailable=truefalls back to the LKG cache and emitspdp_unavailable. Otherwise the response is blocked. - Spec never loaded (cold start).
failOpen.onSpecUnavailablecontrols allow/block; evidence event always fires. - PDP disagrees with cache (hybrid). The local verdict is acted
on, the PDP verdict is recorded, and
pdp_disagreementevidence fires for post-hoc review.
Reference deployment
| Field | Value |
|---|---|
| Gateway | agent-network-ingress-gw (id 35755bec-3177-4d32-a8c9-c9705f5b1c0b, gw 1.13.2) |
| Public URL | https://agent-network-ingress-gw-zovwbn.jeg62f.usa-e2.cloudhub.io/mcp-drift-via-a2d-demo |
| API instance | 20999089 |
| A²D mock asset | 7b26e0d0-dfcf-4c6a-8484-8c907724366d (drift-demo) |
| Policy version | omni-policy-mcp-tool-drift-via-a-2-d-dev/0.1.0-20260629203620 |
The upstream A²D mock declares three tools (lookup_account,
search_accounts, get_account_balance) which form the pinned set.
curl -sS -X POST \
https://agent-network-ingress-gw-zovwbn.jeg62f.usa-e2.cloudhub.io/mcp-drift-via-a2d-demo \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'To exercise drift, edit one tool’s description in the A²D mock UI and
re-issue the request. The policy strips the drifted tool and emits a
descriptor_drift event that surfaces in A²D Test Lab.
Pair with
- MCP Tool Drift Detection (Exchange) — same detection model, Exchange-anchored pin.
- MCP Tool Poisoning Detection (A²D) — extends drift with shadowing + prompt-injection heuristics.
Next Steps
Drift detection turns A²D’s approval gate into a runtime guarantee.