DynamoDB Sage shows how to secure MCP database access: a Go RiskAnalyzer validates tool calls, Kafka async writes prevent throttling, Prometheus tracks security events — apply this pattern to your Claude Code MCP servers.
What Changed — The RiskAnalyzer Pattern for MCP Database Servers
When you let Claude Code talk to your production database through an MCP server, you're giving an LLM direct execution pathways. That's a security nightmare. Prompt injections, catastrophic data deletion, and resource starvation are real risks.
A developer named Taofit built DynamoDB Sage — a Go MCP server that lets LLM agents query and manage Amazon DynamoDB via natural language. The core innovation isn't the LLM integration; it's a custom RiskAnalyzer interceptor that validates every mutating tool call before it touches AWS.
This is the pattern you need for any production MCP server that exposes database operations to Claude Code.
The Technique — Three-Layer Risk Analysis
The RiskAnalyzer sits as a hard gateway boundary between the LLM and AWS. Every mutating or heavy JSON-RPC tool call passes through it. Read-only operations skip the check to keep the chat responsive.
The analyzer does three things:
- Structural validation — Checks the tool call against explicit JSON schemas. If the payload doesn't match, it's rejected.
- Table protection — Enforces data-boundary restrictions: protected tables, read-only tables, and batch-size caps. Destructive operations like mass writes or table drops are blocked.
- Blast-radius estimation — Estimates the impact: PII fields present, capacity/RCU cost, batch size. This assessment can trigger additional approval or rejection.
Here's the simplified Go code from the article:
func (ra *RiskAnalyzer) Analyze(ctx context.Context, req *mcp.CallToolRequest) (Assessment, error) {
// 1. Validate structural integrity against explicit JSON schemas
if err := ra.validateSchema(req); err != nil {
return Assessment{}, fmt.Errorf("validation violation: structural mismatch: %w", err)
}
// 2. Enforce data-boundary restrictions (protected tables, read-only tables, batch-size caps)
if err := ra.checkTableProtection(req); err != nil {
return Assessment{}, fmt.Errorf("authorization violation: execution path blocked")
}
// 3. Estimate blast radius — PII fields present, capacity/RCU cost, batch size
assessment := ra.estimateImpact(req)
return assessment, nil
}
Why It Works — Defense-in-Depth for LLM Tool Calls
You cannot trust the output of an LLM. Even with good prompting, adversarial prompts can trick the model into generating malicious payloads. The RiskAnalyzer is your enforcement layer — it doesn't rely on the LLM being good; it relies on hard code that can't be socially engineered.
This is especially critical for Claude Code users because MCP servers are now a standard way to give Claude access to external systems. If you're building an MCP server for your own database, you need this same pattern.
How To Apply It — Secure Your Own MCP Server
Step 1: Identify your mutating tools
List every tool in your MCP server that writes, deletes, or does a heavy read (like a full table scan). These are the ones that need risk analysis.
Step 2: Implement a RiskAnalyzer middleware
Write a Go (or any language) function that sits between the MCP tool dispatcher and your database client. It should:
- Validate the tool call against a JSON schema
- Check table names against a protected list
- Estimate the cost of the operation
Step 3: Use async writes for large operations
In DynamoDB Sage, large operations (batch writes, table creation) are published to a Kafka topic and processed asynchronously. This prevents DynamoDB throttling from freezing the chat UI. For your own server, consider a message queue for anything that could take more than a few seconds.
Step 4: Add observability
Prometheus metrics track tool latency, DynamoDB consumed capacity, Kafka lag, and security events. This gives you real-time visibility into what the LLM is doing — and when it gets blocked.
Try It Now — A Minimal Example
If you're building an MCP server for your own database, start with a simple interceptor:
func riskCheck(tool string, args map[string]interface{}) error {
// Block dangerous operations
if tool == "drop_table" {
return fmt.Errorf("blocked: drop_table is not allowed")
}
// Validate table names
if table, ok := args["table"].(string); ok {
if protectedTables[table] {
return fmt.Errorf("blocked: %s is protected", table)
}
}
return nil
}
Then wrap every mutating tool call with this check before executing. It's a small change that can save you from catastrophic data loss.
The Bottom Line
If you're exposing a database through MCP to Claude Code, you need a RiskAnalyzer. Don't trust the LLM — enforce boundaries in code. This pattern is production-ready and battle-tested.
For more on MCP security, check out our previous articles on [building secure MCP servers] and [Claude Code's tool permissions].
Source: dev.to
Originally published on gentic.news



Top comments (0)