← Kembali ke Blog

Bikin MCP Server Pertama dengan Python

Chatbot kamu jago jawab pertanyaan. Terus kamu minta dia cek cuaca, dan dia diem aja. Kamu bikin tool function, sambungin ke prompt loop, jalan deh. Lalu kamu mau dia baca database Postgres. Integrasi lagi. Terus sistem tiket kantor. Tiap kemampuan baru artinya nambah glue code khusus, dan nggak ada satu pun yang bisa dipakai ulang di aplikasi berikutnya.

Model Context Protocol (MCP) ada buat mutus siklus ini. Ini protokol terbuka yang dibangun di atas JSON-RPC 2.0, jadi standar cara aplikasi AI manggil tool dan baca data. Kamu tulis server sekali, semua MCP host bisa pake. Claude Desktop, Claude Code, VS Code, Cursor, semuanya ngomong protokol yang sama.

Tutorial ini ngajak kamu bikin MCP server lengkap di Python pakai SDK terbaru, versi 2.x, yang nargetin spesifikasi 2026-07-28. Kebanyakan tutorial di internet masih nunjukin API lama 1.x (FastMCP). API v2 lebih simpel, dan semua command di artikel ini udah dijalanin beneran di SDK versi itu sebelum tayang.

Yang bakal kamu bikin

Weather server dengan dua tool. get_alerts ngambil alert cuaca aktif buat satu negara bagian Amerika. get_forecast ngambil prakiraan lima periode buat satu lokasi. Dua-duanya narik data live dari API National Weather Service, gratis dan nggak butuh API key. Server-nya juga punya satu resource dan satu prompt, jadi tiga primitif MCP keliatan semua dalam satu file.

Prasyarat

  • Python 3.10 atau lebih baru. SDK-nya wajib itu.
  • uv. Dokumentasi resmi pake uv dan ini bikin environment rapi. Pip biasa juga bisa.
  • MCP host kalau mau nyoba dari aplikasi chat. Claude Desktop jalan di macOS dan Windows. Di Linux, pake Claude Code, VS Code, atau test client di artikel ini.

Langkah 1: Siapin project

uv init weather
cd weather
uv venv
source .venv/bin/activate
uv add "mcp[cli]"

Extra [cli] nambahin command mcp di atas SDK: mcp dev, mcp run, mcp install. Kalau pake pip, padanannya pip install "mcp[cli]".

Langkah 2: Tulis server-nya

Bikin file weather.py:

from typing import Any

import httpx2
from mcp.server import MCPServer

mcp = MCPServer("weather")

NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"


async def make_nws_request(url: str) -> dict[str, Any] | None:
    """Make a request to the NWS API with proper error handling."""
    headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"}
    async with httpx2.AsyncClient() as client:
        try:
            response = await client.get(url, headers=headers, timeout=30.0)
            response.raise_for_status()
            return response.json()
        except Exception:
            return None


def format_alert(feature: dict) -> str:
    """Format an alert feature into a readable string."""
    props = feature["properties"]
    return f"""
Event: {props.get("event", "Unknown")}
Area: {props.get("areaDesc", "Unknown")}
Severity: {props.get("severity", "Unknown")}
Description: {props.get("description", "No description available")}
"""


@mcp.tool()
async def get_alerts(state: str) -> str:
    """Get weather alerts for a US state.

    Args:
        state: Two-letter US state code (e.g. CA, NY)
    """
    url = f"{NWS_API_BASE}/alerts/active/area/{state}"
    data = await make_nws_request(url)

    if not data or "features" not in data:
        return "Unable to fetch alerts or no alerts found."

    if not data["features"]:
        return "No active alerts for this state."

    alerts = [format_alert(feature) for feature in data["features"]]
    return "\n---\n".join(alerts)


