Kamu di Claude Desktop atau Cursor, minta model buat ngecek sesuatu yang ada di sisi kamu: API internal, database privat, file di disk. Modelnya nggak bisa nyampe. Nggak ada tool, nggak ada konektor, nggak ada jalan masuk. Itu celah yang diisi Model Context Protocol (MCP): standar buat aplikasi AI manggil kode kamu.
MCP adalah standar terbuka yang diperkenalkan Anthropic bulan November 2024. Anggap aja port USB-C buat aplikasi AI. Host, aplikasi yang kamu ajak ngobrol, jalanin MCP client yang meluncurkan server kamu sebagai child process dan ngomong lewat stdin dan stdout. Server kamu nggak pernah ngomong langsung ke model. Dia expose tiga jenis hal:
- Tools, fungsi yang diputuskan model buat dipanggil demi ngambil aksi. Kurang lebih kayak POST.
- Resources, data yang dimuat host ke konteks model, kayak isi file atau respons API. Kurang lebih kayak GET.
- Prompts, template pesan yang dipanggil user berdasarkan nama, kayak slash command.
Pemisahan ini ada karena tiap primitif punya pengendali yang beda: model, aplikasi, atau user. Kamu nulis satu server, dan semua host yang ngomong MCP bisa langsung pakai: Claude Desktop, Claude Code, Cursor, VS Code.
Di akhir tutorial ini kamu bakal punya server yang jalan dengan tiga primitif lengkap, tes otomatis, dan koneksi ke host yang beneran kamu pakai.
Prerequisites
- Python 3.10+ (syarat dari SDK-nya)
uvataupip. Docs resmi pakai uv, tapi pip sama aja.- Host MCP buat tes: MCP Inspector (nggak perlu install apa-apa), Claude Desktop, Claude Code, Cursor, atau VS Code 1.99+ dengan extension Copilot di mode Agent
- 20 menit
Langkah 1: Install SDK
Python SDK-nya ada di PyPI dengan nama mcp:
pip install "mcp[cli]"
# atau: uv add "mcp[cli]"
Extra [cli] nambahin perintah mcp (mcp dev, mcp run, mcp install). Versi stabil sekarang adalah v2, yang mendukung revisi spesifikasi 2026-07-28 beserta semua revisi sebelumnya. Kalau kamu maintain package yang depend ke mcp dan belum siap pindah, pin mcp>=1.28,<2 biar tetep di jalur 1.x.
Langkah 2: Tulis server-nya
Bikin server.py. Seluruh API-nya tiga decorator di atas fungsi Python biasa:
from typing import Annotated
from mcp.server import MCPServer
from pydantic import Field
mcp = MCPServer("Bookshop")
CATALOG = {
"Dune": "Frank Herbert",
"Neuromancer": "William Gibson",
"The Left Hand of Darkness": "Ursula K. Le Guin",
}
@mcp.tool()
def search_books(
query: Annotated[str, Field(description="Title or author to search for.")],
limit: Annotated[int, Field(ge=1, le=50, description="Maximum number of results.")] = 10,
) -> list[str]:
"""Search the catalog by title or author."""
needle = query.lower()
return [
title
for title, author in CATALOG.items()
if needle in title.lower() or needle in author.lower()
][:limit]
@mcp.tool()
def get_author(title: str) -> str:
"""Look up the author of a book in the catalog."""
if title not in CATALOG:
raise ValueError(f"No book titled {title!r} in the catalog.")
return CATALOG[title]
@mcp.resource("catalog://titles")
def titles() -> str:
"""Every title in the catalog, one per line."""
return "\n".join(sorted(CATALOG))
@mcp.prompt()
def recommend(genre: str) -> str:
"""Recommend a book from the catalog."""
return f"Recommend a {genre} book from this catalog: {', '.join(CATALOG)}"
if __name__ == "__main__":
mcp.run()
Beberapa hal yang penting di sini:
mcp.run()ngejalanin server stdio. Dia ngeblok, baca pesan protokol dari stdin, tulis ke stdout. Nggak ada port, nggak ada yang nge-listen.- Objek server harus global di level module dengan nama
mcp(serverdanappjuga bisa) biar CLI nemu. Kalau kamu namain beda, sebutin eksplisit:mcp run server.py:bookshop. - Type hints itu kontraknya, bukan dokumentasi. SDK bikin JSON Schema dari situ dan nolak input yang salah sebelum fungsi kamu jalan. Kalau client ngirim
"limit": "ten", SDK jawab error dan fungsi kamu nggak pernah dieksekusi. - Exception di dalam tool bukan crash. Dia jadi error result dengan
is_error=Truedan model baca pesannya. Lewat situget_authorngasih tahu model kalau judulnya nggak ada. - Batasin argumen pake pydantic
Field.Annotated[int, Field(ge=1, le=50)]masuk ke schema sebagai"minimum": 1, "maximum": 50. Panggil tool denganlimit=999dan SDK balas "Input should be less than or equal to 50", model baca terus coba lagi dengan nilai yang valid. Agent yang ngekoreksi diri sendiri, gratis.
Langkah 3: Buka di MCP Inspector
uv run mcp dev server.py
# atau kalau SDK udah keinstall di environment kamu: mcp dev server.py
Buka URL yang dia print. Inspector punya satu tab per primitif. Form buat search_books dibangun dari type hints kamu: field query wajib, field limit opsional. Panggil tool-nya, baca resource catalog://titles, jalanin prompt-nya. Client MCP lain bikin UI yang sama dari schema yang sama.
Langkah 4: Tes tanpa host
SDK-nya punya Client dengan transport in-memory. Client(mcp) nyambung langsung ke objek server: nggak ada subprocess, nggak ada port. Konsepnya sama kayak TestClient-nya FastAPI.
import asyncio
from mcp import Client
from server import mcp
async def main() -> None:
async with Client(mcp) as client:
result = await client.call_tool("search_books", {"query": "dune"})
print(result.structured_content) # {'result': ['Dune']}
asyncio.run(main())
Pola yang sama bisa masuk ke pytest:
import pytest
from mcp import Client
from server import mcp
@pytest.fixture
def anyio_backend():
return "asyncio"
@pytest.fixture
async def client():
async with Client(mcp, raise_exceptions=True) as c:
yield c
@pytest.mark.anyio
async def test_search_books(client):
result = await client.call_tool("search_books", {"query": "dune"})
assert result.structured_content == {"result": ["Dune"]}
Install pytest (pip install pytest) terus jalanin. raise_exceptions=True penting di tes: tanpa itu, crash di luar body tool di-sanitize jadi "Internal server error" yang generik sebelum tes kamu liat, persis yang nggak kamu mau pas debugging. Di kode production flag ini nggak punya arti.
Langkah 5: Nyambung ke host beneran
Semua host dapet launch command yang sama:
uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
Satu command buat semua karena uv run --with ngeresolve SDK ke environment baru di tempat: nggak perlu project, nggak perlu virtualenv yang diaktifin. Host meluncurkan server dari working directory mereka sendiri dengan environment yang nyaris kosong, makanya path-nya harus absolut. Kalau host nggak nemu uv, ganti dengan path absolut dari which uv.
Claude Desktop. Satu-satunya host yang bisa dikonfigurasi otomatis oleh SDK:
uv run mcp install server.py
mcp install import file kamu buat baca nama server, nyari config Claude Desktop, terus nulis entry-nya buat kamu. Ini yang dia tulis ke claude_desktop_config.json (~/Library/Application Support/Claude/ di macOS, %APPDATA%\Claude\ di Windows):
{
"mcpServers": {
"Bookshop": {
"command": "/absolute/path/to/uv",
"args": ["run", "--frozen", "--with", "mcp[cli]==2.0.0", "mcp", "run", "/absolute/path/to/server.py"]
}
}
}
Perhatiin tiga tambahannya: path absolut ke uv, --frozen biar uv nggak pernah nulis ulang lockfile yang kebetulan ada di dekatnya, dan pin persis ke versi SDK yang kamu install. Terus quit total Claude Desktop, bukan cuma nutup window-nya, baru buka lagi. Server kamu butuh env var? uv run mcp install server.py -v API_KEY=abc123 atau -f .env nyatet semuanya di entry-nya.
Claude Code. Nggak ada file yang diedit:
claude mcp add bookshop -- uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
Ketik /mcp di dalam sesi buat mastiin bookshop kehubung dan tool-nya keliatan.
Cursor. Bikin .cursor/mcp.json di root project dengan struktur mcpServers yang sama kayak di atas.
VS Code. Bikin .vscode/mcp.json. Dua bedanya dari file Cursor: key pembungkusnya servers, bukan mcpServers, dan tiap entri deklarasi "type": "stdio". Kamu butuh VS Code 1.99+ dengan extension GitHub Copilot yang udah login (Copilot Free cukup), dan Copilot Chat harus di mode Agent, karena mode lain nggak manggil tool.
Langkah 6: Kalau server-nya nggak muncul
Sebelum nyentuh config host mana pun, jalanin dulu launch command-nya sendiri. Dia nggak ngeprint apa-apa dan nggak balik. Diam itu bener: server stdio lagi nunggu host ngomong duluan di stdin (Ctrl-C buat berhenti). Traceback atau langsung exit itu bug aslinya, dan sekarang kamu bisa baca errornya.
Lewat dari situ, hampir selalu salah satu dari tiga hal:
- Relative path. Host meluncurkan server dari working directory mereka sendiri, bukan dari tempat kamu register.
server.pydi posisi yang harusnya path absolut adalah kegagalan paling umum. - Host masih jalan dengan config lama. Host baca config pas launch. Claude Desktop khususnya harus di-quit total sebelum edit-nya kepake.
- Ada yang nulis ke stdout. Di stdio, stdout itu protokolnya.
print()nyasar di wrapper script atau pas import bikin pesan korup dan host mutusin koneksi. Log pake configloggingbawaan yang nulis ke stderr.
Kalau kamu perlu liat server kamu ngapain, Claude Desktop nyimpen mcp-server-<NAME>.log di samping mcp.log di ~/Library/Logs/Claude (macOS) dan %APPDATA%\Claude\logs (Windows).
Kapan bikin server sendiri vs pake yang udah ada
Server siap pakai buat hal umum udah ada: akses filesystem, GitHub, Postgres, dan lainnya ada di repo servers resmi. Pake itu kalau tool-nya generik.
Tulis sendiri kalau itu data kamu: API internal, database privat, workflow yang dijalanin perusahaan kamu. Di situ nggak ada server publik, dan di situ hasilnya paling kerasa.
MCP vs REST API biasa: REST API ngelayani frontend aplikasi kamu. MCP server ada biar host LLM mana pun bisa pake data dan aksi kamu dengan schema yang bisa dibaca model. Backend yang sama bisa dibungkus dua-duanya; masalah yang dipecahin beda. Server MCP stdio juga nggak butuh port, nggak butuh deployment, dan nggak butuh auth server buat pemakaian lokal. Kamu kasih ke host sebagai satu command.
Langkah selanjutnya
Sekarang kamu punya server yang bisa dipanggil host MCP mana pun: dua tools, satu resource, satu prompt, satu tes, dan koneksi beneran. Dari sini:
- Serve objek
mcpyang sama lewat Streamable HTTP biar orang lain nyambung pake URL, bukan command. Itu jalur menuju deployment beneran, dan docs-nya ngebahas auth begitu kamu remote. - Baca docs SDK soal resources dan prompts. Tools itu bagian yang dikendaliin model; resources dan prompts punya detail sendiri yang worth dipelajari.
- Intip repo servers resmi buat liat pola yang dipake server production sebelum kamu bangun sesuatu yang lebih gede.