Skip to content

2026-09-17 · 8 min read · ai-fundamentals

Menjalankan toko demo WebMCP di VPS sendiri

Halaman web bisa mendaftarkan tool-nya sendiri supaya agen AI bisa memanggilnya. Saya pasang demo itu di VPS, tambahkan gerbang persetujuan checkout dan panel jejak tool-call, lalu catat apa saja yang harus disiapkan.


Repo ini saya temukan saat mencari contoh nyata WebMCP: vincanger/webmcp-espresso-store. Isinya toko espresso lengkap, dan setiap halaman yang terbuka mendaftarkan tool-nya sendiri ke browser. Agen yang tersambung ke tab itu bisa mencari produk, membandingkan mesin, mengecek kecocokan aksesori, dan mengisi cart, semuanya lewat tool yang diumumkan halaman.

Saya jalankan dulu di laptop untuk melihat alurnya, lalu pindahkan ke VPS dengan tunnel publik. Demo-nya hidup di scores-dairy-hint-angels.trycloudflare.com: toko bernama forrrehehe dengan harga rupiah. Halaman How to try berisi langkah mencobanya. Alamat itu tunnel sementara, jadi bisa berubah kapan saja.

01

Halaman yang menyediakan tool

Aplikasi biasa menunggu manusia mengklik tombol. Halaman ber-WebMCP mengumumkan daftar tool ke browser lewat document.modelContext, lengkap dengan nama, deskripsi, dan JSON schema argumennya. Agen yang tersambung ke tab itu membaca daftar tersebut, memilih tool, dan memanggilnya dengan argumen terstruktur. Halaman yang mengeksekusi.

Tidak seperti API server, tool ini hidup di dalam tab browser. Tidak ada alamat yang bisa di-curl. Agen butuh browser yang membuka halamannya, tab itu dalam keadaan fokus, dan sesi login kalau tool-nya butuh user. Di demo ini, pengunjung yang belum login melihat 3 tool: search_products, get_product_details, dan compare_products. Setelah login dengan akun demo, jumlahnya 16, termasuk get_my_gear, check_compatibility, highlight_differences, add_to_cart, dan checkout.

Tool tulis di toko ini tidak cuma mengubah data. highlight_differences menyorot baris spesifikasi di tabel compare dan meredupkan sisanya, jadi alasan sebuah rekomendasi terlihat di layar. add_to_cart membuka cart drawer dan memperbarui total tanpa reload. Manusia yang menonton bisa melihat agen bekerja.

02

Tiga syarat sebelum agen bisa memanggil

03

Dari laptop ke VPS

Di laptop, wasp start cukup. Untuk pengunjung publik, mode dev itu rusak. Client-nya memanggil API di http://localhost:3001, alamat yang tidak ada di browser orang lain. Jadi saya pakai production build. URL API di-bake saat build, dan satu origin dijamin oleh reverse proxy.

pengunjung
   |
   v
cloudflared quick tunnel   systemd
   |  http://127.0.0.1:3000
   v
