MCP untuk Tim Django/FastAPI: Server Pertamamu dengan Python SDK v2

Python SDK MCP v2.0.0 rilis 28 Juli 2026 dan mematahkan semua tutorial lama. Ini panduan membangun MCP server pertama untuk tim Django/FastAPI — dengan tabel migrasi v1→v2.

Kalau kamu pernah membaca tutorial MCP Python dalam bahasa apa pun sebelum 28 Juli 2026, ada kabar buruk: kode-nya tidak jalan lagi.

Hari itu SDK Python resmi merilis v2.0.0, bersamaan dengan spesifikasi MCP 2026-07-28, revisi terbesar protokol ini sejak pertama diluncurkan. Kelas FastMCP diganti MCPServer. Konsep session dihapus. Protokolnya jadi stateless. Semua contoh yang memakai from mcp.server.fastmcp import FastMCP, yang masih mendominasi Stack Overflow, blog, dan video tutorial; semuanya tidak jalan lagi.

Berita baiknya: buat tim Django/FastAPI, v2 justru lebih bersih. Dan post ini adalah salah satu referensi v2 pertama yang ditulis khusus untuk kamu, bukan untuk LLM enthusiast.

Kenapa Tim Web Harus Peduli MCP

Masalah yang dipecahkan MCP sama tuanya dengan masalah pre-USB: fragmentasi. Bayangkan kamu sudah punya layanan check_stock(sku) yang rapi. Untuk integrasi ke Claude kamu bungkus jadi tool via adapter Anthropic. Untuk agent internal kamu tulis wrapper function-calling OpenAI. Bulan depan muncul framework agent baru yang “lebih baik”; kamu menulis adapter ketiga. Setiap kali tim AI berganti pendekatan, seluruh lapisan adapter itu ikut dibuang. Sebelum MCP, tiap framework agent punya cara sendiri menghubungkan tools: function calling OpenAI, Anthropic, adapter LangChain, dan seterusnya. Tool yang kamu tulis untuk satu agent tidak bisa dipakai agent lain. Kamu menulis adapter berulang-ulang.

MCP (Model Context Protocol) menstandarkan tiga hal sekaligus: transport, discovery, dan kontrak tool. Protokol ini open-source, dijalankan di bawah Linux Foundation, dan berbasis JSON-RPC 2.0 (konsepnya mirip Language Server Protocol). Sering disebut “USB-C untuk AI”: sekali kamu menulis MCP server, server itu langsung bisa dipakai oleh semua client yang mendukung MCP: Claude, Cursor, VS Code, ChatGPT, atau agent custom kamu.

flowchart LR
    subgraph Host["Host: aplikasi AI"]
        Agent["Agent / LLM app"]
        C["MCP Client"]
    end
    subgraph Web["App Django / FastAPI"]
        S["MCPServer"]
        T["Tools → sync_to_async → ORM"]
    end
    Agent --> C
    C -- "Streamable HTTP / stdio" --> S
    T --> DB[("Postgres")]

