[{"data":1,"prerenderedAt":9242},["ShallowReactive",2],{"writing-mcp-server-asrama-id":3,"writing-id":6731},{"id":4,"title":5,"body":6,"cover":6716,"date":6717,"description":6718,"draft":6719,"extension":6720,"lab":244,"meta":6721,"navigation":244,"original":3801,"path":6722,"seo":6723,"stem":6724,"tags":6725,"__hash__":6730,"lang":3801},"writing_id\u002Fwriting\u002Fmcp-server-asrama.md","Membuat MCP Server untuk Service Data Anak Asrama (Python SDK v2) — Lengkap dengan Isi JSON-RPC Mentahnya",{"type":7,"value":8,"toc":6687},"minimark",[9,18,26,57,81,84,89,100,110,113,175,177,181,187,189,193,274,281,287,290,309,311,315,333,336,1710,1713,1732,1745,1747,1751,1757,3120,3125,3141,3149,3167,3175,3190,3206,3226,3228,3232,3235,3266,3268,3272,3275,3294,3297,3299,3303,3310,3456,3463,3470,3472,3476,3483,3496,3499,3513,3520,3525,3634,3637,3796,3803,3832,3849,3856,3859,3893,3896,4499,4506,4603,4628,4635,4641,4722,4725,5100,5103,5135,5139,5142,5155,5219,5222,5325,5344,5353,5425,5428,5570,5588,5596,5672,5678,5685,5719,5722,5827,5843,5850,5931,5933,6050,6061,6065,6082,6105,6111,6113,6117,6120,6595,6598,6600,6604,6622,6628,6642,6648,6654,6667,6669,6673,6676,6683],[10,11,12,13,17],"p",{},"Bayangkan Anda mengelola sebuah asrama atau pesantren. Data anak sudah tersimpan rapi di sebuah service: nama, kelas, kamar, status, dan absensi harian. Suatu hari muncul kebutuhan baru: pengurus ingin bisa bertanya ke asisten AI, misalnya ",[14,15,16],"em",{},"\"siapa saja anak kelas 7A yang izin hari ini?\"",", lalu AI menjawab berdasarkan data asli, bukan tebakan.",[10,19,20,21,25],{},"Di sinilah ",[22,23,24],"strong",{},"MCP (Model Context Protocol)"," berperan. Dalam artikel ini kita akan:",[27,28,29,33,45,48,51],"ol",{},[30,31,32],"li",{},"Memahami posisi MCP server di antara AI dan service Anda.",[30,34,35,36,39,40,44],{},"Membuat MCP server dengan ",[22,37,38],{},"SDK MCP Python v2"," (",[41,42,43],"code",{},"MCPServer",").",[30,46,47],{},"Mengujinya dengan service tiruan (mock).",[30,49,50],{},"Menyambungkannya ke Claude Desktop.",[30,52,53,56],{},[22,54,55],{},"Membedah pesan JSON-RPC mentah"," yang lewat di antara agent dan server, supaya Anda paham persis apa yang dibaca oleh model.",[58,59,60],"blockquote",{},[10,61,62,65,66,69,70,73,74,76,77,80],{},[22,63,64],{},"Catatan versi:"," Artikel ini memakai paket ",[41,67,68],{},"mcp"," versi 2.x (diuji di 2.3.0). Di versi 2, kelas ",[41,71,72],{},"FastMCP"," yang sering muncul di tutorial lama sudah diganti menjadi ",[41,75,43],{},". Kalau Anda masih memakai kode lama, pin dependensi ke ",[41,78,79],{},"mcp\u003C2",".",[82,83],"hr",{},[85,86,88],"h2",{"id":87},"konsep-singkat-mcp-itu-adaptor","Konsep Singkat: MCP Itu Adaptor",[10,90,91,92,95,96,99],{},"MCP server tidak menggantikan service Anda. Ia hanya ",[22,93,94],{},"membungkus"," endpoint-endpoint yang sudah ada menjadi ",[14,97,98],{},"tools"," yang bisa dipanggil AI.",[101,102,107],"pre",{"className":103,"code":105,"language":106},[104],"language-text","┌──────────────┐     JSON-RPC      ┌──────────────┐      HTTP\u002FREST     ┌──────────────────┐\n│ Host AI      │ ◄───────────────► │ MCP Server   │ ◄────────────────► │ Service Asrama   │\n│ (Claude      │   (stdio \u002F HTTP)  │ (server.py)  │                    │ (API Anda)       │\n│  Desktop dll)│                   │              │                    │                  │\n└──────────────┘                   └──────────────┘                    └──────────────────┘\n       ▲\n       │ definisi tool + hasil tool\n       ▼\n┌──────────────┐\n│ Model AI     │\n└──────────────┘\n","text",[41,108,105],{"__ignoreMap":109},"",[10,111,112],{},"Ada tiga jenis \"kemampuan\" yang bisa ditawarkan MCP server:",[114,115,116,132],"table",{},[117,118,119],"thead",{},[120,121,122,126,129],"tr",{},[123,124,125],"th",{},"Jenis",[123,127,128],{},"Fungsi",[123,130,131],{},"Siapa yang memakai",[133,134,135,149,162],"tbody",{},[120,136,137,143,146],{},[138,139,140],"td",{},[22,141,142],{},"Tool",[138,144,145],{},"Aksi yang bisa dipanggil (cari anak, ambil absensi)",[138,147,148],{},"Dipilih oleh model",[120,150,151,156,159],{},[138,152,153],{},[22,154,155],{},"Resource",[138,157,158],{},"Data yang bisa dibaca sebagai konteks",[138,160,161],{},"Biasanya dipilih host\u002Fuser",[120,163,164,169,172],{},[138,165,166],{},[22,167,168],{},"Prompt",[138,170,171],{},"Template pesan siap pakai",[138,173,174],{},"Biasanya dipilih user dari menu",[82,176],{},[85,178,180],{"id":179},"struktur-proyek","Struktur Proyek",[101,182,185],{"className":183,"code":184,"language":106},[104],"mcp-asrama\u002F\n├── server.py                          # MCP server\n├── mock_service.py                    # service tiruan untuk testing\n├── requirements.txt\n└── claude_desktop_config.example.json\n",[41,186,184],{"__ignoreMap":109},[82,188],{},[85,190,192],{"id":191},"langkah-1-siapkan-proyek","Langkah 1 — Siapkan Proyek",[101,194,198],{"className":195,"code":196,"language":197,"meta":109,"style":109},"language-bash shiki shiki-themes github-light github-dark","mkdir mcp-asrama && cd mcp-asrama\npython -m venv .venv\n\n# Windows\n.venv\\Scripts\\activate\n# Linux \u002F Mac\nsource .venv\u002Fbin\u002Factivate\n","bash",[41,199,200,224,239,246,253,259,265],{"__ignoreMap":109},[201,202,205,209,213,217,221],"span",{"class":203,"line":204},"line",1,[201,206,208],{"class":207},"sScJk","mkdir",[201,210,212],{"class":211},"sZZnC"," mcp-asrama",[201,214,216],{"class":215},"sVt8B"," && ",[201,218,220],{"class":219},"sj4cs","cd",[201,222,223],{"class":211}," mcp-asrama\n",[201,225,227,230,233,236],{"class":203,"line":226},2,[201,228,229],{"class":207},"python",[201,231,232],{"class":219}," -m",[201,234,235],{"class":211}," venv",[201,237,238],{"class":211}," .venv\n",[201,240,242],{"class":203,"line":241},3,[201,243,245],{"emptyLinePlaceholder":244},true,"\n",[201,247,249],{"class":203,"line":248},4,[201,250,252],{"class":251},"sJ8bj","# Windows\n",[201,254,256],{"class":203,"line":255},5,[201,257,258],{"class":207},".venv\\Scripts\\activate\n",[201,260,262],{"class":203,"line":261},6,[201,263,264],{"class":251},"# Linux \u002F Mac\n",[201,266,268,271],{"class":203,"line":267},7,[201,269,270],{"class":219},"source",[201,272,273],{"class":211}," .venv\u002Fbin\u002Factivate\n",[10,275,276,277,280],{},"Isi ",[41,278,279],{},"requirements.txt",":",[101,282,285],{"className":283,"code":284,"language":106,"meta":109},[104],"mcp>=2,\u003C3\nhttpx\n# hanya untuk mock_service.py\nfastapi\nuvicorn\n",[41,286,284],{"__ignoreMap":109},[10,288,289],{},"Lalu install:",[101,291,293],{"className":195,"code":292,"language":197,"meta":109,"style":109},"pip install -r requirements.txt\n",[41,294,295],{"__ignoreMap":109},[201,296,297,300,303,306],{"class":203,"line":204},[201,298,299],{"class":207},"pip",[201,301,302],{"class":211}," install",[201,304,305],{"class":219}," -r",[201,307,308],{"class":211}," requirements.txt\n",[82,310],{},[85,312,314],{"id":313},"langkah-2-siapkan-service-pakai-mock-dulu","Langkah 2 — Siapkan Service (Pakai Mock Dulu)",[10,316,317,318,321,322,321,325,328,329,332],{},"Supaya bisa langsung dicoba, kita buat service tiruan dengan FastAPI. Service ini meniru empat endpoint: ",[41,319,320],{},"\u002Fanak",", ",[41,323,324],{},"\u002Fanak\u002F{id}",[41,326,327],{},"\u002Fkamar",", dan ",[41,330,331],{},"\u002Fabsensi",", dengan autentikasi Bearer token sederhana.",[10,334,335],{},"Kalau service asli Anda sudah siap, langkah ini boleh dilewati.",[101,337,340],{"className":338,"code":339,"language":229,"meta":109,"style":109},"language-python shiki shiki-themes github-light github-dark","\"\"\"\nService tiruan (mock) data anak asrama.\nGanti dengan service asli Anda nanti. File ini hanya untuk testing lokal.\n\nJalankan: uvicorn mock_service:app --port 8000\n\"\"\"\nfrom fastapi import FastAPI, HTTPException, Header, Query\nfrom typing import Optional\n\napp = FastAPI(title=\"Service Data Anak Asrama (Mock)\")\n\nTOKEN_VALID = \"rahasia123\"\n\nDATA_ANAK = [\n    {\"id\": \"A001\", \"nama\": \"Ahmad Fauzi\", \"kelas\": \"7A\", \"kamar\": \"Al-Fatih 1\",\n     \"status\": \"aktif\", \"nik\": \"3171xxxxxxxx0001\", \"no_hp_wali\": \"0812xxxx001\",\n     \"alamat\": \"Jl. Melati No. 1, Bogor\", \"nama_wali\": \"Bapak Hasan\"},\n    {\"id\": \"A002\", \"nama\": \"Budi Santoso\", \"kelas\": \"8B\", \"kamar\": \"Al-Fatih 1\",\n     \"status\": \"aktif\", \"nik\": \"3171xxxxxxxx0002\", \"no_hp_wali\": \"0812xxxx002\",\n     \"alamat\": \"Jl. Mawar No. 2, Depok\", \"nama_wali\": \"Ibu Sari\"},\n    {\"id\": \"A003\", \"nama\": \"Citra Lestari\", \"kelas\": \"7A\", \"kamar\": \"Aisyah 2\",\n     \"status\": \"izin\", \"nik\": \"3171xxxxxxxx0003\", \"no_hp_wali\": \"0812xxxx003\",\n     \"alamat\": \"Jl. Kenanga No. 3, Bekasi\", \"nama_wali\": \"Bapak Joko\"},\n]\n\nDATA_KAMAR = [\n    {\"nama\": \"Al-Fatih 1\", \"gedung\": \"Putra\", \"kapasitas\": 6, \"musyrif\": \"Ust. Ridwan\"},\n    {\"nama\": \"Aisyah 2\", \"gedung\": \"Putri\", \"kapasitas\": 6, \"musyrif\": \"Ustz. Halimah\"},\n]\n\nDATA_ABSENSI = {\n    \"2026-10-06\": [\n        {\"id_anak\": \"A001\", \"status\": \"hadir\"},\n        {\"id_anak\": \"A002\", \"status\": \"hadir\"},\n        {\"id_anak\": \"A003\", \"status\": \"izin\", \"keterangan\": \"Pulang, acara keluarga\"},\n    ]\n}\n\n\ndef cek_token(authorization: Optional[str]):\n    if authorization != f\"Bearer {TOKEN_VALID}\":\n        raise HTTPException(status_code=401, detail=\"Token tidak valid\")\n\n\n@app.get(\"\u002Fanak\")\ndef list_anak(\n    nama: Optional[str] = None,\n    kelas: Optional[str] = None,\n    kamar: Optional[str] = None,\n    limit: int = Query(20, le=100),\n    authorization: Optional[str] = Header(None),\n):\n    cek_token(authorization)\n    hasil = DATA_ANAK\n    if nama:\n        hasil = [a for a in hasil if nama.lower() in a[\"nama\"].lower()]\n    if kelas:\n        hasil = [a for a in hasil if a[\"kelas\"].lower() == kelas.lower()]\n    if kamar:\n        hasil = [a for a in hasil if a[\"kamar\"].lower() == kamar.lower()]\n    return {\"total\": len(hasil), \"data\": hasil[:limit]}\n\n\n@app.get(\"\u002Fanak\u002F{id_anak}\")\ndef detail_anak(id_anak: str, authorization: Optional[str] = Header(None)):\n    cek_token(authorization)\n    for a in DATA_ANAK:\n        if a[\"id\"] == id_anak:\n            return a\n    raise HTTPException(status_code=404, detail=\"Anak tidak ditemukan\")\n\n\n@app.get(\"\u002Fkamar\")\ndef list_kamar(authorization: Optional[str] = Header(None)):\n    cek_token(authorization)\n    hasil = []\n    for k in DATA_KAMAR:\n        terisi = len([a for a in DATA_ANAK if a[\"kamar\"] == k[\"nama\"]])\n        hasil.append({**k, \"terisi\": terisi})\n    return {\"data\": hasil}\n\n\n@app.get(\"\u002Fabsensi\")\ndef absensi(tanggal: str, authorization: Optional[str] = Header(None)):\n    cek_token(authorization)\n    return {\"tanggal\": tanggal, \"data\": DATA_ABSENSI.get(tanggal, [])}\n",[41,341,342,347,352,357,361,366,370,385,398,403,427,432,444,449,460,508,541,565,605,634,655,695,725,746,752,757,767,810,849,854,859,870,879,904,925,956,962,968,973,978,996,1023,1052,1057,1062,1076,1087,1105,1121,1137,1167,1187,1193,1199,1210,1218,1257,1265,1297,1305,1335,1361,1366,1371,1388,1417,1422,1437,1454,1463,1489,1494,1499,1511,1533,1538,1548,1563,1604,1622,1634,1639,1644,1656,1683,1688],{"__ignoreMap":109},[201,343,344],{"class":203,"line":204},[201,345,346],{"class":211},"\"\"\"\n",[201,348,349],{"class":203,"line":226},[201,350,351],{"class":211},"Service tiruan (mock) data anak asrama.\n",[201,353,354],{"class":203,"line":241},[201,355,356],{"class":211},"Ganti dengan service asli Anda nanti. File ini hanya untuk testing lokal.\n",[201,358,359],{"class":203,"line":248},[201,360,245],{"emptyLinePlaceholder":244},[201,362,363],{"class":203,"line":255},[201,364,365],{"class":211},"Jalankan: uvicorn mock_service:app --port 8000\n",[201,367,368],{"class":203,"line":261},[201,369,346],{"class":211},[201,371,372,376,379,382],{"class":203,"line":267},[201,373,375],{"class":374},"szBVR","from",[201,377,378],{"class":215}," fastapi ",[201,380,381],{"class":374},"import",[201,383,384],{"class":215}," FastAPI, HTTPException, Header, Query\n",[201,386,388,390,393,395],{"class":203,"line":387},8,[201,389,375],{"class":374},[201,391,392],{"class":215}," typing ",[201,394,381],{"class":374},[201,396,397],{"class":215}," Optional\n",[201,399,401],{"class":203,"line":400},9,[201,402,245],{"emptyLinePlaceholder":244},[201,404,406,409,412,415,419,421,424],{"class":203,"line":405},10,[201,407,408],{"class":215},"app ",[201,410,411],{"class":374},"=",[201,413,414],{"class":215}," FastAPI(",[201,416,418],{"class":417},"s4XuR","title",[201,420,411],{"class":374},[201,422,423],{"class":211},"\"Service Data Anak Asrama (Mock)\"",[201,425,426],{"class":215},")\n",[201,428,430],{"class":203,"line":429},11,[201,431,245],{"emptyLinePlaceholder":244},[201,433,435,438,441],{"class":203,"line":434},12,[201,436,437],{"class":219},"TOKEN_VALID",[201,439,440],{"class":374}," =",[201,442,443],{"class":211}," \"rahasia123\"\n",[201,445,447],{"class":203,"line":446},13,[201,448,245],{"emptyLinePlaceholder":244},[201,450,452,455,457],{"class":203,"line":451},14,[201,453,454],{"class":219},"DATA_ANAK",[201,456,440],{"class":374},[201,458,459],{"class":215}," [\n",[201,461,463,466,469,472,475,477,480,482,485,487,490,492,495,497,500,502,505],{"class":203,"line":462},15,[201,464,465],{"class":215},"    {",[201,467,468],{"class":211},"\"id\"",[201,470,471],{"class":215},": ",[201,473,474],{"class":211},"\"A001\"",[201,476,321],{"class":215},[201,478,479],{"class":211},"\"nama\"",[201,481,471],{"class":215},[201,483,484],{"class":211},"\"Ahmad Fauzi\"",[201,486,321],{"class":215},[201,488,489],{"class":211},"\"kelas\"",[201,491,471],{"class":215},[201,493,494],{"class":211},"\"7A\"",[201,496,321],{"class":215},[201,498,499],{"class":211},"\"kamar\"",[201,501,471],{"class":215},[201,503,504],{"class":211},"\"Al-Fatih 1\"",[201,506,507],{"class":215},",\n",[201,509,511,514,516,519,521,524,526,529,531,534,536,539],{"class":203,"line":510},16,[201,512,513],{"class":211},"     \"status\"",[201,515,471],{"class":215},[201,517,518],{"class":211},"\"aktif\"",[201,520,321],{"class":215},[201,522,523],{"class":211},"\"nik\"",[201,525,471],{"class":215},[201,527,528],{"class":211},"\"3171xxxxxxxx0001\"",[201,530,321],{"class":215},[201,532,533],{"class":211},"\"no_hp_wali\"",[201,535,471],{"class":215},[201,537,538],{"class":211},"\"0812xxxx001\"",[201,540,507],{"class":215},[201,542,544,547,549,552,554,557,559,562],{"class":203,"line":543},17,[201,545,546],{"class":211},"     \"alamat\"",[201,548,471],{"class":215},[201,550,551],{"class":211},"\"Jl. Melati No. 1, Bogor\"",[201,553,321],{"class":215},[201,555,556],{"class":211},"\"nama_wali\"",[201,558,471],{"class":215},[201,560,561],{"class":211},"\"Bapak Hasan\"",[201,563,564],{"class":215},"},\n",[201,566,568,570,572,574,577,579,581,583,586,588,590,592,595,597,599,601,603],{"class":203,"line":567},18,[201,569,465],{"class":215},[201,571,468],{"class":211},[201,573,471],{"class":215},[201,575,576],{"class":211},"\"A002\"",[201,578,321],{"class":215},[201,580,479],{"class":211},[201,582,471],{"class":215},[201,584,585],{"class":211},"\"Budi Santoso\"",[201,587,321],{"class":215},[201,589,489],{"class":211},[201,591,471],{"class":215},[201,593,594],{"class":211},"\"8B\"",[201,596,321],{"class":215},[201,598,499],{"class":211},[201,600,471],{"class":215},[201,602,504],{"class":211},[201,604,507],{"class":215},[201,606,608,610,612,614,616,618,620,623,625,627,629,632],{"class":203,"line":607},19,[201,609,513],{"class":211},[201,611,471],{"class":215},[201,613,518],{"class":211},[201,615,321],{"class":215},[201,617,523],{"class":211},[201,619,471],{"class":215},[201,621,622],{"class":211},"\"3171xxxxxxxx0002\"",[201,624,321],{"class":215},[201,626,533],{"class":211},[201,628,471],{"class":215},[201,630,631],{"class":211},"\"0812xxxx002\"",[201,633,507],{"class":215},[201,635,637,639,641,644,646,648,650,653],{"class":203,"line":636},20,[201,638,546],{"class":211},[201,640,471],{"class":215},[201,642,643],{"class":211},"\"Jl. Mawar No. 2, Depok\"",[201,645,321],{"class":215},[201,647,556],{"class":211},[201,649,471],{"class":215},[201,651,652],{"class":211},"\"Ibu Sari\"",[201,654,564],{"class":215},[201,656,658,660,662,664,667,669,671,673,676,678,680,682,684,686,688,690,693],{"class":203,"line":657},21,[201,659,465],{"class":215},[201,661,468],{"class":211},[201,663,471],{"class":215},[201,665,666],{"class":211},"\"A003\"",[201,668,321],{"class":215},[201,670,479],{"class":211},[201,672,471],{"class":215},[201,674,675],{"class":211},"\"Citra Lestari\"",[201,677,321],{"class":215},[201,679,489],{"class":211},[201,681,471],{"class":215},[201,683,494],{"class":211},[201,685,321],{"class":215},[201,687,499],{"class":211},[201,689,471],{"class":215},[201,691,692],{"class":211},"\"Aisyah 2\"",[201,694,507],{"class":215},[201,696,698,700,702,705,707,709,711,714,716,718,720,723],{"class":203,"line":697},22,[201,699,513],{"class":211},[201,701,471],{"class":215},[201,703,704],{"class":211},"\"izin\"",[201,706,321],{"class":215},[201,708,523],{"class":211},[201,710,471],{"class":215},[201,712,713],{"class":211},"\"3171xxxxxxxx0003\"",[201,715,321],{"class":215},[201,717,533],{"class":211},[201,719,471],{"class":215},[201,721,722],{"class":211},"\"0812xxxx003\"",[201,724,507],{"class":215},[201,726,728,730,732,735,737,739,741,744],{"class":203,"line":727},23,[201,729,546],{"class":211},[201,731,471],{"class":215},[201,733,734],{"class":211},"\"Jl. Kenanga No. 3, Bekasi\"",[201,736,321],{"class":215},[201,738,556],{"class":211},[201,740,471],{"class":215},[201,742,743],{"class":211},"\"Bapak Joko\"",[201,745,564],{"class":215},[201,747,749],{"class":203,"line":748},24,[201,750,751],{"class":215},"]\n",[201,753,755],{"class":203,"line":754},25,[201,756,245],{"emptyLinePlaceholder":244},[201,758,760,763,765],{"class":203,"line":759},26,[201,761,762],{"class":219},"DATA_KAMAR",[201,764,440],{"class":374},[201,766,459],{"class":215},[201,768,770,772,774,776,778,780,783,785,788,790,793,795,798,800,803,805,808],{"class":203,"line":769},27,[201,771,465],{"class":215},[201,773,479],{"class":211},[201,775,471],{"class":215},[201,777,504],{"class":211},[201,779,321],{"class":215},[201,781,782],{"class":211},"\"gedung\"",[201,784,471],{"class":215},[201,786,787],{"class":211},"\"Putra\"",[201,789,321],{"class":215},[201,791,792],{"class":211},"\"kapasitas\"",[201,794,471],{"class":215},[201,796,797],{"class":219},"6",[201,799,321],{"class":215},[201,801,802],{"class":211},"\"musyrif\"",[201,804,471],{"class":215},[201,806,807],{"class":211},"\"Ust. Ridwan\"",[201,809,564],{"class":215},[201,811,813,815,817,819,821,823,825,827,830,832,834,836,838,840,842,844,847],{"class":203,"line":812},28,[201,814,465],{"class":215},[201,816,479],{"class":211},[201,818,471],{"class":215},[201,820,692],{"class":211},[201,822,321],{"class":215},[201,824,782],{"class":211},[201,826,471],{"class":215},[201,828,829],{"class":211},"\"Putri\"",[201,831,321],{"class":215},[201,833,792],{"class":211},[201,835,471],{"class":215},[201,837,797],{"class":219},[201,839,321],{"class":215},[201,841,802],{"class":211},[201,843,471],{"class":215},[201,845,846],{"class":211},"\"Ustz. Halimah\"",[201,848,564],{"class":215},[201,850,852],{"class":203,"line":851},29,[201,853,751],{"class":215},[201,855,857],{"class":203,"line":856},30,[201,858,245],{"emptyLinePlaceholder":244},[201,860,862,865,867],{"class":203,"line":861},31,[201,863,864],{"class":219},"DATA_ABSENSI",[201,866,440],{"class":374},[201,868,869],{"class":215}," {\n",[201,871,873,876],{"class":203,"line":872},32,[201,874,875],{"class":211},"    \"2026-10-06\"",[201,877,878],{"class":215},": [\n",[201,880,882,885,888,890,892,894,897,899,902],{"class":203,"line":881},33,[201,883,884],{"class":215},"        {",[201,886,887],{"class":211},"\"id_anak\"",[201,889,471],{"class":215},[201,891,474],{"class":211},[201,893,321],{"class":215},[201,895,896],{"class":211},"\"status\"",[201,898,471],{"class":215},[201,900,901],{"class":211},"\"hadir\"",[201,903,564],{"class":215},[201,905,907,909,911,913,915,917,919,921,923],{"class":203,"line":906},34,[201,908,884],{"class":215},[201,910,887],{"class":211},[201,912,471],{"class":215},[201,914,576],{"class":211},[201,916,321],{"class":215},[201,918,896],{"class":211},[201,920,471],{"class":215},[201,922,901],{"class":211},[201,924,564],{"class":215},[201,926,928,930,932,934,936,938,940,942,944,946,949,951,954],{"class":203,"line":927},35,[201,929,884],{"class":215},[201,931,887],{"class":211},[201,933,471],{"class":215},[201,935,666],{"class":211},[201,937,321],{"class":215},[201,939,896],{"class":211},[201,941,471],{"class":215},[201,943,704],{"class":211},[201,945,321],{"class":215},[201,947,948],{"class":211},"\"keterangan\"",[201,950,471],{"class":215},[201,952,953],{"class":211},"\"Pulang, acara keluarga\"",[201,955,564],{"class":215},[201,957,959],{"class":203,"line":958},36,[201,960,961],{"class":215},"    ]\n",[201,963,965],{"class":203,"line":964},37,[201,966,967],{"class":215},"}\n",[201,969,971],{"class":203,"line":970},38,[201,972,245],{"emptyLinePlaceholder":244},[201,974,976],{"class":203,"line":975},39,[201,977,245],{"emptyLinePlaceholder":244},[201,979,981,984,987,990,993],{"class":203,"line":980},40,[201,982,983],{"class":374},"def",[201,985,986],{"class":207}," cek_token",[201,988,989],{"class":215},"(authorization: Optional[",[201,991,992],{"class":219},"str",[201,994,995],{"class":215},"]):\n",[201,997,999,1002,1005,1008,1011,1014,1017,1020],{"class":203,"line":998},41,[201,1000,1001],{"class":374},"    if",[201,1003,1004],{"class":215}," authorization ",[201,1006,1007],{"class":374},"!=",[201,1009,1010],{"class":374}," f",[201,1012,1013],{"class":211},"\"Bearer ",[201,1015,1016],{"class":219},"{TOKEN_VALID}",[201,1018,1019],{"class":211},"\"",[201,1021,1022],{"class":215},":\n",[201,1024,1026,1029,1032,1035,1037,1040,1042,1045,1047,1050],{"class":203,"line":1025},42,[201,1027,1028],{"class":374},"        raise",[201,1030,1031],{"class":215}," HTTPException(",[201,1033,1034],{"class":417},"status_code",[201,1036,411],{"class":374},[201,1038,1039],{"class":219},"401",[201,1041,321],{"class":215},[201,1043,1044],{"class":417},"detail",[201,1046,411],{"class":374},[201,1048,1049],{"class":211},"\"Token tidak valid\"",[201,1051,426],{"class":215},[201,1053,1055],{"class":203,"line":1054},43,[201,1056,245],{"emptyLinePlaceholder":244},[201,1058,1060],{"class":203,"line":1059},44,[201,1061,245],{"emptyLinePlaceholder":244},[201,1063,1065,1068,1071,1074],{"class":203,"line":1064},45,[201,1066,1067],{"class":207},"@app.get",[201,1069,1070],{"class":215},"(",[201,1072,1073],{"class":211},"\"\u002Fanak\"",[201,1075,426],{"class":215},[201,1077,1079,1081,1084],{"class":203,"line":1078},46,[201,1080,983],{"class":374},[201,1082,1083],{"class":207}," list_anak",[201,1085,1086],{"class":215},"(\n",[201,1088,1090,1093,1095,1098,1100,1103],{"class":203,"line":1089},47,[201,1091,1092],{"class":215},"    nama: Optional[",[201,1094,992],{"class":219},[201,1096,1097],{"class":215},"] ",[201,1099,411],{"class":374},[201,1101,1102],{"class":219}," None",[201,1104,507],{"class":215},[201,1106,1108,1111,1113,1115,1117,1119],{"class":203,"line":1107},48,[201,1109,1110],{"class":215},"    kelas: Optional[",[201,1112,992],{"class":219},[201,1114,1097],{"class":215},[201,1116,411],{"class":374},[201,1118,1102],{"class":219},[201,1120,507],{"class":215},[201,1122,1124,1127,1129,1131,1133,1135],{"class":203,"line":1123},49,[201,1125,1126],{"class":215},"    kamar: Optional[",[201,1128,992],{"class":219},[201,1130,1097],{"class":215},[201,1132,411],{"class":374},[201,1134,1102],{"class":219},[201,1136,507],{"class":215},[201,1138,1140,1143,1146,1148,1151,1154,1156,1159,1161,1164],{"class":203,"line":1139},50,[201,1141,1142],{"class":215},"    limit: ",[201,1144,1145],{"class":219},"int",[201,1147,440],{"class":374},[201,1149,1150],{"class":215}," Query(",[201,1152,1153],{"class":219},"20",[201,1155,321],{"class":215},[201,1157,1158],{"class":417},"le",[201,1160,411],{"class":374},[201,1162,1163],{"class":219},"100",[201,1165,1166],{"class":215},"),\n",[201,1168,1170,1173,1175,1177,1179,1182,1185],{"class":203,"line":1169},51,[201,1171,1172],{"class":215},"    authorization: Optional[",[201,1174,992],{"class":219},[201,1176,1097],{"class":215},[201,1178,411],{"class":374},[201,1180,1181],{"class":215}," Header(",[201,1183,1184],{"class":219},"None",[201,1186,1166],{"class":215},[201,1188,1190],{"class":203,"line":1189},52,[201,1191,1192],{"class":215},"):\n",[201,1194,1196],{"class":203,"line":1195},53,[201,1197,1198],{"class":215},"    cek_token(authorization)\n",[201,1200,1202,1205,1207],{"class":203,"line":1201},54,[201,1203,1204],{"class":215},"    hasil ",[201,1206,411],{"class":374},[201,1208,1209],{"class":219}," DATA_ANAK\n",[201,1211,1213,1215],{"class":203,"line":1212},55,[201,1214,1001],{"class":374},[201,1216,1217],{"class":215}," nama:\n",[201,1219,1221,1224,1226,1229,1232,1235,1238,1241,1244,1247,1249,1252,1254],{"class":203,"line":1220},56,[201,1222,1223],{"class":215},"        hasil ",[201,1225,411],{"class":374},[201,1227,1228],{"class":215}," [a ",[201,1230,1231],{"class":374},"for",[201,1233,1234],{"class":215}," a ",[201,1236,1237],{"class":374},"in",[201,1239,1240],{"class":215}," hasil ",[201,1242,1243],{"class":374},"if",[201,1245,1246],{"class":215}," nama.lower() ",[201,1248,1237],{"class":374},[201,1250,1251],{"class":215}," a[",[201,1253,479],{"class":211},[201,1255,1256],{"class":215},"].lower()]\n",[201,1258,1260,1262],{"class":203,"line":1259},57,[201,1261,1001],{"class":374},[201,1263,1264],{"class":215}," kelas:\n",[201,1266,1268,1270,1272,1274,1276,1278,1280,1282,1284,1286,1288,1291,1294],{"class":203,"line":1267},58,[201,1269,1223],{"class":215},[201,1271,411],{"class":374},[201,1273,1228],{"class":215},[201,1275,1231],{"class":374},[201,1277,1234],{"class":215},[201,1279,1237],{"class":374},[201,1281,1240],{"class":215},[201,1283,1243],{"class":374},[201,1285,1251],{"class":215},[201,1287,489],{"class":211},[201,1289,1290],{"class":215},"].lower() ",[201,1292,1293],{"class":374},"==",[201,1295,1296],{"class":215}," kelas.lower()]\n",[201,1298,1300,1302],{"class":203,"line":1299},59,[201,1301,1001],{"class":374},[201,1303,1304],{"class":215}," kamar:\n",[201,1306,1308,1310,1312,1314,1316,1318,1320,1322,1324,1326,1328,1330,1332],{"class":203,"line":1307},60,[201,1309,1223],{"class":215},[201,1311,411],{"class":374},[201,1313,1228],{"class":215},[201,1315,1231],{"class":374},[201,1317,1234],{"class":215},[201,1319,1237],{"class":374},[201,1321,1240],{"class":215},[201,1323,1243],{"class":374},[201,1325,1251],{"class":215},[201,1327,499],{"class":211},[201,1329,1290],{"class":215},[201,1331,1293],{"class":374},[201,1333,1334],{"class":215}," kamar.lower()]\n",[201,1336,1338,1341,1344,1347,1349,1352,1355,1358],{"class":203,"line":1337},61,[201,1339,1340],{"class":374},"    return",[201,1342,1343],{"class":215}," {",[201,1345,1346],{"class":211},"\"total\"",[201,1348,471],{"class":215},[201,1350,1351],{"class":219},"len",[201,1353,1354],{"class":215},"(hasil), ",[201,1356,1357],{"class":211},"\"data\"",[201,1359,1360],{"class":215},": hasil[:limit]}\n",[201,1362,1364],{"class":203,"line":1363},62,[201,1365,245],{"emptyLinePlaceholder":244},[201,1367,1369],{"class":203,"line":1368},63,[201,1370,245],{"emptyLinePlaceholder":244},[201,1372,1374,1376,1378,1381,1384,1386],{"class":203,"line":1373},64,[201,1375,1067],{"class":207},[201,1377,1070],{"class":215},[201,1379,1380],{"class":211},"\"\u002Fanak\u002F",[201,1382,1383],{"class":219},"{id_anak}",[201,1385,1019],{"class":211},[201,1387,426],{"class":215},[201,1389,1391,1393,1396,1399,1401,1404,1406,1408,1410,1412,1414],{"class":203,"line":1390},65,[201,1392,983],{"class":374},[201,1394,1395],{"class":207}," detail_anak",[201,1397,1398],{"class":215},"(id_anak: ",[201,1400,992],{"class":219},[201,1402,1403],{"class":215},", authorization: Optional[",[201,1405,992],{"class":219},[201,1407,1097],{"class":215},[201,1409,411],{"class":374},[201,1411,1181],{"class":215},[201,1413,1184],{"class":219},[201,1415,1416],{"class":215},")):\n",[201,1418,1420],{"class":203,"line":1419},66,[201,1421,1198],{"class":215},[201,1423,1425,1428,1430,1432,1435],{"class":203,"line":1424},67,[201,1426,1427],{"class":374},"    for",[201,1429,1234],{"class":215},[201,1431,1237],{"class":374},[201,1433,1434],{"class":219}," DATA_ANAK",[201,1436,1022],{"class":215},[201,1438,1440,1443,1445,1447,1449,1451],{"class":203,"line":1439},68,[201,1441,1442],{"class":374},"        if",[201,1444,1251],{"class":215},[201,1446,468],{"class":211},[201,1448,1097],{"class":215},[201,1450,1293],{"class":374},[201,1452,1453],{"class":215}," id_anak:\n",[201,1455,1457,1460],{"class":203,"line":1456},69,[201,1458,1459],{"class":374},"            return",[201,1461,1462],{"class":215}," a\n",[201,1464,1466,1469,1471,1473,1475,1478,1480,1482,1484,1487],{"class":203,"line":1465},70,[201,1467,1468],{"class":374},"    raise",[201,1470,1031],{"class":215},[201,1472,1034],{"class":417},[201,1474,411],{"class":374},[201,1476,1477],{"class":219},"404",[201,1479,321],{"class":215},[201,1481,1044],{"class":417},[201,1483,411],{"class":374},[201,1485,1486],{"class":211},"\"Anak tidak ditemukan\"",[201,1488,426],{"class":215},[201,1490,1492],{"class":203,"line":1491},71,[201,1493,245],{"emptyLinePlaceholder":244},[201,1495,1497],{"class":203,"line":1496},72,[201,1498,245],{"emptyLinePlaceholder":244},[201,1500,1502,1504,1506,1509],{"class":203,"line":1501},73,[201,1503,1067],{"class":207},[201,1505,1070],{"class":215},[201,1507,1508],{"class":211},"\"\u002Fkamar\"",[201,1510,426],{"class":215},[201,1512,1514,1516,1519,1521,1523,1525,1527,1529,1531],{"class":203,"line":1513},74,[201,1515,983],{"class":374},[201,1517,1518],{"class":207}," list_kamar",[201,1520,989],{"class":215},[201,1522,992],{"class":219},[201,1524,1097],{"class":215},[201,1526,411],{"class":374},[201,1528,1181],{"class":215},[201,1530,1184],{"class":219},[201,1532,1416],{"class":215},[201,1534,1536],{"class":203,"line":1535},75,[201,1537,1198],{"class":215},[201,1539,1541,1543,1545],{"class":203,"line":1540},76,[201,1542,1204],{"class":215},[201,1544,411],{"class":374},[201,1546,1547],{"class":215}," []\n",[201,1549,1551,1553,1556,1558,1561],{"class":203,"line":1550},77,[201,1552,1427],{"class":374},[201,1554,1555],{"class":215}," k ",[201,1557,1237],{"class":374},[201,1559,1560],{"class":219}," DATA_KAMAR",[201,1562,1022],{"class":215},[201,1564,1566,1569,1571,1574,1577,1579,1581,1583,1585,1588,1590,1592,1594,1596,1599,1601],{"class":203,"line":1565},78,[201,1567,1568],{"class":215},"        terisi ",[201,1570,411],{"class":374},[201,1572,1573],{"class":219}," len",[201,1575,1576],{"class":215},"([a ",[201,1578,1231],{"class":374},[201,1580,1234],{"class":215},[201,1582,1237],{"class":374},[201,1584,1434],{"class":219},[201,1586,1587],{"class":374}," if",[201,1589,1251],{"class":215},[201,1591,499],{"class":211},[201,1593,1097],{"class":215},[201,1595,1293],{"class":374},[201,1597,1598],{"class":215}," k[",[201,1600,479],{"class":211},[201,1602,1603],{"class":215},"]])\n",[201,1605,1607,1610,1613,1616,1619],{"class":203,"line":1606},79,[201,1608,1609],{"class":215},"        hasil.append({",[201,1611,1612],{"class":374},"**",[201,1614,1615],{"class":215},"k, ",[201,1617,1618],{"class":211},"\"terisi\"",[201,1620,1621],{"class":215},": terisi})\n",[201,1623,1625,1627,1629,1631],{"class":203,"line":1624},80,[201,1626,1340],{"class":374},[201,1628,1343],{"class":215},[201,1630,1357],{"class":211},[201,1632,1633],{"class":215},": hasil}\n",[201,1635,1637],{"class":203,"line":1636},81,[201,1638,245],{"emptyLinePlaceholder":244},[201,1640,1642],{"class":203,"line":1641},82,[201,1643,245],{"emptyLinePlaceholder":244},[201,1645,1647,1649,1651,1654],{"class":203,"line":1646},83,[201,1648,1067],{"class":207},[201,1650,1070],{"class":215},[201,1652,1653],{"class":211},"\"\u002Fabsensi\"",[201,1655,426],{"class":215},[201,1657,1659,1661,1664,1667,1669,1671,1673,1675,1677,1679,1681],{"class":203,"line":1658},84,[201,1660,983],{"class":374},[201,1662,1663],{"class":207}," absensi",[201,1665,1666],{"class":215},"(tanggal: ",[201,1668,992],{"class":219},[201,1670,1403],{"class":215},[201,1672,992],{"class":219},[201,1674,1097],{"class":215},[201,1676,411],{"class":374},[201,1678,1181],{"class":215},[201,1680,1184],{"class":219},[201,1682,1416],{"class":215},[201,1684,1686],{"class":203,"line":1685},85,[201,1687,1198],{"class":215},[201,1689,1691,1693,1695,1698,1701,1703,1705,1707],{"class":203,"line":1690},86,[201,1692,1340],{"class":374},[201,1694,1343],{"class":215},[201,1696,1697],{"class":211},"\"tanggal\"",[201,1699,1700],{"class":215},": tanggal, ",[201,1702,1357],{"class":211},[201,1704,471],{"class":215},[201,1706,864],{"class":219},[201,1708,1709],{"class":215},".get(tanggal, [])}\n",[10,1711,1712],{},"Jalankan:",[101,1714,1716],{"className":195,"code":1715,"language":197,"meta":109,"style":109},"uvicorn mock_service:app --port 8000\n",[41,1717,1718],{"__ignoreMap":109},[201,1719,1720,1723,1726,1729],{"class":203,"line":204},[201,1721,1722],{"class":207},"uvicorn",[201,1724,1725],{"class":211}," mock_service:app",[201,1727,1728],{"class":219}," --port",[201,1730,1731],{"class":219}," 8000\n",[10,1733,1734,1735,321,1738,321,1741,1744],{},"Perhatikan bahwa data mock sengaja berisi field sensitif (",[41,1736,1737],{},"nik",[41,1739,1740],{},"no_hp_wali",[41,1742,1743],{},"alamat","). Nanti kita akan menyaringnya di MCP server agar tidak sampai ke model AI.",[82,1746],{},[85,1748,1750],{"id":1749},"langkah-3-tulis-mcp-server","Langkah 3 — Tulis MCP Server",[10,1752,1753,1754,280],{},"Inilah inti artikel ini. Simpan sebagai ",[41,1755,1756],{},"server.py",[101,1758,1760],{"className":338,"code":1759,"language":229,"meta":109,"style":109},"\"\"\"\nMCP Server: Data Anak Asrama\nMenjembatani AI (Claude, dll.) dengan service data anak asrama via REST API.\n\nJalankan (stdio): python server.py\nTesting:          npx @modelcontextprotocol\u002Finspector python server.py\n\"\"\"\nimport os\nimport sys\nimport logging\nfrom typing import Any, Optional\n\nimport httpx\nfrom mcp.server.mcpserver import MCPServer  # SDK v2 (dulu: FastMCP)\n\n# ---------------------------------------------------------------------------\n# Konfigurasi\n# ---------------------------------------------------------------------------\nAPI_BASE_URL = os.getenv(\"ASRAMA_API_URL\", \"http:\u002F\u002Flocalhost:8000\")\nAPI_TOKEN = os.getenv(\"ASRAMA_API_TOKEN\", \"\")\n\n# PENTING: mode stdio memakai stdout untuk protokol MCP.\n# Jangan pernah print() ke stdout, log harus ke stderr.\nlogging.basicConfig(stream=sys.stderr, level=logging.INFO)\nlog = logging.getLogger(\"mcp-asrama\")\n\n# Field sensitif yang tidak boleh dikirim ke AI (data anak = data pribadi).\nFIELD_SENSITIF = {\"nik\", \"no_hp_wali\", \"alamat\"}\n\nmcp = MCPServer(\n    \"asrama\",\n    instructions=\"Akses read-only ke data anak asrama: pencarian anak, detail, kamar, dan absensi.\",\n)\n\n\n# ---------------------------------------------------------------------------\n# Helper\n# ---------------------------------------------------------------------------\nasync def panggil_api(path: str, params: Optional[dict] = None) -> Any:\n    \"\"\"Panggil service asrama. Error dikembalikan sebagai dict agar AI bisa membacanya.\"\"\"\n    headers = {\"Authorization\": f\"Bearer {API_TOKEN}\"} if API_TOKEN else {}\n    # buang parameter yang kosong\n    params = {k: v for k, v in (params or {}).items() if v not in (None, \"\")}\n    try:\n        async with httpx.AsyncClient(base_url=API_BASE_URL, timeout=15) as client:\n            resp = await client.get(path, params=params, headers=headers)\n            resp.raise_for_status()\n            return resp.json()\n    except httpx.HTTPStatusError as e:\n        log.warning(\"API error %s pada %s\", e.response.status_code, path)\n        return {\"error\": f\"Service mengembalikan status {e.response.status_code}\",\n                \"detail\": e.response.text[:300]}\n    except httpx.RequestError as e:\n        log.error(\"Gagal konek ke service: %s\", e)\n        return {\"error\": \"Tidak bisa terhubung ke service asrama\"}\n\n\ndef sembunyikan_sensitif(data: Any) -> Any:\n    \"\"\"Hapus field sensitif secara rekursif sebelum dikirim ke model.\"\"\"\n    if isinstance(data, dict):\n        return {k: sembunyikan_sensitif(v) for k, v in data.items() if k not in FIELD_SENSITIF}\n    if isinstance(data, list):\n        return [sembunyikan_sensitif(x) for x in data]\n    return data\n\n\n# ---------------------------------------------------------------------------\n# Tools: aksi yang bisa dipanggil AI\n# Docstring + type hint dipakai otomatis sebagai deskripsi & schema tool,\n# jadi tulis sejelas mungkin.\n# ---------------------------------------------------------------------------\n@mcp.tool()\nasync def cari_anak(\n    nama: Optional[str] = None,\n    kelas: Optional[str] = None,\n    kamar: Optional[str] = None,\n    limit: int = 20,\n) -> dict:\n    \"\"\"Cari anak asrama berdasarkan nama (sebagian), kelas (mis. '7A'), atau nama kamar.\n    Semua filter opsional. Mengembalikan daftar ringkas anak beserta id-nya.\"\"\"\n    hasil = await panggil_api(\"\u002Fanak\", {\"nama\": nama, \"kelas\": kelas,\n                                        \"kamar\": kamar, \"limit\": min(limit, 100)})\n    return sembunyikan_sensitif(hasil)\n\n\n@mcp.tool()\nasync def detail_anak(id_anak: str) -> dict:\n    \"\"\"Ambil detail satu anak asrama berdasarkan id (mis. 'A001').\n    Gunakan cari_anak dulu jika id belum diketahui.\"\"\"\n    hasil = await panggil_api(f\"\u002Fanak\u002F{id_anak}\")\n    return sembunyikan_sensitif(hasil)\n\n\n@mcp.tool()\nasync def daftar_kamar() -> dict:\n    \"\"\"Daftar semua kamar asrama beserta gedung, kapasitas, jumlah terisi, dan musyrif.\"\"\"\n    return await panggil_api(\"\u002Fkamar\")\n\n\n@mcp.tool()\nasync def absensi_harian(tanggal: str) -> dict:\n    \"\"\"Rekap absensi anak asrama pada tanggal tertentu. Format tanggal: YYYY-MM-DD.\"\"\"\n    return await panggil_api(\"\u002Fabsensi\", {\"tanggal\": tanggal})\n\n\n# ---------------------------------------------------------------------------\n# Resource: data yang bisa \"dibaca\" sebagai konteks (opsional)\n# ---------------------------------------------------------------------------\n@mcp.resource(\"asrama:\u002F\u002Fkamar\")\nasync def resource_kamar() -> str:\n    \"\"\"Daftar kamar asrama sebagai konteks.\"\"\"\n    import json\n    data = await panggil_api(\"\u002Fkamar\")\n    return json.dumps(data, ensure_ascii=False, indent=2)\n\n\n# ---------------------------------------------------------------------------\n# Prompt: template siap pakai (opsional)\n# ---------------------------------------------------------------------------\n@mcp.prompt()\ndef laporan_kamar(nama_kamar: str) -> str:\n    \"\"\"Template untuk membuat laporan singkat satu kamar.\"\"\"\n    return (f\"Buatkan laporan singkat untuk kamar '{nama_kamar}': daftar penghuni, \"\n            f\"jumlah terisi vs kapasitas, dan siapa yang tidak hadir hari ini.\")\n\n\nif __name__ == \"__main__\":\n    log.info(\"MCP Asrama berjalan, API: %s\", API_BASE_URL)\n    mcp.run()  # default transport: stdio\n",[41,1761,1762,1766,1771,1776,1780,1785,1790,1794,1801,1808,1815,1826,1830,1837,1852,1856,1861,1866,1870,1890,1909,1913,1918,1923,1949,1964,1968,1973,1995,1999,2009,2016,2028,2032,2036,2040,2044,2049,2053,2084,2089,2127,2132,2180,2187,2224,2253,2258,2265,2278,2299,2329,2343,2354,2369,2384,2388,2392,2402,2407,2421,2450,2463,2480,2487,2491,2495,2499,2504,2509,2514,2518,2526,2537,2551,2565,2579,2592,2601,2606,2611,2637,2661,2668,2672,2676,2682,2701,2707,2713,2739,2746,2751,2756,2763,2780,2786,2799,2804,2809,2816,2836,2842,2860,2865,2870,2875,2881,2886,2899,2915,2921,2930,2946,2974,2979,2984,2989,2995,3000,3008,3027,3033,3055,3066,3071,3076,3092,3111],{"__ignoreMap":109},[201,1763,1764],{"class":203,"line":204},[201,1765,346],{"class":211},[201,1767,1768],{"class":203,"line":226},[201,1769,1770],{"class":211},"MCP Server: Data Anak Asrama\n",[201,1772,1773],{"class":203,"line":241},[201,1774,1775],{"class":211},"Menjembatani AI (Claude, dll.) dengan service data anak asrama via REST API.\n",[201,1777,1778],{"class":203,"line":248},[201,1779,245],{"emptyLinePlaceholder":244},[201,1781,1782],{"class":203,"line":255},[201,1783,1784],{"class":211},"Jalankan (stdio): python server.py\n",[201,1786,1787],{"class":203,"line":261},[201,1788,1789],{"class":211},"Testing:          npx @modelcontextprotocol\u002Finspector python server.py\n",[201,1791,1792],{"class":203,"line":267},[201,1793,346],{"class":211},[201,1795,1796,1798],{"class":203,"line":387},[201,1797,381],{"class":374},[201,1799,1800],{"class":215}," os\n",[201,1802,1803,1805],{"class":203,"line":400},[201,1804,381],{"class":374},[201,1806,1807],{"class":215}," sys\n",[201,1809,1810,1812],{"class":203,"line":405},[201,1811,381],{"class":374},[201,1813,1814],{"class":215}," logging\n",[201,1816,1817,1819,1821,1823],{"class":203,"line":429},[201,1818,375],{"class":374},[201,1820,392],{"class":215},[201,1822,381],{"class":374},[201,1824,1825],{"class":215}," Any, Optional\n",[201,1827,1828],{"class":203,"line":434},[201,1829,245],{"emptyLinePlaceholder":244},[201,1831,1832,1834],{"class":203,"line":446},[201,1833,381],{"class":374},[201,1835,1836],{"class":215}," httpx\n",[201,1838,1839,1841,1844,1846,1849],{"class":203,"line":451},[201,1840,375],{"class":374},[201,1842,1843],{"class":215}," mcp.server.mcpserver ",[201,1845,381],{"class":374},[201,1847,1848],{"class":215}," MCPServer  ",[201,1850,1851],{"class":251},"# SDK v2 (dulu: FastMCP)\n",[201,1853,1854],{"class":203,"line":462},[201,1855,245],{"emptyLinePlaceholder":244},[201,1857,1858],{"class":203,"line":510},[201,1859,1860],{"class":251},"# ---------------------------------------------------------------------------\n",[201,1862,1863],{"class":203,"line":543},[201,1864,1865],{"class":251},"# Konfigurasi\n",[201,1867,1868],{"class":203,"line":567},[201,1869,1860],{"class":251},[201,1871,1872,1875,1877,1880,1883,1885,1888],{"class":203,"line":607},[201,1873,1874],{"class":219},"API_BASE_URL",[201,1876,440],{"class":374},[201,1878,1879],{"class":215}," os.getenv(",[201,1881,1882],{"class":211},"\"ASRAMA_API_URL\"",[201,1884,321],{"class":215},[201,1886,1887],{"class":211},"\"http:\u002F\u002Flocalhost:8000\"",[201,1889,426],{"class":215},[201,1891,1892,1895,1897,1899,1902,1904,1907],{"class":203,"line":636},[201,1893,1894],{"class":219},"API_TOKEN",[201,1896,440],{"class":374},[201,1898,1879],{"class":215},[201,1900,1901],{"class":211},"\"ASRAMA_API_TOKEN\"",[201,1903,321],{"class":215},[201,1905,1906],{"class":211},"\"\"",[201,1908,426],{"class":215},[201,1910,1911],{"class":203,"line":657},[201,1912,245],{"emptyLinePlaceholder":244},[201,1914,1915],{"class":203,"line":697},[201,1916,1917],{"class":251},"# PENTING: mode stdio memakai stdout untuk protokol MCP.\n",[201,1919,1920],{"class":203,"line":727},[201,1921,1922],{"class":251},"# Jangan pernah print() ke stdout, log harus ke stderr.\n",[201,1924,1925,1928,1931,1933,1936,1939,1941,1944,1947],{"class":203,"line":748},[201,1926,1927],{"class":215},"logging.basicConfig(",[201,1929,1930],{"class":417},"stream",[201,1932,411],{"class":374},[201,1934,1935],{"class":215},"sys.stderr, ",[201,1937,1938],{"class":417},"level",[201,1940,411],{"class":374},[201,1942,1943],{"class":215},"logging.",[201,1945,1946],{"class":219},"INFO",[201,1948,426],{"class":215},[201,1950,1951,1954,1956,1959,1962],{"class":203,"line":754},[201,1952,1953],{"class":215},"log ",[201,1955,411],{"class":374},[201,1957,1958],{"class":215}," logging.getLogger(",[201,1960,1961],{"class":211},"\"mcp-asrama\"",[201,1963,426],{"class":215},[201,1965,1966],{"class":203,"line":759},[201,1967,245],{"emptyLinePlaceholder":244},[201,1969,1970],{"class":203,"line":769},[201,1971,1972],{"class":251},"# Field sensitif yang tidak boleh dikirim ke AI (data anak = data pribadi).\n",[201,1974,1975,1978,1980,1982,1984,1986,1988,1990,1993],{"class":203,"line":812},[201,1976,1977],{"class":219},"FIELD_SENSITIF",[201,1979,440],{"class":374},[201,1981,1343],{"class":215},[201,1983,523],{"class":211},[201,1985,321],{"class":215},[201,1987,533],{"class":211},[201,1989,321],{"class":215},[201,1991,1992],{"class":211},"\"alamat\"",[201,1994,967],{"class":215},[201,1996,1997],{"class":203,"line":851},[201,1998,245],{"emptyLinePlaceholder":244},[201,2000,2001,2004,2006],{"class":203,"line":856},[201,2002,2003],{"class":215},"mcp ",[201,2005,411],{"class":374},[201,2007,2008],{"class":215}," MCPServer(\n",[201,2010,2011,2014],{"class":203,"line":861},[201,2012,2013],{"class":211},"    \"asrama\"",[201,2015,507],{"class":215},[201,2017,2018,2021,2023,2026],{"class":203,"line":872},[201,2019,2020],{"class":417},"    instructions",[201,2022,411],{"class":374},[201,2024,2025],{"class":211},"\"Akses read-only ke data anak asrama: pencarian anak, detail, kamar, dan absensi.\"",[201,2027,507],{"class":215},[201,2029,2030],{"class":203,"line":881},[201,2031,426],{"class":215},[201,2033,2034],{"class":203,"line":906},[201,2035,245],{"emptyLinePlaceholder":244},[201,2037,2038],{"class":203,"line":927},[201,2039,245],{"emptyLinePlaceholder":244},[201,2041,2042],{"class":203,"line":958},[201,2043,1860],{"class":251},[201,2045,2046],{"class":203,"line":964},[201,2047,2048],{"class":251},"# Helper\n",[201,2050,2051],{"class":203,"line":970},[201,2052,1860],{"class":251},[201,2054,2055,2058,2061,2064,2067,2069,2072,2075,2077,2079,2081],{"class":203,"line":975},[201,2056,2057],{"class":374},"async",[201,2059,2060],{"class":374}," def",[201,2062,2063],{"class":207}," panggil_api",[201,2065,2066],{"class":215},"(path: ",[201,2068,992],{"class":219},[201,2070,2071],{"class":215},", params: Optional[",[201,2073,2074],{"class":219},"dict",[201,2076,1097],{"class":215},[201,2078,411],{"class":374},[201,2080,1102],{"class":219},[201,2082,2083],{"class":215},") -> Any:\n",[201,2085,2086],{"class":203,"line":980},[201,2087,2088],{"class":211},"    \"\"\"Panggil service asrama. Error dikembalikan sebagai dict agar AI bisa membacanya.\"\"\"\n",[201,2090,2091,2094,2096,2098,2101,2103,2106,2108,2111,2113,2116,2118,2121,2124],{"class":203,"line":998},[201,2092,2093],{"class":215},"    headers ",[201,2095,411],{"class":374},[201,2097,1343],{"class":215},[201,2099,2100],{"class":211},"\"Authorization\"",[201,2102,471],{"class":215},[201,2104,2105],{"class":374},"f",[201,2107,1013],{"class":211},[201,2109,2110],{"class":219},"{API_TOKEN}",[201,2112,1019],{"class":211},[201,2114,2115],{"class":215},"} ",[201,2117,1243],{"class":374},[201,2119,2120],{"class":219}," API_TOKEN",[201,2122,2123],{"class":374}," else",[201,2125,2126],{"class":215}," {}\n",[201,2128,2129],{"class":203,"line":1025},[201,2130,2131],{"class":251},"    # buang parameter yang kosong\n",[201,2133,2134,2137,2139,2142,2144,2147,2149,2152,2155,2158,2160,2163,2166,2169,2171,2173,2175,2177],{"class":203,"line":1054},[201,2135,2136],{"class":215},"    params ",[201,2138,411],{"class":374},[201,2140,2141],{"class":215}," {k: v ",[201,2143,1231],{"class":374},[201,2145,2146],{"class":215}," k, v ",[201,2148,1237],{"class":374},[201,2150,2151],{"class":215}," (params ",[201,2153,2154],{"class":374},"or",[201,2156,2157],{"class":215}," {}).items() ",[201,2159,1243],{"class":374},[201,2161,2162],{"class":215}," v ",[201,2164,2165],{"class":374},"not",[201,2167,2168],{"class":374}," in",[201,2170,39],{"class":215},[201,2172,1184],{"class":219},[201,2174,321],{"class":215},[201,2176,1906],{"class":211},[201,2178,2179],{"class":215},")}\n",[201,2181,2182,2185],{"class":203,"line":1059},[201,2183,2184],{"class":374},"    try",[201,2186,1022],{"class":215},[201,2188,2189,2192,2195,2198,2201,2203,2205,2207,2210,2212,2215,2218,2221],{"class":203,"line":1064},[201,2190,2191],{"class":374},"        async",[201,2193,2194],{"class":374}," with",[201,2196,2197],{"class":215}," httpx.AsyncClient(",[201,2199,2200],{"class":417},"base_url",[201,2202,411],{"class":374},[201,2204,1874],{"class":219},[201,2206,321],{"class":215},[201,2208,2209],{"class":417},"timeout",[201,2211,411],{"class":374},[201,2213,2214],{"class":219},"15",[201,2216,2217],{"class":215},") ",[201,2219,2220],{"class":374},"as",[201,2222,2223],{"class":215}," client:\n",[201,2225,2226,2229,2231,2234,2237,2240,2242,2245,2248,2250],{"class":203,"line":1078},[201,2227,2228],{"class":215},"            resp ",[201,2230,411],{"class":374},[201,2232,2233],{"class":374}," await",[201,2235,2236],{"class":215}," client.get(path, ",[201,2238,2239],{"class":417},"params",[201,2241,411],{"class":374},[201,2243,2244],{"class":215},"params, ",[201,2246,2247],{"class":417},"headers",[201,2249,411],{"class":374},[201,2251,2252],{"class":215},"headers)\n",[201,2254,2255],{"class":203,"line":1089},[201,2256,2257],{"class":215},"            resp.raise_for_status()\n",[201,2259,2260,2262],{"class":203,"line":1107},[201,2261,1459],{"class":374},[201,2263,2264],{"class":215}," resp.json()\n",[201,2266,2267,2270,2273,2275],{"class":203,"line":1123},[201,2268,2269],{"class":374},"    except",[201,2271,2272],{"class":215}," httpx.HTTPStatusError ",[201,2274,2220],{"class":374},[201,2276,2277],{"class":215}," e:\n",[201,2279,2280,2283,2286,2289,2292,2294,2296],{"class":203,"line":1139},[201,2281,2282],{"class":215},"        log.warning(",[201,2284,2285],{"class":211},"\"API error ",[201,2287,2288],{"class":219},"%s",[201,2290,2291],{"class":211}," pada ",[201,2293,2288],{"class":219},[201,2295,1019],{"class":211},[201,2297,2298],{"class":215},", e.response.status_code, path)\n",[201,2300,2301,2304,2306,2309,2311,2313,2316,2319,2322,2325,2327],{"class":203,"line":1169},[201,2302,2303],{"class":374},"        return",[201,2305,1343],{"class":215},[201,2307,2308],{"class":211},"\"error\"",[201,2310,471],{"class":215},[201,2312,2105],{"class":374},[201,2314,2315],{"class":211},"\"Service mengembalikan status ",[201,2317,2318],{"class":219},"{",[201,2320,2321],{"class":215},"e.response.status_code",[201,2323,2324],{"class":219},"}",[201,2326,1019],{"class":211},[201,2328,507],{"class":215},[201,2330,2331,2334,2337,2340],{"class":203,"line":1189},[201,2332,2333],{"class":211},"                \"detail\"",[201,2335,2336],{"class":215},": e.response.text[:",[201,2338,2339],{"class":219},"300",[201,2341,2342],{"class":215},"]}\n",[201,2344,2345,2347,2350,2352],{"class":203,"line":1195},[201,2346,2269],{"class":374},[201,2348,2349],{"class":215}," httpx.RequestError ",[201,2351,2220],{"class":374},[201,2353,2277],{"class":215},[201,2355,2356,2359,2362,2364,2366],{"class":203,"line":1201},[201,2357,2358],{"class":215},"        log.error(",[201,2360,2361],{"class":211},"\"Gagal konek ke service: ",[201,2363,2288],{"class":219},[201,2365,1019],{"class":211},[201,2367,2368],{"class":215},", e)\n",[201,2370,2371,2373,2375,2377,2379,2382],{"class":203,"line":1212},[201,2372,2303],{"class":374},[201,2374,1343],{"class":215},[201,2376,2308],{"class":211},[201,2378,471],{"class":215},[201,2380,2381],{"class":211},"\"Tidak bisa terhubung ke service asrama\"",[201,2383,967],{"class":215},[201,2385,2386],{"class":203,"line":1220},[201,2387,245],{"emptyLinePlaceholder":244},[201,2389,2390],{"class":203,"line":1259},[201,2391,245],{"emptyLinePlaceholder":244},[201,2393,2394,2396,2399],{"class":203,"line":1267},[201,2395,983],{"class":374},[201,2397,2398],{"class":207}," sembunyikan_sensitif",[201,2400,2401],{"class":215},"(data: Any) -> Any:\n",[201,2403,2404],{"class":203,"line":1299},[201,2405,2406],{"class":211},"    \"\"\"Hapus field sensitif secara rekursif sebelum dikirim ke model.\"\"\"\n",[201,2408,2409,2411,2414,2417,2419],{"class":203,"line":1307},[201,2410,1001],{"class":374},[201,2412,2413],{"class":219}," isinstance",[201,2415,2416],{"class":215},"(data, ",[201,2418,2074],{"class":219},[201,2420,1192],{"class":215},[201,2422,2423,2425,2428,2430,2432,2434,2437,2439,2441,2443,2445,2448],{"class":203,"line":1337},[201,2424,2303],{"class":374},[201,2426,2427],{"class":215}," {k: sembunyikan_sensitif(v) ",[201,2429,1231],{"class":374},[201,2431,2146],{"class":215},[201,2433,1237],{"class":374},[201,2435,2436],{"class":215}," data.items() ",[201,2438,1243],{"class":374},[201,2440,1555],{"class":215},[201,2442,2165],{"class":374},[201,2444,2168],{"class":374},[201,2446,2447],{"class":219}," FIELD_SENSITIF",[201,2449,967],{"class":215},[201,2451,2452,2454,2456,2458,2461],{"class":203,"line":1363},[201,2453,1001],{"class":374},[201,2455,2413],{"class":219},[201,2457,2416],{"class":215},[201,2459,2460],{"class":219},"list",[201,2462,1192],{"class":215},[201,2464,2465,2467,2470,2472,2475,2477],{"class":203,"line":1368},[201,2466,2303],{"class":374},[201,2468,2469],{"class":215}," [sembunyikan_sensitif(x) ",[201,2471,1231],{"class":374},[201,2473,2474],{"class":215}," x ",[201,2476,1237],{"class":374},[201,2478,2479],{"class":215}," data]\n",[201,2481,2482,2484],{"class":203,"line":1373},[201,2483,1340],{"class":374},[201,2485,2486],{"class":215}," data\n",[201,2488,2489],{"class":203,"line":1390},[201,2490,245],{"emptyLinePlaceholder":244},[201,2492,2493],{"class":203,"line":1419},[201,2494,245],{"emptyLinePlaceholder":244},[201,2496,2497],{"class":203,"line":1424},[201,2498,1860],{"class":251},[201,2500,2501],{"class":203,"line":1439},[201,2502,2503],{"class":251},"# Tools: aksi yang bisa dipanggil AI\n",[201,2505,2506],{"class":203,"line":1456},[201,2507,2508],{"class":251},"# Docstring + type hint dipakai otomatis sebagai deskripsi & schema tool,\n",[201,2510,2511],{"class":203,"line":1465},[201,2512,2513],{"class":251},"# jadi tulis sejelas mungkin.\n",[201,2515,2516],{"class":203,"line":1491},[201,2517,1860],{"class":251},[201,2519,2520,2523],{"class":203,"line":1496},[201,2521,2522],{"class":207},"@mcp.tool",[201,2524,2525],{"class":215},"()\n",[201,2527,2528,2530,2532,2535],{"class":203,"line":1501},[201,2529,2057],{"class":374},[201,2531,2060],{"class":374},[201,2533,2534],{"class":207}," cari_anak",[201,2536,1086],{"class":215},[201,2538,2539,2541,2543,2545,2547,2549],{"class":203,"line":1513},[201,2540,1092],{"class":215},[201,2542,992],{"class":219},[201,2544,1097],{"class":215},[201,2546,411],{"class":374},[201,2548,1102],{"class":219},[201,2550,507],{"class":215},[201,2552,2553,2555,2557,2559,2561,2563],{"class":203,"line":1535},[201,2554,1110],{"class":215},[201,2556,992],{"class":219},[201,2558,1097],{"class":215},[201,2560,411],{"class":374},[201,2562,1102],{"class":219},[201,2564,507],{"class":215},[201,2566,2567,2569,2571,2573,2575,2577],{"class":203,"line":1540},[201,2568,1126],{"class":215},[201,2570,992],{"class":219},[201,2572,1097],{"class":215},[201,2574,411],{"class":374},[201,2576,1102],{"class":219},[201,2578,507],{"class":215},[201,2580,2581,2583,2585,2587,2590],{"class":203,"line":1550},[201,2582,1142],{"class":215},[201,2584,1145],{"class":219},[201,2586,440],{"class":374},[201,2588,2589],{"class":219}," 20",[201,2591,507],{"class":215},[201,2593,2594,2597,2599],{"class":203,"line":1565},[201,2595,2596],{"class":215},") -> ",[201,2598,2074],{"class":219},[201,2600,1022],{"class":215},[201,2602,2603],{"class":203,"line":1606},[201,2604,2605],{"class":211},"    \"\"\"Cari anak asrama berdasarkan nama (sebagian), kelas (mis. '7A'), atau nama kamar.\n",[201,2607,2608],{"class":203,"line":1624},[201,2609,2610],{"class":211},"    Semua filter opsional. Mengembalikan daftar ringkas anak beserta id-nya.\"\"\"\n",[201,2612,2613,2615,2617,2619,2622,2624,2627,2629,2632,2634],{"class":203,"line":1636},[201,2614,1204],{"class":215},[201,2616,411],{"class":374},[201,2618,2233],{"class":374},[201,2620,2621],{"class":215}," panggil_api(",[201,2623,1073],{"class":211},[201,2625,2626],{"class":215},", {",[201,2628,479],{"class":211},[201,2630,2631],{"class":215},": nama, ",[201,2633,489],{"class":211},[201,2635,2636],{"class":215},": kelas,\n",[201,2638,2639,2642,2645,2648,2650,2653,2656,2658],{"class":203,"line":1641},[201,2640,2641],{"class":211},"                                        \"kamar\"",[201,2643,2644],{"class":215},": kamar, ",[201,2646,2647],{"class":211},"\"limit\"",[201,2649,471],{"class":215},[201,2651,2652],{"class":219},"min",[201,2654,2655],{"class":215},"(limit, ",[201,2657,1163],{"class":219},[201,2659,2660],{"class":215},")})\n",[201,2662,2663,2665],{"class":203,"line":1646},[201,2664,1340],{"class":374},[201,2666,2667],{"class":215}," sembunyikan_sensitif(hasil)\n",[201,2669,2670],{"class":203,"line":1658},[201,2671,245],{"emptyLinePlaceholder":244},[201,2673,2674],{"class":203,"line":1685},[201,2675,245],{"emptyLinePlaceholder":244},[201,2677,2678,2680],{"class":203,"line":1690},[201,2679,2522],{"class":207},[201,2681,2525],{"class":215},[201,2683,2685,2687,2689,2691,2693,2695,2697,2699],{"class":203,"line":2684},87,[201,2686,2057],{"class":374},[201,2688,2060],{"class":374},[201,2690,1395],{"class":207},[201,2692,1398],{"class":215},[201,2694,992],{"class":219},[201,2696,2596],{"class":215},[201,2698,2074],{"class":219},[201,2700,1022],{"class":215},[201,2702,2704],{"class":203,"line":2703},88,[201,2705,2706],{"class":211},"    \"\"\"Ambil detail satu anak asrama berdasarkan id (mis. 'A001').\n",[201,2708,2710],{"class":203,"line":2709},89,[201,2711,2712],{"class":211},"    Gunakan cari_anak dulu jika id belum diketahui.\"\"\"\n",[201,2714,2716,2718,2720,2722,2724,2726,2728,2730,2733,2735,2737],{"class":203,"line":2715},90,[201,2717,1204],{"class":215},[201,2719,411],{"class":374},[201,2721,2233],{"class":374},[201,2723,2621],{"class":215},[201,2725,2105],{"class":374},[201,2727,1380],{"class":211},[201,2729,2318],{"class":219},[201,2731,2732],{"class":215},"id_anak",[201,2734,2324],{"class":219},[201,2736,1019],{"class":211},[201,2738,426],{"class":215},[201,2740,2742,2744],{"class":203,"line":2741},91,[201,2743,1340],{"class":374},[201,2745,2667],{"class":215},[201,2747,2749],{"class":203,"line":2748},92,[201,2750,245],{"emptyLinePlaceholder":244},[201,2752,2754],{"class":203,"line":2753},93,[201,2755,245],{"emptyLinePlaceholder":244},[201,2757,2759,2761],{"class":203,"line":2758},94,[201,2760,2522],{"class":207},[201,2762,2525],{"class":215},[201,2764,2766,2768,2770,2773,2776,2778],{"class":203,"line":2765},95,[201,2767,2057],{"class":374},[201,2769,2060],{"class":374},[201,2771,2772],{"class":207}," daftar_kamar",[201,2774,2775],{"class":215},"() -> ",[201,2777,2074],{"class":219},[201,2779,1022],{"class":215},[201,2781,2783],{"class":203,"line":2782},96,[201,2784,2785],{"class":211},"    \"\"\"Daftar semua kamar asrama beserta gedung, kapasitas, jumlah terisi, dan musyrif.\"\"\"\n",[201,2787,2789,2791,2793,2795,2797],{"class":203,"line":2788},97,[201,2790,1340],{"class":374},[201,2792,2233],{"class":374},[201,2794,2621],{"class":215},[201,2796,1508],{"class":211},[201,2798,426],{"class":215},[201,2800,2802],{"class":203,"line":2801},98,[201,2803,245],{"emptyLinePlaceholder":244},[201,2805,2807],{"class":203,"line":2806},99,[201,2808,245],{"emptyLinePlaceholder":244},[201,2810,2812,2814],{"class":203,"line":2811},100,[201,2813,2522],{"class":207},[201,2815,2525],{"class":215},[201,2817,2819,2821,2823,2826,2828,2830,2832,2834],{"class":203,"line":2818},101,[201,2820,2057],{"class":374},[201,2822,2060],{"class":374},[201,2824,2825],{"class":207}," absensi_harian",[201,2827,1666],{"class":215},[201,2829,992],{"class":219},[201,2831,2596],{"class":215},[201,2833,2074],{"class":219},[201,2835,1022],{"class":215},[201,2837,2839],{"class":203,"line":2838},102,[201,2840,2841],{"class":211},"    \"\"\"Rekap absensi anak asrama pada tanggal tertentu. Format tanggal: YYYY-MM-DD.\"\"\"\n",[201,2843,2845,2847,2849,2851,2853,2855,2857],{"class":203,"line":2844},103,[201,2846,1340],{"class":374},[201,2848,2233],{"class":374},[201,2850,2621],{"class":215},[201,2852,1653],{"class":211},[201,2854,2626],{"class":215},[201,2856,1697],{"class":211},[201,2858,2859],{"class":215},": tanggal})\n",[201,2861,2863],{"class":203,"line":2862},104,[201,2864,245],{"emptyLinePlaceholder":244},[201,2866,2868],{"class":203,"line":2867},105,[201,2869,245],{"emptyLinePlaceholder":244},[201,2871,2873],{"class":203,"line":2872},106,[201,2874,1860],{"class":251},[201,2876,2878],{"class":203,"line":2877},107,[201,2879,2880],{"class":251},"# Resource: data yang bisa \"dibaca\" sebagai konteks (opsional)\n",[201,2882,2884],{"class":203,"line":2883},108,[201,2885,1860],{"class":251},[201,2887,2889,2892,2894,2897],{"class":203,"line":2888},109,[201,2890,2891],{"class":207},"@mcp.resource",[201,2893,1070],{"class":215},[201,2895,2896],{"class":211},"\"asrama:\u002F\u002Fkamar\"",[201,2898,426],{"class":215},[201,2900,2902,2904,2906,2909,2911,2913],{"class":203,"line":2901},110,[201,2903,2057],{"class":374},[201,2905,2060],{"class":374},[201,2907,2908],{"class":207}," resource_kamar",[201,2910,2775],{"class":215},[201,2912,992],{"class":219},[201,2914,1022],{"class":215},[201,2916,2918],{"class":203,"line":2917},111,[201,2919,2920],{"class":211},"    \"\"\"Daftar kamar asrama sebagai konteks.\"\"\"\n",[201,2922,2924,2927],{"class":203,"line":2923},112,[201,2925,2926],{"class":374},"    import",[201,2928,2929],{"class":215}," json\n",[201,2931,2933,2936,2938,2940,2942,2944],{"class":203,"line":2932},113,[201,2934,2935],{"class":215},"    data ",[201,2937,411],{"class":374},[201,2939,2233],{"class":374},[201,2941,2621],{"class":215},[201,2943,1508],{"class":211},[201,2945,426],{"class":215},[201,2947,2949,2951,2954,2957,2959,2962,2964,2967,2969,2972],{"class":203,"line":2948},114,[201,2950,1340],{"class":374},[201,2952,2953],{"class":215}," json.dumps(data, ",[201,2955,2956],{"class":417},"ensure_ascii",[201,2958,411],{"class":374},[201,2960,2961],{"class":219},"False",[201,2963,321],{"class":215},[201,2965,2966],{"class":417},"indent",[201,2968,411],{"class":374},[201,2970,2971],{"class":219},"2",[201,2973,426],{"class":215},[201,2975,2977],{"class":203,"line":2976},115,[201,2978,245],{"emptyLinePlaceholder":244},[201,2980,2982],{"class":203,"line":2981},116,[201,2983,245],{"emptyLinePlaceholder":244},[201,2985,2987],{"class":203,"line":2986},117,[201,2988,1860],{"class":251},[201,2990,2992],{"class":203,"line":2991},118,[201,2993,2994],{"class":251},"# Prompt: template siap pakai (opsional)\n",[201,2996,2998],{"class":203,"line":2997},119,[201,2999,1860],{"class":251},[201,3001,3003,3006],{"class":203,"line":3002},120,[201,3004,3005],{"class":207},"@mcp.prompt",[201,3007,2525],{"class":215},[201,3009,3011,3013,3016,3019,3021,3023,3025],{"class":203,"line":3010},121,[201,3012,983],{"class":374},[201,3014,3015],{"class":207}," laporan_kamar",[201,3017,3018],{"class":215},"(nama_kamar: ",[201,3020,992],{"class":219},[201,3022,2596],{"class":215},[201,3024,992],{"class":219},[201,3026,1022],{"class":215},[201,3028,3030],{"class":203,"line":3029},122,[201,3031,3032],{"class":211},"    \"\"\"Template untuk membuat laporan singkat satu kamar.\"\"\"\n",[201,3034,3036,3038,3040,3042,3045,3047,3050,3052],{"class":203,"line":3035},123,[201,3037,1340],{"class":374},[201,3039,39],{"class":215},[201,3041,2105],{"class":374},[201,3043,3044],{"class":211},"\"Buatkan laporan singkat untuk kamar '",[201,3046,2318],{"class":219},[201,3048,3049],{"class":215},"nama_kamar",[201,3051,2324],{"class":219},[201,3053,3054],{"class":211},"': daftar penghuni, \"\n",[201,3056,3058,3061,3064],{"class":203,"line":3057},124,[201,3059,3060],{"class":374},"            f",[201,3062,3063],{"class":211},"\"jumlah terisi vs kapasitas, dan siapa yang tidak hadir hari ini.\"",[201,3065,426],{"class":215},[201,3067,3069],{"class":203,"line":3068},125,[201,3070,245],{"emptyLinePlaceholder":244},[201,3072,3074],{"class":203,"line":3073},126,[201,3075,245],{"emptyLinePlaceholder":244},[201,3077,3079,3081,3084,3087,3090],{"class":203,"line":3078},127,[201,3080,1243],{"class":374},[201,3082,3083],{"class":219}," __name__",[201,3085,3086],{"class":374}," ==",[201,3088,3089],{"class":211}," \"__main__\"",[201,3091,1022],{"class":215},[201,3093,3095,3098,3101,3103,3105,3107,3109],{"class":203,"line":3094},128,[201,3096,3097],{"class":215},"    log.info(",[201,3099,3100],{"class":211},"\"MCP Asrama berjalan, API: ",[201,3102,2288],{"class":219},[201,3104,1019],{"class":211},[201,3106,321],{"class":215},[201,3108,1874],{"class":219},[201,3110,426],{"class":215},[201,3112,3114,3117],{"class":203,"line":3113},129,[201,3115,3116],{"class":215},"    mcp.run()  ",[201,3118,3119],{"class":251},"# default transport: stdio\n",[3121,3122,3124],"h3",{"id":3123},"bedah-kode","Bedah Kode",[10,3126,3127,3132,3133,3136,3137,3140],{},[22,3128,3129],{},[41,3130,3131],{},"MCPServer(\"asrama\", instructions=...)"," membuat server bernama ",[41,3134,3135],{},"asrama",". Teks ",[41,3138,3139],{},"instructions"," akan dikirim ke host saat handshake dan sering diteruskan ke model sebagai petunjuk umum.",[10,3142,3143,3148],{},[22,3144,3145],{},[41,3146,3147],{},"panggil_api()"," adalah satu-satunya pintu ke service Anda. Semua error HTTP dan error koneksi ditangkap di sini, lalu dikembalikan sebagai pesan yang bisa dibaca AI. Parameter yang kosong dibuang supaya query string tetap bersih.",[10,3150,3151,3156,3157,321,3159,328,3161,3163,3164,80],{},[22,3152,3153],{},[41,3154,3155],{},"sembunyikan_sensitif()"," menghapus ",[41,3158,1737],{},[41,3160,1740],{},[41,3162,1743],{}," sebelum data dikirim ke model. Data anak termasuk data pribadi (relevan dengan UU Pelindungan Data Pribadi), jadi prinsipnya: ",[22,3165,3166],{},"kirim ke model hanya yang benar-benar dibutuhkan",[10,3168,3169,3174],{},[22,3170,3171],{},[41,3172,3173],{},"@mcp.tool()"," mengubah fungsi Python menjadi tool MCP. Yang perlu Anda ingat:",[3176,3177,3178,3181,3187],"ul",{},[30,3179,3180],{},"Nama fungsi menjadi nama tool.",[30,3182,3183,3184,80],{},"Docstring menjadi deskripsi tool, dan ",[22,3185,3186],{},"model memilih tool berdasarkan teks ini",[30,3188,3189],{},"Type hint diubah menjadi JSON Schema untuk validasi argumen.",[10,3191,3192,3197,3198,3201,3202,3205],{},[22,3193,3194],{},[41,3195,3196],{},"mcp.run()"," menjalankan server dengan transport ",[41,3199,3200],{},"stdio"," secara default. Host akan menjalankan ",[41,3203,3204],{},"python server.py"," sebagai subprocess lalu berkomunikasi lewat stdin\u002Fstdout.",[58,3207,3208],{},[10,3209,3210,3211,3218,3219,3221,3222,3225],{},"⚠️ ",[22,3212,3213,3214,3217],{},"Jangan pernah ",[41,3215,3216],{},"print()"," di server stdio."," Stdout dipakai untuk pesan protokol. Satu ",[41,3220,3216],{}," yang nyasar bisa merusak koneksi. Gunakan ",[41,3223,3224],{},"logging"," ke stderr seperti di contoh.",[82,3227],{},[85,3229,3231],{"id":3230},"langkah-4-sesuaikan-dengan-service-asli-anda","Langkah 4 — Sesuaikan dengan Service Asli Anda",[10,3233,3234],{},"Untuk menyambungkan ke service sungguhan:",[27,3236,3237,3245,3255,3261],{},[30,3238,3239,3240,321,3242,3244],{},"Ubah path endpoint di setiap tool (",[41,3241,320],{},[41,3243,327],{},", dst.) agar sesuai dengan API Anda.",[30,3246,3247,3248,3251,3252,80],{},"Atur URL dan token lewat environment variable ",[41,3249,3250],{},"ASRAMA_API_URL"," dan ",[41,3253,3254],{},"ASRAMA_API_TOKEN",[30,3256,3257,3258,3260],{},"Sesuaikan ",[41,3259,1977],{}," dengan nama field di service Anda.",[30,3262,3263,3264,80],{},"Untuk fitur tambahan (pelanggaran, hafalan, kesehatan), cukup tambah fungsi baru dengan ",[41,3265,3173],{},[82,3267],{},[85,3269,3271],{"id":3270},"langkah-5-uji-dengan-mcp-inspector","Langkah 5 — Uji dengan MCP Inspector",[10,3273,3274],{},"MCP Inspector adalah UI berbasis browser untuk mencoba server MCP tanpa perlu AI:",[101,3276,3278],{"className":195,"code":3277,"language":197,"meta":109,"style":109},"npx @modelcontextprotocol\u002Finspector python server.py\n",[41,3279,3280],{"__ignoreMap":109},[201,3281,3282,3285,3288,3291],{"class":203,"line":204},[201,3283,3284],{"class":207},"npx",[201,3286,3287],{"class":211}," @modelcontextprotocol\u002Finspector",[201,3289,3290],{"class":211}," python",[201,3292,3293],{"class":211}," server.py\n",[10,3295,3296],{},"Dari Inspector Anda bisa melihat daftar tool, mengisi argumen, dan memanggilnya secara manual. Biasakan menguji di sini dulu sebelum menyambungkan ke AI.",[82,3298],{},[85,3300,3302],{"id":3301},"langkah-6-sambungkan-ke-claude-desktop","Langkah 6 — Sambungkan ke Claude Desktop",[10,3304,3305,3306,3309],{},"Buka ",[22,3307,3308],{},"Settings → Developer → Edit Config",", lalu tambahkan:",[101,3311,3315],{"className":3312,"code":3313,"language":3314,"meta":109,"style":109},"language-json shiki shiki-themes github-light github-dark","{\n  \"mcpServers\": {\n    \"asrama\": {\n      \"command\": \"C:\\\\path\\\\ke\\\\mcp-asrama\\\\.venv\\\\Scripts\\\\python.exe\",\n      \"args\": [\"C:\\\\path\\\\ke\\\\mcp-asrama\\\\server.py\"],\n      \"env\": {\n        \"ASRAMA_API_URL\": \"http:\u002F\u002Flocalhost:8000\",\n        \"ASRAMA_API_TOKEN\": \"rahasia123\"\n      }\n    }\n  }\n}\n","json",[41,3316,3317,3322,3330,3336,3379,3409,3416,3427,3437,3442,3447,3452],{"__ignoreMap":109},[201,3318,3319],{"class":203,"line":204},[201,3320,3321],{"class":215},"{\n",[201,3323,3324,3327],{"class":203,"line":226},[201,3325,3326],{"class":219},"  \"mcpServers\"",[201,3328,3329],{"class":215},": {\n",[201,3331,3332,3334],{"class":203,"line":241},[201,3333,2013],{"class":219},[201,3335,3329],{"class":215},[201,3337,3338,3341,3343,3346,3349,3352,3354,3357,3359,3362,3364,3367,3369,3372,3374,3377],{"class":203,"line":248},[201,3339,3340],{"class":219},"      \"command\"",[201,3342,471],{"class":215},[201,3344,3345],{"class":211},"\"C:",[201,3347,3348],{"class":219},"\\\\",[201,3350,3351],{"class":211},"path",[201,3353,3348],{"class":219},[201,3355,3356],{"class":211},"ke",[201,3358,3348],{"class":219},[201,3360,3361],{"class":211},"mcp-asrama",[201,3363,3348],{"class":219},[201,3365,3366],{"class":211},".venv",[201,3368,3348],{"class":219},[201,3370,3371],{"class":211},"Scripts",[201,3373,3348],{"class":219},[201,3375,3376],{"class":211},"python.exe\"",[201,3378,507],{"class":215},[201,3380,3381,3384,3387,3389,3391,3393,3395,3397,3399,3401,3403,3406],{"class":203,"line":255},[201,3382,3383],{"class":219},"      \"args\"",[201,3385,3386],{"class":215},": [",[201,3388,3345],{"class":211},[201,3390,3348],{"class":219},[201,3392,3351],{"class":211},[201,3394,3348],{"class":219},[201,3396,3356],{"class":211},[201,3398,3348],{"class":219},[201,3400,3361],{"class":211},[201,3402,3348],{"class":219},[201,3404,3405],{"class":211},"server.py\"",[201,3407,3408],{"class":215},"],\n",[201,3410,3411,3414],{"class":203,"line":261},[201,3412,3413],{"class":219},"      \"env\"",[201,3415,3329],{"class":215},[201,3417,3418,3421,3423,3425],{"class":203,"line":267},[201,3419,3420],{"class":219},"        \"ASRAMA_API_URL\"",[201,3422,471],{"class":215},[201,3424,1887],{"class":211},[201,3426,507],{"class":215},[201,3428,3429,3432,3434],{"class":203,"line":387},[201,3430,3431],{"class":219},"        \"ASRAMA_API_TOKEN\"",[201,3433,471],{"class":215},[201,3435,3436],{"class":211},"\"rahasia123\"\n",[201,3438,3439],{"class":203,"line":400},[201,3440,3441],{"class":215},"      }\n",[201,3443,3444],{"class":203,"line":405},[201,3445,3446],{"class":215},"    }\n",[201,3448,3449],{"class":203,"line":429},[201,3450,3451],{"class":215},"  }\n",[201,3453,3454],{"class":203,"line":434},[201,3455,967],{"class":215},[10,3457,3458,3459,3462],{},"Di Linux\u002FMac, ganti path-nya menjadi seperti ",[41,3460,3461],{},"\u002Fhome\u002Fuser\u002Fmcp-asrama\u002F.venv\u002Fbin\u002Fpython",". Setelah Claude Desktop di-restart, coba tanya:",[58,3464,3465],{},[10,3466,3467],{},[14,3468,3469],{},"\"Siapa saja anak kelas 7A, dan siapa yang sedang izin?\"",[82,3471],{},[85,3473,3475],{"id":3474},"membedah-json-rpc-apa-yang-sebenarnya-dibaca-agent","Membedah JSON-RPC: Apa yang Sebenarnya Dibaca Agent?",[10,3477,3478,3479,3482],{},"Bagian ini sering terlewat di tutorial, padahal sangat membantu saat debugging. MCP memakai ",[22,3480,3481],{},"JSON-RPC 2.0",". Di mode stdio, setiap pesan adalah satu baris JSON yang dikirim lewat stdin\u002Fstdout.",[10,3484,3485,3486,3489,3490,3492,3493,3495],{},"Semua contoh di bawah adalah ",[22,3487,3488],{},"tangkapan asli"," dari menjalankan ",[41,3491,1756],{}," di atas (paket ",[41,3494,68],{}," 2.3.0) terhadap service mock.",[10,3497,3498],{},"Ada dua lapisan pembaca yang perlu dibedakan:",[27,3500,3501,3507],{},[30,3502,3503,3506],{},[22,3504,3505],{},"Host"," (Claude Desktop, Cursor, dll.) membaca seluruh pesan JSON-RPC.",[30,3508,3509,3512],{},[22,3510,3511],{},"Model AI"," hanya menerima sebagian isinya, yang diteruskan oleh host.",[3121,3514,3516,3517],{"id":3515},"_1-handshake-initialize","1. Handshake: ",[41,3518,3519],{},"initialize",[10,3521,3522,3523,280],{},"Begitu terhubung, host mengirim ",[41,3524,3519],{},[101,3526,3528],{"className":3312,"code":3527,"language":3314,"meta":109,"style":109},"{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"method\": \"initialize\",\n  \"params\": {\n    \"protocolVersion\": \"2025-06-18\",\n    \"capabilities\": {},\n    \"clientInfo\": { \"name\": \"probe\", \"version\": \"1\" }\n  }\n}\n",[41,3529,3530,3534,3546,3558,3570,3577,3589,3597,3626,3630],{"__ignoreMap":109},[201,3531,3532],{"class":203,"line":204},[201,3533,3321],{"class":215},[201,3535,3536,3539,3541,3544],{"class":203,"line":226},[201,3537,3538],{"class":219},"  \"jsonrpc\"",[201,3540,471],{"class":215},[201,3542,3543],{"class":211},"\"2.0\"",[201,3545,507],{"class":215},[201,3547,3548,3551,3553,3556],{"class":203,"line":241},[201,3549,3550],{"class":219},"  \"id\"",[201,3552,471],{"class":215},[201,3554,3555],{"class":219},"1",[201,3557,507],{"class":215},[201,3559,3560,3563,3565,3568],{"class":203,"line":248},[201,3561,3562],{"class":219},"  \"method\"",[201,3564,471],{"class":215},[201,3566,3567],{"class":211},"\"initialize\"",[201,3569,507],{"class":215},[201,3571,3572,3575],{"class":203,"line":255},[201,3573,3574],{"class":219},"  \"params\"",[201,3576,3329],{"class":215},[201,3578,3579,3582,3584,3587],{"class":203,"line":261},[201,3580,3581],{"class":219},"    \"protocolVersion\"",[201,3583,471],{"class":215},[201,3585,3586],{"class":211},"\"2025-06-18\"",[201,3588,507],{"class":215},[201,3590,3591,3594],{"class":203,"line":267},[201,3592,3593],{"class":219},"    \"capabilities\"",[201,3595,3596],{"class":215},": {},\n",[201,3598,3599,3602,3605,3608,3610,3613,3615,3618,3620,3623],{"class":203,"line":387},[201,3600,3601],{"class":219},"    \"clientInfo\"",[201,3603,3604],{"class":215},": { ",[201,3606,3607],{"class":219},"\"name\"",[201,3609,471],{"class":215},[201,3611,3612],{"class":211},"\"probe\"",[201,3614,321],{"class":215},[201,3616,3617],{"class":219},"\"version\"",[201,3619,471],{"class":215},[201,3621,3622],{"class":211},"\"1\"",[201,3624,3625],{"class":215}," }\n",[201,3627,3628],{"class":203,"line":400},[201,3629,3451],{"class":215},[201,3631,3632],{"class":203,"line":405},[201,3633,967],{"class":215},[10,3635,3636],{},"Server membalas dengan kemampuan yang dimilikinya:",[101,3638,3640],{"className":3312,"code":3639,"language":3314,"meta":109,"style":109},"{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"result\": {\n    \"capabilities\": {\n      \"prompts\":   { \"listChanged\": false },\n      \"resources\": { \"listChanged\": false, \"subscribe\": false },\n      \"tools\":     { \"listChanged\": false }\n    },\n    \"instructions\": \"Akses read-only ke data anak asrama: pencarian anak, detail, kamar, dan absensi.\",\n    \"protocolVersion\": \"2025-06-18\",\n    \"serverInfo\": { \"name\": \"asrama\", \"version\": \"\" }\n  }\n}\n",[41,3641,3642,3646,3656,3666,3673,3679,3698,3722,3738,3743,3754,3764,3788,3792],{"__ignoreMap":109},[201,3643,3644],{"class":203,"line":204},[201,3645,3321],{"class":215},[201,3647,3648,3650,3652,3654],{"class":203,"line":226},[201,3649,3538],{"class":219},[201,3651,471],{"class":215},[201,3653,3543],{"class":211},[201,3655,507],{"class":215},[201,3657,3658,3660,3662,3664],{"class":203,"line":241},[201,3659,3550],{"class":219},[201,3661,471],{"class":215},[201,3663,3555],{"class":219},[201,3665,507],{"class":215},[201,3667,3668,3671],{"class":203,"line":248},[201,3669,3670],{"class":219},"  \"result\"",[201,3672,3329],{"class":215},[201,3674,3675,3677],{"class":203,"line":255},[201,3676,3593],{"class":219},[201,3678,3329],{"class":215},[201,3680,3681,3684,3687,3690,3692,3695],{"class":203,"line":261},[201,3682,3683],{"class":219},"      \"prompts\"",[201,3685,3686],{"class":215},":   { ",[201,3688,3689],{"class":219},"\"listChanged\"",[201,3691,471],{"class":215},[201,3693,3694],{"class":219},"false",[201,3696,3697],{"class":215}," },\n",[201,3699,3700,3703,3705,3707,3709,3711,3713,3716,3718,3720],{"class":203,"line":267},[201,3701,3702],{"class":219},"      \"resources\"",[201,3704,3604],{"class":215},[201,3706,3689],{"class":219},[201,3708,471],{"class":215},[201,3710,3694],{"class":219},[201,3712,321],{"class":215},[201,3714,3715],{"class":219},"\"subscribe\"",[201,3717,471],{"class":215},[201,3719,3694],{"class":219},[201,3721,3697],{"class":215},[201,3723,3724,3727,3730,3732,3734,3736],{"class":203,"line":387},[201,3725,3726],{"class":219},"      \"tools\"",[201,3728,3729],{"class":215},":     { ",[201,3731,3689],{"class":219},[201,3733,471],{"class":215},[201,3735,3694],{"class":219},[201,3737,3625],{"class":215},[201,3739,3740],{"class":203,"line":400},[201,3741,3742],{"class":215},"    },\n",[201,3744,3745,3748,3750,3752],{"class":203,"line":405},[201,3746,3747],{"class":219},"    \"instructions\"",[201,3749,471],{"class":215},[201,3751,2025],{"class":211},[201,3753,507],{"class":215},[201,3755,3756,3758,3760,3762],{"class":203,"line":429},[201,3757,3581],{"class":219},[201,3759,471],{"class":215},[201,3761,3586],{"class":211},[201,3763,507],{"class":215},[201,3765,3766,3769,3771,3773,3775,3778,3780,3782,3784,3786],{"class":203,"line":434},[201,3767,3768],{"class":219},"    \"serverInfo\"",[201,3770,3604],{"class":215},[201,3772,3607],{"class":219},[201,3774,471],{"class":215},[201,3776,3777],{"class":211},"\"asrama\"",[201,3779,321],{"class":215},[201,3781,3617],{"class":219},[201,3783,471],{"class":215},[201,3785,1906],{"class":211},[201,3787,3625],{"class":215},[201,3789,3790],{"class":203,"line":446},[201,3791,3451],{"class":215},[201,3793,3794],{"class":203,"line":451},[201,3795,967],{"class":215},[10,3797,3798,3799,3802],{},"Host kemudian mengirim notifikasi bahwa inisialisasi selesai. Karena ini notifikasi (tanpa ",[41,3800,3801],{},"id","), server tidak membalas:",[101,3804,3806],{"className":3312,"code":3805,"language":3314,"meta":109,"style":109},"{ \"jsonrpc\": \"2.0\", \"method\": \"notifications\u002Finitialized\" }\n",[41,3807,3808],{"__ignoreMap":109},[201,3809,3810,3813,3816,3818,3820,3822,3825,3827,3830],{"class":203,"line":204},[201,3811,3812],{"class":215},"{ ",[201,3814,3815],{"class":219},"\"jsonrpc\"",[201,3817,471],{"class":215},[201,3819,3543],{"class":211},[201,3821,321],{"class":215},[201,3823,3824],{"class":219},"\"method\"",[201,3826,471],{"class":215},[201,3828,3829],{"class":211},"\"notifications\u002Finitialized\"",[201,3831,3625],{"class":215},[10,3833,3834,3835,3837,3838,3840,3841,3844,3845,3848],{},"Perhatikan bahwa ",[41,3836,3139],{}," dari konstruktor ",[41,3839,43],{}," muncul di sini. ",[41,3842,3843],{},"serverInfo.version"," kosong karena kita tidak mengisi parameter ",[41,3846,3847],{},"version"," di konstruktor.",[3121,3850,3852,3853],{"id":3851},"_2-daftar-tool-toolslist","2. Daftar Tool: ",[41,3854,3855],{},"tools\u002Flist",[10,3857,3858],{},"Host meminta daftar tool:",[101,3860,3862],{"className":3312,"code":3861,"language":3314,"meta":109,"style":109},"{ \"jsonrpc\": \"2.0\", \"id\": 2, \"method\": \"tools\u002Flist\" }\n",[41,3863,3864],{"__ignoreMap":109},[201,3865,3866,3868,3870,3872,3874,3876,3878,3880,3882,3884,3886,3888,3891],{"class":203,"line":204},[201,3867,3812],{"class":215},[201,3869,3815],{"class":219},[201,3871,471],{"class":215},[201,3873,3543],{"class":211},[201,3875,321],{"class":215},[201,3877,468],{"class":219},[201,3879,471],{"class":215},[201,3881,2971],{"class":219},[201,3883,321],{"class":215},[201,3885,3824],{"class":219},[201,3887,471],{"class":215},[201,3889,3890],{"class":211},"\"tools\u002Flist\"",[201,3892,3625],{"class":215},[10,3894,3895],{},"Balasan server (lengkap, keempat tool):",[101,3897,3899],{"className":3312,"code":3898,"language":3314,"meta":109,"style":109},"{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 2,\n  \"result\": {\n    \"tools\": [\n      {\n        \"name\": \"cari_anak\",\n        \"description\": \"Cari anak asrama berdasarkan nama (sebagian), kelas (mis. '7A'), atau nama kamar.\\n    Semua filter opsional. Mengembalikan daftar ringkas anak beserta id-nya.\",\n        \"inputSchema\": {\n          \"type\": \"object\",\n          \"title\": \"cari_anakArguments\",\n          \"properties\": {\n            \"nama\":  { \"anyOf\": [{ \"type\": \"string\" }, { \"type\": \"null\" }], \"default\": null, \"title\": \"Nama\" },\n            \"kelas\": { \"anyOf\": [{ \"type\": \"string\" }, { \"type\": \"null\" }], \"default\": null, \"title\": \"Kelas\" },\n            \"kamar\": { \"anyOf\": [{ \"type\": \"string\" }, { \"type\": \"null\" }], \"default\": null, \"title\": \"Kamar\" },\n            \"limit\": { \"type\": \"integer\", \"default\": 20, \"title\": \"Limit\" }\n          }\n        }\n      },\n      {\n        \"name\": \"detail_anak\",\n        \"description\": \"Ambil detail satu anak asrama berdasarkan id (mis. 'A001').\\n    Gunakan cari_anak dulu jika id belum diketahui.\",\n        \"inputSchema\": {\n          \"type\": \"object\",\n          \"title\": \"detail_anakArguments\",\n          \"properties\": {\n            \"id_anak\": { \"type\": \"string\", \"title\": \"Id Anak\" }\n          },\n          \"required\": [\"id_anak\"]\n        }\n      },\n      {\n        \"name\": \"daftar_kamar\",\n        \"description\": \"Daftar semua kamar asrama beserta gedung, kapasitas, jumlah terisi, dan musyrif.\",\n        \"inputSchema\": {\n          \"type\": \"object\",\n          \"title\": \"daftar_kamarArguments\",\n          \"properties\": {}\n        }\n      },\n      {\n        \"name\": \"absensi_harian\",\n        \"description\": \"Rekap absensi anak asrama pada tanggal tertentu. Format tanggal: YYYY-MM-DD.\",\n        \"inputSchema\": {\n          \"type\": \"object\",\n          \"title\": \"absensi_harianArguments\",\n          \"properties\": {\n            \"tanggal\": { \"type\": \"string\", \"title\": \"Tanggal\" }\n          },\n          \"required\": [\"tanggal\"]\n        }\n      }\n    ]\n  }\n}\n",[41,3900,3901,3905,3915,3925,3931,3938,3943,3955,3973,3980,3992,4004,4011,4066,4110,4154,4187,4192,4197,4202,4206,4217,4233,4239,4249,4260,4266,4290,4295,4306,4310,4314,4318,4329,4340,4346,4356,4367,4374,4378,4382,4386,4397,4408,4414,4424,4435,4441,4465,4469,4479,4483,4487,4491,4495],{"__ignoreMap":109},[201,3902,3903],{"class":203,"line":204},[201,3904,3321],{"class":215},[201,3906,3907,3909,3911,3913],{"class":203,"line":226},[201,3908,3538],{"class":219},[201,3910,471],{"class":215},[201,3912,3543],{"class":211},[201,3914,507],{"class":215},[201,3916,3917,3919,3921,3923],{"class":203,"line":241},[201,3918,3550],{"class":219},[201,3920,471],{"class":215},[201,3922,2971],{"class":219},[201,3924,507],{"class":215},[201,3926,3927,3929],{"class":203,"line":248},[201,3928,3670],{"class":219},[201,3930,3329],{"class":215},[201,3932,3933,3936],{"class":203,"line":255},[201,3934,3935],{"class":219},"    \"tools\"",[201,3937,878],{"class":215},[201,3939,3940],{"class":203,"line":261},[201,3941,3942],{"class":215},"      {\n",[201,3944,3945,3948,3950,3953],{"class":203,"line":267},[201,3946,3947],{"class":219},"        \"name\"",[201,3949,471],{"class":215},[201,3951,3952],{"class":211},"\"cari_anak\"",[201,3954,507],{"class":215},[201,3956,3957,3960,3962,3965,3968,3971],{"class":203,"line":387},[201,3958,3959],{"class":219},"        \"description\"",[201,3961,471],{"class":215},[201,3963,3964],{"class":211},"\"Cari anak asrama berdasarkan nama (sebagian), kelas (mis. '7A'), atau nama kamar.",[201,3966,3967],{"class":219},"\\n",[201,3969,3970],{"class":211},"    Semua filter opsional. Mengembalikan daftar ringkas anak beserta id-nya.\"",[201,3972,507],{"class":215},[201,3974,3975,3978],{"class":203,"line":400},[201,3976,3977],{"class":219},"        \"inputSchema\"",[201,3979,3329],{"class":215},[201,3981,3982,3985,3987,3990],{"class":203,"line":405},[201,3983,3984],{"class":219},"          \"type\"",[201,3986,471],{"class":215},[201,3988,3989],{"class":211},"\"object\"",[201,3991,507],{"class":215},[201,3993,3994,3997,3999,4002],{"class":203,"line":429},[201,3995,3996],{"class":219},"          \"title\"",[201,3998,471],{"class":215},[201,4000,4001],{"class":211},"\"cari_anakArguments\"",[201,4003,507],{"class":215},[201,4005,4006,4009],{"class":203,"line":434},[201,4007,4008],{"class":219},"          \"properties\"",[201,4010,3329],{"class":215},[201,4012,4013,4016,4019,4022,4025,4028,4030,4033,4036,4038,4040,4043,4046,4049,4051,4054,4056,4059,4061,4064],{"class":203,"line":446},[201,4014,4015],{"class":219},"            \"nama\"",[201,4017,4018],{"class":215},":  { ",[201,4020,4021],{"class":219},"\"anyOf\"",[201,4023,4024],{"class":215},": [{ ",[201,4026,4027],{"class":219},"\"type\"",[201,4029,471],{"class":215},[201,4031,4032],{"class":211},"\"string\"",[201,4034,4035],{"class":215}," }, { ",[201,4037,4027],{"class":219},[201,4039,471],{"class":215},[201,4041,4042],{"class":211},"\"null\"",[201,4044,4045],{"class":215}," }], ",[201,4047,4048],{"class":219},"\"default\"",[201,4050,471],{"class":215},[201,4052,4053],{"class":219},"null",[201,4055,321],{"class":215},[201,4057,4058],{"class":219},"\"title\"",[201,4060,471],{"class":215},[201,4062,4063],{"class":211},"\"Nama\"",[201,4065,3697],{"class":215},[201,4067,4068,4071,4073,4075,4077,4079,4081,4083,4085,4087,4089,4091,4093,4095,4097,4099,4101,4103,4105,4108],{"class":203,"line":451},[201,4069,4070],{"class":219},"            \"kelas\"",[201,4072,3604],{"class":215},[201,4074,4021],{"class":219},[201,4076,4024],{"class":215},[201,4078,4027],{"class":219},[201,4080,471],{"class":215},[201,4082,4032],{"class":211},[201,4084,4035],{"class":215},[201,4086,4027],{"class":219},[201,4088,471],{"class":215},[201,4090,4042],{"class":211},[201,4092,4045],{"class":215},[201,4094,4048],{"class":219},[201,4096,471],{"class":215},[201,4098,4053],{"class":219},[201,4100,321],{"class":215},[201,4102,4058],{"class":219},[201,4104,471],{"class":215},[201,4106,4107],{"class":211},"\"Kelas\"",[201,4109,3697],{"class":215},[201,4111,4112,4115,4117,4119,4121,4123,4125,4127,4129,4131,4133,4135,4137,4139,4141,4143,4145,4147,4149,4152],{"class":203,"line":462},[201,4113,4114],{"class":219},"            \"kamar\"",[201,4116,3604],{"class":215},[201,4118,4021],{"class":219},[201,4120,4024],{"class":215},[201,4122,4027],{"class":219},[201,4124,471],{"class":215},[201,4126,4032],{"class":211},[201,4128,4035],{"class":215},[201,4130,4027],{"class":219},[201,4132,471],{"class":215},[201,4134,4042],{"class":211},[201,4136,4045],{"class":215},[201,4138,4048],{"class":219},[201,4140,471],{"class":215},[201,4142,4053],{"class":219},[201,4144,321],{"class":215},[201,4146,4058],{"class":219},[201,4148,471],{"class":215},[201,4150,4151],{"class":211},"\"Kamar\"",[201,4153,3697],{"class":215},[201,4155,4156,4159,4161,4163,4165,4168,4170,4172,4174,4176,4178,4180,4182,4185],{"class":203,"line":510},[201,4157,4158],{"class":219},"            \"limit\"",[201,4160,3604],{"class":215},[201,4162,4027],{"class":219},[201,4164,471],{"class":215},[201,4166,4167],{"class":211},"\"integer\"",[201,4169,321],{"class":215},[201,4171,4048],{"class":219},[201,4173,471],{"class":215},[201,4175,1153],{"class":219},[201,4177,321],{"class":215},[201,4179,4058],{"class":219},[201,4181,471],{"class":215},[201,4183,4184],{"class":211},"\"Limit\"",[201,4186,3625],{"class":215},[201,4188,4189],{"class":203,"line":543},[201,4190,4191],{"class":215},"          }\n",[201,4193,4194],{"class":203,"line":567},[201,4195,4196],{"class":215},"        }\n",[201,4198,4199],{"class":203,"line":607},[201,4200,4201],{"class":215},"      },\n",[201,4203,4204],{"class":203,"line":636},[201,4205,3942],{"class":215},[201,4207,4208,4210,4212,4215],{"class":203,"line":657},[201,4209,3947],{"class":219},[201,4211,471],{"class":215},[201,4213,4214],{"class":211},"\"detail_anak\"",[201,4216,507],{"class":215},[201,4218,4219,4221,4223,4226,4228,4231],{"class":203,"line":697},[201,4220,3959],{"class":219},[201,4222,471],{"class":215},[201,4224,4225],{"class":211},"\"Ambil detail satu anak asrama berdasarkan id (mis. 'A001').",[201,4227,3967],{"class":219},[201,4229,4230],{"class":211},"    Gunakan cari_anak dulu jika id belum diketahui.\"",[201,4232,507],{"class":215},[201,4234,4235,4237],{"class":203,"line":727},[201,4236,3977],{"class":219},[201,4238,3329],{"class":215},[201,4240,4241,4243,4245,4247],{"class":203,"line":748},[201,4242,3984],{"class":219},[201,4244,471],{"class":215},[201,4246,3989],{"class":211},[201,4248,507],{"class":215},[201,4250,4251,4253,4255,4258],{"class":203,"line":754},[201,4252,3996],{"class":219},[201,4254,471],{"class":215},[201,4256,4257],{"class":211},"\"detail_anakArguments\"",[201,4259,507],{"class":215},[201,4261,4262,4264],{"class":203,"line":759},[201,4263,4008],{"class":219},[201,4265,3329],{"class":215},[201,4267,4268,4271,4273,4275,4277,4279,4281,4283,4285,4288],{"class":203,"line":769},[201,4269,4270],{"class":219},"            \"id_anak\"",[201,4272,3604],{"class":215},[201,4274,4027],{"class":219},[201,4276,471],{"class":215},[201,4278,4032],{"class":211},[201,4280,321],{"class":215},[201,4282,4058],{"class":219},[201,4284,471],{"class":215},[201,4286,4287],{"class":211},"\"Id Anak\"",[201,4289,3625],{"class":215},[201,4291,4292],{"class":203,"line":812},[201,4293,4294],{"class":215},"          },\n",[201,4296,4297,4300,4302,4304],{"class":203,"line":851},[201,4298,4299],{"class":219},"          \"required\"",[201,4301,3386],{"class":215},[201,4303,887],{"class":211},[201,4305,751],{"class":215},[201,4307,4308],{"class":203,"line":856},[201,4309,4196],{"class":215},[201,4311,4312],{"class":203,"line":861},[201,4313,4201],{"class":215},[201,4315,4316],{"class":203,"line":872},[201,4317,3942],{"class":215},[201,4319,4320,4322,4324,4327],{"class":203,"line":881},[201,4321,3947],{"class":219},[201,4323,471],{"class":215},[201,4325,4326],{"class":211},"\"daftar_kamar\"",[201,4328,507],{"class":215},[201,4330,4331,4333,4335,4338],{"class":203,"line":906},[201,4332,3959],{"class":219},[201,4334,471],{"class":215},[201,4336,4337],{"class":211},"\"Daftar semua kamar asrama beserta gedung, kapasitas, jumlah terisi, dan musyrif.\"",[201,4339,507],{"class":215},[201,4341,4342,4344],{"class":203,"line":927},[201,4343,3977],{"class":219},[201,4345,3329],{"class":215},[201,4347,4348,4350,4352,4354],{"class":203,"line":958},[201,4349,3984],{"class":219},[201,4351,471],{"class":215},[201,4353,3989],{"class":211},[201,4355,507],{"class":215},[201,4357,4358,4360,4362,4365],{"class":203,"line":964},[201,4359,3996],{"class":219},[201,4361,471],{"class":215},[201,4363,4364],{"class":211},"\"daftar_kamarArguments\"",[201,4366,507],{"class":215},[201,4368,4369,4371],{"class":203,"line":970},[201,4370,4008],{"class":219},[201,4372,4373],{"class":215},": {}\n",[201,4375,4376],{"class":203,"line":975},[201,4377,4196],{"class":215},[201,4379,4380],{"class":203,"line":980},[201,4381,4201],{"class":215},[201,4383,4384],{"class":203,"line":998},[201,4385,3942],{"class":215},[201,4387,4388,4390,4392,4395],{"class":203,"line":1025},[201,4389,3947],{"class":219},[201,4391,471],{"class":215},[201,4393,4394],{"class":211},"\"absensi_harian\"",[201,4396,507],{"class":215},[201,4398,4399,4401,4403,4406],{"class":203,"line":1054},[201,4400,3959],{"class":219},[201,4402,471],{"class":215},[201,4404,4405],{"class":211},"\"Rekap absensi anak asrama pada tanggal tertentu. Format tanggal: YYYY-MM-DD.\"",[201,4407,507],{"class":215},[201,4409,4410,4412],{"class":203,"line":1059},[201,4411,3977],{"class":219},[201,4413,3329],{"class":215},[201,4415,4416,4418,4420,4422],{"class":203,"line":1064},[201,4417,3984],{"class":219},[201,4419,471],{"class":215},[201,4421,3989],{"class":211},[201,4423,507],{"class":215},[201,4425,4426,4428,4430,4433],{"class":203,"line":1078},[201,4427,3996],{"class":219},[201,4429,471],{"class":215},[201,4431,4432],{"class":211},"\"absensi_harianArguments\"",[201,4434,507],{"class":215},[201,4436,4437,4439],{"class":203,"line":1089},[201,4438,4008],{"class":219},[201,4440,3329],{"class":215},[201,4442,4443,4446,4448,4450,4452,4454,4456,4458,4460,4463],{"class":203,"line":1107},[201,4444,4445],{"class":219},"            \"tanggal\"",[201,4447,3604],{"class":215},[201,4449,4027],{"class":219},[201,4451,471],{"class":215},[201,4453,4032],{"class":211},[201,4455,321],{"class":215},[201,4457,4058],{"class":219},[201,4459,471],{"class":215},[201,4461,4462],{"class":211},"\"Tanggal\"",[201,4464,3625],{"class":215},[201,4466,4467],{"class":203,"line":1123},[201,4468,4294],{"class":215},[201,4470,4471,4473,4475,4477],{"class":203,"line":1139},[201,4472,4299],{"class":219},[201,4474,3386],{"class":215},[201,4476,1697],{"class":211},[201,4478,751],{"class":215},[201,4480,4481],{"class":203,"line":1169},[201,4482,4196],{"class":215},[201,4484,4485],{"class":203,"line":1189},[201,4486,3441],{"class":215},[201,4488,4489],{"class":203,"line":1195},[201,4490,961],{"class":215},[201,4492,4493],{"class":203,"line":1201},[201,4494,3451],{"class":215},[201,4496,4497],{"class":203,"line":1212},[201,4498,967],{"class":215},[10,4500,4501,4502,4505],{},"Inilah bagian yang ",[22,4503,4504],{},"paling menentukan perilaku agent",". Pemetaan dari kode Python ke JSON-nya:",[114,4507,4508,4518],{},[117,4509,4510],{},[120,4511,4512,4515],{},[123,4513,4514],{},"Di kode Python",[123,4516,4517],{},"Menjadi di JSON",[133,4519,4520,4533,4547,4563,4578,4593],{},[120,4521,4522,4528],{},[138,4523,4524,4525],{},"Nama fungsi ",[41,4526,4527],{},"cari_anak",[138,4529,4530],{},[41,4531,4532],{},"\"name\": \"cari_anak\"",[120,4534,4535,4538],{},[138,4536,4537],{},"Docstring",[138,4539,4540,4543,4544],{},[41,4541,4542],{},"\"description\"",", apa adanya, termasuk indentasi ",[41,4545,4546],{},"\\n    ",[120,4548,4549,4554],{},[138,4550,4551],{},[41,4552,4553],{},"Optional[str] = None",[138,4555,4556,4559,4560],{},[41,4557,4558],{},"anyOf: [string, null]"," dengan ",[41,4561,4562],{},"default: null",[120,4564,4565,4570],{},[138,4566,4567],{},[41,4568,4569],{},"limit: int = 20",[138,4571,4572,4559,4575],{},[41,4573,4574],{},"type: integer",[41,4576,4577],{},"default: 20",[120,4579,4580,4587],{},[138,4581,4582,4583,4586],{},"Parameter tanpa default (",[41,4584,4585],{},"id_anak: str",")",[138,4588,4589,4590],{},"Masuk ke ",[41,4591,4592],{},"required",[120,4594,4595,4598],{},[138,4596,4597],{},"Fungsi tanpa parameter",[138,4599,4600],{},[41,4601,4602],{},"properties: {}",[10,4604,4605,4606,321,4609,328,4612,4615,4616,4619,4620,4623,4624,4627],{},"Host meneruskan ",[41,4607,4608],{},"name",[41,4610,4611],{},"description",[41,4613,4614],{},"inputSchema"," ke model sebagai definisi tool. Model ",[22,4617,4618],{},"tidak melihat kode Anda","; ia memutuskan kapan dan bagaimana memanggil tool hanya dari teks ini. Kalimat seperti ",[14,4621,4622],{},"\"Gunakan cari_anak dulu jika id belum diketahui\""," di docstring ",[41,4625,4626],{},"detail_anak"," benar-benar membantu model menyusun urutan pemanggilan yang tepat.",[3121,4629,4631,4632],{"id":4630},"_3-memanggil-tool-toolscall","3. Memanggil Tool: ",[41,4633,4634],{},"tools\u002Fcall",[10,4636,4637,4638,4640],{},"Misalkan user bertanya tentang anak kelas 7A. Model memutuskan memanggil ",[41,4639,4527],{},", dan host mengirim:",[101,4642,4644],{"className":3312,"code":4643,"language":3314,"meta":109,"style":109},"{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 3,\n  \"method\": \"tools\u002Fcall\",\n  \"params\": {\n    \"name\": \"cari_anak\",\n    \"arguments\": { \"kelas\": \"7A\" }\n  }\n}\n",[41,4645,4646,4650,4660,4671,4682,4688,4699,4714,4718],{"__ignoreMap":109},[201,4647,4648],{"class":203,"line":204},[201,4649,3321],{"class":215},[201,4651,4652,4654,4656,4658],{"class":203,"line":226},[201,4653,3538],{"class":219},[201,4655,471],{"class":215},[201,4657,3543],{"class":211},[201,4659,507],{"class":215},[201,4661,4662,4664,4666,4669],{"class":203,"line":241},[201,4663,3550],{"class":219},[201,4665,471],{"class":215},[201,4667,4668],{"class":219},"3",[201,4670,507],{"class":215},[201,4672,4673,4675,4677,4680],{"class":203,"line":248},[201,4674,3562],{"class":219},[201,4676,471],{"class":215},[201,4678,4679],{"class":211},"\"tools\u002Fcall\"",[201,4681,507],{"class":215},[201,4683,4684,4686],{"class":203,"line":255},[201,4685,3574],{"class":219},[201,4687,3329],{"class":215},[201,4689,4690,4693,4695,4697],{"class":203,"line":261},[201,4691,4692],{"class":219},"    \"name\"",[201,4694,471],{"class":215},[201,4696,3952],{"class":211},[201,4698,507],{"class":215},[201,4700,4701,4704,4706,4708,4710,4712],{"class":203,"line":267},[201,4702,4703],{"class":219},"    \"arguments\"",[201,4705,3604],{"class":215},[201,4707,489],{"class":219},[201,4709,471],{"class":215},[201,4711,494],{"class":211},[201,4713,3625],{"class":215},[201,4715,4716],{"class":203,"line":387},[201,4717,3451],{"class":215},[201,4719,4720],{"class":203,"line":400},[201,4721,967],{"class":215},[10,4723,4724],{},"Balasan server:",[101,4726,4728],{"className":3312,"code":4727,"language":3314,"meta":109,"style":109},"{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 3,\n  \"result\": {\n    \"content\": [\n      {\n        \"type\": \"text\",\n        \"text\": \"{\\n  \\\"total\\\": 2,\\n  \\\"data\\\": [\\n    {\\n      \\\"id\\\": \\\"A001\\\",\\n      \\\"nama\\\": \\\"Ahmad Fauzi\\\",\\n      \\\"kelas\\\": \\\"7A\\\",\\n      \\\"kamar\\\": \\\"Al-Fatih 1\\\",\\n      \\\"status\\\": \\\"aktif\\\",\\n      \\\"nama_wali\\\": \\\"Bapak Hasan\\\"\\n    },\\n    {\\n      \\\"id\\\": \\\"A003\\\",\\n      \\\"nama\\\": \\\"Citra Lestari\\\",\\n      \\\"kelas\\\": \\\"7A\\\",\\n      \\\"kamar\\\": \\\"Aisyah 2\\\",\\n      \\\"status\\\": \\\"izin\\\",\\n      \\\"nama_wali\\\": \\\"Bapak Joko\\\"\\n    }\\n  ]\\n}\"\n      }\n    ],\n    \"isError\": false\n  }\n}\n",[41,4729,4730,4734,4744,4754,4760,4767,4771,4783,5073,5077,5082,5092,5096],{"__ignoreMap":109},[201,4731,4732],{"class":203,"line":204},[201,4733,3321],{"class":215},[201,4735,4736,4738,4740,4742],{"class":203,"line":226},[201,4737,3538],{"class":219},[201,4739,471],{"class":215},[201,4741,3543],{"class":211},[201,4743,507],{"class":215},[201,4745,4746,4748,4750,4752],{"class":203,"line":241},[201,4747,3550],{"class":219},[201,4749,471],{"class":215},[201,4751,4668],{"class":219},[201,4753,507],{"class":215},[201,4755,4756,4758],{"class":203,"line":248},[201,4757,3670],{"class":219},[201,4759,3329],{"class":215},[201,4761,4762,4765],{"class":203,"line":255},[201,4763,4764],{"class":219},"    \"content\"",[201,4766,878],{"class":215},[201,4768,4769],{"class":203,"line":261},[201,4770,3942],{"class":215},[201,4772,4773,4776,4778,4781],{"class":203,"line":267},[201,4774,4775],{"class":219},"        \"type\"",[201,4777,471],{"class":215},[201,4779,4780],{"class":211},"\"text\"",[201,4782,507],{"class":215},[201,4784,4785,4788,4790,4793,4795,4798,4801,4804,4807,4809,4811,4814,4816,4818,4820,4822,4824,4827,4829,4831,4833,4835,4838,4840,4843,4845,4847,4850,4852,4854,4856,4859,4861,4863,4865,4867,4870,4872,4874,4876,4879,4881,4883,4885,4887,4890,4892,4894,4896,4899,4901,4903,4905,4907,4910,4912,4914,4916,4919,4921,4923,4925,4927,4930,4932,4934,4936,4939,4942,4945,4947,4949,4951,4953,4955,4957,4959,4961,4964,4966,4968,4970,4972,4974,4976,4978,4980,4983,4985,4987,4989,4991,4993,4995,4997,4999,5001,5003,5005,5007,5009,5011,5013,5015,5017,5020,5022,5024,5026,5028,5030,5032,5034,5036,5039,5041,5043,5045,5047,5049,5051,5053,5055,5058,5060,5063,5065,5068,5070],{"class":203,"line":387},[201,4786,4787],{"class":219},"        \"text\"",[201,4789,471],{"class":215},[201,4791,4792],{"class":211},"\"{",[201,4794,3967],{"class":219},[201,4796,4797],{"class":219},"  \\\"",[201,4799,4800],{"class":211},"total",[201,4802,4803],{"class":219},"\\\"",[201,4805,4806],{"class":211},": 2,",[201,4808,3967],{"class":219},[201,4810,4797],{"class":219},[201,4812,4813],{"class":211},"data",[201,4815,4803],{"class":219},[201,4817,3386],{"class":211},[201,4819,3967],{"class":219},[201,4821,465],{"class":211},[201,4823,3967],{"class":219},[201,4825,4826],{"class":219},"      \\\"",[201,4828,3801],{"class":211},[201,4830,4803],{"class":219},[201,4832,471],{"class":211},[201,4834,4803],{"class":219},[201,4836,4837],{"class":211},"A001",[201,4839,4803],{"class":219},[201,4841,4842],{"class":211},",",[201,4844,3967],{"class":219},[201,4846,4826],{"class":219},[201,4848,4849],{"class":211},"nama",[201,4851,4803],{"class":219},[201,4853,471],{"class":211},[201,4855,4803],{"class":219},[201,4857,4858],{"class":211},"Ahmad Fauzi",[201,4860,4803],{"class":219},[201,4862,4842],{"class":211},[201,4864,3967],{"class":219},[201,4866,4826],{"class":219},[201,4868,4869],{"class":211},"kelas",[201,4871,4803],{"class":219},[201,4873,471],{"class":211},[201,4875,4803],{"class":219},[201,4877,4878],{"class":211},"7A",[201,4880,4803],{"class":219},[201,4882,4842],{"class":211},[201,4884,3967],{"class":219},[201,4886,4826],{"class":219},[201,4888,4889],{"class":211},"kamar",[201,4891,4803],{"class":219},[201,4893,471],{"class":211},[201,4895,4803],{"class":219},[201,4897,4898],{"class":211},"Al-Fatih 1",[201,4900,4803],{"class":219},[201,4902,4842],{"class":211},[201,4904,3967],{"class":219},[201,4906,4826],{"class":219},[201,4908,4909],{"class":211},"status",[201,4911,4803],{"class":219},[201,4913,471],{"class":211},[201,4915,4803],{"class":219},[201,4917,4918],{"class":211},"aktif",[201,4920,4803],{"class":219},[201,4922,4842],{"class":211},[201,4924,3967],{"class":219},[201,4926,4826],{"class":219},[201,4928,4929],{"class":211},"nama_wali",[201,4931,4803],{"class":219},[201,4933,471],{"class":211},[201,4935,4803],{"class":219},[201,4937,4938],{"class":211},"Bapak Hasan",[201,4940,4941],{"class":219},"\\\"\\n",[201,4943,4944],{"class":211},"    },",[201,4946,3967],{"class":219},[201,4948,465],{"class":211},[201,4950,3967],{"class":219},[201,4952,4826],{"class":219},[201,4954,3801],{"class":211},[201,4956,4803],{"class":219},[201,4958,471],{"class":211},[201,4960,4803],{"class":219},[201,4962,4963],{"class":211},"A003",[201,4965,4803],{"class":219},[201,4967,4842],{"class":211},[201,4969,3967],{"class":219},[201,4971,4826],{"class":219},[201,4973,4849],{"class":211},[201,4975,4803],{"class":219},[201,4977,471],{"class":211},[201,4979,4803],{"class":219},[201,4981,4982],{"class":211},"Citra Lestari",[201,4984,4803],{"class":219},[201,4986,4842],{"class":211},[201,4988,3967],{"class":219},[201,4990,4826],{"class":219},[201,4992,4869],{"class":211},[201,4994,4803],{"class":219},[201,4996,471],{"class":211},[201,4998,4803],{"class":219},[201,5000,4878],{"class":211},[201,5002,4803],{"class":219},[201,5004,4842],{"class":211},[201,5006,3967],{"class":219},[201,5008,4826],{"class":219},[201,5010,4889],{"class":211},[201,5012,4803],{"class":219},[201,5014,471],{"class":211},[201,5016,4803],{"class":219},[201,5018,5019],{"class":211},"Aisyah 2",[201,5021,4803],{"class":219},[201,5023,4842],{"class":211},[201,5025,3967],{"class":219},[201,5027,4826],{"class":219},[201,5029,4909],{"class":211},[201,5031,4803],{"class":219},[201,5033,471],{"class":211},[201,5035,4803],{"class":219},[201,5037,5038],{"class":211},"izin",[201,5040,4803],{"class":219},[201,5042,4842],{"class":211},[201,5044,3967],{"class":219},[201,5046,4826],{"class":219},[201,5048,4929],{"class":211},[201,5050,4803],{"class":219},[201,5052,471],{"class":211},[201,5054,4803],{"class":219},[201,5056,5057],{"class":211},"Bapak Joko",[201,5059,4941],{"class":219},[201,5061,5062],{"class":211},"    }",[201,5064,3967],{"class":219},[201,5066,5067],{"class":211},"  ]",[201,5069,3967],{"class":219},[201,5071,5072],{"class":211},"}\"\n",[201,5074,5075],{"class":203,"line":400},[201,5076,3441],{"class":215},[201,5078,5079],{"class":203,"line":405},[201,5080,5081],{"class":215},"    ],\n",[201,5083,5084,5087,5089],{"class":203,"line":429},[201,5085,5086],{"class":219},"    \"isError\"",[201,5088,471],{"class":215},[201,5090,5091],{"class":219},"false\n",[201,5093,5094],{"class":203,"line":434},[201,5095,3451],{"class":215},[201,5097,5098],{"class":203,"line":446},[201,5099,967],{"class":215},[10,5101,5102],{},"Ada dua hal penting di sini:",[27,5104,5105,5118],{},[30,5106,5107,5109,5110,5113,5114,5117],{},[41,5108,2074],{}," yang dikembalikan fungsi Python diserialisasi menjadi ",[22,5111,5112],{},"teks JSON"," di dalam ",[41,5115,5116],{},"content[].text",". Teks inilah yang diterima model sebagai hasil tool.",[30,5119,5120,5121,321,5123,328,5125,5127,5128,5131,5132,5134],{},"Field ",[41,5122,1737],{},[41,5124,1740],{},[41,5126,1743],{}," ",[22,5129,5130],{},"tidak ada",". Penyaringan oleh ",[41,5133,3155],{}," bekerja, jadi data sensitif tidak pernah sampai ke model.",[3121,5136,5138],{"id":5137},"_4-ketika-terjadi-error","4. Ketika Terjadi Error",[10,5140,5141],{},"Ada dua jenis error, dan hasilnya ternyata berbeda.",[10,5143,5144,5147,5148,5151,5152,280],{},[22,5145,5146],{},"a) Argumen tidak valid."," Misalnya model memanggil ",[41,5149,5150],{},"absensi_harian"," tanpa ",[41,5153,5154],{},"tanggal",[101,5156,5158],{"className":3312,"code":5157,"language":3314,"meta":109,"style":109},"{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 5,\n  \"method\": \"tools\u002Fcall\",\n  \"params\": { \"name\": \"absensi_harian\", \"arguments\": {} }\n}\n",[41,5159,5160,5164,5174,5185,5195,5215],{"__ignoreMap":109},[201,5161,5162],{"class":203,"line":204},[201,5163,3321],{"class":215},[201,5165,5166,5168,5170,5172],{"class":203,"line":226},[201,5167,3538],{"class":219},[201,5169,471],{"class":215},[201,5171,3543],{"class":211},[201,5173,507],{"class":215},[201,5175,5176,5178,5180,5183],{"class":203,"line":241},[201,5177,3550],{"class":219},[201,5179,471],{"class":215},[201,5181,5182],{"class":219},"5",[201,5184,507],{"class":215},[201,5186,5187,5189,5191,5193],{"class":203,"line":248},[201,5188,3562],{"class":219},[201,5190,471],{"class":215},[201,5192,4679],{"class":211},[201,5194,507],{"class":215},[201,5196,5197,5199,5201,5203,5205,5207,5209,5212],{"class":203,"line":255},[201,5198,3574],{"class":219},[201,5200,3604],{"class":215},[201,5202,3607],{"class":219},[201,5204,471],{"class":215},[201,5206,4394],{"class":211},[201,5208,321],{"class":215},[201,5210,5211],{"class":219},"\"arguments\"",[201,5213,5214],{"class":215},": {} }\n",[201,5216,5217],{"class":203,"line":261},[201,5218,967],{"class":215},[10,5220,5221],{},"SDK memvalidasi argumen secara otomatis berdasarkan schema, lalu membalas:",[101,5223,5225],{"className":3312,"code":5224,"language":3314,"meta":109,"style":109},"{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 5,\n  \"result\": {\n    \"content\": [\n      {\n        \"type\": \"text\",\n        \"text\": \"Error executing tool absensi_harian: 1 validation error for absensi_harianArguments\\ntanggal\\n  Field required [type=missing, input_value={}, input_type=dict]\\n    For further information visit https:\u002F\u002Ferrors.pydantic.dev\u002F2.13\u002Fv\u002Fmissing\"\n      }\n    ],\n    \"isError\": true\n  }\n}\n",[41,5226,5227,5231,5241,5251,5257,5263,5267,5277,5300,5304,5308,5317,5321],{"__ignoreMap":109},[201,5228,5229],{"class":203,"line":204},[201,5230,3321],{"class":215},[201,5232,5233,5235,5237,5239],{"class":203,"line":226},[201,5234,3538],{"class":219},[201,5236,471],{"class":215},[201,5238,3543],{"class":211},[201,5240,507],{"class":215},[201,5242,5243,5245,5247,5249],{"class":203,"line":241},[201,5244,3550],{"class":219},[201,5246,471],{"class":215},[201,5248,5182],{"class":219},[201,5250,507],{"class":215},[201,5252,5253,5255],{"class":203,"line":248},[201,5254,3670],{"class":219},[201,5256,3329],{"class":215},[201,5258,5259,5261],{"class":203,"line":255},[201,5260,4764],{"class":219},[201,5262,878],{"class":215},[201,5264,5265],{"class":203,"line":261},[201,5266,3942],{"class":215},[201,5268,5269,5271,5273,5275],{"class":203,"line":267},[201,5270,4775],{"class":219},[201,5272,471],{"class":215},[201,5274,4780],{"class":211},[201,5276,507],{"class":215},[201,5278,5279,5281,5283,5286,5288,5290,5292,5295,5297],{"class":203,"line":387},[201,5280,4787],{"class":219},[201,5282,471],{"class":215},[201,5284,5285],{"class":211},"\"Error executing tool absensi_harian: 1 validation error for absensi_harianArguments",[201,5287,3967],{"class":219},[201,5289,5154],{"class":211},[201,5291,3967],{"class":219},[201,5293,5294],{"class":211},"  Field required [type=missing, input_value={}, input_type=dict]",[201,5296,3967],{"class":219},[201,5298,5299],{"class":211},"    For further information visit https:\u002F\u002Ferrors.pydantic.dev\u002F2.13\u002Fv\u002Fmissing\"\n",[201,5301,5302],{"class":203,"line":400},[201,5303,3441],{"class":215},[201,5305,5306],{"class":203,"line":405},[201,5307,5081],{"class":215},[201,5309,5310,5312,5314],{"class":203,"line":429},[201,5311,5086],{"class":219},[201,5313,471],{"class":215},[201,5315,5316],{"class":219},"true\n",[201,5318,5319],{"class":203,"line":434},[201,5320,3451],{"class":215},[201,5322,5323],{"class":203,"line":446},[201,5324,967],{"class":215},[10,5326,5327,5328,5331,5332,5335,5336,5339,5340,5343],{},"Perhatikan bahwa ini ",[22,5329,5330],{},"bukan"," error JSON-RPC (tidak ada field ",[41,5333,5334],{},"error"," di level atas). Error tool dikirim sebagai ",[41,5337,5338],{},"result"," biasa dengan ",[41,5341,5342],{},"isError: true",". Dengan begitu, model bisa membaca pesannya dan mencoba lagi dengan argumen yang benar.",[10,5345,5346,5349,5350,5352],{},[22,5347,5348],{},"b) Data tidak ditemukan."," Misalnya ",[41,5351,4626],{}," dipanggil dengan id yang tidak ada:",[101,5354,5356],{"className":3312,"code":5355,"language":3314,"meta":109,"style":109},"{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 4,\n  \"method\": \"tools\u002Fcall\",\n  \"params\": { \"name\": \"detail_anak\", \"arguments\": { \"id_anak\": \"X999\" } }\n}\n",[41,5357,5358,5362,5372,5383,5393,5421],{"__ignoreMap":109},[201,5359,5360],{"class":203,"line":204},[201,5361,3321],{"class":215},[201,5363,5364,5366,5368,5370],{"class":203,"line":226},[201,5365,3538],{"class":219},[201,5367,471],{"class":215},[201,5369,3543],{"class":211},[201,5371,507],{"class":215},[201,5373,5374,5376,5378,5381],{"class":203,"line":241},[201,5375,3550],{"class":219},[201,5377,471],{"class":215},[201,5379,5380],{"class":219},"4",[201,5382,507],{"class":215},[201,5384,5385,5387,5389,5391],{"class":203,"line":248},[201,5386,3562],{"class":219},[201,5388,471],{"class":215},[201,5390,4679],{"class":211},[201,5392,507],{"class":215},[201,5394,5395,5397,5399,5401,5403,5405,5407,5409,5411,5413,5415,5418],{"class":203,"line":255},[201,5396,3574],{"class":219},[201,5398,3604],{"class":215},[201,5400,3607],{"class":219},[201,5402,471],{"class":215},[201,5404,4214],{"class":211},[201,5406,321],{"class":215},[201,5408,5211],{"class":219},[201,5410,3604],{"class":215},[201,5412,887],{"class":219},[201,5414,471],{"class":215},[201,5416,5417],{"class":211},"\"X999\"",[201,5419,5420],{"class":215}," } }\n",[201,5422,5423],{"class":203,"line":261},[201,5424,967],{"class":215},[10,5426,5427],{},"Balasannya:",[101,5429,5431],{"className":3312,"code":5430,"language":3314,"meta":109,"style":109},"{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 4,\n  \"result\": {\n    \"content\": [\n      {\n        \"type\": \"text\",\n        \"text\": \"{\\n  \\\"error\\\": \\\"Service mengembalikan status 404\\\",\\n  \\\"detail\\\": \\\"{\\\\\\\"detail\\\\\\\":\\\\\\\"Anak tidak ditemukan\\\\\\\"}\\\"\\n}\"\n      }\n    ],\n    \"isError\": false\n  }\n}\n",[41,5432,5433,5437,5447,5457,5463,5469,5473,5483,5546,5550,5554,5562,5566],{"__ignoreMap":109},[201,5434,5435],{"class":203,"line":204},[201,5436,3321],{"class":215},[201,5438,5439,5441,5443,5445],{"class":203,"line":226},[201,5440,3538],{"class":219},[201,5442,471],{"class":215},[201,5444,3543],{"class":211},[201,5446,507],{"class":215},[201,5448,5449,5451,5453,5455],{"class":203,"line":241},[201,5450,3550],{"class":219},[201,5452,471],{"class":215},[201,5454,5380],{"class":219},[201,5456,507],{"class":215},[201,5458,5459,5461],{"class":203,"line":248},[201,5460,3670],{"class":219},[201,5462,3329],{"class":215},[201,5464,5465,5467],{"class":203,"line":255},[201,5466,4764],{"class":219},[201,5468,878],{"class":215},[201,5470,5471],{"class":203,"line":261},[201,5472,3942],{"class":215},[201,5474,5475,5477,5479,5481],{"class":203,"line":267},[201,5476,4775],{"class":219},[201,5478,471],{"class":215},[201,5480,4780],{"class":211},[201,5482,507],{"class":215},[201,5484,5485,5487,5489,5491,5493,5495,5497,5499,5501,5503,5506,5508,5510,5512,5514,5516,5518,5520,5522,5524,5527,5529,5531,5533,5535,5538,5540,5542,5544],{"class":203,"line":387},[201,5486,4787],{"class":219},[201,5488,471],{"class":215},[201,5490,4792],{"class":211},[201,5492,3967],{"class":219},[201,5494,4797],{"class":219},[201,5496,5334],{"class":211},[201,5498,4803],{"class":219},[201,5500,471],{"class":211},[201,5502,4803],{"class":219},[201,5504,5505],{"class":211},"Service mengembalikan status 404",[201,5507,4803],{"class":219},[201,5509,4842],{"class":211},[201,5511,3967],{"class":219},[201,5513,4797],{"class":219},[201,5515,1044],{"class":211},[201,5517,4803],{"class":219},[201,5519,471],{"class":211},[201,5521,4803],{"class":219},[201,5523,2318],{"class":211},[201,5525,5526],{"class":219},"\\\\\\\"",[201,5528,1044],{"class":211},[201,5530,5526],{"class":219},[201,5532,280],{"class":211},[201,5534,5526],{"class":219},[201,5536,5537],{"class":211},"Anak tidak ditemukan",[201,5539,5526],{"class":219},[201,5541,2324],{"class":211},[201,5543,4941],{"class":219},[201,5545,5072],{"class":211},[201,5547,5548],{"class":203,"line":400},[201,5549,3441],{"class":215},[201,5551,5552],{"class":203,"line":405},[201,5553,5081],{"class":215},[201,5555,5556,5558,5560],{"class":203,"line":429},[201,5557,5086],{"class":219},[201,5559,471],{"class":215},[201,5561,5091],{"class":219},[201,5563,5564],{"class":203,"line":434},[201,5565,3451],{"class":215},[201,5567,5568],{"class":203,"line":446},[201,5569,967],{"class":215},[10,5571,5572,5573,5576,5577,5581,5582,5584,5585,5587],{},"Di sini ",[41,5574,5575],{},"isError"," bernilai ",[22,5578,5579],{},[41,5580,3694],{},", padahal sebenarnya terjadi error. Penyebabnya, ",[41,5583,3147],{}," menangkap error lalu mengembalikannya sebagai ",[41,5586,2074],{}," biasa, sehingga SDK menganggapnya hasil sukses. Model biasanya tetap paham dari isi teksnya, tetapi host tidak bisa membedakannya dari hasil sukses.",[10,5589,5590,5593,5594,280],{},[22,5591,5592],{},"Tips:"," kalau ingin error ditandai dengan benar, lempar exception alih-alih mengembalikan ",[41,5595,2074],{},[101,5597,5599],{"className":338,"code":5598,"language":229,"meta":109,"style":109},"    except httpx.HTTPStatusError as e:\n        log.warning(\"API error %s pada %s\", e.response.status_code, path)\n        raise ValueError(f\"Service mengembalikan status {e.response.status_code}: \"\n                         f\"{e.response.text[:300]}\")\n",[41,5600,5601,5611,5627,5649],{"__ignoreMap":109},[201,5602,5603,5605,5607,5609],{"class":203,"line":204},[201,5604,2269],{"class":374},[201,5606,2272],{"class":215},[201,5608,2220],{"class":374},[201,5610,2277],{"class":215},[201,5612,5613,5615,5617,5619,5621,5623,5625],{"class":203,"line":226},[201,5614,2282],{"class":215},[201,5616,2285],{"class":211},[201,5618,2288],{"class":219},[201,5620,2291],{"class":211},[201,5622,2288],{"class":219},[201,5624,1019],{"class":211},[201,5626,2298],{"class":215},[201,5628,5629,5631,5634,5636,5638,5640,5642,5644,5646],{"class":203,"line":241},[201,5630,1028],{"class":374},[201,5632,5633],{"class":219}," ValueError",[201,5635,1070],{"class":215},[201,5637,2105],{"class":374},[201,5639,2315],{"class":211},[201,5641,2318],{"class":219},[201,5643,2321],{"class":215},[201,5645,2324],{"class":219},[201,5647,5648],{"class":211},": \"\n",[201,5650,5651,5654,5656,5658,5661,5663,5666,5668,5670],{"class":203,"line":248},[201,5652,5653],{"class":374},"                         f",[201,5655,1019],{"class":211},[201,5657,2318],{"class":219},[201,5659,5660],{"class":215},"e.response.text[:",[201,5662,2339],{"class":219},[201,5664,5665],{"class":215},"]",[201,5667,2324],{"class":219},[201,5669,1019],{"class":211},[201,5671,426],{"class":215},[10,5673,5674,5675,5677],{},"SDK akan otomatis membungkus exception tersebut menjadi hasil dengan ",[41,5676,5342],{},", seperti pada kasus validasi di atas.",[3121,5679,5681,5682],{"id":5680},"_5-resource-resourceslist","5. Resource: ",[41,5683,5684],{},"resources\u002Flist",[101,5686,5688],{"className":3312,"code":5687,"language":3314,"meta":109,"style":109},"{ \"jsonrpc\": \"2.0\", \"id\": 6, \"method\": \"resources\u002Flist\" }\n",[41,5689,5690],{"__ignoreMap":109},[201,5691,5692,5694,5696,5698,5700,5702,5704,5706,5708,5710,5712,5714,5717],{"class":203,"line":204},[201,5693,3812],{"class":215},[201,5695,3815],{"class":219},[201,5697,471],{"class":215},[201,5699,3543],{"class":211},[201,5701,321],{"class":215},[201,5703,468],{"class":219},[201,5705,471],{"class":215},[201,5707,797],{"class":219},[201,5709,321],{"class":215},[201,5711,3824],{"class":219},[201,5713,471],{"class":215},[201,5715,5716],{"class":211},"\"resources\u002Flist\"",[201,5718,3625],{"class":215},[10,5720,5721],{},"Balasan:",[101,5723,5725],{"className":3312,"code":5724,"language":3314,"meta":109,"style":109},"{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 6,\n  \"result\": {\n    \"resources\": [\n      {\n        \"uri\": \"asrama:\u002F\u002Fkamar\",\n        \"name\": \"resource_kamar\",\n        \"description\": \"Daftar kamar asrama sebagai konteks.\",\n        \"mimeType\": \"text\u002Fplain\"\n      }\n    ]\n  }\n}\n",[41,5726,5727,5731,5741,5751,5757,5764,5768,5779,5790,5801,5811,5815,5819,5823],{"__ignoreMap":109},[201,5728,5729],{"class":203,"line":204},[201,5730,3321],{"class":215},[201,5732,5733,5735,5737,5739],{"class":203,"line":226},[201,5734,3538],{"class":219},[201,5736,471],{"class":215},[201,5738,3543],{"class":211},[201,5740,507],{"class":215},[201,5742,5743,5745,5747,5749],{"class":203,"line":241},[201,5744,3550],{"class":219},[201,5746,471],{"class":215},[201,5748,797],{"class":219},[201,5750,507],{"class":215},[201,5752,5753,5755],{"class":203,"line":248},[201,5754,3670],{"class":219},[201,5756,3329],{"class":215},[201,5758,5759,5762],{"class":203,"line":255},[201,5760,5761],{"class":219},"    \"resources\"",[201,5763,878],{"class":215},[201,5765,5766],{"class":203,"line":261},[201,5767,3942],{"class":215},[201,5769,5770,5773,5775,5777],{"class":203,"line":267},[201,5771,5772],{"class":219},"        \"uri\"",[201,5774,471],{"class":215},[201,5776,2896],{"class":211},[201,5778,507],{"class":215},[201,5780,5781,5783,5785,5788],{"class":203,"line":387},[201,5782,3947],{"class":219},[201,5784,471],{"class":215},[201,5786,5787],{"class":211},"\"resource_kamar\"",[201,5789,507],{"class":215},[201,5791,5792,5794,5796,5799],{"class":203,"line":400},[201,5793,3959],{"class":219},[201,5795,471],{"class":215},[201,5797,5798],{"class":211},"\"Daftar kamar asrama sebagai konteks.\"",[201,5800,507],{"class":215},[201,5802,5803,5806,5808],{"class":203,"line":405},[201,5804,5805],{"class":219},"        \"mimeType\"",[201,5807,471],{"class":215},[201,5809,5810],{"class":211},"\"text\u002Fplain\"\n",[201,5812,5813],{"class":203,"line":429},[201,5814,3441],{"class":215},[201,5816,5817],{"class":203,"line":434},[201,5818,961],{"class":215},[201,5820,5821],{"class":203,"line":446},[201,5822,3451],{"class":215},[201,5824,5825],{"class":203,"line":451},[201,5826,967],{"class":215},[10,5828,5829,5831,5832,5835,5836,4559,5839,5842],{},[41,5830,5684],{}," hanya berisi ",[22,5833,5834],{},"metadata",". Isi datanya baru diambil ketika host memanggil ",[41,5837,5838],{},"resources\u002Fread",[41,5840,5841],{},"uri"," tersebut. Berbeda dengan tool, resource biasanya dipilih oleh host atau user untuk dilampirkan sebagai konteks, bukan dipanggil sendiri oleh model.",[3121,5844,5846,5847],{"id":5845},"_6-prompt-promptsget","6. Prompt: ",[41,5848,5849],{},"prompts\u002Fget",[101,5851,5853],{"className":3312,"code":5852,"language":3314,"meta":109,"style":109},"{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 7,\n  \"method\": \"prompts\u002Fget\",\n  \"params\": {\n    \"name\": \"laporan_kamar\",\n    \"arguments\": { \"nama_kamar\": \"Al-Fatih 1\" }\n  }\n}\n",[41,5854,5855,5859,5869,5880,5891,5897,5908,5923,5927],{"__ignoreMap":109},[201,5856,5857],{"class":203,"line":204},[201,5858,3321],{"class":215},[201,5860,5861,5863,5865,5867],{"class":203,"line":226},[201,5862,3538],{"class":219},[201,5864,471],{"class":215},[201,5866,3543],{"class":211},[201,5868,507],{"class":215},[201,5870,5871,5873,5875,5878],{"class":203,"line":241},[201,5872,3550],{"class":219},[201,5874,471],{"class":215},[201,5876,5877],{"class":219},"7",[201,5879,507],{"class":215},[201,5881,5882,5884,5886,5889],{"class":203,"line":248},[201,5883,3562],{"class":219},[201,5885,471],{"class":215},[201,5887,5888],{"class":211},"\"prompts\u002Fget\"",[201,5890,507],{"class":215},[201,5892,5893,5895],{"class":203,"line":255},[201,5894,3574],{"class":219},[201,5896,3329],{"class":215},[201,5898,5899,5901,5903,5906],{"class":203,"line":261},[201,5900,4692],{"class":219},[201,5902,471],{"class":215},[201,5904,5905],{"class":211},"\"laporan_kamar\"",[201,5907,507],{"class":215},[201,5909,5910,5912,5914,5917,5919,5921],{"class":203,"line":267},[201,5911,4703],{"class":219},[201,5913,3604],{"class":215},[201,5915,5916],{"class":219},"\"nama_kamar\"",[201,5918,471],{"class":215},[201,5920,504],{"class":211},[201,5922,3625],{"class":215},[201,5924,5925],{"class":203,"line":387},[201,5926,3451],{"class":215},[201,5928,5929],{"class":203,"line":400},[201,5930,967],{"class":215},[10,5932,5721],{},[101,5934,5936],{"className":3312,"code":5935,"language":3314,"meta":109,"style":109},"{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 7,\n  \"result\": {\n    \"description\": \"Template untuk membuat laporan singkat satu kamar.\",\n    \"messages\": [\n      {\n        \"role\": \"user\",\n        \"content\": {\n          \"type\": \"text\",\n          \"text\": \"Buatkan laporan singkat untuk kamar 'Al-Fatih 1': daftar penghuni, jumlah terisi vs kapasitas, dan siapa yang tidak hadir hari ini.\"\n        }\n      }\n    ]\n  }\n}\n",[41,5937,5938,5942,5952,5962,5968,5980,5987,5991,6003,6010,6020,6030,6034,6038,6042,6046],{"__ignoreMap":109},[201,5939,5940],{"class":203,"line":204},[201,5941,3321],{"class":215},[201,5943,5944,5946,5948,5950],{"class":203,"line":226},[201,5945,3538],{"class":219},[201,5947,471],{"class":215},[201,5949,3543],{"class":211},[201,5951,507],{"class":215},[201,5953,5954,5956,5958,5960],{"class":203,"line":241},[201,5955,3550],{"class":219},[201,5957,471],{"class":215},[201,5959,5877],{"class":219},[201,5961,507],{"class":215},[201,5963,5964,5966],{"class":203,"line":248},[201,5965,3670],{"class":219},[201,5967,3329],{"class":215},[201,5969,5970,5973,5975,5978],{"class":203,"line":255},[201,5971,5972],{"class":219},"    \"description\"",[201,5974,471],{"class":215},[201,5976,5977],{"class":211},"\"Template untuk membuat laporan singkat satu kamar.\"",[201,5979,507],{"class":215},[201,5981,5982,5985],{"class":203,"line":261},[201,5983,5984],{"class":219},"    \"messages\"",[201,5986,878],{"class":215},[201,5988,5989],{"class":203,"line":267},[201,5990,3942],{"class":215},[201,5992,5993,5996,5998,6001],{"class":203,"line":387},[201,5994,5995],{"class":219},"        \"role\"",[201,5997,471],{"class":215},[201,5999,6000],{"class":211},"\"user\"",[201,6002,507],{"class":215},[201,6004,6005,6008],{"class":203,"line":400},[201,6006,6007],{"class":219},"        \"content\"",[201,6009,3329],{"class":215},[201,6011,6012,6014,6016,6018],{"class":203,"line":405},[201,6013,3984],{"class":219},[201,6015,471],{"class":215},[201,6017,4780],{"class":211},[201,6019,507],{"class":215},[201,6021,6022,6025,6027],{"class":203,"line":429},[201,6023,6024],{"class":219},"          \"text\"",[201,6026,471],{"class":215},[201,6028,6029],{"class":211},"\"Buatkan laporan singkat untuk kamar 'Al-Fatih 1': daftar penghuni, jumlah terisi vs kapasitas, dan siapa yang tidak hadir hari ini.\"\n",[201,6031,6032],{"class":203,"line":434},[201,6033,4196],{"class":215},[201,6035,6036],{"class":203,"line":446},[201,6037,3441],{"class":215},[201,6039,6040],{"class":203,"line":451},[201,6041,961],{"class":215},[201,6043,6044],{"class":203,"line":462},[201,6045,3451],{"class":215},[201,6047,6048],{"class":203,"line":510},[201,6049,967],{"class":215},[10,6051,6052,6053,6056,6057,6060],{},"String yang dikembalikan fungsi ",[41,6054,6055],{},"laporan_kamar"," dibungkus menjadi pesan ber-",[41,6058,6059],{},"role: \"user\"",". Di Claude Desktop, prompt seperti ini biasanya muncul sebagai pilihan di menu, lalu disisipkan ke percakapan seolah-olah user yang mengetiknya.",[3121,6062,6064],{"id":6063},"ringkasan-yang-benar-benar-dibaca-model","Ringkasan: Yang Benar-Benar Dibaca Model",[10,6066,6067,6068,6071,6072,321,6075,6077,6078,6081],{},"Model ",[22,6069,6070],{},"tidak"," melihat amplop ",[41,6073,6074],{},"jsonrpc",[41,6076,3801],{},", atau ",[41,6079,6080],{},"method",". Yang sampai ke model hanyalah:",[3176,6083,6084,6089,6098],{},[30,6085,6086,6088],{},[41,6087,3139],{}," dari server (tergantung host),",[30,6090,6091,321,6093,328,6095,6097],{},[41,6092,4608],{},[41,6094,4611],{},[41,6096,4614],{}," setiap tool,",[30,6099,6100,6101,6104],{},"teks di dalam ",[41,6102,6103],{},"content"," setiap hasil tool, beserta status error-nya.",[10,6106,6107,6108,80],{},"Artinya, kualitas agent Anda sangat bergantung pada tiga hal: ",[22,6109,6110],{},"nama tool yang jelas, docstring yang informatif, dan hasil tool yang ringkas serta relevan",[82,6112],{},[85,6114,6116],{"id":6115},"cara-menangkap-json-rpc-sendiri","Cara Menangkap JSON-RPC Sendiri",[10,6118,6119],{},"Kalau ingin melihat pesan mentah dari server Anda sendiri, berikut skrip kecil yang menjalankan server sebagai subprocess lalu mengirim pesan satu per satu:",[101,6121,6123],{"className":338,"code":6122,"language":229,"meta":109,"style":109},"import json, subprocess, os\n\nenv = {**os.environ, \"ASRAMA_API_TOKEN\": \"rahasia123\"}\np = subprocess.Popen(\n    [\"python\", \"server.py\"],\n    stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,\n    text=True, env=env,\n)\n\ndef send(msg, expect=True):\n    print(\">>> CLIENT KIRIM:\\n\" + json.dumps(msg, indent=2, ensure_ascii=False))\n    p.stdin.write(json.dumps(msg) + \"\\n\")\n    p.stdin.flush()\n    if expect:\n        r = json.loads(p.stdout.readline())\n        print(\"\u003C\u003C\u003C SERVER BALAS:\\n\" + json.dumps(r, indent=2, ensure_ascii=False))\n        print(\"=\" * 60)\n\nsend({\"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"initialize\",\n      \"params\": {\"protocolVersion\": \"2025-06-18\", \"capabilities\": {},\n                 \"clientInfo\": {\"name\": \"probe\", \"version\": \"1\"}}})\nsend({\"jsonrpc\": \"2.0\", \"method\": \"notifications\u002Finitialized\"}, expect=False)\nsend({\"jsonrpc\": \"2.0\", \"id\": 2, \"method\": \"tools\u002Flist\"})\nsend({\"jsonrpc\": \"2.0\", \"id\": 3, \"method\": \"tools\u002Fcall\",\n      \"params\": {\"name\": \"cari_anak\", \"arguments\": {\"kelas\": \"7A\"}}})\n\np.terminate()\n",[41,6124,6125,6132,6136,6159,6169,6184,6222,6242,6246,6250,6266,6303,6320,6325,6332,6342,6377,6394,6398,6427,6449,6473,6503,6532,6560,6586,6590],{"__ignoreMap":109},[201,6126,6127,6129],{"class":203,"line":204},[201,6128,381],{"class":374},[201,6130,6131],{"class":215}," json, subprocess, os\n",[201,6133,6134],{"class":203,"line":226},[201,6135,245],{"emptyLinePlaceholder":244},[201,6137,6138,6141,6143,6145,6147,6150,6152,6154,6157],{"class":203,"line":241},[201,6139,6140],{"class":215},"env ",[201,6142,411],{"class":374},[201,6144,1343],{"class":215},[201,6146,1612],{"class":374},[201,6148,6149],{"class":215},"os.environ, ",[201,6151,1901],{"class":211},[201,6153,471],{"class":215},[201,6155,6156],{"class":211},"\"rahasia123\"",[201,6158,967],{"class":215},[201,6160,6161,6164,6166],{"class":203,"line":248},[201,6162,6163],{"class":215},"p ",[201,6165,411],{"class":374},[201,6167,6168],{"class":215}," subprocess.Popen(\n",[201,6170,6171,6174,6177,6179,6182],{"class":203,"line":255},[201,6172,6173],{"class":215},"    [",[201,6175,6176],{"class":211},"\"python\"",[201,6178,321],{"class":215},[201,6180,6181],{"class":211},"\"server.py\"",[201,6183,3408],{"class":215},[201,6185,6186,6189,6191,6194,6197,6199,6202,6204,6206,6208,6210,6213,6215,6217,6220],{"class":203,"line":261},[201,6187,6188],{"class":417},"    stdin",[201,6190,411],{"class":374},[201,6192,6193],{"class":215},"subprocess.",[201,6195,6196],{"class":219},"PIPE",[201,6198,321],{"class":215},[201,6200,6201],{"class":417},"stdout",[201,6203,411],{"class":374},[201,6205,6193],{"class":215},[201,6207,6196],{"class":219},[201,6209,321],{"class":215},[201,6211,6212],{"class":417},"stderr",[201,6214,411],{"class":374},[201,6216,6193],{"class":215},[201,6218,6219],{"class":219},"DEVNULL",[201,6221,507],{"class":215},[201,6223,6224,6227,6229,6232,6234,6237,6239],{"class":203,"line":267},[201,6225,6226],{"class":417},"    text",[201,6228,411],{"class":374},[201,6230,6231],{"class":219},"True",[201,6233,321],{"class":215},[201,6235,6236],{"class":417},"env",[201,6238,411],{"class":374},[201,6240,6241],{"class":215},"env,\n",[201,6243,6244],{"class":203,"line":387},[201,6245,426],{"class":215},[201,6247,6248],{"class":203,"line":400},[201,6249,245],{"emptyLinePlaceholder":244},[201,6251,6252,6254,6257,6260,6262,6264],{"class":203,"line":405},[201,6253,983],{"class":374},[201,6255,6256],{"class":207}," send",[201,6258,6259],{"class":215},"(msg, expect",[201,6261,411],{"class":374},[201,6263,6231],{"class":219},[201,6265,1192],{"class":215},[201,6267,6268,6271,6273,6276,6278,6280,6283,6286,6288,6290,6292,6294,6296,6298,6300],{"class":203,"line":429},[201,6269,6270],{"class":219},"    print",[201,6272,1070],{"class":215},[201,6274,6275],{"class":211},"\">>> CLIENT KIRIM:",[201,6277,3967],{"class":219},[201,6279,1019],{"class":211},[201,6281,6282],{"class":374}," +",[201,6284,6285],{"class":215}," json.dumps(msg, ",[201,6287,2966],{"class":417},[201,6289,411],{"class":374},[201,6291,2971],{"class":219},[201,6293,321],{"class":215},[201,6295,2956],{"class":417},[201,6297,411],{"class":374},[201,6299,2961],{"class":219},[201,6301,6302],{"class":215},"))\n",[201,6304,6305,6308,6311,6314,6316,6318],{"class":203,"line":434},[201,6306,6307],{"class":215},"    p.stdin.write(json.dumps(msg) ",[201,6309,6310],{"class":374},"+",[201,6312,6313],{"class":211}," \"",[201,6315,3967],{"class":219},[201,6317,1019],{"class":211},[201,6319,426],{"class":215},[201,6321,6322],{"class":203,"line":446},[201,6323,6324],{"class":215},"    p.stdin.flush()\n",[201,6326,6327,6329],{"class":203,"line":451},[201,6328,1001],{"class":374},[201,6330,6331],{"class":215}," expect:\n",[201,6333,6334,6337,6339],{"class":203,"line":462},[201,6335,6336],{"class":215},"        r ",[201,6338,411],{"class":374},[201,6340,6341],{"class":215}," json.loads(p.stdout.readline())\n",[201,6343,6344,6347,6349,6352,6354,6356,6358,6361,6363,6365,6367,6369,6371,6373,6375],{"class":203,"line":510},[201,6345,6346],{"class":219},"        print",[201,6348,1070],{"class":215},[201,6350,6351],{"class":211},"\"\u003C\u003C\u003C SERVER BALAS:",[201,6353,3967],{"class":219},[201,6355,1019],{"class":211},[201,6357,6282],{"class":374},[201,6359,6360],{"class":215}," json.dumps(r, ",[201,6362,2966],{"class":417},[201,6364,411],{"class":374},[201,6366,2971],{"class":219},[201,6368,321],{"class":215},[201,6370,2956],{"class":417},[201,6372,411],{"class":374},[201,6374,2961],{"class":219},[201,6376,6302],{"class":215},[201,6378,6379,6381,6383,6386,6389,6392],{"class":203,"line":543},[201,6380,6346],{"class":219},[201,6382,1070],{"class":215},[201,6384,6385],{"class":211},"\"=\"",[201,6387,6388],{"class":374}," *",[201,6390,6391],{"class":219}," 60",[201,6393,426],{"class":215},[201,6395,6396],{"class":203,"line":567},[201,6397,245],{"emptyLinePlaceholder":244},[201,6399,6400,6403,6405,6407,6409,6411,6413,6415,6417,6419,6421,6423,6425],{"class":203,"line":607},[201,6401,6402],{"class":215},"send({",[201,6404,3815],{"class":211},[201,6406,471],{"class":215},[201,6408,3543],{"class":211},[201,6410,321],{"class":215},[201,6412,468],{"class":211},[201,6414,471],{"class":215},[201,6416,3555],{"class":219},[201,6418,321],{"class":215},[201,6420,3824],{"class":211},[201,6422,471],{"class":215},[201,6424,3567],{"class":211},[201,6426,507],{"class":215},[201,6428,6429,6432,6435,6438,6440,6442,6444,6447],{"class":203,"line":636},[201,6430,6431],{"class":211},"      \"params\"",[201,6433,6434],{"class":215},": {",[201,6436,6437],{"class":211},"\"protocolVersion\"",[201,6439,471],{"class":215},[201,6441,3586],{"class":211},[201,6443,321],{"class":215},[201,6445,6446],{"class":211},"\"capabilities\"",[201,6448,3596],{"class":215},[201,6450,6451,6454,6456,6458,6460,6462,6464,6466,6468,6470],{"class":203,"line":657},[201,6452,6453],{"class":211},"                 \"clientInfo\"",[201,6455,6434],{"class":215},[201,6457,3607],{"class":211},[201,6459,471],{"class":215},[201,6461,3612],{"class":211},[201,6463,321],{"class":215},[201,6465,3617],{"class":211},[201,6467,471],{"class":215},[201,6469,3622],{"class":211},[201,6471,6472],{"class":215},"}}})\n",[201,6474,6475,6477,6479,6481,6483,6485,6487,6489,6491,6494,6497,6499,6501],{"class":203,"line":697},[201,6476,6402],{"class":215},[201,6478,3815],{"class":211},[201,6480,471],{"class":215},[201,6482,3543],{"class":211},[201,6484,321],{"class":215},[201,6486,3824],{"class":211},[201,6488,471],{"class":215},[201,6490,3829],{"class":211},[201,6492,6493],{"class":215},"}, ",[201,6495,6496],{"class":417},"expect",[201,6498,411],{"class":374},[201,6500,2961],{"class":219},[201,6502,426],{"class":215},[201,6504,6505,6507,6509,6511,6513,6515,6517,6519,6521,6523,6525,6527,6529],{"class":203,"line":727},[201,6506,6402],{"class":215},[201,6508,3815],{"class":211},[201,6510,471],{"class":215},[201,6512,3543],{"class":211},[201,6514,321],{"class":215},[201,6516,468],{"class":211},[201,6518,471],{"class":215},[201,6520,2971],{"class":219},[201,6522,321],{"class":215},[201,6524,3824],{"class":211},[201,6526,471],{"class":215},[201,6528,3890],{"class":211},[201,6530,6531],{"class":215},"})\n",[201,6533,6534,6536,6538,6540,6542,6544,6546,6548,6550,6552,6554,6556,6558],{"class":203,"line":748},[201,6535,6402],{"class":215},[201,6537,3815],{"class":211},[201,6539,471],{"class":215},[201,6541,3543],{"class":211},[201,6543,321],{"class":215},[201,6545,468],{"class":211},[201,6547,471],{"class":215},[201,6549,4668],{"class":219},[201,6551,321],{"class":215},[201,6553,3824],{"class":211},[201,6555,471],{"class":215},[201,6557,4679],{"class":211},[201,6559,507],{"class":215},[201,6561,6562,6564,6566,6568,6570,6572,6574,6576,6578,6580,6582,6584],{"class":203,"line":754},[201,6563,6431],{"class":211},[201,6565,6434],{"class":215},[201,6567,3607],{"class":211},[201,6569,471],{"class":215},[201,6571,3952],{"class":211},[201,6573,321],{"class":215},[201,6575,5211],{"class":211},[201,6577,6434],{"class":215},[201,6579,489],{"class":211},[201,6581,471],{"class":215},[201,6583,494],{"class":211},[201,6585,6472],{"class":215},[201,6587,6588],{"class":203,"line":759},[201,6589,245],{"emptyLinePlaceholder":244},[201,6591,6592],{"class":203,"line":769},[201,6593,6594],{"class":215},"p.terminate()\n",[10,6596,6597],{},"Pastikan service mock sudah berjalan di port 8000 sebelum menjalankan skrip ini.",[82,6599],{},[85,6601,6603],{"id":6602},"tips-dan-praktik-baik","Tips dan Praktik Baik",[10,6605,6606,6609,6610,6613,6614,6617,6618,6621],{},[22,6607,6608],{},"Tulis docstring untuk model, bukan untuk manusia saja."," Sebutkan format input (misalnya ",[41,6611,6612],{},"YYYY-MM-DD","), contoh nilai (",[41,6615,6616],{},"'7A'","), dan kapan tool sebaiknya dipakai. Docstring dikirim mentah, termasuk indentasinya. Kalau ingin lebih bersih, gunakan ",[41,6619,6620],{},"@mcp.tool(description=\"...\")"," secara eksplisit.",[10,6623,6624,6627],{},[22,6625,6626],{},"Saring data sebelum dikirim."," Hasil tool masuk ke konteks model. Buang field sensitif dan field yang tidak relevan; selain lebih aman, ini juga menghemat token.",[10,6629,6630,6633,6634,6637,6638,6641],{},[22,6631,6632],{},"Batasi jumlah hasil."," Parameter ",[41,6635,6636],{},"limit"," dengan batas atas (",[41,6639,6640],{},"min(limit, 100)",") mencegah model menarik ribuan baris sekaligus.",[10,6643,6644,6647],{},[22,6645,6646],{},"Mulai dari read-only."," Tool yang mengubah data (misalnya input absensi atau mengubah status anak) sebaiknya ditambahkan belakangan, dengan validasi dan pembatasan akses yang ketat.",[10,6649,6650,6653],{},[22,6651,6652],{},"Gunakan stderr untuk log."," Ini sudah dibahas, tetapi layak diulang karena merupakan penyebab error paling umum pada server stdio.",[10,6655,6656,6659,6660,6662,6663,6666],{},[22,6657,6658],{},"Untuk akses jarak jauh, ganti transport."," Kalau MCP server ingin dipakai banyak orang lewat jaringan, ganti ",[41,6661,3196],{}," menjadi ",[41,6664,6665],{},"mcp.run(transport=\"streamable-http\")"," dan tambahkan autentikasi.",[82,6668],{},[85,6670,6672],{"id":6671},"penutup","Penutup",[10,6674,6675],{},"MCP server pada dasarnya adalah lapisan tipis di atas API yang sudah Anda miliki. Dengan SDK Python v2, cukup beberapa dekorator untuk mengubah fungsi biasa menjadi tool yang bisa dipakai agent AI.",[10,6677,6678,6679,6682],{},"Yang membuat perbedaan besar bukan kerumitan kodenya, melainkan pemahaman tentang ",[22,6680,6681],{},"apa yang sebenarnya dikirim ke model",". Setelah melihat JSON-RPC mentahnya, jelas bahwa docstring Anda adalah \"dokumentasi API\" untuk model, dan setiap hasil tool adalah konteks yang ikut ia baca. Rancang keduanya dengan cermat, dan agent Anda akan jauh lebih bisa diandalkan.",[6684,6685,6686],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":109,"searchDepth":226,"depth":226,"links":6688},[6689,6690,6691,6692,6693,6696,6697,6698,6699,6713,6714,6715],{"id":87,"depth":226,"text":88},{"id":179,"depth":226,"text":180},{"id":191,"depth":226,"text":192},{"id":313,"depth":226,"text":314},{"id":1749,"depth":226,"text":1750,"children":6694},[6695],{"id":3123,"depth":241,"text":3124},{"id":3230,"depth":226,"text":3231},{"id":3270,"depth":226,"text":3271},{"id":3301,"depth":226,"text":3302},{"id":3474,"depth":226,"text":3475,"children":6700},[6701,6703,6705,6707,6708,6710,6712],{"id":3515,"depth":241,"text":6702},"1. Handshake: initialize",{"id":3851,"depth":241,"text":6704},"2. Daftar Tool: tools\u002Flist",{"id":4630,"depth":241,"text":6706},"3. Memanggil Tool: tools\u002Fcall",{"id":5137,"depth":241,"text":5138},{"id":5680,"depth":241,"text":6709},"5. Resource: resources\u002Flist",{"id":5845,"depth":241,"text":6711},"6. Prompt: prompts\u002Fget",{"id":6063,"depth":241,"text":6064},{"id":6115,"depth":226,"text":6116},{"id":6602,"depth":226,"text":6603},{"id":6671,"depth":226,"text":6672},null,"2026-10-06","Panduan langkah demi langkah membungkus REST API data anak asrama menjadi MCP server dengan MCPServer (SDK MCP Python v2), lalu membedah pesan JSON-RPC yang sebenarnya dibaca oleh agent AI.",false,"md",{},"\u002Fwriting\u002Fmcp-server-asrama",{"title":5,"description":6718},"writing\u002Fmcp-server-asrama",[6726,6727,6728,6729],"Engineering","AI","MCP","Python","dOLTyCrO920AsXuZ9ZqhrXM4vGW9Z92CG2LwnGLC1iA",[6732,8453,8948,9213],{"id":6733,"title":6734,"body":6735,"cover":6716,"date":6717,"description":8446,"draft":6719,"extension":6720,"lab":6719,"meta":8447,"navigation":244,"original":3801,"path":8448,"seo":8449,"stem":8450,"tags":8451,"__hash__":8452},"writing_id\u002Fwriting\u002Fmcp-di-balik-layar.md","Di balik layar MCP: apa yang sebenarnya dibaca AI?",{"type":7,"value":6736,"toc":8430},[6737,6749,6778,6781,6785,6788,6794,6863,6870,6874,6880,6884,6903,6906,6910,6913,7311,7314,7358,7373,7377,7380,7521,7532,7536,7554,7561,7568,7806,7823,7838,7842,7848,7878,7881,7911,7915,7928,7934,7951,7963,7978,7989,7993,7999,8082,8088,8092,8103,8379,8386,8390,8427],[10,6738,6739,6740,6744,6745,6748],{},"Beberapa waktu lalu saya membuat ",[6741,6742,6743],"a",{"href":6722},"MCP server kecil untuk data anak asrama"," sebagai percobaan. Servernya jalan, Claude bisa menjawab ",[14,6746,6747],{},"\"siapa anak kelas 7A yang izin hari ini?\"",". Tapi setelah itu muncul pertanyaan yang lebih menarik dari kodenya sendiri:",[3176,6750,6751,6758,6769],{},[30,6752,6753,6754,6757],{},"Sebenarnya ",[22,6755,6756],{},"apa yang dibaca"," oleh model?",[30,6759,6760,6761,6764,6765,6768],{},"Apakah model membaca ",[22,6762,6763],{},"semua"," definisi tool dan semua data setiap kali, atau ada semacam ",[22,6766,6767],{},"glosarium\u002Findeks"," yang ia buka seperlunya?",[30,6770,6771,6772,6774,6775,6777],{},"Kalau model tidak melihat kode saya, dari mana ia tahu harus memanggil ",[41,6773,4527],{}," dulu, baru ",[41,6776,5150],{},"?",[10,6779,6780],{},"Artikel ini tidak membahas cara membuat server (itu ada di catatan lab tadi), tapi apa yang terjadi di belakangnya.",[85,6782,6784],{"id":6783},"tiga-pemain-bukan-dua","Tiga pemain, bukan dua",[10,6786,6787],{},"Kesalahpahaman paling umum: \"AI-nya terhubung ke MCP server\". Sebenarnya ada tiga pihak, dan model justru yang paling \"buta\":",[101,6789,6792],{"className":6790,"code":6791,"language":106},[104],"┌──────────────┐  teks\u002Ftoken   ┌──────────────┐   JSON-RPC    ┌──────────────┐\n│ Model        │ ◄───────────► │ Host         │ ◄───────────► │ MCP server   │\n│ (Claude)     │  (Messages    │ (Claude      │  (stdio \u002F     │ (server.py)  │\n│              │   API)        │  Desktop dll)│   HTTP)       │              │\n└──────────────┘               └──────────────┘               └──────────────┘\n",[41,6793,6791],{"__ignoreMap":109},[114,6795,6796,6812],{},[117,6797,6798],{},[120,6799,6800,6803,6806],{},[123,6801,6802],{},"Pihak",[123,6804,6805],{},"Yang ia lakukan",[123,6807,6808,6809,6811],{},"Yang ia ",[22,6810,6070],{}," lakukan",[133,6813,6814,6833,6850],{},[120,6815,6816,6821,6830],{},[138,6817,6818],{},[22,6819,6820],{},"MCP server",[138,6822,6823,6824,6826,6827,6829],{},"Menjawab ",[41,6825,3855],{},", menjalankan ",[41,6828,4634],{},", memanggil API Anda",[138,6831,6832],{},"Tidak pernah bicara dengan model",[120,6834,6835,6840,6847],{},[138,6836,6837,6839],{},[22,6838,3505],{}," (Claude Desktop, Claude Code, Cursor, …)",[138,6841,6842,6843,6846],{},"Bicara MCP ke server ",[14,6844,6845],{},"dan"," bicara API ke model, menerjemahkan di antara keduanya",[138,6848,6849],{},"Tidak memutuskan tool mana yang dipanggil",[120,6851,6852,6857,6860],{},[138,6853,6854],{},[22,6855,6856],{},"Model",[138,6858,6859],{},"Membaca teks, lalu menulis teks: jawaban, atau \"permintaan memanggil tool\"",[138,6861,6862],{},"Tidak membuka koneksi, tidak menjalankan kode, tidak tahu kata \"MCP\"",[10,6864,6865,6866,6869],{},"Model hanya menerima satu hal: ",[22,6867,6868],{},"sebuah request berisi teks",", lalu mengembalikan teks. Semua \"kecerdasan memakai tool\" terjadi karena host menyusun teks itu dengan cara tertentu.",[85,6871,6873],{"id":6872},"perjalanan-satu-pertanyaan","Perjalanan satu pertanyaan",[10,6875,6876,6877,80],{},"Mari ikuti apa yang terjadi saat pengurus asrama bertanya ",[14,6878,6879],{},"\"Siapa saja anak kelas 7A yang izin hari ini?\"",[3121,6881,6883],{"id":6882},"_0-sebelum-ada-pertanyaan","0. Sebelum ada pertanyaan",[10,6885,6886,6887,6889,6890,6892,6893,6895,6896,6899,6900,80],{},"Saat host dijalankan, ia menjalankan ",[41,6888,3204],{},", melakukan handshake ",[41,6891,3519],{},", lalu memanggil ",[41,6894,3855],{},". Daftar tool ini ",[22,6897,6898],{},"disimpan oleh host",", bukan oleh model. Host baru meminta ulang kalau server mengirim notifikasi ",[41,6901,6902],{},"notifications\u002Ftools\u002Flist_changed",[10,6904,6905],{},"Jadi sebelum pengguna mengetik apa pun, model belum melihat apa-apa. Model bahkan belum \"ada\" dalam percakapan ini.",[3121,6907,6909],{"id":6908},"_1-host-menyusun-request-pertama","1. Host menyusun request pertama",[10,6911,6912],{},"Begitu pengguna menekan Enter, host menyusun request ke model. Bentuknya kira-kira seperti ini (ilustrasi dengan format Claude Messages API; isi persisnya tergantung host):",[101,6914,6916],{"className":3312,"code":6915,"language":3314,"meta":109,"style":109},"{\n  \"model\": \"claude-opus-5-5\",\n  \"system\": \"Kamu adalah asisten ... Hari ini 2026-10-06.\\n\\n# MCP Server Instructions\\n## asrama\\nAkses read-only ke data anak asrama: pencarian anak, detail, kamar, dan absensi.\",\n  \"tools\": [\n    {\n      \"name\": \"mcp__asrama__cari_anak\",\n      \"description\": \"Cari anak asrama berdasarkan nama (sebagian), kelas (mis. '7A'), atau nama kamar.\\n    Semua filter opsional. Mengembalikan daftar ringkas anak beserta id-nya.\",\n      \"input_schema\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"nama\":  { \"anyOf\": [{ \"type\": \"string\" }, { \"type\": \"null\" }], \"default\": null },\n          \"kelas\": { \"anyOf\": [{ \"type\": \"string\" }, { \"type\": \"null\" }], \"default\": null },\n          \"kamar\": { \"anyOf\": [{ \"type\": \"string\" }, { \"type\": \"null\" }], \"default\": null },\n          \"limit\": { \"type\": \"integer\", \"default\": 20 }\n        }\n      }\n    },\n    { \"name\": \"mcp__asrama__detail_anak\", \"description\": \"...\", \"input_schema\": { \"...\": \"...\" } },\n    { \"name\": \"mcp__asrama__daftar_kamar\", \"description\": \"...\", \"input_schema\": { \"...\": \"...\" } },\n    { \"name\": \"mcp__asrama__absensi_harian\", \"description\": \"...\", \"input_schema\": { \"...\": \"...\" } }\n  ],\n  \"messages\": [\n    { \"role\": \"user\", \"content\": \"Siapa saja anak kelas 7A yang izin hari ini?\" }\n  ]\n}\n",[41,6917,6918,6922,6934,6962,6969,6974,6986,7001,7008,7018,7025,7060,7095,7130,7153,7157,7161,7165,7202,7235,7268,7273,7280,7302,7307],{"__ignoreMap":109},[201,6919,6920],{"class":203,"line":204},[201,6921,3321],{"class":215},[201,6923,6924,6927,6929,6932],{"class":203,"line":226},[201,6925,6926],{"class":219},"  \"model\"",[201,6928,471],{"class":215},[201,6930,6931],{"class":211},"\"claude-opus-5-5\"",[201,6933,507],{"class":215},[201,6935,6936,6939,6941,6944,6947,6950,6952,6955,6957,6960],{"class":203,"line":241},[201,6937,6938],{"class":219},"  \"system\"",[201,6940,471],{"class":215},[201,6942,6943],{"class":211},"\"Kamu adalah asisten ... Hari ini 2026-10-06.",[201,6945,6946],{"class":219},"\\n\\n",[201,6948,6949],{"class":211},"# MCP Server Instructions",[201,6951,3967],{"class":219},[201,6953,6954],{"class":211},"## asrama",[201,6956,3967],{"class":219},[201,6958,6959],{"class":211},"Akses read-only ke data anak asrama: pencarian anak, detail, kamar, dan absensi.\"",[201,6961,507],{"class":215},[201,6963,6964,6967],{"class":203,"line":248},[201,6965,6966],{"class":219},"  \"tools\"",[201,6968,878],{"class":215},[201,6970,6971],{"class":203,"line":255},[201,6972,6973],{"class":215},"    {\n",[201,6975,6976,6979,6981,6984],{"class":203,"line":261},[201,6977,6978],{"class":219},"      \"name\"",[201,6980,471],{"class":215},[201,6982,6983],{"class":211},"\"mcp__asrama__cari_anak\"",[201,6985,507],{"class":215},[201,6987,6988,6991,6993,6995,6997,6999],{"class":203,"line":267},[201,6989,6990],{"class":219},"      \"description\"",[201,6992,471],{"class":215},[201,6994,3964],{"class":211},[201,6996,3967],{"class":219},[201,6998,3970],{"class":211},[201,7000,507],{"class":215},[201,7002,7003,7006],{"class":203,"line":387},[201,7004,7005],{"class":219},"      \"input_schema\"",[201,7007,3329],{"class":215},[201,7009,7010,7012,7014,7016],{"class":203,"line":400},[201,7011,4775],{"class":219},[201,7013,471],{"class":215},[201,7015,3989],{"class":211},[201,7017,507],{"class":215},[201,7019,7020,7023],{"class":203,"line":405},[201,7021,7022],{"class":219},"        \"properties\"",[201,7024,3329],{"class":215},[201,7026,7027,7030,7032,7034,7036,7038,7040,7042,7044,7046,7048,7050,7052,7054,7056,7058],{"class":203,"line":429},[201,7028,7029],{"class":219},"          \"nama\"",[201,7031,4018],{"class":215},[201,7033,4021],{"class":219},[201,7035,4024],{"class":215},[201,7037,4027],{"class":219},[201,7039,471],{"class":215},[201,7041,4032],{"class":211},[201,7043,4035],{"class":215},[201,7045,4027],{"class":219},[201,7047,471],{"class":215},[201,7049,4042],{"class":211},[201,7051,4045],{"class":215},[201,7053,4048],{"class":219},[201,7055,471],{"class":215},[201,7057,4053],{"class":219},[201,7059,3697],{"class":215},[201,7061,7062,7065,7067,7069,7071,7073,7075,7077,7079,7081,7083,7085,7087,7089,7091,7093],{"class":203,"line":434},[201,7063,7064],{"class":219},"          \"kelas\"",[201,7066,3604],{"class":215},[201,7068,4021],{"class":219},[201,7070,4024],{"class":215},[201,7072,4027],{"class":219},[201,7074,471],{"class":215},[201,7076,4032],{"class":211},[201,7078,4035],{"class":215},[201,7080,4027],{"class":219},[201,7082,471],{"class":215},[201,7084,4042],{"class":211},[201,7086,4045],{"class":215},[201,7088,4048],{"class":219},[201,7090,471],{"class":215},[201,7092,4053],{"class":219},[201,7094,3697],{"class":215},[201,7096,7097,7100,7102,7104,7106,7108,7110,7112,7114,7116,7118,7120,7122,7124,7126,7128],{"class":203,"line":446},[201,7098,7099],{"class":219},"          \"kamar\"",[201,7101,3604],{"class":215},[201,7103,4021],{"class":219},[201,7105,4024],{"class":215},[201,7107,4027],{"class":219},[201,7109,471],{"class":215},[201,7111,4032],{"class":211},[201,7113,4035],{"class":215},[201,7115,4027],{"class":219},[201,7117,471],{"class":215},[201,7119,4042],{"class":211},[201,7121,4045],{"class":215},[201,7123,4048],{"class":219},[201,7125,471],{"class":215},[201,7127,4053],{"class":219},[201,7129,3697],{"class":215},[201,7131,7132,7135,7137,7139,7141,7143,7145,7147,7149,7151],{"class":203,"line":451},[201,7133,7134],{"class":219},"          \"limit\"",[201,7136,3604],{"class":215},[201,7138,4027],{"class":219},[201,7140,471],{"class":215},[201,7142,4167],{"class":211},[201,7144,321],{"class":215},[201,7146,4048],{"class":219},[201,7148,471],{"class":215},[201,7150,1153],{"class":219},[201,7152,3625],{"class":215},[201,7154,7155],{"class":203,"line":462},[201,7156,4196],{"class":215},[201,7158,7159],{"class":203,"line":510},[201,7160,3441],{"class":215},[201,7162,7163],{"class":203,"line":543},[201,7164,3742],{"class":215},[201,7166,7167,7170,7172,7174,7177,7179,7181,7183,7186,7188,7191,7193,7195,7197,7199],{"class":203,"line":567},[201,7168,7169],{"class":215},"    { ",[201,7171,3607],{"class":219},[201,7173,471],{"class":215},[201,7175,7176],{"class":211},"\"mcp__asrama__detail_anak\"",[201,7178,321],{"class":215},[201,7180,4542],{"class":219},[201,7182,471],{"class":215},[201,7184,7185],{"class":211},"\"...\"",[201,7187,321],{"class":215},[201,7189,7190],{"class":219},"\"input_schema\"",[201,7192,3604],{"class":215},[201,7194,7185],{"class":219},[201,7196,471],{"class":215},[201,7198,7185],{"class":211},[201,7200,7201],{"class":215}," } },\n",[201,7203,7204,7206,7208,7210,7213,7215,7217,7219,7221,7223,7225,7227,7229,7231,7233],{"class":203,"line":607},[201,7205,7169],{"class":215},[201,7207,3607],{"class":219},[201,7209,471],{"class":215},[201,7211,7212],{"class":211},"\"mcp__asrama__daftar_kamar\"",[201,7214,321],{"class":215},[201,7216,4542],{"class":219},[201,7218,471],{"class":215},[201,7220,7185],{"class":211},[201,7222,321],{"class":215},[201,7224,7190],{"class":219},[201,7226,3604],{"class":215},[201,7228,7185],{"class":219},[201,7230,471],{"class":215},[201,7232,7185],{"class":211},[201,7234,7201],{"class":215},[201,7236,7237,7239,7241,7243,7246,7248,7250,7252,7254,7256,7258,7260,7262,7264,7266],{"class":203,"line":636},[201,7238,7169],{"class":215},[201,7240,3607],{"class":219},[201,7242,471],{"class":215},[201,7244,7245],{"class":211},"\"mcp__asrama__absensi_harian\"",[201,7247,321],{"class":215},[201,7249,4542],{"class":219},[201,7251,471],{"class":215},[201,7253,7185],{"class":211},[201,7255,321],{"class":215},[201,7257,7190],{"class":219},[201,7259,3604],{"class":215},[201,7261,7185],{"class":219},[201,7263,471],{"class":215},[201,7265,7185],{"class":211},[201,7267,5420],{"class":215},[201,7269,7270],{"class":203,"line":657},[201,7271,7272],{"class":215},"  ],\n",[201,7274,7275,7278],{"class":203,"line":697},[201,7276,7277],{"class":219},"  \"messages\"",[201,7279,878],{"class":215},[201,7281,7282,7284,7287,7289,7291,7293,7296,7298,7300],{"class":203,"line":727},[201,7283,7169],{"class":215},[201,7285,7286],{"class":219},"\"role\"",[201,7288,471],{"class":215},[201,7290,6000],{"class":211},[201,7292,321],{"class":215},[201,7294,7295],{"class":219},"\"content\"",[201,7297,471],{"class":215},[201,7299,6879],{"class":211},[201,7301,3625],{"class":215},[201,7303,7304],{"class":203,"line":748},[201,7305,7306],{"class":215},"  ]\n",[201,7308,7309],{"class":203,"line":754},[201,7310,967],{"class":215},[10,7312,7313],{},"Perhatikan beberapa hal:",[3176,7315,7316,7334,7344,7352],{},[30,7317,7318,7326,7327,3251,7329,7331,7332,80],{},[22,7319,7320,7322,7323],{},[41,7321,4614],{}," dari MCP menjadi ",[41,7324,7325],{},"input_schema"," di API. Host hanya memindahkan isinya. ",[41,7328,4608],{},[41,7330,4611],{}," dari docstring Anda ikut apa adanya, termasuk indentasi ",[41,7333,4546],{},[30,7335,7336,7339,7340,7343],{},[22,7337,7338],{},"Nama tool bisa diberi awalan oleh host."," Claude Code misalnya menamainya ",[41,7341,7342],{},"mcp__asrama__cari_anak"," supaya tidak bentrok dengan tool dari server lain. Model tidak tahu itu \"tool MCP\"; baginya itu tool biasa bernama panjang.",[30,7345,7346,7351],{},[22,7347,7348,7350],{},[41,7349,3139],{}," server masuk ke system prompt",", tapi ini keputusan host. Ada host yang memasukkannya, ada yang tidak.",[30,7353,7354,7357],{},[22,7355,7356],{},"Tanggal hari ini"," datang dari host. Model tidak punya jam. Kalau host tidak menulis tanggal di system prompt, model tidak tahu apa arti \"hari ini\" dan harus menebak atau bertanya.",[10,7359,7360,7361,7364,7365,7368,7369,7372],{},"Lalu, apakah JSON di atas yang dibaca model? Tidak persis. API mengubah definisi tool, system prompt, dan pesan menjadi satu rangkaian ",[22,7362,7363],{},"token"," dengan urutan tetap: ",[14,7366,7367],{},"tools → system → messages",". Dokumentasi Anthropic menyebut definisi tool dirangkai menjadi bagian khusus dari system prompt. Jadi bagi model, docstring Anda adalah ",[22,7370,7371],{},"teks di awal prompt",", sejajar dengan instruksi lainnya.",[3121,7374,7376],{"id":7375},"_2-model-memanggil-tool-padahal-hanya-menulis-teks","2. Model \"memanggil\" tool, padahal hanya menulis teks",[10,7378,7379],{},"Model membaca semuanya, lalu membalas:",[101,7381,7383],{"className":3312,"code":7382,"language":3314,"meta":109,"style":109},"{\n  \"stop_reason\": \"tool_use\",\n  \"content\": [\n    { \"type\": \"text\", \"text\": \"Saya cek dulu daftar anak kelas 7A dan absensi hari ini.\" },\n    { \"type\": \"tool_use\", \"id\": \"toolu_01\", \"name\": \"mcp__asrama__cari_anak\", \"input\": { \"kelas\": \"7A\" } },\n    { \"type\": \"tool_use\", \"id\": \"toolu_02\", \"name\": \"mcp__asrama__absensi_harian\", \"input\": { \"tanggal\": \"2026-10-06\" } }\n  ]\n}\n",[41,7384,7385,7389,7401,7408,7429,7471,7513,7517],{"__ignoreMap":109},[201,7386,7387],{"class":203,"line":204},[201,7388,3321],{"class":215},[201,7390,7391,7394,7396,7399],{"class":203,"line":226},[201,7392,7393],{"class":219},"  \"stop_reason\"",[201,7395,471],{"class":215},[201,7397,7398],{"class":211},"\"tool_use\"",[201,7400,507],{"class":215},[201,7402,7403,7406],{"class":203,"line":241},[201,7404,7405],{"class":219},"  \"content\"",[201,7407,878],{"class":215},[201,7409,7410,7412,7414,7416,7418,7420,7422,7424,7427],{"class":203,"line":248},[201,7411,7169],{"class":215},[201,7413,4027],{"class":219},[201,7415,471],{"class":215},[201,7417,4780],{"class":211},[201,7419,321],{"class":215},[201,7421,4780],{"class":219},[201,7423,471],{"class":215},[201,7425,7426],{"class":211},"\"Saya cek dulu daftar anak kelas 7A dan absensi hari ini.\"",[201,7428,3697],{"class":215},[201,7430,7431,7433,7435,7437,7439,7441,7443,7445,7448,7450,7452,7454,7456,7458,7461,7463,7465,7467,7469],{"class":203,"line":255},[201,7432,7169],{"class":215},[201,7434,4027],{"class":219},[201,7436,471],{"class":215},[201,7438,7398],{"class":211},[201,7440,321],{"class":215},[201,7442,468],{"class":219},[201,7444,471],{"class":215},[201,7446,7447],{"class":211},"\"toolu_01\"",[201,7449,321],{"class":215},[201,7451,3607],{"class":219},[201,7453,471],{"class":215},[201,7455,6983],{"class":211},[201,7457,321],{"class":215},[201,7459,7460],{"class":219},"\"input\"",[201,7462,3604],{"class":215},[201,7464,489],{"class":219},[201,7466,471],{"class":215},[201,7468,494],{"class":211},[201,7470,7201],{"class":215},[201,7472,7473,7475,7477,7479,7481,7483,7485,7487,7490,7492,7494,7496,7498,7500,7502,7504,7506,7508,7511],{"class":203,"line":261},[201,7474,7169],{"class":215},[201,7476,4027],{"class":219},[201,7478,471],{"class":215},[201,7480,7398],{"class":211},[201,7482,321],{"class":215},[201,7484,468],{"class":219},[201,7486,471],{"class":215},[201,7488,7489],{"class":211},"\"toolu_02\"",[201,7491,321],{"class":215},[201,7493,3607],{"class":219},[201,7495,471],{"class":215},[201,7497,7245],{"class":211},[201,7499,321],{"class":215},[201,7501,7460],{"class":219},[201,7503,3604],{"class":215},[201,7505,1697],{"class":219},[201,7507,471],{"class":215},[201,7509,7510],{"class":211},"\"2026-10-06\"",[201,7512,5420],{"class":215},[201,7514,7515],{"class":203,"line":267},[201,7516,7306],{"class":215},[201,7518,7519],{"class":203,"line":387},[201,7520,967],{"class":215},[10,7522,7523,7524,7527,7528,7531],{},"Model tidak menjalankan apa pun. Ia hanya menghasilkan blok terstruktur yang berkata \"tolong jalankan ini\", lalu berhenti dengan ",[41,7525,7526],{},"stop_reason: \"tool_use\"",". Di sini model meminta ",[22,7529,7530],{},"dua tool sekaligus"," (parallel tool use) karena keduanya tidak saling bergantung. Tanggalnya ia ambil dari system prompt.",[3121,7533,7535],{"id":7534},"_3-host-menerjemahkan-ke-json-rpc","3. Host menerjemahkan ke JSON-RPC",[10,7537,7538,7539,7541,7542,7544,7545,7547,7548,7550,7551,7553],{},"Host melihat nama ",[41,7540,7342],{},", tahu itu milik server ",[41,7543,3135],{},", lalu mengirim ",[41,7546,4634],{}," dengan nama asli ",[41,7549,4527],{},", persis seperti yang dibedah di catatan lab. Server memanggil REST API, menyaring field sensitif, lalu membalas ",[41,7552,5116],{}," berisi JSON.",[3121,7555,7557,7558],{"id":7556},"_4-hasil-tool-masuk-ke-percakapan-lalu-semuanya-dikirim-ulang","4. Hasil tool masuk ke percakapan, lalu ",[22,7559,7560],{},"semuanya dikirim ulang",[10,7562,7563,7564,7567],{},"Ini bagian yang paling sering tidak disadari. Host menambahkan dua pesan ke riwayat, lalu mengirim ",[22,7565,7566],{},"request baru yang lengkap"," ke model:",[101,7569,7571],{"className":3312,"code":7570,"language":3314,"meta":109,"style":109},"{\n  \"system\": \"... (sama seperti tadi) ...\",\n  \"tools\": [ \"... (keempat tool, lengkap, sama seperti tadi) ...\" ],\n  \"messages\": [\n    { \"role\": \"user\", \"content\": \"Siapa saja anak kelas 7A yang izin hari ini?\" },\n    { \"role\": \"assistant\", \"content\": [ \"... blok text + dua blok tool_use di atas ...\" ] },\n    {\n      \"role\": \"user\",\n      \"content\": [\n        { \"type\": \"tool_result\", \"tool_use_id\": \"toolu_01\", \"content\": \"{\\n  \\\"total\\\": 2,\\n  \\\"data\\\": [ ... ]\\n}\" },\n        { \"type\": \"tool_result\", \"tool_use_id\": \"toolu_02\", \"content\": \"{\\n  \\\"tanggal\\\": \\\"2026-10-06\\\",\\n  \\\"data\\\": [ ... ]\\n}\" }\n      ]\n    }\n  ]\n}\n",[41,7572,7573,7577,7588,7601,7607,7627,7650,7654,7665,7672,7729,7789,7794,7798,7802],{"__ignoreMap":109},[201,7574,7575],{"class":203,"line":204},[201,7576,3321],{"class":215},[201,7578,7579,7581,7583,7586],{"class":203,"line":226},[201,7580,6938],{"class":219},[201,7582,471],{"class":215},[201,7584,7585],{"class":211},"\"... (sama seperti tadi) ...\"",[201,7587,507],{"class":215},[201,7589,7590,7592,7595,7598],{"class":203,"line":241},[201,7591,6966],{"class":219},[201,7593,7594],{"class":215},": [ ",[201,7596,7597],{"class":211},"\"... (keempat tool, lengkap, sama seperti tadi) ...\"",[201,7599,7600],{"class":215}," ],\n",[201,7602,7603,7605],{"class":203,"line":248},[201,7604,7277],{"class":219},[201,7606,878],{"class":215},[201,7608,7609,7611,7613,7615,7617,7619,7621,7623,7625],{"class":203,"line":255},[201,7610,7169],{"class":215},[201,7612,7286],{"class":219},[201,7614,471],{"class":215},[201,7616,6000],{"class":211},[201,7618,321],{"class":215},[201,7620,7295],{"class":219},[201,7622,471],{"class":215},[201,7624,6879],{"class":211},[201,7626,3697],{"class":215},[201,7628,7629,7631,7633,7635,7638,7640,7642,7644,7647],{"class":203,"line":261},[201,7630,7169],{"class":215},[201,7632,7286],{"class":219},[201,7634,471],{"class":215},[201,7636,7637],{"class":211},"\"assistant\"",[201,7639,321],{"class":215},[201,7641,7295],{"class":219},[201,7643,7594],{"class":215},[201,7645,7646],{"class":211},"\"... blok text + dua blok tool_use di atas ...\"",[201,7648,7649],{"class":215}," ] },\n",[201,7651,7652],{"class":203,"line":267},[201,7653,6973],{"class":215},[201,7655,7656,7659,7661,7663],{"class":203,"line":387},[201,7657,7658],{"class":219},"      \"role\"",[201,7660,471],{"class":215},[201,7662,6000],{"class":211},[201,7664,507],{"class":215},[201,7666,7667,7670],{"class":203,"line":400},[201,7668,7669],{"class":219},"      \"content\"",[201,7671,878],{"class":215},[201,7673,7674,7677,7679,7681,7684,7686,7689,7691,7693,7695,7697,7699,7701,7703,7705,7707,7709,7711,7713,7715,7717,7719,7722,7724,7727],{"class":203,"line":405},[201,7675,7676],{"class":215},"        { ",[201,7678,4027],{"class":219},[201,7680,471],{"class":215},[201,7682,7683],{"class":211},"\"tool_result\"",[201,7685,321],{"class":215},[201,7687,7688],{"class":219},"\"tool_use_id\"",[201,7690,471],{"class":215},[201,7692,7447],{"class":211},[201,7694,321],{"class":215},[201,7696,7295],{"class":219},[201,7698,471],{"class":215},[201,7700,4792],{"class":211},[201,7702,3967],{"class":219},[201,7704,4797],{"class":219},[201,7706,4800],{"class":211},[201,7708,4803],{"class":219},[201,7710,4806],{"class":211},[201,7712,3967],{"class":219},[201,7714,4797],{"class":219},[201,7716,4813],{"class":211},[201,7718,4803],{"class":219},[201,7720,7721],{"class":211},": [ ... ]",[201,7723,3967],{"class":219},[201,7725,7726],{"class":211},"}\"",[201,7728,3697],{"class":215},[201,7730,7731,7733,7735,7737,7739,7741,7743,7745,7747,7749,7751,7753,7755,7757,7759,7761,7763,7765,7767,7769,7771,7773,7775,7777,7779,7781,7783,7785,7787],{"class":203,"line":429},[201,7732,7676],{"class":215},[201,7734,4027],{"class":219},[201,7736,471],{"class":215},[201,7738,7683],{"class":211},[201,7740,321],{"class":215},[201,7742,7688],{"class":219},[201,7744,471],{"class":215},[201,7746,7489],{"class":211},[201,7748,321],{"class":215},[201,7750,7295],{"class":219},[201,7752,471],{"class":215},[201,7754,4792],{"class":211},[201,7756,3967],{"class":219},[201,7758,4797],{"class":219},[201,7760,5154],{"class":211},[201,7762,4803],{"class":219},[201,7764,471],{"class":211},[201,7766,4803],{"class":219},[201,7768,6717],{"class":211},[201,7770,4803],{"class":219},[201,7772,4842],{"class":211},[201,7774,3967],{"class":219},[201,7776,4797],{"class":219},[201,7778,4813],{"class":211},[201,7780,4803],{"class":219},[201,7782,7721],{"class":211},[201,7784,3967],{"class":219},[201,7786,7726],{"class":211},[201,7788,3625],{"class":215},[201,7790,7791],{"class":203,"line":434},[201,7792,7793],{"class":215},"      ]\n",[201,7795,7796],{"class":203,"line":446},[201,7797,3446],{"class":215},[201,7799,7800],{"class":203,"line":451},[201,7801,7306],{"class":215},[201,7803,7804],{"class":203,"line":462},[201,7805,967],{"class":215},[10,7807,7808,7809,7811,7812,7815,7816,7818,7819,7822],{},"Hasil tool dikirim sebagai pesan ber-",[41,7810,6059],{},", dipasangkan dengan ",[41,7813,7814],{},"tool_use_id",". Kalau MCP server membalas ",[41,7817,5342],{},", host biasanya meneruskannya sebagai ",[41,7820,7821],{},"is_error: true",". Itu sebabnya error yang \"disembunyikan\" sebagai dict biasa (dibahas di catatan lab) tetap terbaca sebagai sukses.",[10,7824,7825,7826,7829,7830,7833,7834,7837],{},"Model membaca ulang semuanya dari awal, mencocokkan dua hasil tadi, lalu menjawab: ",[14,7827,7828],{},"\"Dari dua anak kelas 7A, yang izin hari ini adalah Citra Lestari (pulang, acara keluarga).\""," Kali ini ",[41,7831,7832],{},"stop_reason","-nya ",[41,7835,7836],{},"end_turn",", dan host menampilkan jawabannya.",[85,7839,7841],{"id":7840},"jadi-apakah-semuanya-dibaca","Jadi, apakah semuanya dibaca?",[10,7843,7844,7847],{},[22,7845,7846],{},"Ya. Setiap request, semuanya."," Inilah jawaban untuk pertanyaan utama:",[27,7849,7850,7860,7866,7872],{},[30,7851,7852,7855,7856,7859],{},[22,7853,7854],{},"Model tidak punya ingatan antar request."," API-nya ",[14,7857,7858],{},"stateless",". \"Ingatan\" percakapan hanyalah riwayat yang dikirim ulang oleh host setiap kali. Di langkah 4 tadi model tidak \"melanjutkan\"; ia membaca ulang seluruh percakapan dari awal.",[30,7861,7862,7865],{},[22,7863,7864],{},"Semua definisi tool ikut di setiap request."," Keempat tool, lengkap dengan docstring dan schema, dikirim lagi di request kedua, ketiga, dan seterusnya, walaupun yang dipakai cuma satu.",[30,7867,7868,7871],{},[22,7869,7870],{},"Hasil tool lama juga ikut."," JSON daftar anak dari langkah 3 tetap ada di riwayat dan terbaca lagi di setiap giliran berikutnya, sampai host memangkas atau meringkas riwayatnya.",[30,7873,7874,7877],{},[22,7875,7876],{},"\"Membaca\" berarti semua token ada di konteks sekaligus."," Model tidak membuka-buka halaman. Semua token masuk bersamaan dan model memperhatikan seluruhnya saat menghasilkan setiap kata. Tidak ada bagian yang dilewati secara sengaja. Tapi pada konteks yang sangat panjang, perhatian terhadap detail kecil di tengah bisa kurang tajam. Ini alasan bagus untuk menjaga konteks tetap ringkas.",[10,7879,7880],{},"Konsekuensinya praktis:",[3176,7882,7883,7889,7905],{},[30,7884,7885,7888],{},[22,7886,7887],{},"Setiap tool punya \"biaya sewa\" token di setiap giliran."," Empat tool kecil tidak terasa. Tapi sepuluh server dengan dua puluh tool masing-masing berarti ribuan token definisi tool sudah terpakai sebelum pengguna mengetik apa pun.",[30,7890,7891,7894,7895,7898,7899,7901,7902,7904],{},[22,7892,7893],{},"Hasil tool yang gemuk dibayar berulang kali."," JSON dengan ",[41,7896,7897],{},"indent=2"," dan field yang tidak relevan terbaca lagi di setiap giliran setelahnya. Menyaring field (seperti ",[41,7900,3155],{},") dan membatasi ",[41,7903,6636],{}," tidak cuma soal privasi, tapi juga soal konteks.",[30,7906,7907,7910],{},[22,7908,7909],{},"Prompt caching membantu biaya, bukan isi."," Karena bagian awal (tools, system) sama dari request ke request, API bisa menyimpannya dalam cache sehingga lebih murah dan cepat. Tapi isinya tetap ada di konteks dan tetap \"dibaca\" model.",[85,7912,7914],{"id":7913},"apakah-ada-semacam-glosarium","Apakah ada semacam glosarium?",[10,7916,7917,7920,7921,7923,7924,7927],{},[22,7918,7919],{},"Secara default, tidak."," Tidak ada indeks, daftar isi, atau ringkasan yang dibuka seperlunya. Host mengirim definisi lengkap semua tool yang aktif. Protokol MCP sendiri juga tidak punya konsep glosarium. ",[41,7922,3855],{}," memang mendukung paginasi (",[41,7925,7926],{},"cursor","), tapi itu hanya urusan host dan server; host tetap mengambil semuanya lalu meneruskannya ke model.",[10,7929,7930,7931,280],{},"Tapi justru karena \"semua dibaca\" itu mahal, ada beberapa pola yang ",[22,7932,7933],{},"mirip glosarium",[10,7935,7936,7939,7940,7943,7944,7947,7948,80],{},[22,7937,7938],{},"1. Tool search \u002F deferred loading (di sisi host atau API)."," Ketika tool-nya sangat banyak, host bisa menahan definisi lengkap dan hanya memberi model sebuah tool pencari. Di Claude API ini disebut ",[14,7941,7942],{},"tool search tool",": tool lain ditandai ",[41,7945,7946],{},"defer_loading: true",", model awalnya hanya melihat alat pencarinya, lalu mencari tool yang relevan. Schema tool yang ditemukan baru ditambahkan ke konteks saat itu. Claude Code memakai pola serupa untuk MCP tool dalam jumlah besar: model hanya melihat daftar nama, lalu memuat schema lengkap lewat pencarian sebelum memanggilnya. Inilah yang paling mendekati \"glosarium\": ",[22,7949,7950],{},"nama dulu, detail belakangan",[10,7952,7953,5127,7956,7958,7959,7962],{},[22,7954,7955],{},"2. Resource tidak dibaca kecuali dilampirkan.",[41,7957,5684],{}," hanya berisi metadata (uri, nama, deskripsi), dan itu pun untuk host, bukan otomatis untuk model. Isi resource baru masuk ke konteks saat pengguna atau host melampirkannya, dan begitu masuk, ",[22,7960,7961],{},"seluruh isinya"," masuk (kecuali host memotongnya). Tidak ada pencarian bagian yang relevan secara otomatis.",[10,7964,7965,7968,7969,4559,7972,6077,7974,7977],{},[22,7966,7967],{},"3. Glosarium yang Anda buat sendiri: tool pencarian."," Kalau datanya besar, misalnya ribuan anak atau dokumen peraturan asrama yang panjang, jangan jadikan resource raksasa. Buat tool yang menyaring di sisi server, seperti ",[41,7970,7971],{},"cari_anak(kelas=...)",[41,7973,6636],{},[41,7975,7976],{},"cari_peraturan(kata_kunci=...)"," yang mengembalikan potongan yang relevan saja. Server yang melakukan \"membuka glosarium\", lalu model hanya menerima halaman yang dibutuhkan. Pola inilah yang biasanya disebut RAG; MCP tidak menyediakannya otomatis, tapi sangat mudah dibungkus sebagai tool.",[10,7979,7980,7983,7984,4623,7986,7988],{},[22,7981,7982],{},"4. Docstring sebagai petunjuk urutan."," Kalimat ",[14,7985,4622],{},[41,7987,4626],{}," adalah semacam peta mini. Model tidak punya dokumentasi lain tentang server Anda; docstring-lah satu-satunya manual yang ia baca.",[85,7990,7992],{"id":7991},"yang-tidak-pernah-sampai-ke-model","Yang tidak pernah sampai ke model",[10,7994,7995,7996,7998],{},"Sama pentingnya dengan yang dibaca adalah yang ",[22,7997,6070],{}," dibaca:",[114,8000,8001,8011],{},[117,8002,8003],{},[120,8004,8005,8008],{},[123,8006,8007],{},"Tidak dibaca model",[123,8009,8010],{},"Kenapa",[133,8012,8013,8021,8031,8045,8053,8074],{},[120,8014,8015,8018],{},[138,8016,8017],{},"Kode Python Anda",[138,8019,8020],{},"Hanya nama fungsi, docstring, dan schema hasil type hint yang dikirim",[120,8022,8023,8028],{},[138,8024,8025,8026],{},"URL API dan token ",[41,8027,3254],{},[138,8029,8030],{},"Hidup di environment server, tidak pernah dikirim ke host",[120,8032,8033,8042],{},[138,8034,8035,8036,321,8038,321,8040,4586],{},"Field yang disaring (",[41,8037,1737],{},[41,8039,1740],{},[41,8041,1743],{},[138,8043,8044],{},"Dibuang server sebelum dikirim",[120,8046,8047,8050],{},[138,8048,8049],{},"Log di stderr",[138,8051,8052],{},"Hanya stdout yang dipakai protokol, dan host tidak meneruskan log ke model",[120,8054,8055,8064],{},[138,8056,8057,8058,321,8060,321,8062,4586],{},"Amplop JSON-RPC (",[41,8059,6074],{},[41,8061,3801],{},[41,8063,6080],{},[138,8065,8066,8067,8070,8071],{},"Diterjemahkan host menjadi blok ",[41,8068,8069],{},"tool_use"," \u002F ",[41,8072,8073],{},"tool_result",[120,8075,8076,8079],{},[138,8077,8078],{},"Tool dari server yang tidak aktif",[138,8080,8081],{},"Host hanya mengirim tool yang sedang diaktifkan pengguna",[10,8083,8084,8085,8087],{},"Batas ini juga batas keamanan Anda. Apa pun yang dikembalikan tool akan dibaca model ",[22,8086,6845],{}," tetap ada di riwayat percakapan. Jadi saringlah di server, jangan berharap model \"tidak memperhatikan\".",[85,8089,8091],{"id":8090},"coba-ukur-sendiri","Coba ukur sendiri",[10,8093,8094,8095,8097,8098,6662,8100,8102],{},"Cara paling jujur untuk merasakan \"biaya sewa\" definisi tool adalah menghitung token-nya. Ambil hasil ",[41,8096,3855],{}," dari server Anda, ganti ",[41,8099,4614],{},[41,8101,7325],{},", lalu bandingkan jumlah token dengan dan tanpa tools memakai endpoint token counting:",[101,8104,8106],{"className":338,"code":8105,"language":229,"meta":109,"style":109},"import json\nimport anthropic\n\nclient = anthropic.Anthropic()\n\n# hasil tools\u002Flist dari MCP server, disimpan ke file\nmcp_tools = json.load(open(\"tools_list.json\"))[\"result\"][\"tools\"]\ntools = [\n    {\"name\": t[\"name\"], \"description\": t.get(\"description\", \"\"), \"input_schema\": t[\"inputSchema\"]}\n    for t in mcp_tools\n]\nmessages = [{\"role\": \"user\", \"content\": \"Siapa saja anak kelas 7A yang izin hari ini?\"}]\n\ntanpa = client.messages.count_tokens(model=\"claude-opus-5-5\", messages=messages)\ndengan = client.messages.count_tokens(model=\"claude-opus-5-5\", messages=messages, tools=tools)\n\nprint(\"tanpa tools :\", tanpa.input_tokens)\nprint(\"dengan tools:\", dengan.input_tokens)\nprint(\"biaya sewa  :\", dengan.input_tokens - tanpa.input_tokens, \"token per giliran\")\n",[41,8107,8108,8114,8121,8125,8135,8139,8144,8176,8185,8222,8234,8238,8265,8269,8296,8327,8331,8344,8356],{"__ignoreMap":109},[201,8109,8110,8112],{"class":203,"line":204},[201,8111,381],{"class":374},[201,8113,2929],{"class":215},[201,8115,8116,8118],{"class":203,"line":226},[201,8117,381],{"class":374},[201,8119,8120],{"class":215}," anthropic\n",[201,8122,8123],{"class":203,"line":241},[201,8124,245],{"emptyLinePlaceholder":244},[201,8126,8127,8130,8132],{"class":203,"line":248},[201,8128,8129],{"class":215},"client ",[201,8131,411],{"class":374},[201,8133,8134],{"class":215}," anthropic.Anthropic()\n",[201,8136,8137],{"class":203,"line":255},[201,8138,245],{"emptyLinePlaceholder":244},[201,8140,8141],{"class":203,"line":261},[201,8142,8143],{"class":251},"# hasil tools\u002Flist dari MCP server, disimpan ke file\n",[201,8145,8146,8149,8151,8154,8157,8159,8162,8165,8168,8171,8174],{"class":203,"line":267},[201,8147,8148],{"class":215},"mcp_tools ",[201,8150,411],{"class":374},[201,8152,8153],{"class":215}," json.load(",[201,8155,8156],{"class":219},"open",[201,8158,1070],{"class":215},[201,8160,8161],{"class":211},"\"tools_list.json\"",[201,8163,8164],{"class":215},"))[",[201,8166,8167],{"class":211},"\"result\"",[201,8169,8170],{"class":215},"][",[201,8172,8173],{"class":211},"\"tools\"",[201,8175,751],{"class":215},[201,8177,8178,8181,8183],{"class":203,"line":387},[201,8179,8180],{"class":215},"tools ",[201,8182,411],{"class":374},[201,8184,459],{"class":215},[201,8186,8187,8189,8191,8194,8196,8199,8201,8204,8206,8208,8210,8213,8215,8217,8220],{"class":203,"line":400},[201,8188,465],{"class":215},[201,8190,3607],{"class":211},[201,8192,8193],{"class":215},": t[",[201,8195,3607],{"class":211},[201,8197,8198],{"class":215},"], ",[201,8200,4542],{"class":211},[201,8202,8203],{"class":215},": t.get(",[201,8205,4542],{"class":211},[201,8207,321],{"class":215},[201,8209,1906],{"class":211},[201,8211,8212],{"class":215},"), ",[201,8214,7190],{"class":211},[201,8216,8193],{"class":215},[201,8218,8219],{"class":211},"\"inputSchema\"",[201,8221,2342],{"class":215},[201,8223,8224,8226,8229,8231],{"class":203,"line":405},[201,8225,1427],{"class":374},[201,8227,8228],{"class":215}," t ",[201,8230,1237],{"class":374},[201,8232,8233],{"class":215}," mcp_tools\n",[201,8235,8236],{"class":203,"line":429},[201,8237,751],{"class":215},[201,8239,8240,8243,8245,8248,8250,8252,8254,8256,8258,8260,8262],{"class":203,"line":434},[201,8241,8242],{"class":215},"messages ",[201,8244,411],{"class":374},[201,8246,8247],{"class":215}," [{",[201,8249,7286],{"class":211},[201,8251,471],{"class":215},[201,8253,6000],{"class":211},[201,8255,321],{"class":215},[201,8257,7295],{"class":211},[201,8259,471],{"class":215},[201,8261,6879],{"class":211},[201,8263,8264],{"class":215},"}]\n",[201,8266,8267],{"class":203,"line":446},[201,8268,245],{"emptyLinePlaceholder":244},[201,8270,8271,8274,8276,8279,8282,8284,8286,8288,8291,8293],{"class":203,"line":451},[201,8272,8273],{"class":215},"tanpa ",[201,8275,411],{"class":374},[201,8277,8278],{"class":215}," client.messages.count_tokens(",[201,8280,8281],{"class":417},"model",[201,8283,411],{"class":374},[201,8285,6931],{"class":211},[201,8287,321],{"class":215},[201,8289,8290],{"class":417},"messages",[201,8292,411],{"class":374},[201,8294,8295],{"class":215},"messages)\n",[201,8297,8298,8301,8303,8305,8307,8309,8311,8313,8315,8317,8320,8322,8324],{"class":203,"line":462},[201,8299,8300],{"class":215},"dengan ",[201,8302,411],{"class":374},[201,8304,8278],{"class":215},[201,8306,8281],{"class":417},[201,8308,411],{"class":374},[201,8310,6931],{"class":211},[201,8312,321],{"class":215},[201,8314,8290],{"class":417},[201,8316,411],{"class":374},[201,8318,8319],{"class":215},"messages, ",[201,8321,98],{"class":417},[201,8323,411],{"class":374},[201,8325,8326],{"class":215},"tools)\n",[201,8328,8329],{"class":203,"line":510},[201,8330,245],{"emptyLinePlaceholder":244},[201,8332,8333,8336,8338,8341],{"class":203,"line":543},[201,8334,8335],{"class":219},"print",[201,8337,1070],{"class":215},[201,8339,8340],{"class":211},"\"tanpa tools :\"",[201,8342,8343],{"class":215},", tanpa.input_tokens)\n",[201,8345,8346,8348,8350,8353],{"class":203,"line":567},[201,8347,8335],{"class":219},[201,8349,1070],{"class":215},[201,8351,8352],{"class":211},"\"dengan tools:\"",[201,8354,8355],{"class":215},", dengan.input_tokens)\n",[201,8357,8358,8360,8362,8365,8368,8371,8374,8377],{"class":203,"line":607},[201,8359,8335],{"class":219},[201,8361,1070],{"class":215},[201,8363,8364],{"class":211},"\"biaya sewa  :\"",[201,8366,8367],{"class":215},", dengan.input_tokens ",[201,8369,8370],{"class":374},"-",[201,8372,8373],{"class":215}," tanpa.input_tokens, ",[201,8375,8376],{"class":211},"\"token per giliran\"",[201,8378,426],{"class":215},[10,8380,8381,8382,8385],{},"Selisihnya adalah token yang dibaca model ",[22,8383,8384],{},"di setiap giliran"," hanya untuk mengenal tool Anda. Coba juga rapikan docstring (hapus indentasi, perjelas kalimat) dan lihat bagaimana angkanya berubah.",[85,8387,8389],{"id":8388},"ringkasan","Ringkasan",[3176,8391,8392,8398,8408,8413,8424],{},[30,8393,8394,8395,8397],{},"Model tidak terhubung ke MCP server. ",[22,8396,3505],{}," yang bicara ke keduanya dan menerjemahkan di antaranya.",[30,8399,8400,8401,8403,8404,8407],{},"Yang dibaca model: system prompt (termasuk ",[41,8402,3139],{}," server, kalau host memasukkannya), ",[22,8405,8406],{},"nama + docstring + schema"," setiap tool, dan seluruh riwayat percakapan termasuk semua hasil tool.",[30,8409,6067,8410,8412],{},[22,8411,7858],{},": semuanya dikirim ulang dan dibaca ulang di setiap request. Tidak ada glosarium bawaan.",[30,8414,8415,8416,8419,8420,8423],{},"Pola mirip glosarium ada, tapi harus dibangun: ",[22,8417,8418],{},"tool search \u002F deferred loading"," di host, dan ",[22,8421,8422],{},"tool pencarian"," di server Anda sendiri.",[30,8425,8426],{},"Karena itu, tiga hal paling menentukan kualitas agent tetap sama: nama tool yang jelas, docstring yang informatif, dan hasil tool yang ringkas. Sekarang alasannya lebih jelas: semua itu adalah teks yang dibaca model, berulang kali, di setiap giliran.",[6684,8428,8429],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":109,"searchDepth":226,"depth":226,"links":8431},[8432,8433,8441,8442,8443,8444,8445],{"id":6783,"depth":226,"text":6784},{"id":6872,"depth":226,"text":6873,"children":8434},[8435,8436,8437,8438,8439],{"id":6882,"depth":241,"text":6883},{"id":6908,"depth":241,"text":6909},{"id":7375,"depth":241,"text":7376},{"id":7534,"depth":241,"text":7535},{"id":7556,"depth":241,"text":8440},"4. Hasil tool masuk ke percakapan, lalu semuanya dikirim ulang",{"id":7840,"depth":226,"text":7841},{"id":7913,"depth":226,"text":7914},{"id":7991,"depth":226,"text":7992},{"id":8090,"depth":226,"text":8091},{"id":8388,"depth":226,"text":8389},"Model tidak pernah melihat kode Anda, tidak bicara JSON-RPC, dan tidak punya ingatan. Lalu bagaimana ia tahu tool mana yang harus dipanggil? Membedah perjalanan satu pertanyaan dari host ke model, dan menjawab: apakah semuanya dibaca, atau ada semacam glosarium?",{},"\u002Fwriting\u002Fmcp-di-balik-layar",{"title":6734,"description":8446},"writing\u002Fmcp-di-balik-layar",[6726,6727,6728],"kMzEnMiDD4jmQ-17_C8YG32wi1Zyrv7bAvxET2wdQDQ",{"id":8454,"title":8455,"body":8456,"cover":8532,"date":8937,"description":8938,"draft":6719,"extension":6720,"lab":6719,"meta":8939,"navigation":244,"original":8940,"path":8941,"seo":8942,"stem":8943,"tags":8944,"__hash__":8947},"writing_id\u002Fwriting\u002Fclaude-code-content-plan.md","Mengubah rencana konten 30 hari menjadi draft artikel dengan Claude Code",{"type":7,"value":8457,"toc":8925},[8458,8467,8470,8474,8477,8497,8504,8508,8515,8526,8533,8537,8548,8615,8618,8639,8643,8657,8724,8727,8731,8738,8744,8751,8754,8760,8764,8767,8770,8798,8802,8805,8808,8819,8830,8848,8851,8854,8879,8883,8903,8907,8910,8916,8922],[10,8459,8460,8466],{},[6741,8461,8465],{"href":8462,"rel":8463},"https:\u002F\u002Fkatili.dev",[8464],"nofollow","katili.dev"," — studio hosting dan web development milik saya — punya blog bilingual. Tiap bulan saya merencanakan sekitar 30 artikel di spreadsheet (judul, deskripsi, kategori, bahasa, link referensi, dan prompt siap pakai), meng-import-nya ke admin panel Laravel, lalu mengerjakannya satu per satu, hari demi hari.",[10,8468,8469],{},"Bagian yang paling makan waktu adalah menghasilkan artikelnya — dan penyebabnya justru bukan proses menulis itu sendiri.",[85,8471,8473],{"id":8472},"rutinitas-copy-paste","Rutinitas copy-paste",[10,8475,8476],{},"Untuk setiap artikel, alurnya kurang lebih begini:",[27,8478,8479,8482,8488,8491,8494],{},[30,8480,8481],{},"Buka project Claude yang instruksinya berisi panduan menulis saya (struktur, gaya bahasa, field SEO, dan format JSON persis yang dibutuhkan importer saya).",[30,8483,8484,8485,80],{},"Kembali ke admin panel, cari hari yang dimaksud, klik ",[22,8486,8487],{},"Copy Prompt",[30,8489,8490],{},"Pindah tab, buka chat baru di project tadi, paste, lalu tunggu.",[30,8492,8493],{},"Copy response JSON-nya, buka halaman import, paste, import.",[30,8495,8496],{},"Kembali ke content plan, tandai hari itu selesai, lalu link ke post yang baru.",[10,8498,8499,8500,8503],{},"Lima kali context switch untuk satu artikel, tiga puluh artikel sebulan. Tidak ada yang sulit; semuanya cuma bikin repot. Saya ingin cukup mengetik satu perintah lalu kembali dan mendapati draft-nya sudah jadi — ",[22,8501,8502],{},"tanpa"," memasang API key AI di server dan membayar per token, padahal saya sudah berlangganan dan memakainya setiap hari.",[85,8505,8507],{"id":8506},"idenya-claude-code-yang-mengemudi-server-cukup-jadi-pelayan","Idenya: Claude Code yang mengemudi, server cukup jadi \"pelayan\"",[10,8509,8510,8511,8514],{},"Claude Code sudah berjalan di terminal saya, di dalam repository. Ia bisa menjalankan shell command, fetch halaman web, menulis file, dan menjalankan subagent dengan model pilihan. Jadi server tidak perlu ",[14,8512,8513],{},"menghasilkan"," apa-apa. Server cukup:",[3176,8516,8517,8520,8523],{},[30,8518,8519],{},"memberi tahu Claude isi content plan-nya,",[30,8521,8522],{},"memberikan prompt untuk satu item,",[30,8524,8525],{},"menerima JSON yang sudah jadi lalu meng-import-nya.",[10,8527,8528],{},[8529,8530],"img",{"alt":8531,"src":8532},"Alurnya: Claude Code memanggil API kecil melalui script bash; satu subagent per artikel","\u002Fimages\u002Fwriting\u002Fclaude-code-content-plan\u002Fflow.svg",[3121,8534,8536],{"id":8535},"_1-api-kecil-yang-sengaja-dibuat-sederhana","1. API kecil yang sengaja dibuat sederhana",[10,8538,8539,8540,8543,8544,8547],{},"Lima route di ",[41,8541,8542],{},"routes\u002Fapi.php",", di balik satu middleware yang mencocokkan Bearer token dengan environment variable memakai ",[41,8545,8546],{},"hash_equals",". Tanpa Sanctum, tanpa tabel user — ini tool untuk single user.",[114,8549,8550,8560],{},[117,8551,8552],{},[120,8553,8554,8557],{},[123,8555,8556],{},"Endpoint",[123,8558,8559],{},"Kegunaan",[133,8561,8562,8572,8585,8595,8605],{},[120,8563,8564,8569],{},[138,8565,8566],{},[41,8567,8568],{},"GET \u002Fapi\u002Fcontent-plans",[138,8570,8571],{},"daftar content plan beserta jumlah item selesai \u002F sedang dikerjakan \u002F belum",[120,8573,8574,8579],{},[138,8575,8576],{},[41,8577,8578],{},"GET \u002Fapi\u002Fcontent-plans\u002F{plan}\u002Fitems",[138,8580,8581,8582,8584],{},"daftar item ",[22,8583,8502],{}," prompt",[120,8586,8587,8592],{},[138,8588,8589],{},[41,8590,8591],{},"GET \u002Fapi\u002Fcontent-plan-items\u002F{item}",[138,8593,8594],{},"satu item lengkap dengan prompt dan referensinya",[120,8596,8597,8602],{},[138,8598,8599],{},[41,8600,8601],{},"PATCH \u002Fapi\u002Fcontent-plan-items\u002F{item}",[138,8603,8604],{},"mengubah status",[120,8606,8607,8612],{},[138,8608,8609],{},[41,8610,8611],{},"POST \u002Fapi\u002Fcontent-plan-items\u002F{item}\u002Fimport",[138,8613,8614],{},"meng-import artikel yang sudah di-generate",[10,8616,8617],{},"Ada dua keputusan yang lebih penting daripada route-nya sendiri:",[3176,8619,8620,8626],{},[30,8621,8622,8625],{},[22,8623,8624],{},"Kalau token kosong, API mati"," (403), bukan malah terbuka. Lupa mengisi env var jangan sampai membuat endpoint import bisa diakses siapa saja.",[30,8627,8628,8635,8636,80],{},[22,8629,8630,8631,8634],{},"Import memakai ulang command ",[41,8632,8633],{},"blog:import-json"," yang sudah ada"," — code path yang sama dengan yang dipakai halaman admin. Endpoint menyimpan JSON di disk yang sama, menjalankan command itu, lalu membaca kembali ID post yang dibuatnya. Jadi tetap hanya ada satu pintu masuk artikel ke database, dan setiap post baru selalu masuk sebagai ",[22,8637,8638],{},"draft",[3121,8640,8642],{"id":8641},"_2-client-bash-bukan-client-yang-canggih","2. Client bash, bukan client yang canggih",[10,8644,8645,8648,8649,8652,8653,8656],{},[41,8646,8647],{},"bin\u002Fcontent-plan"," hanyalah bash dan ",[41,8650,8651],{},"curl"," — tanpa ",[41,8654,8655],{},"jq",", tidak perlu install apa pun:",[101,8658,8660],{"className":195,"code":8659,"language":197,"meta":109,"style":109},"bin\u002Fcontent-plan plans\nbin\u002Fcontent-plan items 1          # apa yang masih harus dikerjakan\nbin\u002Fcontent-plan show 42          # prompt lengkapnya\nbin\u002Fcontent-plan check 42 a.json  # hanya validasi (dry run)\nbin\u002Fcontent-plan import 42 a.json # post draft, item ditandai selesai\n",[41,8661,8662,8669,8682,8695,8710],{"__ignoreMap":109},[201,8663,8664,8666],{"class":203,"line":204},[201,8665,8647],{"class":207},[201,8667,8668],{"class":211}," plans\n",[201,8670,8671,8673,8676,8679],{"class":203,"line":226},[201,8672,8647],{"class":207},[201,8674,8675],{"class":211}," items",[201,8677,8678],{"class":219}," 1",[201,8680,8681],{"class":251},"          # apa yang masih harus dikerjakan\n",[201,8683,8684,8686,8689,8692],{"class":203,"line":241},[201,8685,8647],{"class":207},[201,8687,8688],{"class":211}," show",[201,8690,8691],{"class":219}," 42",[201,8693,8694],{"class":251},"          # prompt lengkapnya\n",[201,8696,8697,8699,8702,8704,8707],{"class":203,"line":248},[201,8698,8647],{"class":207},[201,8700,8701],{"class":211}," check",[201,8703,8691],{"class":219},[201,8705,8706],{"class":211}," a.json",[201,8708,8709],{"class":251},"  # hanya validasi (dry run)\n",[201,8711,8712,8714,8717,8719,8721],{"class":203,"line":255},[201,8713,8647],{"class":207},[201,8715,8716],{"class":211}," import",[201,8718,8691],{"class":219},[201,8720,8706],{"class":211},[201,8722,8723],{"class":251}," # post draft, item ditandai selesai\n",[10,8725,8726],{},"Script ini sama enaknya dipakai manual maupun oleh Claude — tanda bahwa interface-nya sudah pas.",[3121,8728,8730],{"id":8729},"_3-satu-skill-satu-subagent-per-artikel","3. Satu skill, satu subagent per artikel",[10,8732,8733,8734,8737],{},"Sebuah project skill (",[41,8735,8736],{},".claude\u002Fskills\u002Fgenerate-blog\u002FSKILL.md",") meringkas seluruh rutinitas tadi menjadi:",[101,8739,8742],{"className":8740,"code":8741,"language":106,"meta":109},[104],"\u002Fgenerate-blog 1 3,5,7-9 sonnet\n",[41,8743,8741],{"__ignoreMap":109},[10,8745,8746,8747,8750],{},"Artinya: content plan 1, item 3, 5, 7, 8, dan 9, ditulis oleh Sonnet (default-nya Opus). Untuk setiap item, main session menjalankan ",[22,8748,8749],{},"satu subagent"," dengan model yang dipilih. Subagent itu membaca panduan menulis langsung dari repository, meminta prompt item tersebut ke API, fetch link referensi, menulis JSON bilingual, memvalidasinya lewat dry run, membereskan apa pun yang ditolak server, lalu meng-import-nya.",[10,8752,8753],{},"Setelah selesai, ia cukup membalas dengan satu baris:",[101,8755,8758],{"className":8756,"code":8757,"language":106,"meta":109},[104],"OK 42 | Apa Itu Harness dalam AI Agent? | #101 (id), #102 (en) · draft\n",[41,8759,8757],{"__ignoreMap":109},[85,8761,8763],{"id":8762},"mendesain-demi-hemat-token-bukan-sekadar-fitur","Mendesain demi hemat token, bukan sekadar fitur",[10,8765,8766],{},"Tantangan yang menarik di sini bukan soal fungsionalitas, melainkan ukuran context. Versi yang naif akan menjejalkan panduan 7,6 KB, tiga puluh prompt, belasan halaman web, dan semua artikel yang sudah jadi ke dalam satu percakapan. Di artikel kelima session-nya sudah lambat dan mahal; di artikel kesepuluh ia mulai lupa isi panduannya.",[10,8768,8769],{},"Ada tiga pilihan yang membuatnya tetap ramping:",[3176,8771,8772,8786,8792],{},[30,8773,8774,8777,8778,8781,8782,8785],{},[22,8775,8776],{},"Teks, bukan JSON, untuk apa pun yang dibaca model."," Setiap ",[41,8779,8780],{},"GET"," menerima ",[41,8783,8784],{},"?format=text"," dan mengembalikan tabel ringkas yang pipe-delimited. JSON mengulang setiap key di setiap baris, dan model harus \"membayar\" setiap pengulangan itu.",[30,8787,8788,8791],{},[22,8789,8790],{},"Prompt hanya diambil saat dibutuhkan."," Daftar item tidak pernah menyertakan prompt. Hanya item yang sedang ditulis yang mengambil prompt-nya.",[30,8793,8794,8797],{},[22,8795,8796],{},"Subagent sebagai context firewall."," Panduan, referensi, dan artikelnya hanya ada di dalam context subagent, lalu hilang begitu ia selesai. Main session hanya melihat satu tabel di awal dan satu baris hasil per artikel — jadi biaya artikel kesepuluh kurang lebih sama dengan artikel pertama.",[85,8799,8801],{"id":8800},"supaya-bisa-jalan-dari-browser-juga","Supaya bisa jalan dari browser juga",[10,8803,8804],{},"Saya ingin perintah yang sama bisa dijalankan dari Claude Code versi web, supaya bisa memulai satu batch langsung dari ponsel. Cloud session punya tiga perbedaan dengan laptop saya: repository-nya hasil fresh clone (file config lokal saya tidak ikut), outbound traffic-nya melewati network policy, dan tidak ada orang yang meng-approve setiap command.",[10,8806,8807],{},"Urusan config gampang — script sekarang membaca environment variable lebih dulu, baru file lokal, jadi token bisa disimpan di environment settings cloud.",[10,8809,8810,8811,8814,8815,8818],{},"Urusan network-nya yang lebih menarik. Waktu saya coba, proxy session menolak koneksi ke domain saya dengan ",[22,8812,8813],{},"403",". Padahal API saya ",[14,8816,8817],{},"juga"," menjawab 403 saat dimatikan, dan 401 untuk token yang salah. Dari sisi script, \"token kamu salah\" dan \"kamu tidak boleh keluar dari sandbox ini\" kelihatan sama persis, padahal cara memperbaikinya jauh berbeda.",[10,8820,8821,8822,8825,8826,8829],{},"Solusinya cukup satu response header. Middleware menyisipkan ",[41,8823,8824],{},"X-Content-Plan-Api: 1"," di ",[22,8827,8828],{},"setiap"," response, termasuk 401 dan 403 miliknya sendiri. Script lalu memeriksanya:",[3176,8831,8832,8838],{},[30,8833,8834,8835,8837],{},"ada header → yang menjawab adalah API; exit code ",[22,8836,3555],{}," beserta pesan dari API itu sendiri;",[30,8839,8840,8841,8843,8844,8847],{},"tidak ada header → yang menjawab pihak lain (proxy, host yang salah, atau server yang belum men-deploy route-nya); exit code ",[22,8842,2971],{}," dengan awalan ",[41,8845,8846],{},"NETWORK:"," dan petunjuk untuk mengizinkan domain tersebut.",[10,8849,8850],{},"Skill membaca exit code itu dan memberi tahu saya setting mana persisnya yang perlu diubah — tanpa pernah meminta saya paste token ke chat.",[10,8852,8853],{},"Dua safeguard kecil lainnya muncul dari pertanyaan yang sama: \"bagaimana kalau ada dua 'saya' yang menjalankan ini bersamaan?\"",[3176,8855,8856,8870],{},[30,8857,8858,8861,8862,8865,8866,8869],{},[22,8859,8860],{},"Lock per item saat import."," Session di laptop dan session di web (atau dua subagent yang jalan paralel) bisa sama-sama lolos pengecekan \"apakah item ini sudah punya post?\" sebelum salah satunya selesai. Sekarang import memasang cache lock per item, membaca ulang item di dalam lock itu, dan membalas ",[22,8863,8864],{},"423"," ke pihak yang kalah. Item yang sudah punya post mendapat ",[22,8867,8868],{},"409",", kecuali saya sengaja memaksa re-import.",[30,8871,8872,5127,8875,8878],{},[22,8873,8874],{},"Non-interactive mode.",[41,8876,8877],{},"\u002Fgenerate-blog next 2"," mengambil dua item berikutnya yang belum selesai tanpa bertanya apa-apa, jadi bisa dijalankan sebagai scheduled task. Hasilnya tetap draft; keputusan untuk publish tetap di tangan manusia.",[85,8880,8882],{"id":8881},"yang-sengaja-tidak-saya-buat","Yang sengaja tidak saya buat",[3176,8884,8885,8891,8897],{},[30,8886,8887,8890],{},[22,8888,8889],{},"Tidak ada AI call di server."," Server hanya menyimpan dan meng-import; penulisannya terjadi di tool yang memang sudah saya bayar.",[30,8892,8893,8896],{},[22,8894,8895],{},"Tidak ada auto-publish."," Artikel yang di-generate berstatus draft. Saya tetap membaca, mengedit, dan menjadwalkan setiap artikel sendiri.",[30,8898,8899,8902],{},[22,8900,8901],{},"Tidak ada perubahan permission yang di-commit atas nama saya."," Allow-list yang memungkinkan cloud session berjalan tanpa diawasi hanya didokumentasikan, tidak di-commit — itu keputusan pemilik repository, bukan agent yang menulis fiturnya.",[85,8904,8906],{"id":8905},"hasilnya","Hasilnya",[10,8908,8909],{},"Rutinitas yang tadinya lima kali pindah tab per artikel kini cukup satu baris untuk satu batch penuh:",[101,8911,8914],{"className":8912,"code":8913,"language":106,"meta":109},[104],"\u002Fgenerate-blog next 3\n",[41,8915,8913],{"__ignoreMap":109},[10,8917,8918,8919,8921],{},"Beberapa menit kemudian, tiga draft bilingual sudah ada di admin panel, masing-masing ter-link ke harinya di kalender content plan, dengan source JSON-nya tersimpan bersama hasil import lainnya. Seluruh fitur ini kira-kira 550 baris PHP dan bash plus satu skill Markdown, dengan 14 feature test — dan sebagian besar pemikirannya justru tercurah pada apa yang ",[14,8920,6070],{}," perlu dibaca model.",[6684,8923,8924],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":109,"searchDepth":226,"depth":226,"links":8926},[8927,8928,8933,8934,8935,8936],{"id":8472,"depth":226,"text":8473},{"id":8506,"depth":226,"text":8507,"children":8929},[8930,8931,8932],{"id":8535,"depth":241,"text":8536},{"id":8641,"depth":241,"text":8642},{"id":8729,"depth":241,"text":8730},{"id":8762,"depth":226,"text":8763},{"id":8800,"depth":226,"text":8801},{"id":8881,"depth":226,"text":8882},{"id":8905,"depth":226,"text":8906},"2026-10-03","Cara saya mengganti rutinitas copy-paste antartab browser dengan satu slash command — API kecil yang diproteksi token, client bash, dan satu subagent per artikel supaya context-nya tetap kecil.",{},"en","\u002Fwriting\u002Fclaude-code-content-plan",{"title":8455,"description":8938},"writing\u002Fclaude-code-content-plan",[6726,6727,8945,8946],"Laravel","Claude Code","a4xpBVpbkuQsR7GJ4DWbnUYXbZE42T9gIhGxbWITQYw",{"id":8949,"title":8950,"body":8951,"cover":6716,"date":9203,"description":9204,"draft":6719,"extension":6720,"lab":6719,"meta":9205,"navigation":244,"original":8940,"path":9206,"seo":9207,"stem":9208,"tags":9209,"__hash__":9212},"writing_id\u002Fwriting\u002Fbuilding-this-portfolio.md","Membangun ulang portofolio saya dengan Nuxt 4 dan Nuxt Content",{"type":7,"value":8952,"toc":9197},[8953,8960,8967,8971,8974,9129,9140,9144,9147,9176,9180,9187,9191,9194],[10,8954,8955,8956,8959],{},"Situs lama saya memakai tema Hugo berbasis Bootstrap, ditambah beberapa inline script dan segudang ",[41,8957,8958],{},"!important"," demi dark mode. Situsnya jalan, tapi setiap kali menambah project saya harus meng-copy tabel gambar di Markdown sambil berharap layout-nya tidak berantakan.",[10,8961,8962,8963,8966],{},"Target rebuild ini: ",[22,8964,8965],{},"content terpisah dari presentation",", dan situsnya sendiri bisa jadi salah satu karya di portofolio.",[85,8968,8970],{"id":8969},"content-sebagai-source-of-truth","Content sebagai source of truth",[10,8972,8973],{},"Setiap project cukup satu file Markdown. Front-matter-nya divalidasi dengan schema, jadi kalau ada field yang lupa diisi, build-nya yang gagal — bukan halamannya yang rusak.",[101,8975,8979],{"className":8976,"code":8977,"language":8978,"meta":109,"style":109},"language-ts shiki shiki-themes github-light github-dark","\u002F\u002F content.config.ts\nprojects: defineCollection({\n  type: 'page',\n  source: 'projects\u002F*.md',\n  schema: z.object({\n    category: z.string(),\n    year: z.number(),\n    role: z.string(),\n    featured: z.boolean().default(false),\n    metrics: z.array(z.object({ value: z.string(), label: z.string() })).default([]),\n    cover: z.string(),\n  }),\n}),\n","ts",[41,8980,8981,8986,8999,9009,9019,9029,9040,9050,9059,9079,9110,9119,9124],{"__ignoreMap":109},[201,8982,8983],{"class":203,"line":204},[201,8984,8985],{"class":251},"\u002F\u002F content.config.ts\n",[201,8987,8988,8991,8993,8996],{"class":203,"line":226},[201,8989,8990],{"class":207},"projects",[201,8992,471],{"class":215},[201,8994,8995],{"class":207},"defineCollection",[201,8997,8998],{"class":215},"({\n",[201,9000,9001,9004,9007],{"class":203,"line":241},[201,9002,9003],{"class":215},"  type: ",[201,9005,9006],{"class":211},"'page'",[201,9008,507],{"class":215},[201,9010,9011,9014,9017],{"class":203,"line":248},[201,9012,9013],{"class":215},"  source: ",[201,9015,9016],{"class":211},"'projects\u002F*.md'",[201,9018,507],{"class":215},[201,9020,9021,9024,9027],{"class":203,"line":255},[201,9022,9023],{"class":215},"  schema: z.",[201,9025,9026],{"class":207},"object",[201,9028,8998],{"class":215},[201,9030,9031,9034,9037],{"class":203,"line":261},[201,9032,9033],{"class":215},"    category: z.",[201,9035,9036],{"class":207},"string",[201,9038,9039],{"class":215},"(),\n",[201,9041,9042,9045,9048],{"class":203,"line":267},[201,9043,9044],{"class":215},"    year: z.",[201,9046,9047],{"class":207},"number",[201,9049,9039],{"class":215},[201,9051,9052,9055,9057],{"class":203,"line":387},[201,9053,9054],{"class":215},"    role: z.",[201,9056,9036],{"class":207},[201,9058,9039],{"class":215},[201,9060,9061,9064,9067,9070,9073,9075,9077],{"class":203,"line":400},[201,9062,9063],{"class":215},"    featured: z.",[201,9065,9066],{"class":207},"boolean",[201,9068,9069],{"class":215},"().",[201,9071,9072],{"class":207},"default",[201,9074,1070],{"class":215},[201,9076,3694],{"class":219},[201,9078,1166],{"class":215},[201,9080,9081,9084,9087,9090,9092,9095,9097,9100,9102,9105,9107],{"class":203,"line":405},[201,9082,9083],{"class":215},"    metrics: z.",[201,9085,9086],{"class":207},"array",[201,9088,9089],{"class":215},"(z.",[201,9091,9026],{"class":207},[201,9093,9094],{"class":215},"({ value: z.",[201,9096,9036],{"class":207},[201,9098,9099],{"class":215},"(), label: z.",[201,9101,9036],{"class":207},[201,9103,9104],{"class":215},"() })).",[201,9106,9072],{"class":207},[201,9108,9109],{"class":215},"([]),\n",[201,9111,9112,9115,9117],{"class":203,"line":429},[201,9113,9114],{"class":215},"    cover: z.",[201,9116,9036],{"class":207},[201,9118,9039],{"class":215},[201,9120,9121],{"class":203,"line":434},[201,9122,9123],{"class":215},"  }),\n",[201,9125,9126],{"class":203,"line":446},[201,9127,9128],{"class":215},"}),\n",[10,9130,9131,9132,9135,9136,9139],{},"Cukup tambahkan ",[41,9133,9134],{},"content\u002Fprojects\u002Fmy-project.md",", halaman ",[41,9137,9138],{},"\u002Fwork\u002Fmy-project"," langsung tersedia — tanpa menyentuh satu component pun.",[85,9141,9143],{"id":9142},"component-langsung-di-markdown","Component langsung di Markdown",[10,9145,9146],{},"Case study bisa memanggil component langsung dari Markdown, misalnya architecture explorer dan animated metrics:",[101,9148,9151],{"className":9149,"code":9150,"language":6720,"meta":109,"style":109},"language-md shiki shiki-themes github-light github-dark","::architecture-explorer{:nodes=\"architecture\"}\n::\n\n::metric-grid{:metrics=\"metrics\"}\n::\n",[41,9152,9153,9158,9163,9167,9172],{"__ignoreMap":109},[201,9154,9155],{"class":203,"line":204},[201,9156,9157],{},"::architecture-explorer{:nodes=\"architecture\"}\n",[201,9159,9160],{"class":203,"line":226},[201,9161,9162],{},"::\n",[201,9164,9165],{"class":203,"line":241},[201,9166,245],{"emptyLinePlaceholder":244},[201,9168,9169],{"class":203,"line":248},[201,9170,9171],{},"::metric-grid{:metrics=\"metrics\"}\n",[201,9173,9174],{"class":203,"line":255},[201,9175,9162],{},[85,9177,9179],{"id":9178},"animasi-secukupnya-hanya-kalau-membantu","Animasi secukupnya, hanya kalau membantu",[10,9181,9182,9183,9186],{},"Cukup satu animation library (GSAP), dan hanya di-load di client-side. Scroll animation dan counter mengikuti setting ",[41,9184,9185],{},"prefers-reduced-motion","; kalau setting itu aktif, semua konten langsung tampil tanpa animasi.",[85,9188,9190],{"id":9189},"static-by-default","Static by default",[10,9192,9193],{},"Semua halaman di-prerender, image dioptimasi saat build, lalu seluruh situs di-deploy ke GitHub Pages sebagai static file.",[6684,9195,9196],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":109,"searchDepth":226,"depth":226,"links":9198},[9199,9200,9201,9202],{"id":8969,"depth":226,"text":8970},{"id":9142,"depth":226,"text":9143},{"id":9178,"depth":226,"text":9179},{"id":9189,"depth":226,"text":9190},"2026-09-29","Kenapa saya pindah dari tema Hugo ke Nuxt, dan bagaimana content, image, serta animasinya saya susun supaya menambah project cukup dengan satu file Markdown.",{},"\u002Fwriting\u002Fbuilding-this-portfolio",{"title":8950,"description":9204},"writing\u002Fbuilding-this-portfolio",[6726,9210,9211],"Nuxt","Desain","-hDDBIrfBeLJ2BRtysVktRDRIzzSACGiiE-YlKqWd3Y",{"id":9214,"title":9215,"body":9216,"cover":9223,"date":9232,"description":9233,"draft":6719,"extension":6720,"lab":6719,"meta":9234,"navigation":244,"original":3801,"path":9235,"seo":9236,"stem":9237,"tags":9238,"__hash__":9241},"writing_id\u002Fwriting\u002Fanak-anak-masjid.md","Anak-anak di Masjid Umar Bin Khattab Bypass",{"type":7,"value":9217,"toc":9230},[9218,9224,9227],[10,9219,9220],{},[8529,9221],{"alt":9222,"src":9223},"Anak-anak di masjid","\u002Fimages\u002Fwriting\u002Fanak-anak-masjid\u002Fcover.png",[10,9225,9226],{},"Kami baru saja selesai salat Jumat di Masjid Umar Bin Khattab, Bypass. Kami duduk sejenak sambil bersantai, menunggu awan lewat di langit untuk melindungi kami dari terik matahari Gorontalo — saking panasnya, konon katanya di sini mataharinya ada dua, wkwk.",[10,9228,9229],{},"Lalu kami dikagetkan oleh kesibukan anak-anak kecil di sekeliling kami. Awalnya saya kira mereka sedang bermain, ternyata mereka sedang membantu merapikan karpet masjid yang baru selesai dipakai jemaah. Wajah-wajah kecil itu langsung berseri-seri ketika bapak takmir memberi mereka uang 2.000 rupiah. Senyum polos mereka menebar kebahagiaan. Pemandangan seperti ini jarang terlihat, jadi saya memutuskan untuk mengabadikan momen itu.",{"title":109,"searchDepth":226,"depth":226,"links":9231},[],"2022-05-24","Catatan kecil selepas salat Jumat — tentang senyum anak-anak yang merapikan karpet masjid.",{},"\u002Fwriting\u002Fanak-anak-masjid",{"title":9215,"description":9233},"writing\u002Fanak-anak-masjid",[9239,9240],"Personal","Cerita","5O5RGPMUwv901O6m44EH6ppcwnAakTV_8RwNGP-rBOE",1791260767081]