Ringkasan Utama
Enterprise MCP reference architecture di Cloudflare punya lima lapisan: remote server dengan Access sebagai OAuth provider, portal untuk discovery dan policy tool per grup, Code Mode untuk mengecilkan definisi tool, Gateway DLP pada traffic portal, dan WAF untuk server publik. Write default-deny ada di lapisan server.
Angka yang membuat saya membaca post enterprise MCP Cloudflare dua kali bukan soal keamanan. Angkanya 52 tool yang memakan sekitar 9.400 token konteks sebelum karyawan mengetik satu kata. Empat MCP server internal, satu portal, dan model sudah membawa buku panduan kecil yang tidak ia perlukan.
Ini adalah pembacaan dekat atas post tersebut (terbit 14 April 2026, diubah 15 Juli 2026) dibandingkan dengan dokumentasi Cloudflare terbaru untuk MCP server portals, tutorial Gateway untuk mendeteksi traffic MCP, dan halaman AI Security for Apps. Saya belum men-deploy stack ini sendiri. Semua angka berasal dari Cloudflare, semua batasan dikutip dari dokumentasi, dan jika dua sumber berbeda saya sebutkan.
Kenapa Cloudflare melarang MCP server lokal lebih dulu
Arsitektur ini dimulai dari sebuah penolakan. Cloudflare menilai MCP server lokal sebagai liabilitas keamanan sejak awal dengan tiga alasan: sumber dan versi software yang tidak diperiksa (masalah supply chain), tool injection atau tool poisoning, dan tidak ada administrasi IT atau keamanan sama sekali karena setiap karyawan memutuskan sendiri. Solusinya adalah menjadikan remote server satu-satunya jalur yang mudah.
Tim pusat menjalankan platform MCP bersama di dalam monorepo. Karyawan yang ingin mengekspos resource internal meminta persetujuan tim AI governance, menyalin template, menulis definisi tool lalu deploy. Template membawa write control default-deny, audit logging, pipeline CI/CD yang dibuat otomatis, dan secrets management. Menurut post-nya ini hanya butuh beberapa menit scaffolding, dan governance yang tertanam di platform itulah yang membuat adopsi menyebar cepat. Saya membacanya sebagai prinsip desain yang sebenarnya: jalur yang aman harus menjadi jalur termurah, kalau tidak orang akan menghindarinya.
Lima lapisan dan tanggung jawab masing-masing
Post ini lebih mudah dipakai kalau lapisannya dipisah, karena tiap lapisan menjawab pertanyaan berbeda dan tiap lapisan punya celah yang ditutup lapisan berikutnya. Tabel ini ringkasan saya sendiri dari post dan dokumentasi, bukan diagram Cloudflare.
| Lapisan | Produk Cloudflare | Pertanyaan yang dijawab | Yang tidak bisa dilakukan sendirian |
|---|---|---|---|
| Server | Workers dengan template monorepo | Apa yang bisa dilakukan tool ini, dan apakah write boleh? | Tidak ada di luar tool-nya sendiri; direct URL bisa melewati policy portal |
| Identitas | Access sebagai OAuth provider | Siapa ini, di device apa, dari mana? | Tidak menentukan tool server mana yang dilihat seseorang |
| Discovery dan policy | MCP server portal | Server dan tool apa yang didapat grup ini? | Tidak memeriksa payload kecuali Gateway routing aktif |
| Data | Gateway HTTP policy dengan DLP | Apakah data sensitif keluar atau kembali? | Hanya memindai 1.024 byte pertama dari body POST |
| Server publik | WAF dengan AI Security for Apps | Apakah traffic masuk berupa prompt injection atau kebocoran PII? | Detection field adalah add-on berbayar; hanya request JSON |
Baca kolom terakhir sebagai alasan perlunya kelimanya. Access saja tidak melihat intent di level tool, portal saja tidak melihat body request, dan Gateway saja tidak tahu siapa yang boleh memanggil tool write. Post juga menempatkan AI Gateway antara MCP client dan model untuk pergantian provider dan batas token per karyawan, tetapi tanpa konfigurasi, jadi saya anggap itu catatan desain, bukan sesuatu untuk disalin.
Lapisan satu: Access sebagai OAuth provider untuk server Anda sendiri
Server publik seperti MCP dokumentasi Cloudflare dan MCP Radar terbuka untuk siapa saja. Yang privat tidak. Template monorepo menghubungkan Cloudflare Access sebagai OAuth provider, sehingga Access menjalankan login flow, menerbitkan token, dan menerapkan SSO, MFA serta pemeriksaan kontekstual seperti alamat IP, lokasi, dan device certificate. Penulis server cukup menulis tool dan tidak menyentuh sistem identitas.
Mendaftarkan server ke portal adalah langkah terpisah. Di dashboard lokasinya Zero Trust, Access controls, MCP Portals, tab MCP servers. Lewat API, field auth_type menerima oauth, bearer, atau unauthenticated. Untuk server yang tidak mendukung Dynamic Client Registration, Anda memberikan client yang sudah terdaftar dan mengirim client secret sebagai field tersendiri; Cloudflare menyimpannya terenkripsi dan tidak pernah mengembalikannya.
# Register an upstream server that cannot do Dynamic Client Registration.
# The client secret is a SIBLING field, never inside auth_credentials.
# Cloudflare encrypts it and never returns it from read, create or update calls.
curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/access/ai-controls/mcp/servers" \
--request POST \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
--json '{
"id": "github",
"name": "GitHub MCP Server",
"hostname": "https://github-mcp.example.com/mcp",
"auth_type": "oauth",
"is_shared_oauth_callback_enabled": true,
"auth_credentials": "{\"auth_mode\":\"manual\",\"config\":{\"authorization_endpoint\":\"https://github.com/login/oauth/authorize\",\"token_endpoint\":\"https://github.com/login/oauth/access_token\"},\"registration_info\":{\"client_id\":\"<client-id>\",\"token_endpoint_auth_method\":\"client_secret_basic\",\"scope\":\"repo read:user\"}}",
"client_secret": "<client-secret>"
}'
# The server id becomes the tool prefix: list_issues is exposed as github_list_issues.
# Server ids cannot contain underscores (the portal splits on the first one). Use hyphens.Peringatan dari Cloudflare sendiri: user yang diblokir policy Access di portal tetap bisa mencapai server lewat direct URL. Menyembunyikan server di portal bukan penegakan. Dokumentasi menyarankan mengonfigurasi Access sebagai OAuth provider server jika Anda ingin autentikasi ditegakkan di server itu sendiri.
Lapisan dua: satu portal per audiens, tool di-allow bukan disembunyikan
Portal menempatkan banyak MCP server di balik satu endpoint HTTP pada path /mcp di domain Anda sendiri. User login sekali lewat Access lalu mengautentikasi terpisah ke tiap upstream yang butuh OAuth. Contoh dalam post adalah pernyataan model yang paling jelas: portal finance yang dibatasi untuk grup finance dan hanya mengekspos tool read-only dari server code repository, serta portal engineering yang dibatasi untuk engineer dengan laptop korporat dan mengekspos tool read dan write dari server yang sama. Satu server, dua portal, dua daftar tool.
Detail yang membuatnya aman adalah allowlist. Secara default semua tool dan prompt dari server yang ditambahkan terlihat. Mengatur default_disabled menjadi true pada mapping server ke portal membalik itu: hanya tool yang Anda daftarkan di updated_tools yang diekspos. Di sinilah write default-deny menjadi properti portal sekaligus server, dan di sini pula jebakan pertama. Update portal yang menyertakan servers mengganti seluruh mapping, jadi menghilangkan sebuah server berarti menghapusnya dan menghilangkan sebuah field berarti mengembalikannya ke default.
# Finance portal: PUT replaces the WHOLE servers array, so list every server
# you want to keep and repeat its existing mapping fields. Omit one and it is removed.
curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/access/ai-controls/mcp/portals/$PORTAL_ID" \
--request PUT \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
--json '{
"servers": [
{
"server_id": "github",
"default_disabled": true,
"updated_tools": [
{ "name": "search_documents", "enabled": true },
{ "name": "list_projects", "enabled": true }
]
}
]
}'
# Every other tool and every prompt of this server is now hidden from this portal.# Terraform creates the portal but NOT its DNS record. Without the proxied CNAME
# the portal hostname answers 522.
resource "cloudflare_zero_trust_access_mcp_server_portal" "engineering" {
account_id = var.cloudflare_account_id
name = "Engineering Portal"
hostname = "mcp.example.com"
}
resource "cloudflare_dns_record" "mcp_portal" {
zone_id = var.cloudflare_zone_id
name = "mcp"
content = "gateway.agents.cloudflare.com"
type = "CNAME"
proxied = true
}Tool diberi namespace berupa server ID, underscore, lalu nama tool, jadi github plus list_issues menjadi github_list_issues. Portal memecah pada underscore pertama, artinya server ID tidak boleh berisi underscore; pakai hyphen. Jika Anda membuat portal lewat API atau Terraform, dokumentasi menegaskan tidak ada DNS record yang dibuat otomatis: tambahkan CNAME dengan proxy ke gateway.agents.cloudflare.com atau portal menjawab 522.
Lapisan tiga: Code Mode dan arti sebenarnya dari 94 persen
Portal internal Cloudflare menaungi empat MCP server. Sebagai tool biasa itu 52 definisi dengan sekitar 9.400 token. Dengan Code Mode, model melihat dua tool portal, portal_codemode_search dan portal_codemode_execute, dengan sekitar 600 token. Post menyebutnya pengurangan 94 persen; 600 dibagi 9.400 adalah 6,4 persen, jadi penghematannya lebih dekat ke 93,6 dan pembulatannya wajar. Kalimat yang lebih penting adalah biayanya tetap saat server ditambah. Post Cloudflare terpisah tentang API server mereka melaporkan sekitar 1.000 token untuk dua tool dibanding perkiraan 1,17 juta tanpa Code Mode.
Code Mode adalah policy pada setiap portal, bukan kebiasaan client. Defaultnya opt-in. Portal juga bisa dipaksa aktif, yang penting jika Anda ingin penghematan token menjadi jaminan platform, bukan sesuatu yang harus diingat setiap engineer.
| Policy (nilai API) | Perilaku default | Override dari client |
|---|---|---|
| Off (off) | Code Mode tidak tersedia | Query parameter diabaikan |
| Opt-in (opt_in) | Code Mode mati | Tambahkan codemode=search_and_execute untuk menyalakan |
| On by default (default_on) | Code Mode nyala | Tambahkan codemode=off untuk mematikan |
| Enforced (enforced) | Code Mode nyala | Query parameter diabaikan |
// Opt-in portal: the client asks for Code Mode in the URL.
// The portal then advertises portal_codemode_search and portal_codemode_execute
// instead of every upstream tool definition.
{
"mcpServers": {
"example-portal": {
"command": "npx",
"args": [
"-y",
"mcp-remote@latest",
"https://mcp.example.com/mcp?codemode=search_and_execute"
]
}
}
}
// Gentler alternative that keeps tools visible by name only:
// https://mcp.example.com/mcp?optimize_context=minimize_tools
// Use command and args as above. The docs advise against the serverURL parameter.Ada opsi kedua yang lebih ringan. Parameter optimize_context menawarkan minimize_tools, yang membuang deskripsi dan schema sehingga hanya nama yang tersisa, dengan klaim dokumentasi hingga 5x penghematan, dan search_and_execute, yang menyembunyikan tool upstream sepenuhnya. Dokumentasi memperingatkan agent lalu sepenuhnya bergantung pada tool query untuk menemukan apa pun. Eksekusi berjalan di Dynamic Worker yang terisolasi, sehingga credential dan environment variable tetap di luar konteks model.
Pilih enforced hanya setelah memeriksa client Anda. Dokumentasi menyebut portal tidak mendukung upstream server yang menjalankan Code Mode sendiri, dan menyarankan opt-in atau on by default jika sebagian client membawa implementasinya sendiri, supaya tidak terjadi eksekusi kode bersarang.
Lapisan empat: Gateway DLP pada traffic portal, dan apa yang bisa dibacanya
Access mencatat tiap tool call lewat portal, tetapi tidak bisa membaca payload. Untuk DLP Anda menyalakan Route traffic through Cloudflare Gateway, untuk seluruh portal atau satu server. Dokumentasi menyebut traffic portal didekripsi otomatis, jadi Anda tidak perlu pengaturan TLS decryption tingkat akun, tetapi policy HTTP Do Not Inspect yang cocok dengan MCP server Anda tetap mengecualikannya dan DLP lalu tidak melihat apa-apa.
# Gateway HTTP policy for portal traffic (Cloudflare's documented example).
# Target the UPSTREAM hostname, not the portal hostname.
Selector Operator Value Logic Action
Host in example-mcp-server.example.workers.dev And Block
DLP Profile in Credentials and Secrets, Financial Information
# DLP regex for MCP JSON-RPC from the detection tutorial (Rust regex syntax).
# Scans only the first 1,024 bytes of a POST body.
"method"\s{0,5}:\s{0,5}"tools/call"Policy harus menarget hostname upstream server, bukan hostname portal. Contoh Cloudflare memblokir ketika Host adalah hostname Workers dan DLP profile-nya Credentials and Secrets atau Financial Information. Blokir bekerja dua arah: request yang membawa data cocok ditolak, dan response yang membawa data cocok diganti dengan error. Itulah mekanisme di balik contoh dalam post tentang aturan DLP yang menjauhkan PII dari server tertentu.
Dua batasan penting. Gateway routing hanya mendukung Streamable HTTP, jadi upstream yang hanya SSE gagal saat routing aktif. Dan DLP AI prompt profile tidak berlaku untuk traffic portal karena menarget path API web client tertentu; pakai profile standar. Untuk traffic yang tidak lewat portal, tutorial Gateway mendeteksi MCP lewat hostname yang memuat mcp, path /mcp dan /sse, serta regex DLP atas method JSON-RPC seperti tools/call. Tutorial juga menyatakan DLP hanya memindai 1.024 byte pertama body POST, dan post sendiri mencatat bahwa response dan notification mungkin tidak membawa field method, jadi anggap ini heuristik.
Di mana desain ini lebih lemah daripada yang digambarkan diagram
Dokumentasi portal mencantumkan batasan yang tidak ditekankan blog post. Inilah yang akan saya masukkan ke risk register sebelum rollout.
- Independent MFA, purpose justification, dan temporary authentication tidak ditegakkan ketika server diotorisasi lewat portal. Policy yang menuntut step-up MFA pada sebuah server tidak akan muncul setelah login portal. Selector seperti email, grup, negara, dan device posture tetap ditegakkan.
- Token OAuth admin bisa kedaluwarsa diam-diam. Server lalu menampilkan Error atau Sync Required, hilang dari portal bagi end user, dan dokumentasi menyebut admin tidak diberi notifikasi. Seseorang harus memantau status server.
- Alias tool dihapus pada sync berikutnya jika upstream server mengganti nama tool, dan background sync berjalan sekitar tiap dua jam untuk server yang memakai Dynamic Client Registration.
- Hanya remote HTTP server yang bekerja. Server stdio-only harus di-host sendiri di balik endpoint HTTP dengan bearer token atau custom header, sebagian server menolak client berbasis proxy dengan 403, dan satu portal menampung hingga 80 server.
- Deteksi untuk server publik tidak gratis: AI Security for Apps memindai endpoint berlabel cf-llm, hanya menangani application/json, dan detection field serta ruleset Log Mode adalah add-on berbayar di Enterprise; endpoint discovery tersedia di semua plan.
Semua ini tidak merusak arsitekturnya. Artinya portal adalah lapisan akses dan routing, bukan pengganti otorisasi di dalam tiap server. Write control default-deny tetap harus ada di template server, karena itu satu-satunya lapisan yang tahu apa yang sebenarnya dilakukan sebuah tool.
Yang dikerjakan hari Senin
Jika Anda sudah menjalankan MCP server, Anda tidak perlu membangun ulang apa pun untuk mendapat sebagian besar manfaat ini. Urutan di bawah menaruh visibilitas termurah di depan.
- Ukur apa yang Anda punya: pindai log Gateway HTTP untuk traffic MCP dengan query di bawah, dan daftar setiap tujuan yang di-allow, bukan diblokir.
- Taruh server yang ingin dipertahankan di balik satu portal per audiens, dengan default_disabled true dan daftar eksplisit tool read untuk grup yang hanya membaca.
- Jadikan Access sebagai OAuth provider di setiap server yang Anda kontrol, sehingga direct URL tidak lagi menjadi jalan pintas.
- Nyalakan Code Mode sebagai opt-in, bandingkan token definisi tool sebelum dan sesudah di server Anda sendiri, baru pertimbangkan enforced.
- Arahkan portal lewat Gateway, tambahkan policy Host plus DLP ke tiap hostname upstream, dan pastikan tidak ada rule Do Not Inspect yang mencakup host tersebut.
Scan-nya adalah query dari tutorial Cloudflare yang diringkas. Butuh API token dengan izin Zero Trust read, dan dataset mencakup hingga 30 hari.
// Step 1: which MCP-looking destinations are allowed, and for whom?
// Dataset: gatewayHttpRequestsAdaptiveGroups, up to 30 days of history.
const query = [
"query MCPTrafficScan($accountTag: string, $since: string, $until: string) {",
" viewer { accounts(filter: { accountTag: $accountTag }) {",
" gatewayHttpRequestsAdaptiveGroups(",
" filter: { datetime_geq: $since, datetime_leq: $until,",
" OR: [ { httpHost_like: \"%mcp%\" },",
" { httpRequestURI_like: \"%/mcp%\" },",
" { httpRequestURI_like: \"%/sse%\" } ] }",
" limit: 10000",
" ) { dimensions { httpHost action users } count }",
" } }",
"}",
].join("\n");
const res = await fetch("https://api.cloudflare.com/client/v4/graphql", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.CLOUDFLARE_API_TOKEN,
"Content-Type": "application/json",
},
body: JSON.stringify({
query,
variables: { accountTag: process.env.ACCOUNT_ID, since: "2026-09-17T00:00:00Z", until: "2026-10-17T00:00:00Z" },
}),
});
const groups = (await res.json()).data?.viewer?.accounts?.[0]?.gatewayHttpRequestsAdaptiveGroups ?? [];
// action "allow" means an active MCP connection nobody has blocked or governed.
const open = groups.filter((g) => g.dimensions.action === "allow");
console.table(open.map((g) => ({ host: g.dimensions.httpHost, count: g.count, users: g.dimensions.users })));Aturan yang saya bawa dari sini: tanam governance di platform yang membuat server murah dibangun, lalu tambah lapisan yang masing-masing menutup apa yang tidak terlihat lapisan di bawahnya. Angka 94 persen token memang judulnya, tetapi ide yang awet adalah write default-deny di server, satu portal per audiens, dan DLP yang diarahkan ke host upstream. Verifikasi batasannya di plan Anda sendiri sebelum menjanjikan apa pun.