@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
    """Get weather forecast for a location.

    Args:
        latitude: Latitude of the location
        longitude: Longitude of the location
    """
    points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
    points_data = await make_nws_request(points_url)

    if not points_data:
        return "Unable to fetch forecast data for this location."

    forecast_url = points_data["properties"]["forecast"]
    forecast_data = await make_nws_request(forecast_url)

    if not forecast_data:
        return "Unable to fetch detailed forecast."

    periods = forecast_data["properties"]["periods"]
    forecasts = []
    for period in periods[:5]:
        forecast = f"""
{period["name"]}:
Temperature: {period["temperature"]}°{period["temperatureUnit"]}
Wind: {period["windSpeed"]} {period["windDirection"]}
Forecast: {period["detailedForecast"]}
"""
        forecasts.append(forecast)

    return "\n---\n".join(forecasts)


@mcp.resource("nws://states")
def supported_states() -> list[str]:
    """US state codes the alerts tool supports."""
    # Full list: all 50 US state codes. Trimmed here for brevity.
    return ["CA", "NY", "TX", "FL", "WA", "CO", "IL", "GA", "HI", "AK"]


@mcp.prompt()
def weather_report(city: str, state: str) -> str:
    """Ask for a weather report for a city."""
    return (
        f"Use get_forecast to fetch the weather for {city}, {state}. "
        f"Then summarize it in two sentences, mentioning temperature and wind."
    )


if __name__ == "__main__":
    mcp.run(transport="stdio")

Beberapa hal yang perlu diperhatiin. Class MCPServer ngubah type hint dan docstring jadi JSON Schema otomatis, jadi kamu nggak pernah nulis schema manual. Tool-nya async karena dia manggil HTTP. Docstring itu penting: itu yang dibaca model pas mutusin mau manggil tool atau nggak.

Import httpx2 itu HTTP client yang dipake SDK-nya sendiri, jadi install mcp aja udah kebawa.

Langkah 3: Jalanin di MCP Inspector

Cara paling cepat nyoba server adalah MCP Inspector, client berbasis browser yang dirawat tim SDK.

uv run mcp dev weather.py

Dia ngeboot web UI di http://localhost:6274. Buka, dan kamu bakal liat get_alerts dan get_forecast di daftar tool. Panggil get_alerts dengan CA, kamu dapet data alert live. Di inspector ini juga kamu bisa liat JSON Schema yang digenerate otomatis buat tiap tool.

Langkah 4: Tes dengan in-memory client

SDK-nya punya class Client, dan kamu bisa langsung oper object server ke dia. Nggak ada subprocess, nggak ada port. Ini cara test suite SDK-nya sendiri jalan, dan ini feedback loop tercepat yang bakal kamu dapet. Simpen sebagai test_client.py:

import asyncio

from mcp import Client

from weather import mcp


async def main() -> None:
    async with Client(mcp) as client:
        print(client.server_info)
        print("protocol:", client.protocol_version)

        tools = await client.list_tools()
        print("tools:", [t.name for t in tools.tools])

        result = await client.call_tool("get_alerts", {"state": "CA"})
        for block in result.content:
            print(block.text[:300])


asyncio.run(main())

Jalanin:

uv run python test_client.py

Output-nya kurang lebih gini (alert itu data live, jadi punyamu bakal beda):

protocol: 2026-07-28
tools: ['get_alerts', 'get_forecast']

Event: Extreme Heat Warning
Area: Coachella Valley; San Diego County Deserts
Severity: Severe

Langkah 5: Konek lewat stdio, kayak host beneran

Aplikasi chat nggak ngimport file Python kamu. Mereka launch server sebagai subprocess dan ngirim pesan JSON-RPC lewat stdin dan stdout. Ini transport stdio, yang dipake server MCP lokal.

Client minimalnya kayak gini. Simpen sebagai test_stdio.py dan jalanin dari direktori project:

import asyncio

from mcp import Client, StdioServerParameters
from mcp.client.stdio import stdio_client

server = StdioServerParameters(
    command="uv",
    args=["run", "weather.py"],
)


async def main() -> None:
    async with Client(stdio_client(server)) as client:
        result = await client.call_tool(
            "get_forecast", {"latitude": 37.7749, "longitude": -122.4194}
        )
        for block in result.content:
            print(block.text)


