Ada jurang antara developer yang bisa membangun MCP server yang bekerja dan developer yang membangun MCP server yang benar-benar production-ready.
Yang pertama bisa menggunakan @mcp.tool() dengan benar dan mengeksekusi tools sederhana. Yang kedua memahami bagaimana sampling memungkinkan server memanggil model AI tanpa memegang API key sendiri, bagaimana progress notifications meningkatkan pengalaman pengguna untuk operasi panjang, bagaimana roots menyediakan security boundary yang tepat untuk akses file, dan bagaimana memilih transport yang tepat untuk deployment yang tepat.
Panduan ini mengisi jurang tersebut. Sebelum melanjutkan, pastikan kamu sudah familiar dengan MCP basics — tools, resources, prompts, dan ClientSession. Kalau belum, baca dulu artikel MCP: Cara Menghubungkan Claude ke Tools dan Data Eksternal.

Bab 1 — Sampling: Server yang Bisa Memanggil Model AI
Apa yang Membuat Sampling Berbeda
Dalam interaksi MCP standar, alurnya satu arah: client bertanya, server menjawab. Sampling membalik ini — server yang menginisiasi permintaan ke model AI melalui client.
Ini bukan sekadar fitur teknis. Ini pergeseran arsitektural yang memungkinkan server memiliki “kecerdasan” tanpa harus menyimpan API key sendiri, mengelola rate limits, atau menanggung biaya inference.
Tanpa sampling, server perlu API key, mengelola billing sendiri, dan bergantung pada model AI tertentu. Dengan sampling, semua kompleksitas itu tetap di sisi client. Server hanya mengirim permintaan “tolong hasilkan teks tentang X” dan menerima hasilnya. Biaya, pemilihan model, dan compliance tetap di tangan client.
Alur Sampling Langkah per Langkah
Berikut yang terjadi ketika tool memanggil sampling:
- Client memanggil tool di server
- Server memulai eksekusi, memutuskan butuh LLM
- Server mengirim sampling request ke client: “Hasilkan ringkasan dokumen ini”
- Client me-review request, opsional meminta approval user, memilih model
- Client meneruskan ke model AI menggunakan API key-nya sendiri
- Model menghasilkan output
- Client mengembalikan hasil ke server
- Server menggunakan hasil dalam eksekusi tool dan mengirim response final
Yang penting dipahami: client selalu memegang kontrol penuh. Server tidak bisa memaksa model tertentu, tidak bisa bypass user approval, dan tidak bisa mengakses hasil di luar yang client izinkan. Ini desain yang disengaja untuk keamanan.
Implementasi Sampling di Server
# sampling_server.py
from mcp.server.fastmcp import FastMCP, Context
from mcp.server.session import ServerSession
from mcp.types import SamplingMessage, TextContent, ModelPreferences, ModelHint
mcp = FastMCP('Sampling Demo Server')
@mcp.tool()
async def analyze_and_summarize(
content: str,
ctx: Context[ServerSession, None]
) -> str:
"""
Analisis konten dan hasilkan ringkasan menggunakan LLM client.
Server mendelegasikan pembuatan ringkasan ke model yang dimiliki client.
"""
result = await ctx.session.create_message(
messages=[
SamplingMessage(
role="user",
content=TextContent(
type="text",
text=f"Buat ringkasan 3 poin dari teks berikut:\n\n{content}"
)
)
],
max_tokens=500,
# Preferensi model — client yang memutuskan final
model_preferences=ModelPreferences(
hints=[ModelHint(name='claude-sonnet')],
speed_priority=0.3,
intelligence_priority=0.8
),
system_prompt="Kamu adalah analis profesional. Berikan ringkasan yang ringkas dan akurat.",
include_context="none"
)
if result.content.type == 'text':
return f'Ringkasan (dari {result.model}):\n{result.content.text}'
return str(result.content)
Implementasi Sampling Handler di Client
Agar sampling berfungsi, client harus mengimplementasikan sampling callback — fungsi yang dipanggil ketika server mengirim sampling request:
# sampling_client.py
import asyncio
import anthropic
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from mcp.types import CreateMessageRequestParams, CreateMessageResult, TextContent
anthropic_client = anthropic.Anthropic()
async def handle_sampling_request(
params: CreateMessageRequestParams
) -> CreateMessageResult:
"""
Callback yang dipanggil saat server mengirim sampling request.
Di sinilah client mengeksekusi actual LLM call.
"""
# Konversi MCP messages ke format Anthropic
anthropic_messages = []
for msg in params.messages:
if msg.content.type == 'text':
anthropic_messages.append({
'role': msg.role,
'content': msg.content.text
})
# Client yang memutuskan model final, bukan server
model = 'claude-sonnet-4-6'
if params.model_preferences and params.model_preferences.hints:
hint = params.model_preferences.hints[0].name
if hint and 'haiku' in hint.lower():
model = 'claude-haiku-4-5'
response = anthropic_client.messages.create(
model=model,
max_tokens=params.max_tokens or 1024,
system=params.system_prompt if params.system_prompt else None,
messages=anthropic_messages
)
return CreateMessageResult(
role='assistant',
content=TextContent(type='text', text=response.content[0].text),
model=model,
stop_reason='end_turn'
)
async def run_with_sampling():
server_params = StdioServerParameters(
command='python', args=['sampling_server.py']
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(
read, write,
sampling_callback=handle_sampling_request # ← Kunci untuk sampling
) as session:
await session.initialize()
result = await session.call_tool(
'analyze_and_summarize',
{'content': 'Laporan kuartal menunjukkan pertumbuhan 15% di Q3...'}
)
print(result.content[0].text)
asyncio.run(run_with_sampling())
Kapan Pakai Sampling vs Alternatif
Gunakan sampling ketika server butuh LLM tapi tidak mau kelola API key atau biaya sendiri, dan butuh human-in-the-loop untuk approval.
Gunakan server langsung panggil API ketika kamu ingin arsitektur yang lebih sederhana dan server bersedia menanggung biaya API-nya sendiri.
Gunakan pre-trained logic ketika task cukup diselesaikan dengan rule atau pattern matching — deterministik, cepat, dan tidak butuh LLM sama sekali.
Catatan penting: Per April 2026, Claude Desktop belum mendukung sampling. Jika server butuh sampling untuk fungsi inti, pastikan client yang digunakan sudah mengimplementasikan sampling callback. Selalu handle kasus di mana sampling tidak tersedia.
Bab 2 — Progress Notifications: Real-Time Feedback untuk Operasi Panjang
Mengapa Notifikasi Penting
Bayangkan tool MCP yang memproses 1.000 file. Tanpa notifikasi, client hanya menunggu tanpa feedback — apakah proses masih berjalan? Ada error? Sudah sampai mana?
MCP menyediakan dua jenis notifikasi untuk memecahkan masalah ini. Logging adalah pesan informatif tentang apa yang sedang terjadi (debug, info, warning, error). Progress adalah update kuantitatif tentang kemajuan operasi (angka current dan total). Keduanya dikirim melalui Context object yang diinjeksi ke dalam tool function.
Implementasi Logging dan Progress
# notification_server.py
from mcp.server.fastmcp import FastMCP, Context
import asyncio
mcp = FastMCP('Notification Demo')
@mcp.tool()
async def batch_process_files(
file_paths: list[str],
ctx: Context
) -> str:
"""Proses sekumpulan file dengan progress reporting real-time."""
total = len(file_paths)
processed = 0
errors = 0
await ctx.info(f'Memulai batch processing {total} file')
for i, file_path in enumerate(file_paths):
# Kirim progress update ke client
await ctx.report_progress(i, total)
await ctx.debug(f'Memproses: {file_path}')
try:
await asyncio.sleep(0.05) # Simulasi pemrosesan
processed += 1
await ctx.info(f'File {file_path} berhasil diproses')
except Exception as e:
errors += 1
await ctx.error(f'Gagal memproses {file_path}: {e}')
# 100% saat selesai
await ctx.report_progress(total, total)
await ctx.info(f'Batch selesai: {processed} berhasil, {errors} gagal')
return f'Batch processing selesai: {processed}/{total} file berhasil'
@mcp.tool()
async def analyze_large_dataset(
dataset_name: str,
num_records: int,
ctx: Context
) -> dict:
"""Analisis dataset besar dengan progress per fase."""
PHASES = [
('Memuat data', 0.1),
('Validasi schema', 0.15),
('Preprocessing', 0.25),
('Analisis statistik', 0.30),
('Deteksi anomali', 0.15),
('Generasi laporan', 0.05),
]
results = {}
current_progress = 0
await ctx.info(f'Memulai analisis {dataset_name} ({num_records:,} record)')
for phase_name, phase_weight in PHASES:
await ctx.info(f'Phase: {phase_name}...')
steps = 10
for step in range(steps):
await asyncio.sleep(0.01)
step_progress = current_progress + (phase_weight * step / steps * num_records)
await ctx.report_progress(int(step_progress), num_records)
current_progress += phase_weight * num_records
results[phase_name] = 'Selesai'
await ctx.debug(f'Phase {phase_name} selesai')
await ctx.report_progress(num_records, num_records) # 100%
await ctx.info('Analisis dataset selesai!')
return {'status': 'success', 'phases': results, 'total_records': num_records}
Semua Method Context yang Tersedia
ctx.debug(msg) — informasi sangat teknis untuk debugging. ctx.info(msg) — status umum dan progress milestone. ctx.warning(msg) — kondisi tidak ideal tapi tidak fatal. ctx.error(msg) — error yang tidak menghentikan eksekusi. ctx.critical(msg) — kondisi sangat serius, system-level failures. ctx.report_progress(current, total) — update progress kuantitatif.
Panduan Logging yang Baik
Gunakan level yang tepat — jangan semua ctx.info(). Report progress di titik-titik bermakna, bukan setiap iterasi mikroskopis. Sertakan konteks yang cukup: bukan “Memproses…” tapi “Memproses file ke-15 dari 100: report_q3.pdf”. Log error dengan informasi yang cukup untuk debugging, tapi hindari mengekspos data sensitif.
Di sisi client, kamu bisa filter level minimum yang diterima: await session.set_logging_level('warning') artinya hanya warning ke atas yang diteruskan ke klienmu.
Bab 3 — Roots: Security Boundary untuk Akses File
Masalah yang Roots Pecahkan
Bayangkan MCP server yang perlu mengakses file. Tanpa Roots, ada dua extreme yang buruk: server bisa mengakses file manapun di sistem (risiko keamanan besar), atau path file di-hardcode dalam kode server (tidak fleksibel dan tidak user-friendly).
Roots memberikan jalan tengah yang elegan: client memberi tahu server direktori mana yang boleh diakses, dan server menghormati batasan tersebut. Client mengkomunikasikan roots saat inisialisasi. Server HARUS menghormati batasan ini dan tidak boleh mengakses lokasi di luar yang sudah di-granted.
Implementasi Roots di Server
# roots_server.py
from mcp.server.fastmcp import FastMCP, Context
from pathlib import Path
import json
mcp = FastMCP('File Access Server')
@mcp.tool()
async def list_accessible_files(
extension_filter: str = '',
ctx: Context
) -> str:
"""
List semua file yang accessible berdasarkan roots yang dikonfigurasi client.
Hanya membaca direktori yang sudah di-grant, tidak lebih.
"""
roots_result = await ctx.session.list_roots()
if not roots_result.roots:
return 'Tidak ada roots yang dikonfigurasi. Client belum memberikan akses direktori.'
all_files = []
for root in roots_result.roots:
root_uri = root.uri
root_name = root.name or root_uri
await ctx.info(f'Scanning root: {root_name}')
if root_uri.startswith('file://'):
local_path = root_uri.replace('file://', '')
dir_path = Path(local_path)
if dir_path.exists() and dir_path.is_dir():
for file_path in dir_path.iterdir():
if file_path.is_file():
if not extension_filter or file_path.suffix == extension_filter:
all_files.append({
'path': str(file_path),
'name': file_path.name,
'size': file_path.stat().st_size,
'root': root_name
})
else:
await ctx.warning(f'Path tidak ditemukan: {local_path}')
return json.dumps({'total': len(all_files), 'files': all_files})
@mcp.tool()
async def read_file_safe(filename: str, ctx: Context) -> str:
"""
Baca file HANYA jika ada dalam salah satu root yang diotorisasi.
Mencegah path traversal dan akses ke lokasi yang tidak diizinkan.
"""
roots_result = await ctx.session.list_roots()
target_path = Path(filename).resolve() # Normalize path
# Validasi: pastikan file ada dalam salah satu root
is_authorized = False
for root in roots_result.roots:
if root.uri.startswith('file://'):
root_path = Path(root.uri.replace('file://', '')).resolve()
try:
target_path.relative_to(root_path) # Akan raise ValueError jika di luar root
is_authorized = True
break
except ValueError:
continue
if not is_authorized:
await ctx.error(f'AKSES DITOLAK: {filename} tidak dalam root yang diotorisasi')
return json.dumps({'error': 'Akses ditolak — file di luar root yang diizinkan'})
try:
with open(target_path, 'r', encoding='utf-8') as f:
content = f.read()
await ctx.info(f'File dibaca: {filename}')
return json.dumps({'filename': filename, 'content': content})
except Exception as e:
await ctx.error(f'Gagal membaca {filename}: {e}')
return json.dumps({'error': str(e)})
Konfigurasi Roots di Client
# roots_client.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from mcp.types import Root
async def run_with_roots():
server_params = StdioServerParameters(command='python', args=['roots_server.py'])
async with stdio_client(server_params) as (read, write):
async with ClientSession(
read, write,
roots=[
Root(uri='file:///home/user/documents', name='My Documents'),
Root(uri='file:///home/user/projects', name='Projects'),
# TIDAK memberikan akses ke /etc atau direktori sensitif
]
) as session:
await session.initialize()
# List file yang accessible
result = await session.call_tool('list_accessible_files', {})
print(result.content[0].text)
# Coba baca file di luar root — akan ditolak
result3 = await session.call_tool(
'read_file_safe',
{'filename': '/etc/passwd'} # Di luar root!
)
print(result3.content[0].text) # {'error': 'Akses ditolak...'}
asyncio.run(run_with_roots())
Roots juga bisa diperbarui secara dinamis tanpa restart server menggunakan await session.send_roots_list_changed().
Penting: Meski roots memberikan security boundary, tool tetap harus memvalidasi bahwa path yang diberikan user benar-benar ada dalam root yang diizinkan. Selalu gunakan Path.resolve() dan cek relative_to() seperti contoh di atas — jangan percaya path dari user input tanpa validasi.
Bab 4 — JSON-RPC: Protokol di Balik Semua Komunikasi MCP
FastMCP menyembunyikan semua detail ini di balik decorator Python yang elegan. Tapi memahami format JSON-RPC penting untuk debugging dan membuat custom implementation.
Tiga Jenis Pesan
Request memiliki field id dan selalu mengharapkan respons. Digunakan untuk tool calls, resource reads, dan sampling.
Response memiliki field id yang cocok dengan request yang ditanggapi. Berisi result untuk sukses atau error untuk kegagalan.
Notification tidak memiliki id dan tidak mengharapkan respons sama sekali — fire-and-forget. Digunakan untuk progress updates, log messages, dan notifikasi perubahan state.
// Request: Client memanggil tool
{
"jsonrpc": "2.0",
"id": "req-001",
"method": "tools/call",
"params": {
"name": "read_file",
"arguments": {"filename": "report.txt"}
}
}
// Response sukses
{
"jsonrpc": "2.0",
"id": "req-001",
"result": {
"content": [{"type": "text", "text": "Isi file..."}],
"isError": false
}
}
// Notification progress (tidak ada id, tidak ada respons)
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {
"progressToken": "proc-001",
"progress": 45,
"total": 100
}
}
Initialization Handshake: Wajib Sebelum Apapun
Setiap koneksi MCP dimulai dengan handshake tiga langkah yang wajib:
- Client kirim
initializerequest — menyatakan versi protokol dan kemampuan client (termasuk apakah support sampling, roots, dll) - Server kirim response — menyatakan kemampuan server (tools, resources, prompts, logging)
- Client kirim
initializednotification — fire-and-forget, menandakan handshake selesai
Hanya setelah langkah ketiga selesai, komunikasi normal bisa dimulai. Jika server mencoba mengirim request sebelum ini — invalid.
Error Codes yang Perlu Diingat
-32601 adalah Method not found — method yang diminta tidak ada di server. -32602 adalah Invalid params — parameter tidak valid atau tidak lengkap. -32603 adalah Internal error — error internal server yang tidak diantisipasi. -32700 adalah Parse error — request JSON tidak bisa di-parse.
Komunikasi Bidirectional
Aspek MCP yang paling sering disalahpahami: komunikasi tidak hanya satu arah. Server juga bisa mengirim request ke client. Inilah yang memungkinkan sampling (server request ke client untuk LLM call) dan list_roots (server meminta daftar roots dari client).
Bab 5 — STDIO Transport: Untuk Local Deployment
Cara Kerja STDIO
STDIO adalah transport paling sederhana: client meluncurkan server sebagai subprocess, lalu mengirim JSON-RPC messages melalui stdin server dan menerima responses melalui stdout. Tidak perlu setup network, port, atau TLS. Ini adalah default transport untuk Claude Desktop dan semua local deployment.
Aturan yang Wajib Dipatuhi
Jangan pernah print() ke stdout di STDIO server — ini akan corrupt JSON-RPC stream dan merusak semua komunikasi. Stdout reserved exclusively untuk JSON-RPC messages. Untuk logging server, gunakan stderr atau ctx.info():
# stdio_server.py
from mcp.server.fastmcp import FastMCP
import sys
import logging
# KRITIS: logging ke stderr, BUKAN stdout
logging.basicConfig(
stream=sys.stderr, # ← PENTING
level=logging.DEBUG,
format='%(asctime)s [%(levelname)s] %(message)s'
)
logger = logging.getLogger(__name__)
mcp = FastMCP('STDIO Demo Server')
@mcp.tool()
def calculate(expression: str) -> str:
"""Evaluasi ekspresi matematika sederhana."""
logger.debug(f'Evaluating: {expression}') # Aman — ke stderr
try:
result = eval(expression, {'__builtins__': {}},
{'abs': abs, 'round': round, 'min': min, 'max': max})
return str(result)
except Exception as e:
logger.error(f'Error: {e}')
return f'Error: {e}'
if __name__ == '__main__':
mcp.run(transport='stdio')
Konfigurasi di Claude Desktop
{
"mcpServers": {
"calculator": {
"command": "python",
"args": ["/absolute/path/to/stdio_server.py"],
"env": {
"PYTHONPATH": "/path/to/project",
"DATABASE_URL": "postgresql://localhost/mydb"
}
}
}
}
Selalu gunakan absolute path untuk args — path relatif tidak reliable di konteks Claude Desktop.
Bab 6 — StreamableHTTP Transport: Untuk Production
Mengapa STDIO Tidak Cukup untuk Production
STDIO sangat baik untuk local deployment, tapi tidak bisa di-deploy ke cloud atau diakses dari remote. Streamable HTTP hadir sebagai solusi untuk production: satu endpoint untuk semua komunikasi, support stateless dan stateful, dan kompatibel dengan infrastruktur HTTP standar.
HTTP+SSE yang lama (sebelum Maret 2025) membutuhkan dua endpoint terpisah. Streamable HTTP menyederhanakan ini menjadi satu endpoint /mcp — ini yang harus digunakan untuk semua remote deployment baru.
Implementasi Production-Ready
# streamable_http_server.py
from mcp.server.fastmcp import FastMCP
# Konfigurasi untuk production: stateless + JSON response
# Ideal untuk horizontal scaling dengan load balancer
mcp = FastMCP(
'Production Server',
stateless_http=True, # Tidak ada session state
json_response=True # Respons JSON langsung, tidak ada SSE streaming
)
@mcp.tool()
async def get_server_time() -> str:
"""Kembalikan waktu server saat ini."""
from datetime import datetime
return datetime.now().isoformat()
@mcp.tool()
async def search_database(query: str, limit: int = 10) -> list:
"""Cari database berdasarkan query."""
return [{'id': i, 'result': f'Result {i} for {query}'} for i in range(limit)]
if __name__ == '__main__':
mcp.run(
transport='streamable-http',
host='0.0.0.0',
port=8000
)
# Accessible di: http://localhost:8000/mcp
Stateless vs Stateful: Mana yang Harus Dipilih?
Stateless (stateless_http=True + json_response=True) adalah rekomendasi untuk production. Setiap request independen, tidak ada session state yang perlu disimpan di memory. Ini memungkinkan horizontal scaling — kamu bisa jalankan banyak instance di belakang load balancer tanpa sticky sessions. Cocok untuk tool-based APIs yang tidak butuh sampling atau server-initiated messages.
Stateful adalah default, menyimpan session state. Diperlukan jika server butuh sampling, karena sampling memerlukan koneksi persisten antara client dan server untuk callback. Tidak bisa di-scale horizontal tanpa sticky session di load balancer.
Multi-Server dengan FastAPI
# multi_server_app.py
import contextlib
from fastapi import FastAPI
from mcp.server.fastmcp import FastMCP
# Server 1
data_mcp = FastMCP('Data API', stateless_http=True, json_response=True)
@data_mcp.tool()
def get_user(user_id: int) -> dict:
return {'id': user_id, 'name': f'User {user_id}'}
# Server 2
analytics_mcp = FastMCP('Analytics API', stateless_http=True, json_response=True)
@analytics_mcp.tool()
def get_stats() -> dict:
return {'requests': 1000, 'errors': 5}
# Mount keduanya ke satu FastAPI app
@contextlib.asynccontextmanager
async def lifespan(app: FastAPI):
async with data_mcp.lifespan(), analytics_mcp.lifespan():
yield
app = FastAPI(lifespan=lifespan)
app.mount('/data', data_mcp.streamable_http_app())
app.mount('/analytics', analytics_mcp.streamable_http_app())
# Accessible di:
# http://localhost:8000/data/mcp
# http://localhost:8000/analytics/mcp
Kapan Pilih STDIO vs StreamableHTTP
Gunakan STDIO ketika: local deployment (Claude Desktop, VS Code, local scripts), single user single machine, development dan testing, butuh latency terendah, dan tidak butuh authentication.
Gunakan Streamable HTTP ketika: remote deployment ke cloud, multi-user, production service yang perlu scale, butuh authentication, dan perlu akses dari banyak client berbeda secara bersamaan.
Ringkasan: Decision Tree untuk MCP Advanced Features
Berikut panduan cepat untuk memutuskan kapan menggunakan masing-masing fitur.
Butuh server yang punya “kecerdasan” tanpa menyimpan API key? → Gunakan Sampling dengan ctx.session.create_message().
Tool membutuhkan waktu lama dan user perlu tahu progresnya? → Gunakan Progress Notifications dengan ctx.report_progress() dan ctx.info().
Server perlu akses file tapi harus aman? → Gunakan Roots dengan ctx.session.list_roots() dan validasi Path.relative_to().
Deployment untuk satu user di komputer lokal? → Gunakan STDIO transport. Ingat: jangan print() ke stdout.
Deployment untuk banyak user di cloud? → Gunakan StreamableHTTP dengan stateless_http=True dan json_response=True untuk scalability terbaik.
Ada error komunikasi yang aneh? → Debug dengan melihat JSON-RPC format secara langsung. Perhatikan apakah pesan adalah Request (ada id), Response (ada id yang cocok), atau Notification (tidak ada id).
Pertanyaan yang Sering Ditanyakan
Apa bedanya sampling dengan server langsung memanggil Claude API? Dengan sampling, server tidak memegang API key dan tidak menanggung biaya inference — semua itu ada di sisi client. Server hanya “meminta” client untuk memanggil model. Dengan memanggil API langsung, server punya kontrol penuh tapi harus mengelola API key, rate limits, dan billing sendiri.
Kapan harus menggunakan stateless vs stateful di StreamableHTTP? Gunakan stateless untuk hampir semua production deployment — lebih mudah di-scale dan lebih sederhana. Gunakan stateful hanya jika server butuh sampling (karena sampling memerlukan koneksi persisten) atau jika ada kebutuhan session state yang tidak bisa dihindari.
Mengapa server STDIO saya tidak berfungsi setelah saya tambahkan print()? Ini adalah kesalahan paling umum di STDIO transport. print() di Python secara default menulis ke stdout, yang dalam STDIO transport exclusive untuk JSON-RPC messages. Tambahan apapun di stdout akan corrupt stream dan membuat client tidak bisa parse response server. Selalu gunakan logging ke stderr atau ctx.info() untuk output informatif.
Apakah roots cukup untuk mengamankan akses file? Roots menyediakan security boundary yang penting, tapi bukan satu-satunya lapisan keamanan. Tetap perlu validasi manual dengan Path.resolve() dan Path.relative_to() untuk mencegah path traversal attacks. Roots hanya memberi tahu server direktori mana yang diizinkan — server tetap bertanggung jawab untuk memvalidasi setiap request file secara individual.
Bagaimana cara debug masalah di MCP server? Mulai dengan MCP Inspector: npx @modelcontextprotocol/inspector python server.py. Tab Notifications di Inspector menampilkan semua pesan JSON-RPC yang dikirim dan diterima — ini yang paling membantu untuk debugging komunikasi. Untuk STDIO server, check apakah ada output yang tidak sengaja masuk ke stdout.
Lanjutkan Perjalanan Belajar Claude Anda
Artikel ini adalah bagian dari seri Belajar Claude Gratis — panduan berbahasa Indonesia yang membahas ekosistem Claude untuk berbagai profil pembaca.
Kembali ke peta besar: Belajar Claude Gratis: Panduan Lengkap dari Nol hingga Mahir
Artikel cluster lainnya:
-
- Claude Code 01: Panduan Lengkap untuk Developer Indonesia
- Claude Code 02: Panduan Pemula untuk Semua Profesi
- Claude 03 Cowork: Panduan Lengkap Otomasi Pekerjaan untuk Profesional
- Claude 04-AI Fluency: Cara Menggunakan Claude AI dengan Benar, Efektif, dan Etis
- Claude 05 API: Panduan Lengkap Membangun Aplikasi AI dengan Python untuk Developer
- Claude 06, Mengenal Model Context Protocol (MCP): Cara Menghubungkan Claude ke Tools dan Data Eksternal
- Claude 7, AI Fluency untuk Pendidik: Panduan Lengkap Menggunakan Claude AI dalam Pengajaran dan Kurikulum
- Claude 08, AI Fluency untuk Mahasiswa: Panduan Lengkap Claude untuk Belajar, Menulis, dan Persiapan Karier
- Claude 09, MCP Advanced Topics: Panduan Teknis Sampling, Notifications, Roots, dan Transport untuk Developer
- Claude 10 Amazon Bedrock: Panduan Lengkap dari Setup hingga Production AI System
- Claude 11 dengan Google Cloud Vertex AI: Panduan Lengkap untuk Developer Indonesia
- Claude 12 Teaching AI Fluency: Panduan Lengkap Mengajarkan dan Menilai Kemampuan AI di Kelas
- Caude 13, AI Fluency untuk Organisasi Nonprofit: Panduan Praktis Menggunakan Claude AI untuk Dampak Misi yang Lebih Besa
- Claude 14 Agent Skills: Panduan Lengkap Membangun dan Mendistribusikan Skills Reusable
- Claude 15, Subagents Claude Code: Panduan Lengkap Mendelegasikan Task dan Mengelola Context Window
- Claude 16, AI Capabilities & Limitations: Model Mental Lengkap untuk Berkolaborasi dengan Claude Secara Cerdas
- Claude 17 Code: Panduan Lengkap Developer Indonesia — Dari Instalasi hingga CI/CD Otomatis
- Claude 18, Belajar Claude API: Panduan Lengkap Membangun Aplikasi AI dengan Python, Dari Hello World hingga Productio
- Claude 19: Cara Pakai Claude dari Nol untuk Semua Profesi — Panduan AI Fluency Lengkap
- Claude 20 untuk Pekerjaan dan Tim: Panduan Lengkap Implementasi Claude di Organisasi Anda
