Belajar MCP - Advanced SDK & Custom Transport
Series/Belajar MCP/Episode 18
Episode 18 of 23

Belajar MCP - Advanced SDK & Custom Transport

Episode 18 membedah bagian dalam SDK MCP: membangun custom transport dengan WebSocket dan gRPC, framing JSON-RPC, notification batching, protocol logging, hingga kode error JSON-RPC dan ISO-JSON-RPC.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Pendahuluan

Di episode 17 kalian belajar negotiation versi antara era modern 2026-07-28 dan era legacy 2025-11-25, lengkap dengan strategi dual-era. Sekarang kita masuk ke bagian paling dalam SDK MCP: bukan lagi sekadar memakai transport bawaan, melainkan membangun transport custom sendiri — WebSocket dan gRPC — sekaligus memahami framing JSON-RPC, notification batching, protocol logging, dan kode error.

Transport: Titik Pertukaran Pesan

Transport adalah abstraksi yang membungkus bagaimana pesan JSON-RPC berpindah antara client dan server. SDK menyediakan tiga transport bawaan: stdio untuk proses lokal, Streamable HTTP untuk remote, dan SSE yang kini legacy. Semuanya berbagi kontrak yang sama — interface dengan method untuk memulai koneksi, mengirim pesan, dan menutup koneksi. Karena kontraknya standar, kita bisa menggantinya dengan implementasi apa pun: WebSocket, gRPC, MQTT, bahkan message queue.

transport-interface.ts
interface Transport {
  onmessage: ((message: JSONRPCMessage) => void) | undefined
  onclose: (() => void) | undefined
  onerror: ((error: Error) => void) | undefined
  start(): Promise<void>
  send(message: JSONRPCMessage): Promise<void>
  close(): Promise<void>
}

Prinsipnya sederhana: selama sebuah class mengimplementasikan kontrak ini, server atau client MCP tidak peduli apakah pesan lewat TCP, Unix socket, atau pipa proses lain.

Membangun Transport WebSocket

Kapan WebSocket lebih baik daripada Streamable HTTP? Ketika kalian butuh komunikasi dua arah yang persisten dengan latensi rendah — misalnya server MCP yang menumpang di infrastruktur real-time. Implementasinya berarti membuat class yang mengonversi event WebSocket menjadi pemanggilan onmessage.

ws-transport.ts
import { WebSocketServer, WebSocket } from 'ws'
 
class WsServerTransport {
  onmessage: ((message: unknown) => void) | undefined
  onclose: (() => void) | undefined
 
  constructor(private server: WebSocketServer) {}
 
  start(): Promise<void> {
    this.server.on('connection', (socket) => {
      socket.on('message', (raw) => {
        this.onmessage?.(JSON.parse(raw.toString()))
      })
      socket.on('close', () => this.onclose?.())
    })
    return Promise.resolve()
  }
 
  async send(message: unknown): Promise<void> {
    const payload = JSON.stringify(message)
    for (const client of this.server.clients) {
      if (client.readyState === WebSocket.OPEN) {
        client.send(payload)
      }
    }
  }
 
  close(): Promise<void> {
    return new Promise((resolve) => this.server.close(resolve))
  }
}

Perhatikan: transport ini tidak memahami isi pesan — ia hanya membungkus JSON menjadi frame lalu menyerahkan parsing ke SDK. Transport adalah jembatan murni, bukan logika bisnis. Kalian bisa menguji transport ini dengan npx @modelcontextprotocol/inspector lewat endpoint WebSocket, lalu list dan call tools seperti biasa.

gRPC dan Transport Non-HTTP Lainnya

WebSocket hanyalah satu pilihan. Di lingkungan enterprise, gRPC sering dipilih karena streaming bidirectional, schema protobuf, dan HTTP/2. Transport gRPC memetakan setiap pesan JSON-RPC ke sebuah method streaming — misalnya satu RPC Exchange yang menerima stream pesan dan mengembalikan stream respons, dengan pesan JsonRpcMessage yang membungkus JSON di dalam protobuf.

Keunggulan gRPC adalah backpressure dan multiplexing bawaan; kelemahannya, ekosistem MCP default berbahasa JSON-RPC sehingga butuh adapter di tiap sisi. Prinsipnya tetap sama untuk transport lain: pertahankan kontrak Transport, sisanya pola adapter.

Framing Pesan JSON-RPC

Setiap pesan adalah JSON-RPC 2.0 dengan tiga bentuk: request yang memakai id, response yang mencocokkan id request, dan notification tanpa id yang tidak butuh jawaban. Framing berarti menentukan batas antar-pesan di dalam satu stream — untuk protokol berbasis teks seperti WebSocket, frame bawaan yang menanganinya; untuk stdio, pesan dipisahkan dengan header panjang konten ala HTTP.

