← Kembali ke Blog

Sampling di MCP Sudah Deprecated: Migrasi Tool yang Pinjam LLM Client

Kalau MCP server-mu punya tool yang meminjam model milik client untuk meringkas, mengklasifikasi, atau menulis ulang teks, tool itu langsung rusak begitu client memakai MCP 2026-07-28. Gagalnya mendadak: ctx.session.create_message() melempar NoBackChannelError, dan pesan error-nya tidak menyebut apa penggantinya. Artikel ini bahas apa yang dihapus dari protokol, dua pengganti yang didukung, kode yang bisa langsung dipakai, dan checklist singkat untuk memilih.

Yang perlu disiapkan

  • Python 3.10 atau lebih baru
  • pip install "mcp>=1.27,<2" untuk jalur v1 yang stabil, atau jalur v2 kalau kamu siap ikut SDK terbaru (v2 GA sejak Agustus 2026)
  • fastmcp>=4 kalau server-mu pakai FastMCP
  • API key Anthropic atau OpenAI untuk jalur panggil langsung
  • Satu MCP tool yang sekarang memakai sampling, biar ada yang benar-benar dimigrasikan

Apa yang berubah

Revisi 2026-07-28 bikin MCP stateless di level protokol. Ada dua akibat untuk sampling.

Pertama, server tidak bisa lagi mendorong request ke client sambil memproses request dari client. SEP-2322 mengganti server-initiated call (roots/list, sampling/createMessage, elicitation/create) dengan Multi Round-Trip Request. Server balas dengan InputRequiredResult yang membawa resultType: "input_required", map inputRequests, dan token requestState yang opaque. Client mengumpulkan jawabannya, lalu memanggil tool yang sama lagi dengan inputResponses plus token tadi.

Kedua, sampling di-deprecate sebagai fitur (SEP-2577), bareng roots dan logging. Saran migrasi di changelog-nya lugas: "integrate directly with LLM provider APIs instead of Sampling."

Keduanya tidak menghapus kemampuan bertanya ke model milik client. Yang dihapus itu kanal push-nya, bukan pertanyaannya. Kamu tetap bisa bertanya ke model pemanggil, cuma sekarang tool-nya harus balik dulu dan biarkan client datang lagi.

Langkah 1: cek dulu kamu di revisi mana

Pastikan client-mu negosiasi versi apa sebelum ubah kode:

curl -si http://localhost:8000/mcp \
  -H 'Content-Type: application/json' \
  -H 'MCP-Protocol-Version: 2026-07-28' \
  -d '{"jsonrpc":"2.0","id":1,"method":"server/discover"}' | head -20

server/discover itu wajib di 2026-07-28 dan mengembalikan versi protokol, capability, serta identitas yang server-mu klaim. Kalau client tidak kirim header MCP-Protocol-Version, berarti kamu masih di jalur era handshake dan create_message masih jalan di sana.

Tiga gejala yang berarti sudah waktunya migrasi:

  • NoBackChannelError dari ctx.session.create_message() atau ctx.elicit()
  • MCPDeprecationWarning di panggilan ctx.info(...) pertama
  • AttributeError: 'Context' object has no attribute 'sample' di FastMCP 4, yang menghapus sample() dan sample_step() untuk semua era protokol

Jalur A: panggil provider langsung

Ini migrasi yang disarankan spesifikasi, dan untuk sebagian besar server kodenya malah lebih sedikit dari panggilan sampling yang digantikan. Simpan key di environment server, bikin client sekali di level modul, lalu generate di dalam tool.

import anthropic
from fastmcp import FastMCP

mcp = FastMCP("Summarizer")
llm = anthropic.AsyncAnthropic()  # level modul, biar koneksi dipakai ulang

@mcp.tool
async def summarize(text: str) -> str:
    """Summarize a document in two sentences."""
    response = await llm.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=512,
        system="Summarize the user's text in exactly two sentences.",
        messages=[{"role": "user", "content": text}],
    )
    return response.content[0].text

Tool-nya sekarang jalan sama untuk semua client, termasuk yang tidak pernah mengimplementasikan capability sampling. Kamu yang pilih model, kamu bisa baca token usage, dan retry, timeout, caching, serta hitung biaya jadi kode aplikasi biasa, bukan negosiasi protokol. Tool juga bisa dites tanpa client, hal yang tidak mungkin di versi sampling.

Jalur B: pinjam model pemanggil lewat round trip

