MCP Policy Enforcement
AxonFlow enforces policies at two phases during MCP connector operations:
- REQUEST Phase: Evaluates incoming queries before they reach the connector
- RESPONSE Phase: Scans connector responses before returning to the client
This provides comprehensive security for both data input and output.
Phase-Aware Evaluation
Request Phase
The request phase evaluates queries before they are sent to the connector:
- SQL Injection Detection: Blocks malicious SQL patterns (DROP, DELETE without WHERE, UNION injection)
- Dangerous Operation Blocking: Prevents schema modifications, privilege escalation
- Access Control: Enforces tenant-specific permissions
If a policy blocks the request, the connector is never called and a 403 response is returned.
Response Phase
The response phase evaluates data after it's returned from the connector:
- PII Detection: Identifies sensitive data patterns (SSN, credit cards, national IDs)
- Data Redaction: Replaces sensitive values with redacted placeholders
- Audit Logging: Records what data was accessed and redacted
Response Schema
All MCP responses now include policy enforcement metadata:
{
"success": true,
"connector": "postgres-demo",
"data": [
{
"name": "John Doe",
"ssn": "[REDACTED]",
"email": "[email protected]"
}
],
"row_count": 1,
"duration_ms": 45,
"redacted": true,
"redacted_fields": ["data[0].ssn"],
"policy_info": {
"policies_evaluated": 15,
"blocked": false,
"redactions_applied": 1,
"processing_time_ms": 3,
"matched_policies": [
{
"policy_id": "pii-us-ssn",
"policy_name": "US Social Security Number",
"category": "pii-us",
"severity": "critical",
"action": "redact"
}
]
}
}
New Response Fields
| Field | Type | Description |
|---|---|---|
redacted | boolean | Whether any fields were redacted |
redacted_fields | string[] | JSON paths of redacted fields |
policy_info | object | Policy evaluation details |
PolicyInfo Object
| Field | Type | Description |
|---|---|---|
policies_evaluated | integer | Number of policies checked |
blocked | boolean | Whether request was blocked |
block_reason | string | Reason if blocked |
redactions_applied | integer | Number of fields redacted |
processing_time_ms | integer | Policy evaluation time |
matched_policies | array | Policies that matched |
PolicyMatchInfo Object
Each entry in matched_policies contains:
| Field | Type | Description |
|---|---|---|
policy_id | string | Unique policy identifier |
policy_name | string | Human-readable policy name |
category | string | Policy category (e.g., pii-us, security-sqli) |
severity | string | Match severity (critical, high, medium, low) |
action | string | Action taken (block, redact, warn, log) |
Policy Categories
Security Policies (Request Phase)
| Category | Description | Action |
|---|---|---|
security-sqli | SQL injection patterns | Block |
security-dangerous | DDL, privilege escalation | Block |
PII Policies (Response Phase)
| Category | Description | Action |
|---|---|---|
pii-us | US SSN, Driver's License | Redact |
pii-global | Credit cards, email, phone | Redact |
pii-india | Aadhaar, PAN | Redact |
pii-eu | National IDs, IBAN | Redact |
SDK Integration
Go SDK
resp, err := client.MCPQuery(ctx, axonflow.MCPQueryRequest{
Connector: "postgres-demo",
Statement: "SELECT name, ssn FROM customers",
})
if resp.Redacted {
fmt.Printf("Redacted fields: %v\n", resp.RedactedFields)
}
if resp.PolicyInfo != nil {
fmt.Printf("Evaluated %d policies in %dms\n",
resp.PolicyInfo.PoliciesEvaluated,
resp.PolicyInfo.ProcessingTimeMs)
}
TypeScript SDK
const resp = await client.mcpQuery({
connector: "postgres-demo",
statement: "SELECT name, ssn FROM customers",
});
if (resp.redacted) {
console.log("Redacted fields:", resp.redacted_fields);
}
if (resp.policy_info) {
console.log(`Evaluated ${resp.policy_info.policies_evaluated} policies`);
}
Python SDK
resp = await client.mcp_query(
connector="postgres-demo",
statement="SELECT name, ssn FROM customers"
)
if resp.redacted:
print(f"Redacted fields: {resp.redacted_fields}")
if resp.policy_info:
print(f"Evaluated {resp.policy_info.policies_evaluated} policies")
Java SDK
ConnectorResponse resp = client.mcpQuery(
"postgres-demo",
"SELECT name, ssn FROM customers"
);
if (resp.isRedacted()) {
System.out.println("Redacted fields: " + resp.getRedactedFields());
}
ConnectorPolicyInfo info = resp.getPolicyInfo();
if (info != null) {
System.out.println("Evaluated " + info.getPoliciesEvaluated() + " policies");
}
Handling Blocked Requests
When a request is blocked by policy, the SDK throws an exception or returns an error:
// Go
resp, err := client.MCPQuery(ctx, req)
if err != nil {
// Check if it's a policy violation
if policyErr, ok := err.(*axonflow.PolicyViolationError); ok {
fmt.Printf("Blocked by policy: %s\n", policyErr.Reason)
}
}
// TypeScript
try {
const resp = await client.mcpQuery(req);
} catch (err) {
if (err instanceof PolicyViolationError) {
console.log("Blocked by policy:", err.reason);
}
}
Performance
The policy engine is designed for minimal latency impact:
- Request Phase:
<5msp99 for policy evaluation - Response Phase:
<10msp99 for redaction - Caching: Compiled regex patterns cached for reuse
- Parallel Evaluation: Policies evaluated concurrently
Configuration
Environment Variables
| Variable | Default | Description |
|---|---|---|
POLICY_CACHE_TTL | 5m | Policy cache refresh interval |
POLICY_BLOCK_ON_ERROR | false | Block if policy engine unavailable |
PII_ACTION | redact | Default PII action (block/redact/warn/log) |
Database Configuration
Policies are stored in the static_policies table with phase-aware columns:
SELECT id, name, category, phase, action_request, action_response
FROM static_policies
WHERE enabled = true AND tenant_id = 'your-tenant';
Examples
See the MCP connector examples in the AxonFlow repository for complete working code in all supported languages.