Blog Post
Membangun MCP Gateway Aman untuk Engineering Agents dengan FastAPI dan Redis
Panduan production-ready untuk membuat gateway tool access bagi engineering agents dengan FastAPI, Redis, rate limit, audit log, dan policy sederhana.
TL;DR
- Agent gateway dibutuhkan ketika tool untuk AI agent mulai menyentuh sistem teknik nyata seperti deploy metadata, ticketing, atau log query.
- FastAPI cukup baik untuk control plane ringan, dan Redis cocok untuk rate limit, lock, serta replay protection sederhana.
- Gateway yang sehat harus punya auth, audit, input validation, dan boundary jelas antara tool yang read-only dan tool yang mutating.
Pendahuluan
Begitu engineering team mulai memakai agent untuk membaca log, membuka incident context, atau memicu workflow internal, satu pertanyaan langsung muncul: apakah agent ini boleh bicara langsung ke semua sistem? Jawaban yang masuk akal biasanya tidak. Tool call agent seharusnya tidak menjadi backdoor baru ke infrastruktur perusahaan. Di sinilah gateway berguna. Ia menjadi lapisan kontrol antara agent client dan tool backend.
Konsepnya sederhana, tetapi implementasi yang aman tidak otomatis. Banyak tim menaruh token admin pada server agent lalu membiarkan setiap tool call berjalan tanpa audit yang memadai. Ketika prompt injection atau request yang salah terjadi, tidak ada boundary yang menahan agent agar tetap berada pada jalur yang diizinkan. Untuk engineering systems, pendekatan seperti itu terlalu mahal risikonya.
Tutorial ini membangun gateway sederhana berbasis FastAPI dengan Redis. Gateway ini menerima request tool execution, memverifikasi API key, membatasi rate, mencatat audit event, dan mem-forward hanya ke tool backend yang sudah didaftarkan. Desainnya tidak bergantung pada satu vendor model tertentu. Fokusnya adalah perimeter aman untuk tool access.
Problem di Production
Masalah paling umum pada internal AI tooling adalah non-human identity sprawl. Satu service agent menyimpan terlalu banyak credential dan terlalu sedikit audit. Begitu tool list bertambah, agent bisa memanggil sistem yang seharusnya tidak relevan untuk request tertentu. Pada saat yang sama, operator sulit menjawab siapa memanggil apa, dengan parameter apa, dan kapan.
Problem kedua adalah abuse. Rate limit yang tidak ada atau terlalu longgar dapat membuat satu sesi agent mengirimkan ratusan permintaan ke backend internal dalam hitungan detik, baik karena bug, prompt loop, atau misuse.
Prerequisites
- Linux host atau Kubernetes workload yang bisa menjalankan Python 3.11+.
- Redis 7+ untuk rate limit dan state ringan.
uvicorn,fastapi,httpx, danredis.- Satu atau lebih internal tool backend HTTP yang siap diproxy, misalnya metadata deploy service atau log search API.
- Secret store atau environment file aman untuk API key internal.
- Reverse proxy atau ingress yang bisa memasang TLS di depan gateway.
Architecture / Context
Arsitektur yang dipakai cukup ringkas. Agent client mengirim request ke gateway. Gateway memvalidasi client API key, mencocokkan tool name dengan registry internal, mengecek rate limit dan policy dasar, lalu mem-forward request ke backend tool yang benar. Redis menyimpan counter, dedupe key, dan lock ringan untuk menghindari replay atau burst yang terlalu agresif. Semua event penting dicatat ke log audit.
Lapisan ini penting karena sebagian besar tool backend internal sebaiknya tidak diekspos langsung ke agent runtime. Tool backend dirancang untuk domain tertentu, bukan untuk menerima request model secara mentah tanpa boundary tambahan.
Step-by-Step Implementation
Step 1: Install dependency
python -m venv .venv
. .venv/bin/activate
pip install fastapi uvicorn[standard] redis httpx pydantic-settings
Step 2: Jalankan Redis
Dengan Docker:
docker run -d --name mcp-redis \
-p 6379:6379 \
redis:7-alpine
Untuk production, Redis sebaiknya punya persistence dan auth yang sesuai. Untuk tutorial ini kita fokus pada jalur fungsional.
Step 3: Buat konfigurasi environment
cat > .env <<'EOF'
GATEWAY_API_KEY=replace-with-long-random-key
REDIS_URL=redis://127.0.0.1:6379/0
DEPLOY_METADATA_URL=http://deploy-metadata.internal/api
LOG_SEARCH_URL=http://log-search.internal/query
EOF
Step 4: Tulis gateway FastAPI
from datetime import datetime, timezone
import hashlib
import json
import os
import httpx
import redis
from fastapi import FastAPI, Header, HTTPException, Request
from pydantic import BaseModel, Field
app = FastAPI(title="Engineering MCP Gateway")
r = redis.from_url(os.environ["REDIS_URL"], decode_responses=True)
TOOL_REGISTRY = {
"deploy_metadata": {
"url": os.environ["DEPLOY_METADATA_URL"],
"method": "POST",
"mode": "read"
},
"log_search": {
"url": os.environ["LOG_SEARCH_URL"],
"method": "POST",
"mode": "read"
}
}
class ToolRequest(BaseModel):
tool: str = Field(min_length=2)
session_id: str = Field(min_length=6)
arguments: dict
def require_api_key(api_key: str | None):
if api_key != os.environ["GATEWAY_API_KEY"]:
raise HTTPException(status_code=401, detail="invalid api key")
def rate_limit(session_id: str, tool: str):
bucket = f"rl:{session_id}:{tool}:{datetime.now(timezone.utc).strftime('%Y%m%d%H%M')}"
count = r.incr(bucket)
if count == 1:
r.expire(bucket, 70)
if count > 30:
raise HTTPException(status_code=429, detail="rate limit exceeded")
def dedupe_key(payload: ToolRequest) -> str:
raw = json.dumps(payload.model_dump(), sort_keys=True).encode()
return "dedupe:" + hashlib.sha256(raw).hexdigest()
async def forward_tool(payload: ToolRequest):
target = TOOL_REGISTRY.get(payload.tool)
if not target:
raise HTTPException(status_code=404, detail="tool not registered")
async with httpx.AsyncClient(timeout=10.0) as client:
response = await client.request(
method=target["method"],
url=target["url"],
json=payload.arguments,
)
if response.status_code >= 400:
raise HTTPException(status_code=502, detail=f"backend error: {response.text}")
return response.json()
@app.post("/v1/tools/execute")
async def execute_tool(
payload: ToolRequest,
request: Request,
x_api_key: str | None = Header(default=None),
):
require_api_key(x_api_key)
rate_limit(payload.session_id, payload.tool)
key = dedupe_key(payload)
if not r.set(key, "1", ex=30, nx=True):
raise HTTPException(status_code=409, detail="duplicate request")
result = await forward_tool(payload)
audit = {
"time": datetime.now(timezone.utc).isoformat(),
"tool": payload.tool,
"session_id": payload.session_id,
"client": request.client.host if request.client else "unknown",
}
print(json.dumps(audit))
return {"ok": True, "result": result}
Gateway ini sengaja hanya mendaftarkan tool yang jelas. Tool yang tidak terdaftar ditolak. Ini baseline yang jauh lebih sehat daripada menerima URL arbitrary dari client agent.
Step 5: Jalankan gateway
export $(grep -v '^#' .env | xargs)
uvicorn main:app --host 0.0.0.0 --port 8080
Step 6: Tambahkan reverse proxy Nginx
server {
listen 443 ssl http2;
server_name mcp-gateway.example.com;
ssl_certificate /etc/letsencrypt/live/mcp-gateway/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mcp-gateway/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 30s;
}
}
Step 7: Tambahkan systemd service
[Unit]
Description=MCP Gateway
After=network.target
[Service]
User=ubuntu
WorkingDirectory=/opt/mcp-gateway
EnvironmentFile=/opt/mcp-gateway/.env
ExecStart=/opt/mcp-gateway/.venv/bin/uvicorn main:app --host 0.0.0.0 --port 8080
Restart=always
[Install]
WantedBy=multi-user.target
Aktifkan:
sudo systemctl daemon-reload
sudo systemctl enable --now mcp-gateway
sudo systemctl status mcp-gateway
Step 8: Uji dengan request tool
curl -X POST http://127.0.0.1:8080/v1/tools/execute \
-H "Content-Type: application/json" \
-H "x-api-key: replace-with-long-random-key" \
-d '{
"tool": "deploy_metadata",
"session_id": "sess-20260425-001",
"arguments": {
"service": "payments-api",
"environment": "production"
}
}'
Jika backend tool tersedia, response harus berupa payload JSON yang dibungkus { "ok": true }.
Step 9: Tambahkan policy sederhana untuk mutating tool
Jika nanti ada tool yang bisa memicu aksi seperti rollback atau restart, jangan aktifkan langsung. Tambahkan approval flag:
if target["mode"] == "write" and not payload.arguments.get("approved"):
raise HTTPException(status_code=403, detail="approval required")
Pemisahan read-only versus mutating tool penting untuk mencegah agent bereksperimen terlalu jauh.
Verification
Cek health process:
sudo systemctl status mcp-gateway
docker logs mcp-redis
Uji API key salah:
curl -X POST http://127.0.0.1:8080/v1/tools/execute \
-H "Content-Type: application/json" \
-H "x-api-key: wrong-key" \
-d '{"tool":"deploy_metadata","session_id":"bad-1","arguments":{"service":"payments-api"}}'
Expected result adalah 401.
Uji rate limit dengan loop:
for i in $(seq 1 35); do
curl -s -o /dev/null -w "%{http_code}\n" \
-X POST http://127.0.0.1:8080/v1/tools/execute \
-H "Content-Type: application/json" \
-H "x-api-key: replace-with-long-random-key" \
-d "{\"tool\":\"log_search\",\"session_id\":\"sess-rate-1\",\"arguments\":{\"query\":\"error\"}}"
done
Seharusnya request setelah ambang tertentu memberi 429.
Troubleshooting
Jika gateway hidup tetapi selalu 502, cek backend tool URL:
curl -v http://deploy-metadata.internal/api
curl -v http://log-search.internal/query
Jika rate limit tidak bekerja, periksa koneksi Redis:
redis-cli ping
redis-cli keys 'rl:*'
Jika duplicate request terus terjadi, cek TTL key dedupe dan pastikan session_id tidak dipakai ulang untuk request yang berbeda. Masalah lain yang sering muncul adalah reverse proxy lupa meneruskan header yang relevan atau timeout terlalu pendek untuk tool yang lambat.
Production Notes
Gateway ini sebaiknya duduk di belakang TLS dan, untuk kasus yang lebih sensitif, di belakang identity-aware proxy atau mTLS internal. Logging audit sebaiknya dikirim ke storage terpisah, bukan hanya stdout. Untuk tool backend yang lambat, tambahkan async job queue daripada membiarkan request sinkron menggantung terlalu lama. Jika gateway dipakai multi-tenant, pisahkan rate limit dan auth berdasarkan tenant atau agent identity, bukan hanya session string.
Pikirkan juga tool taxonomy. Tool read-only, mutation terbatas, dan high-risk action sebaiknya tidak hidup pada endpoint atau credential yang sama. Semakin dini pemisahan ini dibuat, semakin kecil blast radius saat agent salah bertindak.
Trade-offs
Gateway menambah latensi dan satu dependency baru, tetapi memberi tempat yang jelas untuk auth, audit, dan rate limit. Redis sangat praktis untuk kontrol state ringan, tetapi bukan audit store jangka panjang. FastAPI cepat dibangun, tetapi bila daftar tool dan policy tumbuh besar, Anda mungkin perlu policy engine terpisah.
Failure Modes
- API key bocor dan gateway menerima request palsu sampai rate limit per session menjadi tidak cukup.
- Tool backend read-only berubah menjadi side-effecting tanpa policy gateway ikut diperbarui.
- Redis tidak tersedia sehingga semua request gagal atau rate limit tidak lagi efektif.
- Agent prompt loop memicu ledakan request yang membebani backend internal.
Best Practices
- Daftarkan tool secara eksplisit, jangan biarkan client menentukan target URL.
- Pisahkan read-only dan mutating tools sejak awal.
- Simpan audit log dengan field yang mudah dicari: waktu, tool, session, caller, outcome.
- Tambahkan timeout dan retry policy yang konservatif.
- Uji abuse case seperti duplicate request, burst request, dan backend timeout.
Kesalahan Umum
- memberi agent akses langsung ke tool backend internal
- menyimpan credential admin luas di satu service tanpa audit
- tidak membedakan tool baca dan tool ubah
- menganggap gateway internal tidak perlu auth karena “hanya dipakai agent”
Kesimpulan
Gateway tool untuk engineering agents bukan fitur ekstra, tetapi boundary keamanan yang akan semakin penting ketika AI mulai menyentuh sistem operasional nyata. Dengan FastAPI dan Redis, tim bisa membangun lapisan kontrol yang cukup ringan namun tetap memberi auth, audit, dan rate limit yang dibutuhkan sebelum agent diberi akses ke permukaan internal yang lebih sensitif.
Kalau artikel ini membantu, kamu bisa support eksperimen berikutnya.
Apresiasi di TrakteerKeep Reading
Related posts
Hardening vLLM Inference Service di Kubernetes dengan Istio dan OPA
Panduan production-ready untuk menjalankan vLLM di Kubernetes dengan kontrol jaringan, policy admission, mTLS, dan guardrail operasional yang lebih aman.
AI Attack Surface di DevOps Pipeline yang Lebih Berbahaya dari Dugaan
Analisis production-grade tentang attack surface baru saat AI masuk ke DevOps pipeline, termasuk prompt injection, tool abuse, dan governance untuk automation yang menyentuh production.
Konfigurasi Firewall Otomatis untuk Menahan Brute Force di Server Linux
Panduan teknis untuk menggabungkan firewall, rate limiting, dan log-driven automation agar serangan brute force ke server Linux tidak langsung berubah menjadi gangguan operasional.