jsonrpc-request.json
{
  "jsonrpc": "2.0",
  "id": "req-001",
  "method": "tools/call",
  "params": {
    "name": "fetch_url",
    "arguments": {
      "url": "https://example.com"
    }
  }
}

Kode req-001 memungkinkan client mencocokkan respons ke request tertentu meski banyak request berjalan bersamaan (multiplexing). Itulah mengapa id wajib unik per sesi — dan di era MRTR, dipakai berulang dalam satu percakapan multi-langkah tapi tetap harus bisa dibedakan.

Notification Batching

Notification adalah pesan JSON-RPC tanpa id — client dan server tidak menunggu jawabannya. Contohnya notifications/cancelled, notifications/progress, dan notifications/resources/updated.

notification-progress.json
{
  "jsonrpc": "2.0",
  "method": "notifications/progress",
  "params": {
    "progressToken": "prog-7",
    "progress": 50,
    "total": 100
  }
}

Batching bukan menumpuk JSON dalam satu array — JSON-RPC adalah satu pesan per frame. Yang dibatch adalah pengiriman: beberapa notification yang tidak menunggu balasan digabung ke satu write cycle transport, misalnya saat progress naik berkali-kali dalam satu detik. Untuk request yang menunggu jawaban, kurangi jumlahnya dengan MRTR dan pagination, bukan batching.

Kode Error: JSON-RPC vs ISO-JSON-RPC

Saat ada yang gagal, SDK mengembalikan objek error berisi code, message, dan data opsional. Sebelum spec 2026-07-28, kode error mengikuti JSON-RPC klasik:

KodeNamaArti
-32700Parse errorJSON tidak bisa di-parse
-32600Invalid RequestBentuk request tidak valid
-32601Method not foundMethod tidak dikenal server
-32602Invalid paramsParameter gagal validasi
-32603Internal errorError internal tak terduga
-32000 s.d. -32099Server errorRentang bebas untuk implementasi

Spec 2026-07-28 mengadopsi ISO-JSON-RPC, yang melembagakan struktur error: envelope tetap sama, tapi data kini memakai skema terstruktur yang konsisten lintas implementasi, memisahkan error level protokol dari level aplikasi. Kabar baiknya, lima kode klasik di atas tetap berlaku — kode lama kalian tidak rusak, hanya mendapat struktur tambahan.

iso-error.json
{
  "jsonrpc": "2.0",
  "id": "req-001",
  "error": {
    "code": -32602,
    "message": "Invalid params",
    "data": {
      "type": "validation_error",
      "issues": ["arguments.url harus berupa URL valid"]
    }
  }
}

Protocol Logging

Saat debugging, pesan kesalahan saja tidak cukup — kalian butuh rekaman semua yang masuk dan keluar. Pola termudah adalah membungkus transport yang ada dengan decorator yang mencatat setiap pesan sebelum diteruskan.

logging-transport.ts
class LoggingTransport {
  onmessage: ((message: unknown) => void) | undefined
 
  constructor(
    private inner: Transport,
    private sink: (direction: string, message: unknown) => void
  ) {}
 
  async send(message: unknown): Promise<void> {
    this.sink('SEND', message)
    await this.inner.send(message)
  }
 
  async start(): Promise<void> {
    this.inner.onmessage = (m) => {
      this.sink('RECV', m)
      this.onmessage?.(m)
    }
    return this.inner.start()
  }
}

Terapkan di kedua sisi, kirim ke log terstruktur, dan kalian punya jejak protokol lengkap — pasangan request dan respons, notification, plus timing. Jangan log token atau data sensitif: cukup metadata dan versi ringkas pesan, terutama di produksi.

Penutup

Episode 18 membuka bagian dalam SDK: kontrak Transport yang bisa diganti dengan WebSocket atau gRPC, framing JSON-RPC yang menentukan batas antar-pesan, notification batching untuk memangkas round-trips, kode error JSON-RPC dan ISO-JSON-RPC, serta protocol logging untuk debugging.

Inti yang harus dibawa pulang:

  • Kontrak transport menentukan semuanya — selama class memenuhi kontrak Transport, MCP tidak peduli di atas apa ia berjalan.
  • WebSocket dan gRPC adalah pilihan custom yang populer; masing-masing butuh adapter karena default MCP berbahasa JSON-RPC.
  • Request memakai id, response mencocokkan id, dan notification tanpa id tidak butuh jawaban — inilah dasar multiplexing.
  • Batching berlaku untuk notification dan write cycle, bukan untuk menumpuk request yang menunggu balasan.
  • Kode error JSON-RPC klasik tetap berlaku, dilengkapi struktur terstandardisasi dari ISO-JSON-RPC.

Di episode 19 berikutnya kita berburu performa: optimasi payload, batching, caching resources, connection pooling, dan cara menskalakan MCP dari satu host menuju many-to-many. Sampai jumpa!