ในหน้านี้
เมื่อวันที่ 21 กันยายน 2026 เวลา 00:33 UTC Tobi Lütke โพสต์ข้อความนี้ “การถกเถียงเรื่อง MCP กับ CLI สำหรับ LLM เป็นการถกเถียงที่ผิด ผู้คนกำลังถกเถียงกันผิดชั้น” จากนั้นในวันที่ 23 ตุลาคม เขาชี้ไปยังรีโพสิทอรีใหม่ชื่อ dquickts ซึ่งเป็นฟอร์กของ QuickJS ที่หยุดโปรแกรมกลางลูป for ได้ เขียนฮีปทั้งหมดออกมาเป็นไบต์ แล้วไปทำงานต่อในอีกโปรเซสหนึ่ง เขาเขียนว่า “สภาพแวดล้อมสำหรับโหมดโค้ดที่ยอดเยี่ยม”
เขาพูดถูกเรื่องชั้น ผมอยากเห็นด้วยกับเขาก่อนด้วยคำพูดของเขาเอง แล้วค่อยจำกัดข้อกล่าวอ้างให้แคบลงจนมันบอกสิ่งที่การซิงค์นำไปใช้ได้
ชั้นที่เขาหมายถึง
นี่คือข้อโต้แย้งตามที่เขาเขียนไว้ ซึ่งเป็นวิธีเดียวที่ยุติธรรมในการอ้างอิงข้อโต้แย้งที่คุณตั้งใจจะดัดแปลง
ทั้งสองแบบทำงานได้ดีอย่างเหลือเชื่อ ตราบใดที่รันผ่านสภาพแวดล้อมคล้าย REPL ที่เก็บสถานะไว้ได้ เรื่องตลกของ CLI คือ เครื่องมือ CLI มักถูกเรียกผ่าน BASH ซึ่งบังเอิญเป็น REPL ที่มีสถานะถาวร (ระบบไฟล์) ดังนั้น CLI จึงทำงานได้ดีกว่า MCP มาก แต่นี่ไม่ได้ใกล้เคียงกับการเป็นคุณสมบัติโดยกำเนิดของ MCP เลย มันแค่หมายความว่าเราต้องการฮาร์เนสที่ดีกว่านี้
นั่นถูกต้อง และมันอธิบายสิ่งที่หลายคนสังเกตเห็นโดยไม่ได้อธิบาย เอเจนต์ที่ได้ CLI จะเขียนไฟล์ อ่านกลับมาในอีกสามขั้นตอนต่อมา grep มัน และส่งต่อผ่านไปป์ เอเจนต์ที่ได้เซิร์ฟเวอร์ MCP เปล่า ๆ จะได้ผลลัพธ์หนึ่งชิ้นต่อการเรียกหนึ่งครั้ง และต้องแบกทุกอย่างที่เหลือไว้ในหน้าต่างบริบท CLI ไม่ได้ชนะเพราะโปรโตคอล มันชนะเพราะ bash มีที่ให้วางของมาด้วย
การเดิมพันของเขาตามมาจากตรงนั้น จะมีใครสักคนสร้างเอนจินแบบฝังได้ในลักษณะเดียวกับ SQLite ที่เปลี่ยน bash, TypeScript หรือการเรียกเครื่องมือให้เป็นแผนเดียว ตรวจสอบก่อนรัน และออกแบบมาเพื่อการทำงานแบบคงทน dquickts คือก้าวหนึ่งไปสู่สิ่งนั้น README ของมัน อธิบายถึง REPL “ที่ฟังก์ชันและตัวแปรอยู่รอดหลังการรีสตาร์ต” และยังอธิบายการเรียกโฮสต์แบบคงทนไว้ด้วย “สคริปต์เรียกฟังก์ชันของโฮสต์ที่คำตอบจะมาถึงในอีกหลายชั่วโมง โปรเซสจะบันทึกสคริปต์แล้วออกไป”
ถึงตรงนี้ผมเห็นด้วยทุกประการ ต่อไปคือการจำกัดให้แคบลง
สถานะแบบไหน สำหรับงานอะไร
คำว่า “เก็บสถานะไว้” ซ่อนคำถามหนึ่งไว้ นั่นคือสถานะของใคร และอยู่ในรูปแบบใด สำหรับเอเจนต์เขียนโค้ด สถานะคือรีโพสิทอรีและเชลล์ และ REPL ที่อยู่รอดหลังการรีสตาร์ตเป็นคำตอบที่ดี สำหรับเอเจนต์ที่สร้างการซิงค์ระหว่างระบบ SaaS สองระบบ สถานะที่มันต้องใช้ระหว่างขั้นตอนถูกกำหนดไว้แล้วโดยตัวงานเอง เรคคอร์ดสี่ชนิดเป็นตัวแบกสถานะนั้น และไม่มีชนิดใดเป็นตัวแปรในสคริปต์
เคอร์เซอร์ การซิงค์อ่านแหล่งข้อมูลเป็นช่วง ๆ และต้องรู้ว่าช่วงก่อนหน้าจบลงที่ไหน ในเอนจินการซิงค์ของ Tajo สิ่งนี้คือเคอร์เซอร์ที่จัดเก็บแยกตามการซิงค์และขอบเขต ถูกเขียนหลังแต่ละแบตช์ที่ไม่มีเรคคอร์ดล้มเหลวเลย และการรันครั้งถัดไปจะทำต่อจากตรงนั้น การรันตามกำหนดเวลาจะบันทึกจุดเริ่มต้นก่อนทำอย่างอื่น ดังนั้นหากการรันครั้งแรกล้มเหลว การรันครั้งถัดไปก็ยังอ่านจากจุดเดิม หลักการเบื้องหลังเรื่องนี้ คือส่งเฉพาะส่วนที่เปลี่ยนและไม่ส่งทั้งตาราง อยู่ในบทอธิบาย reverse ETL ผมขอฝากเรื่องเคอร์เซอร์ไว้ที่นั่น
ข้อเสนอที่รอดำเนินการ บนเครื่องมือ MCP ของคอนเนกเตอร์ของ Tajo การเขียนหนึ่งครั้งประกอบด้วยการเรียกสองครั้ง brevo_propose ตรวจสอบอินพุตเทียบกับแอ็กชันที่ประกาศไว้และบันทึกข้อเสนอ ไม่มีอะไรถูกส่งออกไป ผลลัพธ์จะมี ID ข้อเสนอ และเมื่อต้องให้คนตัดสินใจ ก็จะมีหน้าที่ใช้อนุมัติข้อเสนอนั้นด้วย brevo_apply รันการเขียนที่ได้รับอนุมัติแล้วของการเชื่อมต่อนั้นตามลำดับ และหยุดที่การเขียนแรกที่ยังรอคนอยู่ เรคคอร์ดที่อยู่ตรงกลางคือสถานะ มันเก็บอินพุตและไดเจสต์ของอินพุตไว้ ดังนั้นการเขียนที่ได้รับอนุมัติแล้วจึงแก้ไขไม่ได้ระหว่างการตัดสินใจกับการ apply มันไม่เก็บค่าที่ผู้ให้บริการตอบกลับมา เพราะเรคคอร์ดการอนุมัติแบบเพิ่มต่อท้ายอย่างเดียวต้องไม่กลายเป็นที่เก็บข้อมูลส่วนบุคคล
สัญญาเช่า (lease) การรันการซิงค์หนึ่งครั้งถือสัญญาเช่าแบบเอกสิทธิ์หนึ่งรายการตลอดอายุการรัน โดยมีโทเค็นเจ้าของ เวลาจำกัดค่าเริ่มต้นสิบห้านาที และการต่ออายุ เจ้าของที่ล้าสมัยไม่สามารถปล่อยสัญญาเช่าที่ใหม่กว่าได้ หากเข้าถึงที่เก็บสัญญาเช่าไม่ได้ การรันจะปฏิเสธที่จะเริ่มแทนที่จะเดา เวิร์กเกอร์ตัวไหนถือการรันอยู่เป็นข้อเท็จจริงในสัญญาเช่า ไม่ได้อยู่ในหน่วยความจำของโปรเซสใด
แผน syncs.dryRun อ่านช่วงถัดไปจากเคอร์เซอร์ คำนวณว่าแต่ละเรคคอร์ดจะเป็นการเพิ่ม อัปเดต ลบ หรือไม่เปลี่ยนแปลง แล้วเขียนแถวเดียวพอดี นั่นคือแผน ซึ่งเก็บไว้หนึ่งชั่วโมง มันไม่ขยับเคอร์เซอร์และไม่เขียนอะไรลงระบบใดเลย syncs.apply เขียนแผนนั้นหนึ่งครั้ง มันจะปฏิเสธหากเคอร์เซอร์ขยับไปแล้ว หรือหากเรคคอร์ดเปลี่ยนไปเกินสัดส่วนที่ยอมรับได้ในระหว่างนั้น การเรียกซ้ำจะคืนผลลัพธ์แรกแทนที่จะเขียนอีกครั้ง
นั่นคือสถานะระหว่างขั้นตอน เรคคอร์ดสี่รายการที่มี ID ซึ่งเอเจนต์ใดก็ตามที่ได้รับอนุญาตให้ดูก็อ่านได้
ทำไมฝั่ง MCP จึงไม่เก็บอะไรไว้ในเซสชัน
เมื่อสถานะอยู่ในเรคคอร์ดแล้ว เซสชันก็ไม่มีอะไรเหลือให้ถือไว้ ดังนั้นเอนด์พอยต์ MCP ของ Tajo จึงไม่ถือเซสชัน ทุกคำขอจะได้เซิร์ฟเวอร์ใหม่และทรานสปอร์ตที่ไม่มี ID เซสชัน การอ่านคือหนึ่งคำขอต่อการเรียกหนึ่งครั้ง และผลลัพธ์มีขีดจำกัดเพื่อไม่ให้คำตอบเดียวท่วมทั้งเทิร์น การเขียนคือเสนอก่อนแล้วจึง apply และสิ่งที่คุณถือติดตัวไปคือ ID ของข้อเสนอ
โปรโตคอลก็เปลี่ยนไปในทางเดียวกัน การปรับปรุงข้อกำหนด MCP ฉบับ 2026-07-28 ได้นำเซสชันระดับโปรโตคอลและเฮดเดอร์ Mcp-Session-Id ออกจากทรานสปอร์ต Streamable HTTP เซิร์ฟเวอร์ที่ต้องการความต่อเนื่องจะสร้างแฮนเดิลของตัวเองและรับมันกลับมาเป็นอาร์กิวเมนต์ธรรมดา ID ข้อเสนอก็คือสิ่งนั้น เซิร์ฟเวอร์ MCP คืออะไร อธิบายการปรับปรุงครั้งนั้นและความแตกต่างระหว่างการเรียกเครื่องมือกับการซิงค์แบบคงทน ส่วนบทความนี้พูดถึงเพียงว่าสถานะไปอยู่ที่ไหน
ผลที่ตามมาซึ่งสำคัญต่องานการเชื่อมต่อคือแบบนี้ สมมติว่าเอเจนต์ใน Claude Code เสนอการเขียนแล้วเซสชันของมันจบลง หนึ่งชั่วโมงต่อมามีคนอนุมัติ เอเจนต์ในไคลเอนต์อื่น หรือเอเจนต์ตัวเดิมในวันรุ่งขึ้น เรียก apply พร้อม ID และการเขียนก็เกิดขึ้นหนึ่งครั้ง เอเจนต์ที่ล่มจะเสียหน้าต่างบริบทของมันและไม่เสียอะไรอื่นเลย ไม่มีอะไรถูกพักไว้ จึงไม่มีอะไรต้องกลับมาทำต่อ
สิ่งที่เราไม่มี และราคาที่ต้องจ่าย
ข้อคัดค้านที่ตรงไปตรงมามาจากฝั่ง REPL และเป็นแบบนี้ “ก็ได้ เรคคอร์ดของคุณอยู่รอด แต่ทุกการเรียกต้องอ่านซ้ำสิ่งที่การเรียกครั้งก่อนมีอยู่แล้ว สคริปต์จะดึงรายชื่อผู้ติดต่อครั้งเดียว join กรอง แล้วคืนตัวเลขหนึ่งตัว เอเจนต์ของคุณเรียกห้าครั้งและจ่ายราคาทุกครั้ง” ข้อคัดค้านนี้ถูกต้องในเชิงกลไก และเรายังไม่ได้วัดราคานั้น การประเมินที่จะนับจำนวนการเรียกต่องานทั้งแบบที่มีและไม่มีการแบกสถานะยังไม่ได้สร้างขึ้น จนกว่าจะสร้างเสร็จ ข้อกล่าวอ้างที่ว่าโหมดการเรียกใช้โทเค็นมากกว่าก็ยังไม่มีคำตอบ
ยังมีอีกสามเรื่องที่ต้องบอกอย่างตรงไปตรงมา เพราะผู้อ่านที่อ่านบทความนี้จบไม่ควรนึกภาพว่าสิ่งเหล่านี้มีอยู่
- ปัจจุบันไม่มีการรันโค้ดที่ลูกค้าเขียน การอ่านผ่านคอนเนกเตอร์คือหนึ่งคำขอต่อการเรียกหนึ่งครั้ง โดยไม่มีแซนด์บ็อกซ์อยู่ในเส้นทาง ตัวรันโค้ดมีอยู่ในสแตกของเรา และเป็นเครื่องมือภายใน ในโปรดักชันมันปิดอยู่เว้นแต่จะตั้งแฟล็กสองตัวที่แยกจากกัน และตอบสนองเฉพาะผู้ดูแลระบบ การเรียกแต่ละครั้งจะได้บริบทใหม่ การผูกที่อนุญาตเฉพาะการอ่าน และงบเวลาสองวินาทีสำหรับโค้ดแบบซิงโครนัส เฮดเดอร์ของมันเองระบุไว้ด้วยตัวพิมพ์ใหญ่ว่ามันเป็นขอบเขตด้านความถูกต้อง ไม่ใช่ขอบเขตด้านความปลอดภัย
- ไม่มี REPL สิ่งใดที่เอเจนต์คำนวณในการเรียกครั้งหนึ่งจะใช้ในการเรียกครั้งถัดไปไม่ได้ เว้นแต่จะถูกเขียนเป็นเรคคอร์ดที่มี ID
- ไม่มีเซสชันเอเจนต์แบบถาวร ไคลเอนต์เก็บบทสนทนาของมัน Tajo เก็บข้อเสนอ แผน เคอร์เซอร์ และสัญญาเช่า ไม่มีสิ่งที่สาม
เอนจินแบบคงทนมีไว้เพื่ออะไร
ทั้งหมดนี้ไม่ใช่ข้อโต้แย้งคัดค้าน dquickts README ของมันระบุว่าสแนปช็อตมีไว้เพื่ออะไร และรายการนั้นดี ทำให้สคริปต์ที่ว่างอยู่จำศีลและคืนหน่วยความจำ รันการคำนวณที่ยาวเป็นช่วง ๆ บนเวิร์กเกอร์ตัวไหนก็ได้ที่ว่าง เริ่มทุกอินสแตนซ์แบบอุ่นเครื่องจากสแนปช็อตแทนการตั้งค่าซ้ำ พักสคริปต์ไว้กับการเรียกโฮสต์ที่คำตอบจะมาถึงภายหลัง สำหรับพื้นที่ทำงานของเอเจนต์เขียนโค้ด หรือโน้ตบุ๊กที่ควรอยู่รอดหลังการรีสตาร์ต นี่คือเครื่องมือที่เหมาะสม
แต่การซิงค์ก็ยังไม่ควรพึ่งพามัน ด้วยเหตุผลสามข้อที่ README เองบอกผม ข้อแรก “ไฟล์เซสชันโหลดได้เฉพาะในบิลด์ qjs ที่เขียนมันขึ้นมา” ฮีปที่เขียนโดยเอนจินเวอร์ชันหนึ่งกลายเป็นไฟล์ที่ใช้ไม่ได้หลังการอัปเกรด ส่วนเคอร์เซอร์คือสตริง ข้อสอง “สแนปช็อตเป็นอินพุตที่เชื่อถือได้ เช่นเดียวกับไบต์โค้ด” และ “สแนปช็อตที่เป็นอันตรายจะไม่ถูกตรวจพบ” ฮีปที่จัดเก็บไว้เป็นสิ่งที่คุณต้องปกป้อง ไม่ใช่สิ่งที่คุณตรวจทานได้ ส่วนข้อเสนอคือแถวที่คนอ่านก่อนอนุมัติ ข้อสาม สแนปช็อตเก็บทุกอย่างที่สคริปต์อ่านไว้ รวมถึงเรคคอร์ดของลูกค้า เรคคอร์ดที่กล่าวถึงข้างต้นถูกสร้างในทางตรงข้าม แผนเก็บไดเจสต์ ไม่ใช่ค่า และเรคคอร์ดข้อเสนอไม่เคยเก็บการตอบกลับของผู้ให้บริการ
หนึ่งในกฎที่เรายึดถือในการสร้างคือ ความแน่นอนชนะความฉลาด ฮีปที่กลับมาทำงานต่อกลางลูปนั้นฉลาด เคอร์เซอร์ ข้อเสนอ สัญญาเช่า และแผนนั้นน่าเบื่อ ตรวจสอบได้ และเหมือนเดิมในทุกเอนจิน ในทุกไคลเอนต์ หลังการล่มทุกครั้ง
Lütke ตั้งคำถามที่ถูกต้อง สถานะอยู่ที่ไหน? สำหรับการซิงค์ คำตอบไม่มีทางเป็นในโปรเซสอยู่แล้ว มันต้องอยู่ในเรคคอร์ดมาตลอด เพราะนั่นคือที่ที่คนผู้อนุมัติการเขียนต้องอ่านมันได้
คำถามที่พบบ่อย
MCP หรือ CLI ดีกว่ากันสำหรับเอเจนต์ AI?
ไม่ใช่ทั้งคู่ หากดูแค่ตัวมันเอง ประเด็นของ Tobi Lütke เมื่อวันที่ 21 กันยายน 2026 ยังเป็นจริง คือทั้งสองแบบใช้ได้ดีเมื่อการเรียกทำงานผ่านสิ่งที่เก็บสถานะไว้ระหว่างขั้นตอน ทุกวันนี้ CLI ให้ความรู้สึกที่ดีกว่าเพราะ bash เก็บสถานะไว้ในระบบไฟล์ ความแตกต่างไม่ได้อยู่ที่โปรโตคอล แต่อยู่ที่ฮาร์เนส
เอเจนต์ต้องการสถานะอะไรระหว่างขั้นตอนต่าง ๆ ของการซิงค์?
เรคคอร์ดของการเชื่อมต่อเอง ได้แก่ เคอร์เซอร์ที่บอกว่าการรันครั้งก่อนหยุดอยู่ที่ไหน ข้อเสนอที่รอการอนุมัติจากคน สัญญาเช่า (lease) ที่บอกว่าเวิร์กเกอร์ตัวไหนถือการรันอยู่ และแผนแบบ dry-run ที่ apply ต้องตรงกัน ไม่มีสิ่งใดในนี้เป็นตัวแปรในสคริปต์ ทั้งหมดคือแถวที่มี ID
ทำไมเซิร์ฟเวอร์ MCP ของ Tajo จึงไม่เก็บอะไรไว้ในเซสชัน?
ทุกคำขอจะได้เซิร์ฟเวอร์ใหม่และทรานสปอร์ตที่ไม่มี ID เซสชัน สิ่งที่การเรียกครั้งถัดไปต้องใช้ล้วนเป็นเรคคอร์ด ได้แก่ ID ข้อเสนอ ID แผน และเคอร์เซอร์ ดังนั้นเอเจนต์ตัวที่สองซึ่งอยู่ในไคลเอนต์อื่นจึงทำงานต่อจากจุดที่ตัวแรกหยุดไว้ได้ และเอเจนต์ที่ล่มจะไม่เสียอะไรเลยนอกจากหน้าต่างบริบทของตัวเอง
Tajo รันโค้ดที่เอเจนต์เขียนหรือไม่?
ไม่รันให้ลูกค้า การอ่านผ่านเครื่องมือคอนเนกเตอร์คือหนึ่งคำขอต่อการเรียกหนึ่งครั้ง โดยไม่มีแซนด์บ็อกซ์ ตัวรันโค้ดที่มีอยู่เป็นเครื่องมือภายในที่อยู่หลังแฟล็กสองตัว ผู้ดูแลระบบเท่านั้นที่เข้าถึงได้ ใช้การผูกแบบอ่านอย่างเดียว และใช้บริบทใหม่ทุกครั้งที่เรียก ตัวมันเองระบุว่าไม่ใช่ขอบเขตด้านความปลอดภัย ไม่มี REPL และไม่มีเซสชันถาวร
เอนจิน JavaScript แบบคงทนอย่าง dquickts เหมาะกับงานอะไร?
README ของมันระบุการใช้งานไว้ ได้แก่ ทำให้สคริปต์ที่ว่างอยู่จำศีล แบ่งการคำนวณที่ยาวไปรันบนหลายเวิร์กเกอร์ เริ่มต้นแบบอุ่นเครื่องจากสแนปช็อต และพักสคริปต์ไว้กับการเรียกโฮสต์ที่คำตอบจะมาถึงในอีกหลายชั่วโมง ทั้งหมดเป็นการใช้งานจริง แต่การซิงค์ก็ยังไม่ควรพึ่งพามัน เพราะสแนปช็อตโหลดได้เฉพาะในบิลด์ของเอนจินที่เขียนมันขึ้นมา ผู้ตรวจทานมองไม่เห็นข้างใน และมันเก็บข้อมูลลูกค้าทุกอย่างที่สคริปต์อ่านไว้