The OWASP foundation just published the OWASP MCP Top 10 (2025) — the first dedicated security taxonomy for Model Context Protocol servers. For anyone building MCP tools that handle real-world data (payments, health records, legal documents), this is the most important security document to read right now.
Here's how we implemented every control in mpesa-mcp — Kenya's M-PESA Daraja API as an MCP server.
Why MCP Security Is Different
Traditional API security is about protecting your server from requests. MCP security is about protecting your tools from the AI agent using them.
The core vulnerability in MCP systems (arXiv:2603.21642, 2026) is that an AI agent decides which tools to call and with what parameters — based on natural language. An attacker who can inject text into what the agent reads (a document, a web page, a returned API response) can redirect tool calls without ever touching application code. The MCP-38 threat taxonomy calls this the tool description poisoning and indirect prompt injection class of attacks.
This is structurally new. It has no direct equivalent in classical software security.
How mpesa-mcp Addresses Each Risk
MCP01: Token Mismanagement & Secret Exposure
# Wrong — never do this
consumer_key = "hXvqiOG2yy0PfvxkFHd3..." # hardcoded
# Right — all credentials via environment variables
consumer_key = os.environ["MPESA_CONSUMER_KEY"]
OAuth tokens are cached in-memory only (1-hour expiry, auto-refreshed). Never written to disk. Never logged. All phone numbers in audit logs are SHA-256 hashed:
def _audit(tool, params, outcome):
safe = {
k: hashlib.sha256(str(v).encode()).hexdigest()[:8] + "..."
if k in {"phone", "party_a", "party_b"}
else str(v)
for k, v in params.items()
}
_log.info("TOOL=%s PARAMS=%s OUTCOME=%s", tool, safe, outcome)
MCP02: Tool Poisoning & Prompt Injection
Tool descriptions in mpesa-mcp are static, versioned in source, and published via PyPI Trusted Publisher (OIDC). An attacker cannot modify them without compromising GitHub Actions. All inputs are validated by type before any API call:
def mpesa_stk_push(
phone: Annotated[str, "Customer phone (any Kenyan format)"],
amount: Annotated[int, "Amount in KES (whole number, min 1, max 150000)"],
account_ref: Annotated[str, "Account reference (max 12 chars)"],
) -> dict:
if not 1 <= amount <= 150_000:
return {"error": f"Amount {amount} out of bounds [1, 150000]"}
# normalize phone to E.164 before API call
phone = _normalize_phone(phone)
MCP03: Excessive Agency — Tool Annotations
All 22 tools in mpesa-mcp declare MCP tool annotations so clients can gate calls correctly:
@mcp.tool(annotations={
"readOnlyHint": True, # query tools: can't modify state
"destructiveHint": False, # or False for write tools: requires confirmation
"idempotentHint": True # safe to retry
})
def mpesa_stk_query(checkout_request_id: str) -> dict: ...
@mcp.tool(annotations={
"readOnlyHint": False,
"destructiveHint": True, # Claude Desktop will show confirmation dialog
"idempotentHint": False
})
def mpesa_stk_push(...) -> dict: ...
This is the single most important control. A well-implemented MCP client won't call mpesa_stk_push without showing the user a confirmation — because destructiveHint: true signals that the action cannot be undone.
MCP06: Audit Logging
Every tool call is logged with structured metadata, PII removed:
TOOL=mpesa_stk_push PARAMS={'phone': 'a3f2b1c4...', 'amount': '500'} OUTCOME=INITIATED
No credentials, no full phone numbers, no transaction receipts in logs.
The NSA/CISA Alignment
The joint NSA/CISA guidance U/OO/143395-24 (April 2024) and the newer AI Data Security guidance (May 2025) both emphasize six principles:
- Govern the AI deployment (versioned releases, CHANGELOG, threat model)
- Understand the model and environment (documented data flows in README)
- Validate before deployment (CI/CD: lint + test gates)
- Secure infrastructure (HTTPS only, env vars, no credential in code)
- Secure at application layer (input validation, structured outputs)
- Operationalize security (audit logging, VDP at contact@aikungfu.dev)
mpesa-mcp implements all six. The formal SECURITY.md documents each control with specific code references.
Why This Matters for Africa
AI infrastructure in Africa handles genuinely high-stakes operations. M-PESA processes more transactions per day than PayPal in Africa. Drought alerts dispatched via wapimaji-mcp may be the first notification a community leader receives that their county is entering Phase 4 Emergency.
The OWASP MCP Top 10 was written primarily with Western fintech and enterprise use cases in mind. The controls apply just as well — and arguably more critically — to financial infrastructure serving 35 million people who have fewer remediation options when things go wrong.
Security is not a checkbox. It's the condition that makes everything else meaningful.
Repos:
- mpesa-mcp — M-PESA Daraja API for AI agents
- wapimaji-mcp — Kenya drought intelligence
- civic-agent-kit — Kenya civic data
Security docs:
Portfolio: gabrielmahia.github.io
Top comments (0)