Arsitekturnya sederhana: host membuat satu client per server. Server mengekspos tiga primitives: Tools (aksi), Resources (data konteks, diakses via URI template seperti product://{sku}), dan Prompts (template interaksi). Client menyediakan elicitation (meminta input user saat tool dipanggil). Ada dua transport: stdio untuk server lokal dan Streamable HTTP untuk remote.

Ini yang paling penting buat kamu: tim Django/FastAPI sudah punya ORM, service layer, dan internal API. MCP adalah cara terstandar untuk membuka semuanya ke agent apa pun; tulis sekali, dipakai semua client, tanpa adapter per-framework. Dan karena schema tools berbasis type hints (Pydantic di belakang layar), rasanya persis seperti menulis endpoint FastAPI.

Apa yang Berubah di Spesifikasi 2026-07-28

  • Protokol sekarang stateless. Handshake initialize dan header Mcp-Session-Id dihapus. Versi protokol, info client, dan capabilities dibawa di field _meta di tiap request. Discovery via server/discover.
  • Implikasi operasional besar: server MCP sekarang bisa jalan di belakang round-robin load balancer tanpa sticky session atau session store.
  • Transport lama dihapus. HTTP berbasis SSE dan WebSocket bukan lagi transport resmi; tersisa stdio dan Streamable HTTP.
  • State antar-call pakai explicit-handle pattern: tool return id (mis. basket_id), lalu model menyerahkan id itu sebagai argumen di call berikutnya.

Server MCP Pertamamu dalam 15 Baris

Ini dia. Install SDK v2, tulis server, dan jalan:

pip install "mcp[cli]"
# server.py
from mcp.server import MCPServer

mcp = MCPServer("Demo")

@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers."""
    return a + b

@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
    """Greet someone by name."""
    return f"Hello, {name}!"

Perhatikan: kamu tidak menulis JSON Schema, tidak parsing request, tidak validasi manual. Type hints (a: int) adalah schema-nya; Pydantic membangunnya untuk kamu. Inilah yang bikin MCP natural untuk developer Python: tool authoring terasa seperti fungsi biasa.

Coba langsung di MCP Inspector:

uv run mcp dev server.py

Client dalam 10 Baris

Untuk test in-memory tanpa transport:

import asyncio
from mcp import Client

from server import mcp

async def main() -> None:
    async with Client(mcp) as client:          # in-memory, tanpa transport
        result = await client.call_tool("add", {"a": 1, "b": 2})
        print(result.structured_content)       # {'result': 3}

asyncio.run(main())

Buat remote server, tinggal ganti mcp dengan URL-nya:

client = Client("http://localhost:8000/mcp")

Studi Kasus: Ekspos ORM Django ke Agent

Sekarang bagian yang relevan untuk kamu. Katakanlah kamu punya model produk di Django dan ingin agent bisa mencari serta menambah stok.

# products/models.py
from django.db import models

class Product(models.Model):
    sku = models.CharField(max_length=32, unique=True)
    name = models.CharField(max_length=200)
    price_cents = models.PositiveIntegerField()
    stock = models.PositiveIntegerField(default=0)

Server MCP-nya, dalam satu file di dalam app Django kamu:

# products/mcp.py
from asgiref.sync import sync_to_async
from django.db.models import Q
from mcp.server import MCPServer

from .models import Product

mcp = MCPServer("Shop")

@mcp.tool()
async def search_products(q: str, in_stock_only: bool = False) -> list[dict]:
    """Mencari produk berdasarkan nama atau SKU."""
    qs = Product.objects.filter(Q(name__icontains=q) | Q(sku__icontains=q))
    if in_stock_only:
        qs = qs.filter(stock__gt=0)
    products = await sync_to_async(list)(
        qs.values("sku", "name", "price_cents", "stock")[:20]
    )
    return products

@mcp.tool()
async def restock(sku: str, qty: int) -> dict:
    """Menambah stok produk berdasarkan SKU."""
    product = await sync_to_async(Product.objects.get)(sku=sku)
    product.stock += qty
    await sync_to_async(product.save)(update_fields=["stock"])
    return {"sku": product.sku, "stock": product.stock}

Selain tools, server ini bisa mengekspos resource: data konteks yang bisa di-attach model sebagai latar belakang, bukan aksi yang dipanggil. Tool dipanggil model untuk melakukan sesuatu; resource disematkan untuk memberi tahu. Dengan product://{sku}, agent bisa ditarik ke konteks detail produk sebelum memutuskan tool mana yang dipakai:

@mcp.resource("product://{sku}")
async def product_resource(sku: str) -> str:
    """Detail produk sebagai konteks untuk agent."""
    product = await sync_to_async(Product.objects.get)(sku=sku)
    return (
        f"{product.sku}: {product.name}, "
        f"Rp{product.price_cents / 100:,.2f} ({product.stock} in stock)"
    )

Kenapa sync_to_async? MCP server modern berjalan async, dan Django ORM itu synchronous. Memanggil ORM langsung di handler async def akan memblokir event loop. sync_to_async memindahkan eksekusi ke thread worker sehingga event loop tetap bernapas. SDK v2 memang mengeksekusi fungsi sync biasa di worker thread, tapi begitu handler kamu async def dan memanggil sesuatu yang sync, itu jalan di event loop; jangan. Bungkus selalu.

Mount sebagai ASGI App

Tool itu belum berguna sebelum bisa diakses lewat HTTP. SDK v2 menyediakan streamable_http_app() yang mengembalikan ASGI app. Di FastAPI:

# http/app.py
from contextlib import asynccontextmanager

from fastapi import FastAPI

from products.mcp import mcp

@asynccontextmanager
async def lifespan(app: FastAPI):
    async with mcp.session_manager.run():
        yield

app = FastAPI(lifespan=lifespan)
app.mount("/mcp", mcp.streamable_http_app())
uvicorn http.app:app --reload

Ini footgun nomor satu. Mounting ASGI app mematikan lifespan bawaan MCP. Kalau kamu tidak menyalakan session_manager.run() di lifespan host, request pertama langsung error RuntimeError: Task group is not initialized. Pola yang sama berlaku kalau kamu me-wrap aplikasi Django via asgi.py; kamu bertanggung jawab menyalakan session manager di sisi host.

Catatan tambahan untuk produksi: streamable_http_app() default-nya melayani localhost saja sebagai perlindungan DNS-rebinding. Di belakang hostname asli, semua request akan ditolak dengan 421 Misdirected Request sampai kamu mengisi allowlist di parameter transport_security=. Ini alasan paling umum “jalan di lokal, gagal di prod”.

Koneksi dari Client Custom

import asyncio
from mcp import Client

async def main() -> None:
    async with Client("http://localhost:8000/mcp") as client:
        result = await client.call_tool(
            "search_products", {"q": "laptop", "in_stock_only": True}
        )
        print(result.structured_content)

asyncio.run(main())

Tabel Migrasi v1 → v2 (Referensi yang Tidak Ada di Tempat Lain)

Inilah bagian yang hampir semua tutorial lain belum punya, karena v2 baru berumur hitungan hari. Kalau kamu menemukan tutorial lama (2024–2025), petakan begini:

v1 (2024–2025) v2 (28 Juli 2026)
from mcp.server.fastmcp import FastMCP from mcp.server import MCPServer
FastMCP("Demo", port=8080): transport di konstruktor MCPServer("Demo"): transport dikonfigurasi di run()
mcp.run(transport="http", mount_path=...) mcp.streamable_http_app() + mount manual
Implicit session (initialize, Mcp-Session-Id) Stateless; versi + capabilities di _meta tiap request
ctx.elicit() untuk minta konfirmasi user Multi Round-Trip Requests: return InputRequiredResult, client balas lalu retry dengan requestState
Wire types camelCase mcp-types dipisah; atribut snake_case
pip install mcp → 1.x pip install mcp → 2.x; pin mcp>=1.28,<2 kalau belum siap migrate

Sumber kebingungan terbesar: ada dua paket yang sejarahnya tumpang-tindih. Official SDK sempat mengadopsi API FastMCP (dari proyek komunitas) lalu di v2 mengganti namanya jadi MCPServer. Sementara itu paket standalone fastmcp (dikelola Prefect) tetap ada dengan kelas FastMCP; dekorator pun beda: @mcp.tool tanpa kurung, sementara SDK v2 memakai @mcp.tool(). Kode dari dua ekosistem ini tidak saling bertukar. Perhatikan import kamu.

Kapan Sebaiknya Tidak Pakai MCP

MCP menambah lapisan: transport, discovery, auth, dan kosakata konsep baru. Untuk agent internal dengan 2–3 tool dan satu client, direct tool calling ke LLM kamu masih lebih sederhana dan lebih cepat dibuat. Jangan pakai MCP karena tren.

MCP mulai worth it ketika kamu masuk ke salah satu dari ini:

  • Multi-client: tool yang sama dipakai dari Claude, VS Code, Cursor, dan agent custom sekaligus.
  • Memproductize tool internal: kamu mau membuka service/ORM ke tim lain dengan kontrak yang terstandar.
  • Memisahkan tool authoring dari agent logic: tool ditulis tim backend, agent logic ditulis tim AI, dan keduanya berkembang independen.

Pitfall lain yang sering muncul di produksi:

  1. Jangan copy reference servers ke produksi. Repo modelcontextprotocol/servers (postgres, sqlite, github, dkk) sudah di-archive dengan peringatan eksplisit: ini implementasi referensi, bukan production-ready. Tool MCP adalah arbitrary code execution; jangan expose akses DB mentah tanpa consent dan RBAC.
  2. Jangan bergantung pada state di memori antar-call. Protokol sekarang stateless. Variabel session akan gagal di deployment multi-replica dan bahkan saat retry. Pakai explicit-handle pattern: return id, minta id itu balik sebagai argumen.
  3. CORS untuk browser client. Kalau client kamu jalan di browser, preflight harus mengizinkan header Mcp-*; kalau tidak, request diam-diam gagal.
  4. Ekosistem sekitar masih mengejar. SDK v2 baru rilis 4 hari. Integrasi framework (Pydantic AI, LangChain, DSPy) boleh jadi belum sepenuhnya port ke v2; cek versi sebelum menulis kode integrasi.

MCP Adalah Protokol, Bukan Framework

Terakhir, satu hal yang sering disalahpahami: MCP tidak menggantikan framework agent. MCP menstandarkan cara tool terhubung, tapi siklus agent (reasoning, perencanaan, evaluasi) tetap kerjaan kamu. Memakai MCP tidak membuatmu bebas dari mengelola agent logic.

Pola yang sudah populer tahun ini: server MCP di sisi backend (Django/FastAPI) mengekspos ORM dan service, lalu agent framework di sisi consumer memakainya. Kalau kamu mengikuti seri ini, kamu sudah punya dua kandidat consumer:

  • Pydantic AI: agent type-safe yang bisa consume MCP servers; koneksi paling natural karena schema di dua-duanya dibangun dari Pydantic.
  • DSPy: programmatic agents yang bisa memakai MCP tools di dalam pipeline ReAct-nya.

Mulai dari sini: install mcp, tulis server 15 baris di atas, dan buka di MCP Inspector. Besok, mount ORM production kamu dan biarkan agent memakainya dengan contract yang terstandar. Tulis sekali, dipakai semua client; itu janji MCP, dan untuk pertama kalinya API-nya benar-benar siap untuk tim web.