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