FeaturesFlex Gateway PoliciesMCP Tool Poisoning Detection (Exchange)

MCP Tool Poisoning Detection (Exchange)

A Flex Gateway / Omni Gateway custom policy that protects agentic workloads against MCP tool poisoning, descriptor drift, and name-shadowing attacks by pinning the trusted tool set in Anypoint Exchange and enforcing that runtime MCP tools/list responses match the published contract.

The detection logic mirrors the A²D variant; only the pin source differs. Use this variant when you run entirely inside Anypoint and want zero external dependencies.

Three classes of attack

  1. Tool poisoning — adversarial instructions injected into a tool’s description, inputSchema, or annotations that instruct the LLM to exfiltrate data, bypass safety filters, or chain into unintended tools.
  2. Descriptor drift / rug-pull — a tool that passed review is silently modified post-publication, causing agents to act on altered instructions without operator awareness.
  3. Tool shadowing — a malicious tool with a near-identical name to a trusted one (get_user vs get_userr) is added to siphon agent calls.

The policy intercepts every MCP tools/list response, compares the runtime descriptors to a pinned set stored in Anypoint Exchange, and (in enforce mode) strips any tool that fails validation before the agent sees it.

Architecture

┌──────────────┐       ┌────────────────┐       ┌─────────────────┐
│              │       │                │       │                 │
│  MCP Server  │──────▶│  Flex Gateway  │──────▶│  Agent / Client │
│              │ list  │  + Policy      │ list' │                 │
└──────────────┘       └────────┬───────┘       └─────────────────┘

                                │ fetch pin
                                │ emit evidence

                       ┌────────────────┐
                       │  Anypoint      │
                       │  Exchange +    │
                       │  Analytics     │
                       └────────────────┘

The policy never sends data outside the customer’s Anypoint tenant. Exchange fetch, pin storage, and evidence emission are all in-tenant operations.

Detection model

The policy runs five independent signal families against every tools/list response.

Descriptor drift

Runtime canonical hash ≠ pinned hash. Canonical hash = SHA-256 over a key-sorted JSON form of name, description, inputSchema, outputSchema, annotations. The policy reports which field changeddescription_changed, input_schema_changed, output_schema_changed, annotation_changed, descriptor_changed.

Severity: critical. Decision: strip in enforce, log in observe, annotate in warn.

Unpinned tool

Tool present at runtime, absent from the pin. By default (enforce.allowAddedTools=false), unpinned tools are stripped in enforce mode. If allowAddedTools=true, they pass through — useful during staged rollout of new tools before the pin is updated.

Severity: warning.

Removed tool

Tool present in the pin, absent at runtime. Informational by default (enforce.allowRemovedTools=true) — normal deprecation workflow.

Severity: info.

Tool shadowing

Unpinned tool name within shadowingDetection.maxEditDistance (Levenshtein) of any pinned tool name. For example, pinned get_user

  • runtime get_userr → distance 1 → flagged.

Severity: critical. Decision: strip in enforce.

Prompt-injection heuristics

Built-in or custom regex matches anywhere in descriptor text fields. Built-in patterns include “ignore previous instructions” variants, exfiltration phrases, base64/hex blobs, and zero-width Unicode. See the A²D variant docs for the full pattern list.

Severity: warning. Heuristics alone do not block — informational by design. Future releases will combine signals (unpinned + heuristic → strip).

Configuration

Required

FieldDescription
exchange.orgIdAnypoint business group that owns the MCP asset.
exchange.groupIdExchange group ID (typically the org ID).
exchange.assetIdExchange asset ID (the MCP asset).
exchange.versionPinned semver or latest.
exchange.credSecretRefFlex secret entry containing {clientId,clientSecret} or {username,password}.

Optional

