FeaturesFlex Gateway PoliciesMCP Tool Drift Detection (Exchange)

MCP Tool Drift Detection (Exchange)

A Mulesoft Flex / Omni Gateway custom policy that detects MCP tool drift against the descriptor set published to an Anypoint Exchange MCP asset — and (optionally) strips the drifted tools from the response before they reach the LLM client.

The Exchange asset is the contract. The gateway is the enforcement point. Decisions are local; evidence flows into the customer’s existing Anypoint analytics pipeline. No external SaaS, no data leaving the tenant.

What it catches

  • Descriptor drift — runtime descriptor hash ≠ published hash. Reported as description_changed, input_schema_changed, output_schema_changed, or annotation_changed.
  • Unpinned tools — present at runtime, absent from the Exchange asset.
  • Removed tools — present in the asset, absent at runtime.
  • Version changes — when exchange.version=latest, a change in the underlying version between refreshes emits version_changed.

How it works

  1. Bootstrap — on policy attach, the Exchange client fetches the pinned descriptor set from {baseUrl}/exchange/api/v2/assets/{groupId}/{assetId}/{version}/mcp.json, authenticating with OAuth2 Connected App client credentials or basic auth.
  2. Pin construction — each tool descriptor is canonicalized (key-sorted JSON over name, description, inputSchema, outputSchema, annotations) and hashed (SHA-256).
  3. Refresh loop — a timer refreshes the pin every refreshIntervalSec (default 300s). On failure, the last-known-good pin is retained.
  4. Response interception — every tools/list response is parsed, and each tool’s canonical hash is compared to the pin.
  5. Enforcement — in enforce mode, offending tools are stripped from the forwarded response before the agent sees them.

Modes

  • enforce — strip drifted tools from tools/list responses.
  • warn — pass through with an x-mcp-drift-warning header.
  • observe — emit structured evidence only.

Configuration

PathTypeDefaultNotes
exchange.orgIdstringrequiredAnypoint business group.
exchange.groupIdstringrequiredExchange group id.
exchange.assetIdstringrequiredExchange asset id (the MCP asset).
exchange.versionstringrequiredPinned semver or latest.
exchange.baseUrlstringhttps://anypoint.mulesoft.comOverride for sovereign / EU.
exchange.authTypeenumoauth2_client_credentialsor basic.
exchange.credSecretRefstringrequiredFlex secret holding the credentials.
exchange.refreshIntervalSecint 30–86400300Refresh cadence.
enforce.exactMatchbooltrueStrict hash equality.
enforce.allowAddedToolsboolfalseUnpinned tools blocked by default.
enforce.allowRemovedToolsbooltrueDeprecation allowed.
modeenumenforceenforce / observe / warn.
failOpen.onPinUnavailableboolfalseAllow traffic on bootstrap when LKG is empty.

Evidence

Every decision lands as a JSON log line through the PDK logger. Anypoint Analytics indexes these automatically; they never leave the customer’s tenant.

{
  "class": "descriptor_drift",
  "severity": "critical",
  "decision": "stripped",
  "asset_id": "demo-mcp-asset",
  "asset_version": "1.4.2",
  "tool_name": "lookup_account",
  "pin_hash": "ab12...",
  "runtime_hash": "cd34...",
  "field": "description_changed"
}

classdescriptor_drift | unpinned_tool | removed_tool | version_changed | pin_stale | pin_unavailable.

Failure modes

  • Exchange unreachable on bootstrap. failOpen.onPinUnavailable controls allow/block. Default is closed (block + pin_unavailable evidence).
  • Exchange unreachable after bootstrap. Last-known-good cache is retained; pin_stale evidence fires at every failed refresh.
  • Asset version moved. When version=latest, the underlying resolution change emits version_changed. Pin a semver to opt out.

Exchange asset format

The policy expects an Anypoint Exchange asset of type mcp with the standard JSON-RPC tool envelope:

{
  "tools": [
    {
      "name": "lookup_account",
      "description": "Look up an account by its identifier.",
      "inputSchema": { "type": "object", "properties": { } },
      "outputSchema": { },
      "annotations": { }
    }
  ]
}

This is the format produced by anypoint-cli-v4 exchange asset upload --type mcp.

Reference deployment

FieldValue
Gatewayagent-network-ingress-gw (id 35755bec-3177-4d32-a8c9-c9705f5b1c0b, gw 1.13.2)
Public URLhttps://agent-network-ingress-gw-zovwbn.jeg62f.usa-e2.cloudhub.io/mcp-drift-via-exchange-demo
API instance20999090
Exchange asset (pin source)82a0453b-22e6-430d-bbf4-35b989d043dc/drift-demo-a2d-mcp/1.0.0
Upstream (A²D mock)https://www.a2d-ai.com/api/platform/7b26e0d0-dfcf-4c6a-8484-8c907724366d/mcp
Policy versionomni-policy-mcp-tool-drift-via-exchange-dev/0.1.0-20260629203732

The Exchange asset and the runtime upstream are derived from the same A²D MCP server, so a healthy tools/list matches the pinned set exactly.

curl -sS -X POST \
  https://agent-network-ingress-gw-zovwbn.jeg62f.usa-e2.cloudhub.io/mcp-drift-via-exchange-demo \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'

FAQ

Why an Exchange-only variant? Some customers run entirely on Anypoint and want a single source of truth they already understand — the Exchange asset. No external dependencies, no remote calls on the request path, no extra signing keys to rotate.

What if I want real-time policy decisions instead of a local cache? That is the role of the platform-managed drift detection (via A²D) variant.

Pair with

Next Steps

Exchange-anchored drift detection keeps every decision inside the tenant.