Kadang yang penting justru model pemanggilnya. Client enterprise bisa mewajibkan generasi lewat gateway mereka sendiri, dengan credential dan tagihan mereka, atau memang menolak menyerahkan API key ke server-mu. Untuk kasus itu, kamu simpan pertanyaannya dan buang push-nya.

Balikin InputRequiredResult di pass pertama, lalu baca jawabannya dari ctx.input_responses saat retry:

from fastmcp import Context, FastMCP
from mcp.types import (
    CreateMessageRequest,
    CreateMessageRequestParams,
    CreateMessageResult,
    InputRequiredResult,
    SamplingMessage,
    TextContent,
)

mcp = FastMCP("Research")

@mcp.tool
async def ask_the_caller(question: str, ctx: Context) -> str | InputRequiredResult:
    """Put a question to the caller's model and report what it answered."""
    responses = ctx.input_responses
    if responses is None:
        return InputRequiredResult(
            result_type="input_required",
            input_requests={
                "answer": CreateMessageRequest(
                    method="sampling/createMessage",
                    params=CreateMessageRequestParams(
                        messages=[
                            SamplingMessage(
                                role="user",
                                content=TextContent(type="text", text=question),
                            )
                        ],
                        max_tokens=100,
                    ),
                )
            },
        )

    answer = responses["answer"]
    if isinstance(answer, CreateMessageResult) and isinstance(answer.content, TextContent):
        return answer.content.text
    return "The client returned no completion."

Ada dua batasan di jalur ini. Client harus negosiasi 2026-07-28, karena client lama tidak punya cara mengembalikan input untuk panggilan yang masih jalan. Dan tiap pertanyaan memakan satu round trip penuh, selama itu tool-nya nge-block. Kalau perlu bawa nilai melintasi beberapa putaran, taruh di requestState dan kembalikan saat retry, karena instance server mana pun bisa menangani request kedua.

Di Python SDK v2 resmi, kamu jarang menulis loop itu manual. Deklarasikan dependency pakai Resolve, biar SDK client yang menjalankan round trip-nya.

Jalur C: tetap di v1 sambil client-mu menyusul

Kebijakan feature lifecycle memberi tiap fitur yang di-deprecate waktu minimal dua belas bulan sebelum boleh dihapus, jadi sampling/createMessage masih jalan untuk client yang bicara 2025-11-25 atau lebih lama. Pin jalur stabilnya dan anggap ini tenggat, bukan tujuan:

pip install "mcp>=1.27,<2"

Pasang batas atas itu dari sekarang kalau kamu publish package yang bergantung ke mcp. Kalau tidak, instalasi berikutnya akan menarik v2 dan bikin penggunamu kaget.

Memilih jalur

Situasi Pakai
Server punya key sendiri dan tool harus sama untuk semua client Jalur A
Client mewajibkan generasi di gateway, credential, atau tagihannya sendiri Jalur B
Client stdio lama di koneksi pra-2026 Jalur C, dengan tanggal migrasi
Tool tidak butuh generasi sama sekali Hapus panggilan sampling
Kamu mau token usage, retry, dan caching di bawah kontrol sendiri Jalur A

Error yang bakal kamu temui

NoBackChannelError artinya koneksinya 2026-07-28 dan tidak ada kanal untuk server-initiated request. Pindah ke Jalur A atau B. MCPDeprecationWarning di panggilan logging itu deprecation yang sama yang kena ctx.info(); tulis ke stderr di stdio atau pakai OpenTelemetry. Kalau tool MRTR tidak pernah dapat jawaban, cek dulu revisi yang dinegosiasi, lalu pastikan client-nya tahu cara memenuhi input request sampling/createMessage.

Kesimpulan

Sampling sudah deprecated, dan kanal tempat dia berjalan sudah dihapus. Dua pengganti menutupi pekerjaannya: panggil provider langsung kalau server-mu punya key sendiri (Jalur A, default), atau balikin InputRequiredResult kalau meminjam model pemanggil memang syaratnya (Jalur B). Kalau masih jalan di v1 untuk client lama, pin mcp>=1.27,<2 dan kasih tanggal untuk migrasi.

Langkah lanjut: grep -rn "create_message\|ctx.sample(\|ctx.elicit(" your-servers/ lalu putuskan per tool. Untuk yang masuk Jalur A, tambahkan test yang memanggil tool tanpa client, karena itu justru mode kegagalan yang dulu bisa lolos tanpa ketahuan.

Referensi

Butuh Bantuan Implementasi?

Saya membantu tim mendesain dan membangun infrastruktur cloud scalable, pipeline DevOps, dan sistem production-grade.

Konsultasi Gratis