FieldDefaultDescription
exchange.baseUrlhttps://anypoint.mulesoft.comControl-plane URL. Use https://eu1.anypoint.mulesoft.com for EU sovereignty.
exchange.authTypeoauth2_client_credentialsor basic.
exchange.refreshIntervalSec300Refresh cadence (30–86400).
enforce.exactMatchtrueHash equality.
enforce.allowAddedToolsfalsePass unpinned tools.
enforce.allowRemovedToolstrueAllow pinned tools to be removed.
heuristics.enabledtrueRun heuristic detectors.
heuristics.patterns[]Operator-supplied regex (validated at attach).
shadowingDetection.enabledtrueDetect near-name twins.
shadowingDetection.maxEditDistance2Levenshtein threshold (1–10).
modeenforceenforce / observe / warn.
failOpen.onPinUnavailablefalseAllow traffic on bootstrap when LKG is empty.

Evidence

Every decision produces a JSON event emitted via the PDK logger. Anypoint Analytics indexes them automatically.

{
  "class": "descriptor_drift",
  "severity": "critical",
  "decision": "stripped",
  "asset_id": "poisoning-demo-a2d-mcp",
  "asset_version": "1.0.0",
  "tool_name": "assist_user",
  "pin_hash": "ab12...",
  "runtime_hash": "cd34...",
  "field": "description_changed",
  "patterns": ["(?i)\\bignore previous instructions\\b"],
  "shadowed_target": null
}

All events are prefixed with mcp-poisoning-evt. Query in Anypoint Analytics with:

"mcp-poisoning-evt" AND severity:critical

Decision modes

  • enforce (default) — strip drifted/poisoned/shadowing tools. tools/call for stripped tools is denied with JSON-RPC error code -32021 (tool_poisoned) or -32022 (tool_unpinned).
  • observe — pass through unchanged; emit evidence with decision: allowed. Use during onboarding to measure false-positive rate.
  • warn — pass through with an x-mcp-poisoning-warning header listing detected classes (comma-separated). Downstream clients can render a warning banner without blocking the agent.

Failure modes

ConditionBehavior
Exchange unreachable on bootstrap, no LKGIf failOpen.onPinUnavailable=true: allow; else deny. pin_unavailable event emitted.
Exchange unreachable mid-flightRetain LKG; emit pin_stale event when age > 2× refresh.
OAuth token mint failsSame as unreachable — LKG preferred.
version=latest and underlying version bumpsFetch new pin, emit version_changed info event. Old LKG dropped after successful refresh.
Invalid heuristic pattern in configPolicy fails to attach with descriptive error.
Runtime body is not valid JSON-RPCPass through untouched.
SSE streaming responsePass through untouched, log warning. SSE rewrite is roadmap.

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-poisoning-via-exchange-demo
API instance20999092
Exchange asset (pin)82a0453b-22e6-430d-bbf4-35b989d043dc/poisoning-demo-a2d-mcp/1.0.0
Upstream (A²D mock)https://www.a2d-ai.com/api/platform/6886e506-0b3b-4641-b8e0-037aad5fc240/mcp
Policy versionomni-policy-mcp-tool-poisoning-detection-exchange-dev/0.1.0-20260629203315

The Exchange asset was published from the same A²D mock that backs the upstream, so a clean tools/list matches the pin. The mock declares a benign fetch_weather and a deliberately poisoned assist_user whose description contains a prompt-injection marker.

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

FAQ

Why pin via Exchange instead of a ConfigMap / CRD / sidecar? Because Anypoint customers already publish MCP assets to Exchange as part of their API lifecycle (Design Center → Exchange → API Manager). Reusing that artifact means the policy inherits the customer’s existing governance — approval workflows, version control, audit logs, and rollback discipline — for free.

Does the policy block tools/call for stripped tools? Not yet — the current release strips tools from tools/list but does not add a request filter to gate tools/call. The agent typically won’t call a tool it didn’t see in the list, so this is defense-in-depth rather than a critical gap.

Does this need any data to leave the tenant? No. The pin is fetched from the customer’s own Exchange instance (in-tenant), every decision happens in-policy (Flex Gateway WASM sandbox), and evidence events are emitted to Anypoint Analytics (in-tenant). There is no external callback, no SaaS dependency, and no cross-tenant data movement.

Pair with

Next Steps

Exchange-anchored poisoning detection keeps the trust contract inside your tenant.