nginx (container), 1 origin
   |-- /operations/* -> API :3001
   |-- /auth/*       -> API :3001
   '-- lainnya      -> SPA statis
              |
              v
Node API (systemd) :3001
              |
              v
Postgres 16   127.0.0.1:55432

Tiga keputusan operasional yang saya ambil:

  1. Satu origin. SPA statis dan API disajikan dari hostname yang sama, jadi tidak ada CORS dan tidak ada dua URL yang harus dijelaskan ke pengunjung.
  2. Port yang mendengarkan. Postgres hanya listen di 127.0.0.1:55432 dan nginx proxy di 127.0.0.1:3000. API Node-nya jalan di :3001, dan Wasp membind-nya ke semua interface, jadi port itu bukan loopback-only; akses publiknya tetap cuma lewat tunnel, dan kalau perlu :3001 bisa saya batasi ke loopback lewat unit systemd.
  3. Semua yang saya pasang adalah file, container, dan unit baru. Layanan yang sudah jalan di VPS itu tidak disentuh.

Alamat tunnelnya sementara. cloudflared quick tunnel memberi URL acak, dan URL itu berubah kalau prosesnya di-restart atau VPS reboot. Saya menulis script yang mengambil URL terbaru dari jurnal, mem-patch bundle client, lalu me-restart API. Satu perintah cukup untuk menghidupkan demo lagi.

04

Asisten chat di dalam halaman

Supaya pengunjung bisa mencoba tanpa memasang apa pun, saya pasang asisten chat di dalam halaman: widget persona (@runtypelabs/persona 4.22.0, MIT) berlabel Barista AI (demo), dengan backend chat sendiri. Backend-nya Node tanpa dependency yang listen di bridge Docker 172.17.0.1:3002, jadi tidak terjangkau dari publik. Model yang saya pakai glm-5.3-flash lewat endpoint Anthropic-compatible Z.ai, dan kuncinya hanya ada di server.

Alur tool-nya mengikuti protokol widget. Widget mengirim snapshot 16 tool halaman ke backend, model memilih tool, backend mengirim frame await lalu berhenti, widget mengeksekusi tool di halaman (otomatis tercatat di panel jejak), dan hasilnya dikirim balik ke backend untuk lanjut. Saya uji dengan prompt pengunjung biasa, "bandingkan dua kandidat dan tambahkan yang terbaik ke cart", dan agen menjalankan get_my_gear, search_products tiga kali, compare_products, check_compatibility, highlight_differences, lalu add_to_cart. Semuanya sukses, dan UI bergerak sambil berjalan.

Karena ini demo publik, saya pasang dua batas: 20 pesan per jam per IP dan 500 pesan per hari. Penolakannya lewat pesan berbahasa Indonesia, dan tiap dispatch, resume, pause, serta penolakan dicatat di satu file JSONL.

Versi demo yang jalan hari ini tidak lagi memakai widget itu. Saya ganti dengan tombol 1-klik di halaman cara-coba: Buka di ChatGPT, Buka di Claude, dan Salin prompt. Tombolnya mengirim briefing siap pakai berisi konteks toko, daftar 16 tool, akun demo, dan misi yang harus dijalankan. Konsekuensinya demo publik tidak lagi menyimpan kunci model. Asisten eksternal tetap butuh koneksi chrome-devtools-mcp di browsermu, dan itu tertulis apa adanya di halaman tersebut.

05

Checkout tetap butuh klik manusia

Dari 16 tool, satu tool sengaja saya beri gerbang. checkout tidak menyelesaikan order sendiri. Kalau cart kosong, tool langsung mengembalikan status denied dengan alasannya. Kalau cart berisi, tool memarkir permintaannya dan halaman menampilkan dialog Approve checkout? berisi ringkasan item, kupon, dan total. Tool baru selesai setelah manusia mengklik Approve order atau Cancel. Tidak ada auto-approve dan tidak ada timeout. Kalau manusia menolak, agen menerima hasil terstruktur: order tidak dibuat, cart tidak berubah, dan agen bisa lanjut.

06

Panel jejak tool-call

Agen bekerja cepat, dan manusia butuh bukti. Saya bungkus execute setiap tool dengan wrapper yang mencatat nama tool, ringkasan argumen maksimal 80 karakter, jam, dan statusnya: ok, denied, atau error. Panel AGENT TOOL CALLS di kiri bawah menampilkan 8 entri terakhir. Setelah satu alur agen, isinya kira-kira begini: ok search_products, ok add_to_cart, denied checkout.

Panel ini juga menangkap bug. Versi pertama panelnya saya taruh di kanan bawah, dan dia menutupi tombol Checkout di cart drawer. Saya menemukannya saat memeriksa tampilan.

07

Reset tiap jam, dan satu jebakan CLI

Demo publik dengan tool tulis butuh data yang bersih sendiri. Saya pasang systemd timer yang menjalankan reset tiap jam. Di sini ada jebakan yang hampir membuat demo mati satu jam sekali. Wrapper pertama saya memanggil wasp db seed demoReset, dan setiap perintah CLI wasp meregenerasi folder .wasp/out, termasuk menghapus hasil build client. Jadi reset jamalan sama dengan mematikan demo secara berkala.

Perbaikannya dua hal. Script reset menjalankan seed langsung lewat node bundle/dbSeed.js dengan WASP_DB_SEED_NAME=demoReset, tanpa CLI wasp. Static root nginx saya pindah ke client-dist, di luar .wasp/out, jadi kebal regenerasi. Setelah itu reset bisa jalan tanpa menyentuh hasil build.

Tampilan mobile juga saya perbaiki di iterasi yang sama. Di viewport 390px, halaman utama dan halaman compare melebar 28px karena baris nav yang kaku dan gambar produk yang mendorong konten. Sekarang nav-nya wrap, kolom produk bertumpuk di layar sempit, dan drawer cart yang tertutup tidak lagi menyumbang lebar. Saya uji di 360, 390, 430, dan 1440 piksel: scrollWidth sama dengan innerWidth di semuanya.

08

Yang saya bawa ke proyek berikutnya

Pekerjaan ini habis di hal-hal biasa: production build yang benar, satu origin, reset data yang tidak merusak deployment, dan tool tulis yang tidak bisa dijalankan agen sendirian.

Kalau kamu mau mencobanya: buka scores-dairy-hint-angels.trycloudflare.com, lalu ikuti halaman cara-coba. Login demo demo / demo123. Alamatnya bisa berubah kapan saja, itu tunnel sementara.

Sources

  • Demo publik: toko WebMCP forrrehehe · first-hand · accessed 2026-09-17Demo yang dibahas artikel ini: 16 tool terdaftar saat login, panel jejak tool-call, gerbang persetujuan checkout, dan data yang direset tiap jam. URL quick tunnel sementara, bisa berubah.
  • vincanger/webmcp-espresso-store · documentation · accessed 2026-09-17Repo pihak ketiga yang menyediakan kode dasar tokonya, commit 7dff5af. GitHub melaporkan lisensi: None, tidak ada berkas lisensi eksplisit di repo.
  • WebMCP (Web Machine Learning Community Group) · documentation · accessed 2026-09-17Proposal WebMCP: halaman mendaftarkan tool yang bisa dipanggil agen lewat document.modelContext.
  • Chrome for Developers, WebMCP · documentation · accessed 2026-09-17Dokumentasi Chrome soal status WebMCP dan cara mengaktifkannya di belakang flag.
  • @mcp-b/webmcp-polyfill (npm) · documentation · accessed 2026-09-17Polyfill MIT yang saya pin di versi 5.1.0. No-op kalau document.modelContext sudah ada, jadi implementasi native selalu menang.
  • Wasp · documentation · accessed 2026-09-17Framework full-stack yang dipakai toko ini: client React, server Node, dan Postgres.

Next steps

Frequently asked

Apa itu WebMCP?

Cara sebuah halaman web mendaftarkan tool-nya sendiri ke browser. Halaman menulis daftar tool di document.modelContext, dan agen yang tersambung ke tab itu bisa membaca lalu memanggilnya dengan argumen terstruktur. Tool-nya hidup di tab browser, bukan di endpoint HTTP, jadi tidak ada alamat yang bisa di-curl.

Apa yang terjadi kalau saya buka demonya di Chrome biasa tanpa flag?

Halaman tetap jalan sebagai toko biasa. Karena saya memasang polyfill @mcp-b/webmcp-polyfill, tool tetap terdaftar di document.modelContext, jadi kamu bisa melihatnya dari console atau memanggilnya dari halaman itu sendiri. Untuk agen eksternal, jalur paling sederhana tetap Chrome/Chromium 149+ dengan --enable-features=WebMCP dan chrome-devtools-mcp@1.8.0.

Kenapa checkout tidak bisa diselesaikan agen sendiri?

Karena pembelian tidak bisa dibatalkan. Tool checkout memarkir permintaan dan menampilkan dialog yang harus diklik manusia. Kalau manusia menolak, agen menerima hasil terstruktur bahwa tidak ada order yang dibuat dan cart tidak berubah. Gerbang ini menjaga jalur agen dan jalur UI; permukaan API-nya sendiri masih bisa dipanggil langsung, makanya data demo direset tiap jam dan tidak ada pembayaran sungguhan.

Apakah alamat demo ini permanen?

Tidak. Itu quick tunnel Cloudflare dengan URL acak. Kalau cloudflared di-restart atau VPS reboot, alamatnya berubah. Untuk jangka panjang saya perlu named tunnel dengan domain sendiri.

Want this working inside your team?

It starts with one conversation about your workflow.