इस पेज पर
21 सितंबर 2026 को, 00:33 UTC पर, Tobi Lütke ने यह पोस्ट किया: “LLM के लिए MCP बनाम CLI गलत बहस है. लोग गलत लेयर पर बहस कर रहे हैं.” फिर 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 के सिंक इंजन में यह हर सिंक और स्कोप के लिए सेव किया गया एक कर्सर है, जो हर ऐसे बैच के बाद लिखा जाता है जिसमें एक भी रिकॉर्ड फ़ेल न हुआ हो, और अगला रन वहीं से आगे बढ़ता है. एक शेड्यूल्ड रन कुछ भी करने से पहले अपना शुरुआती बिंदु सेव करता है, ताकि अगर पहला रन फ़ेल हो जाए तो अगला रन उसी बिंदु से पढ़े. इसके पीछे का सिद्धांत, यानी सिर्फ़ डेल्टा भेजो और कभी पूरी टेबल नहीं, रिवर्स ETL की व्याख्या में है. कर्सर की बात मैं वहीं छोड़ता हूं.
लंबित प्रस्ताव. Tajo के कनेक्टर MCP टूल्स पर एक राइट दो कॉल्स में होता है. brevo_propose इनपुट को घोषित ऐक्शन के मुक़ाबले वैलिडेट करता है और एक प्रस्ताव रिकॉर्ड करता है. कुछ भी भेजा नहीं जाता. नतीजे में प्रस्ताव की ID होती है और, जब किसी व्यक्ति को फ़ैसला करना हो, वह पेज होता है जहां वह उसे मंज़ूर करता है. brevo_apply उस कनेक्शन के मंज़ूर किए गए राइट्स को क्रम से चलाता है और पहले उस राइट पर रुक जाता है जो अब भी किसी व्यक्ति का इंतज़ार कर रहा हो. बीच का रिकॉर्ड ही स्टेट है. उसमें इनपुट और उसका एक डाइजेस्ट रहता है, ताकि किसी मंज़ूर राइट को फ़ैसले और apply के बीच बदला न जा सके. उसमें प्रोवाइडर के रिस्पॉन्स की वैल्यू नहीं रहतीं, क्योंकि सिर्फ़ जोड़ने वाला मंज़ूरी रिकॉर्ड निजी डेटा का भंडार नहीं बनना चाहिए.
लीज़. किसी सिंक का एक रन अपने पूरे जीवनकाल के लिए एक एक्सक्लूसिव लीज़ रखता है, जिसमें एक ओनर टोकन, डिफ़ॉल्ट रूप से पंद्रह मिनट की समय सीमा, और रिन्यूअल होता है. एक पुराना ओनर नई लीज़ को रिलीज़ नहीं कर सकता. अगर लीज़ स्टोर तक पहुंच न हो, तो रन अंदाज़ा लगाने के बजाय शुरू होने से मना कर देता है. रन किस वर्कर के पास है, यह लीज़ में दर्ज एक तथ्य है, किसी प्रोसेस की मेमोरी में नहीं.
प्लान. syncs.dryRun कर्सर से अगली विंडो पढ़ता है, हर रिकॉर्ड के लिए तय करता है कि इन्सर्ट, अपडेट, डिलीट या कोई बदलाव नहीं, और ठीक एक पंक्ति लिखता है: वह प्लान, जो एक घंटे तक रखा जाता है. यह कोई कर्सर आगे नहीं बढ़ाता और किसी भी सिस्टम में कुछ नहीं लिखता. syncs.apply उस प्लान को एक बार लिखता है. अगर इस बीच कर्सर आगे बढ़ गया हो, या रिकॉर्ड्स का सहनीय हिस्से से ज़्यादा भाग बदल गया हो, तो यह मना कर देता है. दोहराने पर यह दोबारा लिखने के बजाय पहला नतीजा लौटाता है.
यही स्टेप्स के बीच की स्टेट है: ID वाले चार रिकॉर्ड, जिन्हें देखने की अनुमति वाला कोई भी एजेंट पढ़ सकता है.
MCP की तरफ़ सेशन में कुछ क्यों नहीं रखा जाता
एक बार स्टेट रिकॉर्ड्स में आ जाए, तो सेशन के पास रखने को कुछ नहीं बचता. इसलिए Tajo के MCP एंडपॉइंट्स कोई सेशन नहीं रखते. हर रिक्वेस्ट को एक नया सर्वर और बिना सेशन ID वाला ट्रांसपोर्ट मिलता है. रीड हर कॉल पर एक रिक्वेस्ट होते हैं, और नतीजों की सीमा तय है ताकि एक जवाब पूरे टर्न को न भर दे. राइट पहले propose, फिर apply होते हैं, और आप प्रस्ताव की ID साथ लेकर चलते हैं.
प्रोटोकॉल भी इसी दिशा में बढ़ा. MCP स्पेसिफ़िकेशन के 2026-07-28 संशोधन ने Streamable HTTP ट्रांसपोर्ट से प्रोटोकॉल-स्तर के सेशन और Mcp-Session-Id हेडर हटा दिए. जिस सर्वर को निरंतरता चाहिए, वह अब अपना हैंडल ख़ुद बनाता है और उसे एक सामान्य आर्ग्युमेंट के रूप में वापस पाता है. प्रस्ताव की ID यही है. MCP सर्वर क्या है उस संशोधन को और इस बात को कवर करता है कि एक टूल कॉल ड्यूरेबल सिंक से कैसे अलग है; यह लेख सिर्फ़ इस बारे में है कि स्टेट कहां जाती है.
इंटीग्रेशन के काम के लिए जो नतीजा मायने रखता है, वह यह है. मान लीजिए Claude Code में एक एजेंट एक राइट का प्रस्ताव रखता है और उसका सेशन ख़त्म हो जाता है. व्यक्ति उसे एक घंटे बाद मंज़ूर करता है. किसी दूसरे क्लाइंट का एजेंट, या अगले दिन वही एजेंट, ID के साथ apply कॉल करता है और राइट एक बार लागू हो जाता है. क्रैश हुआ एजेंट अपनी कॉन्टेक्स्ट विंडो खोता है, और कुछ नहीं. कुछ भी निलंबित नहीं किया गया था, इसलिए किसी चीज़ को फिर से शुरू करने की ज़रूरत नहीं पड़ी.
हमारे पास क्या नहीं है, और उसकी क़ीमत क्या है
ईमानदार आपत्ति REPL की तरफ़ से आती है और कुछ यूं है: “ठीक है, आपके रिकॉर्ड बचे रहते हैं. लेकिन हर कॉल वह दोबारा पढ़ती है जो पिछली कॉल के पास पहले से था. एक स्क्रिप्ट कॉन्टैक्ट्स को एक बार लाती, उन्हें जोड़ती, फ़िल्टर करती और एक संख्या लौटा देती. आपका एजेंट पांच कॉल करता है और हर एक की क़ीमत चुकाता है.” यह आपत्ति मेकैनिज़्म के बारे में सही है, और हमने उसकी क़ीमत नहीं मापी है. जो इवैल्यूएशन साथ ले जाई गई स्टेट के साथ और उसके बिना हर टास्क में कॉल्स गिनेगा, वह अभी बना नहीं है. जब तक वह नहीं बनता, यह दावा कि कॉल मोड ज़्यादा टोकन ख़र्च करता है, अनुत्तरित है.
तीन और बातें साफ़-साफ़ कहनी ज़रूरी हैं, क्योंकि यह लेख पूरा करने वाले पाठक को इनकी कल्पना नहीं करनी चाहिए:
- आज ग्राहक का लिखा कोई कोड नहीं चलता. कनेक्टर रीड हर कॉल पर एक रिक्वेस्ट होते हैं, रास्ते में कोई सैंडबॉक्स नहीं है. हमारे स्टैक में एक कोड रनर मौजूद है, और वह एक आंतरिक टूल है. प्रोडक्शन में वह तब तक बंद रहता है जब तक दो अलग-अलग फ़्लैग्स सेट न हों, और वह सिर्फ़ एडमिनिस्ट्रेटर्स को जवाब देता है. हर कॉल को एक नया कॉन्टेक्स्ट, सिर्फ़ रीड की अनुमति देने वाली बाइंडिंग्स, और सिंक्रोनस कोड के लिए दो सेकंड का बजट मिलता है. उसका अपना हेडर कैपिटल अक्षरों में कहता है कि यह शुद्धता की सीमा है, सुरक्षा की नहीं.
- कोई REPL नहीं. एक एजेंट एक कॉल में जो कुछ गणना करता है, वह अगली कॉल को तभी उपलब्ध होता है जब उसे ID वाले रिकॉर्ड के रूप में लिखा गया हो.
- कोई परसिस्टेंट एजेंट सेशन नहीं. क्लाइंट अपनी बातचीत रखता है. Tajo प्रस्ताव, प्लान, कर्सर और लीज़ रखता है. कोई तीसरी चीज़ नहीं है.
ड्यूरेबल इंजन किस काम का है
इनमें से कुछ भी dquickts के ख़िलाफ़ तर्क नहीं है. उसकी README बताती है कि स्नैपशॉट किस काम के हैं, और सूची अच्छी है. किसी निष्क्रिय स्क्रिप्ट को हाइबरनेट करके उसकी मेमोरी ख़ाली करना. किसी लंबी गणना को टुकड़ों में उस वर्कर पर चलाना जो ख़ाली हो. सेटअप दोहराने के बजाय हर इंस्टेंस को एक स्नैपशॉट से वॉर्म-स्टार्ट करना. किसी स्क्रिप्ट को ऐसी होस्ट कॉल पर रोककर रखना जिसका जवाब बाद में आता है. किसी कोडिंग एजेंट के वर्कस्पेस के लिए, या ऐसी नोटबुक के लिए जो रीस्टार्ट के बाद बची रहनी चाहिए, यही सही टूल है.
फिर भी किसी सिंक को इस पर निर्भर नहीं होना चाहिए, और इसके तीन कारण ख़ुद README मुझे देती है. पहला: “एक सेशन फ़ाइल सिर्फ़ उसी qjs बिल्ड में लोड होती है जिसने उसे लिखा.” एक इंजन वर्ज़न का लिखा हीप अपग्रेड के बाद एक मरी हुई फ़ाइल है; कर्सर एक स्ट्रिंग है. दूसरा: “स्नैपशॉट भरोसेमंद इनपुट हैं, बाइटकोड की तरह,” और “दुर्भावनापूर्ण स्नैपशॉट पकड़े नहीं जाते.” सेव किया गया हीप ऐसी चीज़ है जिसकी आपको रक्षा करनी होती है, ऐसी चीज़ नहीं जिसकी आप समीक्षा कर सकें; प्रस्ताव एक पंक्ति है जिसे व्यक्ति मंज़ूरी देने से पहले पढ़ता है. तीसरा, स्नैपशॉट में वह सब रहता है जो स्क्रिप्ट ने पढ़ा था, ग्राहकों के रिकॉर्ड समेत. ऊपर के रिकॉर्ड इसके उलट बनाए गए हैं: प्लान वैल्यू नहीं, डाइजेस्ट रखता है, और प्रस्ताव रिकॉर्ड प्रोवाइडर का रिस्पॉन्स कभी नहीं रखता.
हम जिन नियमों पर चलकर बनाते हैं, उनमें से एक यह है कि डिटर्मिनिस्टिक, चतुराई से बेहतर है. लूप के बीच से फिर शुरू होने वाला हीप चतुर है. एक कर्सर, एक प्रस्ताव, एक लीज़ और एक प्लान नीरस हैं, जांचे जा सकते हैं, और हर इंजन पर, हर क्लाइंट में, हर क्रैश के बाद एक जैसे रहते हैं.
Lütke ने सही सवाल पूछा. स्टेट कहां रहती है? किसी सिंक के लिए जवाब कभी प्रोसेस में नहीं होने वाला था. वह हमेशा रिकॉर्ड्स में होना था, क्योंकि राइट को मंज़ूरी देने वाले व्यक्ति को उसे वहीं पढ़ पाना चाहिए.
अक्सर पूछे जाने वाले प्रश्न
AI एजेंट के लिए MCP बेहतर है या CLI?
अपने आप में कोई भी नहीं. 21 सितंबर 2026 को Tobi Lütke की कही बात सही है: जब कॉल्स किसी ऐसी चीज़ से होकर चलती हैं जो स्टेप्स के बीच स्टेट संभालकर रखती है, तो दोनों काम करते हैं. आज CLI बेहतर लगता है क्योंकि bash फ़ाइल सिस्टम में स्टेट रखता है. फ़र्क़ प्रोटोकॉल का नहीं, हार्नेस का है.
किसी सिंक के स्टेप्स के बीच एजेंट को कौन-सी स्टेट चाहिए?
इंटीग्रेशन के अपने रिकॉर्ड: एक कर्सर जो बताता है कि पिछला रन कहां रुका, एक प्रस्ताव जो किसी व्यक्ति की मंज़ूरी का इंतज़ार कर रहा है, एक लीज़ जो बताती है कि रन किस वर्कर के पास है, और एक ड्राई-रन प्लान जिससे apply का मेल खाना ज़रूरी है. इनमें से कुछ भी किसी स्क्रिप्ट का वेरिएबल नहीं है. ये सब ID वाली पंक्तियां हैं.
Tajo का MCP सर्वर सेशन में कुछ भी क्यों नहीं रखता?
हर रिक्वेस्ट को एक नया सर्वर और बिना सेशन ID वाला ट्रांसपोर्ट मिलता है. अगली कॉल को जो कुछ भी चाहिए, वह एक रिकॉर्ड है: प्रस्ताव की ID, प्लान की ID, एक कर्सर. इसलिए किसी दूसरे क्लाइंट में दूसरा एजेंट वहीं से आगे बढ़ सकता है जहां पहला रुका था, और क्रैश हुआ एजेंट अपनी कॉन्टेक्स्ट विंडो के अलावा कुछ नहीं खोता.
क्या Tajo एजेंट का लिखा कोड चलाता है?
ग्राहकों के लिए नहीं. कनेक्टर टूल्स पर रीड हर कॉल पर एक रिक्वेस्ट होते हैं, बिना किसी सैंडबॉक्स के. जो कोड रनर मौजूद है, वह दो फ़्लैग्स के पीछे एक आंतरिक टूल है, जिस तक सिर्फ़ एडमिनिस्ट्रेटर पहुंच सकते हैं, जिसकी बाइंडिंग्स सिर्फ़ पढ़ने के लिए हैं और जो हर कॉल पर नया कॉन्टेक्स्ट लेता है. वह ख़ुद के बारे में कहता है कि वह सुरक्षा सीमा नहीं है. कोई REPL नहीं है और कोई परसिस्टेंट सेशन नहीं है.
dquickts जैसा ड्यूरेबल JavaScript इंजन किस काम का है?
उसकी README उपयोग गिनाती है: किसी निष्क्रिय स्क्रिप्ट को हाइबरनेट करना, किसी लंबी गणना को कई वर्कर्स में टुकड़ों में बांटना, स्नैपशॉट से वॉर्म-स्टार्ट करना, और किसी स्क्रिप्ट को ऐसी होस्ट कॉल पर रोककर रखना जिसका जवाब घंटों बाद आता है. ये सब असली हैं. फिर भी किसी सिंक को इस पर निर्भर नहीं होना चाहिए: स्नैपशॉट सिर्फ़ उसी इंजन बिल्ड में लोड होता है जिसने उसे लिखा, समीक्षक के लिए अपारदर्शी होता है, और स्क्रिप्ट ने ग्राहक का जो भी डेटा पढ़ा हो, उसे अपने अंदर रखता है.