asyncio.run(main())

Ini bentuk kode yang sama kayak yang dijalanin MCP host di dalem. Jalanin, dan kamu dapet prakiraan cuaca San Francisco beneran:

Tonight:
Temperature: 58°F
Wind: 6 to 13 mph WSW
Forecast: Mostly cloudy, with a low around 58.

Saturday:
Temperature: 72°F
Wind: 6 to 12 mph WSW
Forecast: Mostly sunny, with a high near 72.

Langkah 6: Pasang ke aplikasi chat

Di macOS atau Windows, Claude Desktop baca config dari ~/Library/Application Support/Claude/claude_desktop_config.json (Windows: %APPDATA%\Claude\claude_desktop_config.json). Tambahin server kamu di bawah mcpServers:

{
  "mcpServers": {
    "weather": {
      "command": "uv",
      "args": ["--directory", "/ABSOLUTE/PATH/TO/weather", "run", "weather.py"]
    }
  }
}

Restart Claude Desktop, tool-nya muncul. Dua catatan dari dokumentasi resmi: kamu mungkin perlu path lengkap ke executable uv (which uv), dan directory di args harus absolute. Claude Desktop nggak tersedia di Linux. Di sana pake Claude Code, VS Code dengan ekstensi MCP-nya, atau client stdio dari Langkah 5.

Kapan bikin server sendiri vs pake yang udah ada

Cek dulu server registry resmi dan referensi implementasi di repo modelcontextprotocol/servers sebelum nulis apa-apa. Filesystem, Postgres, GitHub, Slack, Sentry, semua udah punya server yang dirawat dan tinggal diarahin ke host kamu.

Bikin sendiri kalau:

  • Kamu punya API internal atau skema database yang nggak dicover server publik mana pun
  • Server yang ada nggak cocok sama model auth atau bentuk data kamu
  • Kamu mau satu tool yang gabungin beberapa sistem internal

SDK Python vs TypeScript lebih ke masalah tim. Pilih bahasa yang udah dipake codebase kamu, protokolnya sama persis. Lokal vs remote itu masalah runtime. Stdio naruh server di mesin yang sama dengan host, tanpa network dan tanpa auth. Kalau butuh diakses banyak host atau banyak user, jalanin di atas Streamable HTTP dan lindungi dengan OAuth, sesuai rekomendasi spesifikasi.

Dua kesalahan yang bikin buang waktu sejam

  1. print() di server stdio. Stdout itu jalur pesan JSON-RPC. Print nyasar dikit, stream-nya rusak dan koneksinya mati dengan error yang bikin bingung. Pake module logging, yang nulis ke stderr. Dokumentasi resminya tegas: jangan pernah nulis ke stdout di server stdio.

  2. Launch server pake Python yang salah. Kalau subprocess-nya nggak bisa import mcp, koneksi gagal dari awal. Pastiin command di config host kamu jalan di environment tempat SDK keinstall. Ini alasan contoh resmi selalu pake uv run.

Langkah selanjutnya

Sekarang kamu punya server yang bisa dipake beberapa aplikasi chat, plus loop testing yang jalan dalam hitungan milidetik. Next steps, urut kasar:

  • Ganti API cuaca dengan sesuatu yang kamu punya sendiri. Skema Postgres kamu, API internal, folder dokumen. Bentuk tool-nya tetap sama.
  • Baca halaman server concepts buat dalemin resource dan prompt, plus tutorial build-server resmi buat walkthrough lengkap.
  • Kalau server-nya harus melayani banyak user, pindahin ke Streamable HTTP dan tambahin OAuth. SDK-nya punya integrasi ASGI, jadi server bisa numpang di aplikasi FastAPI yang udah ada.

Inti MCP adalah bangun kemampuan sekali. Satu server, semua host di toolchain kamu bisa pake. Itu bayaran buat sejam yang barusan kamu habisin.

Referensi

Butuh Bantuan Implementasi?

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

Konsultasi Gratis