# Orphaned MCP प्रक्रियाएं अब भी काम पीछे छोड़ सकती हैं

क्लाइंट क्रैश होने से यह पता नहीं चलता कि उसका MCP सर्वर बंद हुआ या नहीं। इससे केवल इतना पता चलता है कि एक प्रक्रिया समाप्त हुई या कम से कम जवाब देना बंद कर दिया। दोनों बातों में फर्क महत्वपूर्ण है, क्योंकि stdio एक transport है, ऐसा kill switch नहीं जो किसी दूसरी प्रक्रिया की सीमा पार कर चुके काम को रोक दे।

मैंने टीमों को पुराने helper processes के ढेर को सिर्फ housekeeping की समस्या मानते देखा है। बाद में पता चला कि उनमें से एक के पास अब भी cloud token, SSH control socket या ऐसा काम कतार में था जिसे किसी ने विफल run से जोड़ा ही नहीं था। सफाई की command आसान थी। असली कठिनाई यह पता लगाना था कि आखिरी कार्रवाई की अनुमति किसने दी थी।

सही प्रतिक्रिया में तीन अलग काम होते हैं: बची हुई प्रक्रिया की सही पहचान करना, यह साबित करना कि उसके पास अब भी कार्रवाई का रास्ता है या नहीं, और ऐसा रिकॉर्ड सुरक्षित रखना जो उसकी आखिरी कॉल को उस session से जोड़े जिसका वह हिस्सा था। इन्हें इसी क्रम में करें। पहले प्रक्रिया को बंद करने से परेशानी रुक सकती है, लेकिन इससे यह पता लगाने का सबसे अच्छा सबूत मिट सकता है कि हुआ क्या था।

## बिना पैरेंट वाली प्रक्रिया संकेत है, अंतिम फैसला नहीं

Orphaned प्रक्रिया वह child होती है जिसका मूल parent समाप्त हो गया हो और जिसका operating-system parent बदल गया हो, अक्सर PID 1 हो जाता है। यह उपयोगी सबूत है, लेकिन unsafe MCP server की परिभाषा नहीं। Process supervisor, shell, IDE और launch service सामान्य काम के दौरान भी स्वस्थ child processes को किसी नए parent से जोड़ सकते हैं।

stdio MCP server के लिए सवाल को सीमित रखें: क्या यह प्रक्रिया अब भी किसी live client connection का हिस्सा है, या उस client के गायब होने के बाद बची है जिसके पास इसका stdin और stdout था? इसका जवाब process ancestry, खुले descriptors, बीते समय और activity record को साथ देखकर मिलता है।

Model Context Protocol के documentation में stdio को स्थानीय, process-spawned integration बताया गया है। क्लाइंट एक command शुरू करता है और server के standard input तथा output पर newline-delimited JSON-RPC का आदान-प्रदान करता है। सही तरीके से लिखा server अपने जीवनकाल के अंत का सरल संकेत पा सकता है: जब उसका input EOF तक पहुंचता है, client side जा चुकी होती है।

EOF टूटे या बंद transport का सबूत है। यह सबूत नहीं कि हर worker, child process, network connection, timer या remote job भी रुक गया है। Server को tool call मिल सकती है, वह काम शुरू कर सकता है और JSON-RPC response भेजने से पहले client खो सकता है। अगर handler ने subprocess शुरू किया या remote system को request भेजी, तो उस काम का अपना अलग lifetime हो सकता है।

यहीं दो अलग failures को अक्सर एक समझ लिया जाता है:

- **Operating-system orphan** ने अपना मूल parent खो दिया है या expected process tree से अलग हो गया है।
- **Logical orphan** ने वह session खो दिया है जिसने उसके काम को अर्थ दिया था, भले ही उसका parent PID अभी सामान्य दिखता हो।

पहली स्थिति memory बर्बाद कर सकती है या कोई port खुला रख सकती है। दूसरी अवांछित बाहरी बदलाव कर सकती है। आपको दोनों की जांच करनी होगी।

PPID 1, पुराना elapsed time और खुला stdin न होना संदिग्ध है। Live terminal से जुड़ी प्रक्रिया भी खतरनाक हो सकती है, अगर उसका owning client चुपचाप अटक गया हो और उसके पास credential या दोबारा इस्तेमाल होने वाला connection हो। दूसरी ओर, crash के बाद बची प्रक्रिया harmless हो सकती है, अगर उसके पास कोई अधिकार न हो और वह किसी action channel तक न पहुंच सके।

ऐसा नियम न बनाएं कि «PPID 1 का मतलब kill»। ऐसा record बनाएं जिसमें लिखा हो कि यह प्रक्रिया क्यों मौजूद है, किस run ने इसे बनाया, यह अब भी कहां पहुंच सकती है और आपने इसके साथ क्या किया।

## ऐसे process inventory से शुरुआत करें जिसका बचाव कर सकें

किसी प्रक्रिया को signal भेजने से पहले snapshot लें। प्रक्रिया खत्म होने के बाद command line, process group या open descriptor वापस नहीं मिलते। ऊपर से PID reuse ढीले-ढाले notes को बहुत जल्दी अनुमान में बदल देता है।

macOS पर किसी clever one-liner से शुरुआत न करें जो जरूरी evidence को filter कर दे। पहले पूरा view लें:

```sh
ps -axo user,pid,ppid,pgid,stat,etime,command
```

उन commands को खोजें जिन्हें आपका MCP client शुरू करता है। यह सीधा server binary, `node` या `python` जैसा interpreter, package launcher, SSH helper या `sp mcp` shim हो सकता है। Search सीमित करने से पहले संबंधित rows को incident note में कॉपी करें।

हर field अलग सवाल का जवाब देती है:

- `pid` इस जांच के लिए प्रक्रिया की पहचान करता है।
- `ppid` बताता है कि उसका immediate parent अब भी मौजूद है या नहीं।
- `pgid` process group की पहचान करता है और साथ शुरू किए गए sibling helpers दिखा सकता है।
- `stat` sleeping, stopped या uninterruptible process state दिखा सकता है।
- `etime` बताता है कि हाल की समझी जा रही प्रक्रिया घंटों या दिनों से चल रही है या नहीं।
- `command` अक्सर executable और उसे शुरू करने के लिए इस्तेमाल arguments का बचा हुआ एकमात्र record होता है।

इसके बाद हर candidate की सीधे जांच करें। असली PID डालें और output को timestamp के साथ incident record में सुरक्षित करें।

```sh
ps -o pid=,ppid=,pgid=,stat=,etime=,user=,command= -p 48271
lsof -nP -p 48271
```

पहली command compact identity record देती है। दूसरी बताती है कि प्रक्रिया ने अब भी क्या खुला रखा है। File descriptors 0, 1 और 2 पर खास ध्यान दें। सामान्य stdio server में stdin के लिए read end और stdout तथा stderr के लिए write ends होते हैं। stdin EOF तक पहुंच चुका हो तो भी process pipe descriptor दिखा सकता है, इसलिए केवल descriptor की मौजूदगी से liveness का अनुमान न लगाएं। व्यापक तस्वीर देखें: peer process, terminals, regular files, Unix sockets, TCP connections और child workers।

`lsof` ऐसी गलती भी दिखा सकता है जिसे process listing छिपा देती है। मान लें server का parent जा चुका है, लेकिन उसके पास internal API से established TCP connection अब भी है। इससे यह साबित नहीं होता कि वह नई request कर सकता है, लेकिन जांच के लिए ठोस capability जरूर मिलती है। अगर उसके पास SSH control socket या credential helper से जुड़ा local Unix socket है, तो उसे lead मानें, safety का प्रमाण नहीं।

कार्रवाई करने से पहले process group देखें:

```sh
ps -axo pid,ppid,pgid,stat,etime,command | awk '$3 == 48271 || $2 == 48271'
```

इस उदाहरण में `48271` उस group ID को दर्शाता है जिसे आपने देखा है। इसे वास्तविक value से बदलें। Output ऐसा wrapper, server और helper child दिखा सकता है जिसे केवल एक PID पर `kill` चलाने से पीछे छोड़ दिया जाता। यह भी साबित हो सकता है कि कोई sibling नहीं बचा और प्रक्रिया को अलग करना आसान है।

एक आम shortcut से बचें: केवल `node`, `python` या `npx` खोजकर हर match को kill करना। ये runtime के नाम हैं, identity नहीं। व्यापक cleanup editor extension, local test run, build task या किसी असंबंधित automation job को तोड़ सकता है। पूरी command line को अपेक्षित server configuration से मिलाएं, फिर उसके आसपास के parent और group की जांच करें।

अगर आपका client launch के समय server command record करता है, तो उस command को session record के साथ रखें। बाद की जांच अस्पष्ट process-name search के बजाय सीधी तुलना बन जाती है।

## stdio pipe कार्रवाई की सीमा तय नहीं करती

सही तरीके से लिखा stdio server stdin बंद होने पर नए requests स्वीकार करना बंद कर देना चाहिए। उसे शुरू न हुए काम को cancel करना चाहिए, transport बंद करना चाहिए और सीमित cleanup पूरा करने के बाद exit करना चाहिए। Official TypeScript MCP SDK guide भी shutdown discussion में यही व्यावहारिक बात कहती है: transport बंद करने से in-flight tool handlers अपने आप drain नहीं होते।

इस आखिरी हिस्से पर अधिक ध्यान चाहिए। Client crash होने पर tool handler कई अवस्थाओं में हो सकता है:

1. उसने बाहरी काम शुरू नहीं किया और साफ तौर पर cancel किया जा सकता है।
2. उसने request भेजी है, लेकिन response नहीं मिला।
3. उसने remote बदलाव पूरा कर दिया, लेकिन success report करने से पहले response path खो दिया।
4. उसने local child या remote job शुरू की है जो handler से अधिक समय तक चलती है।
5. वह external dependency का इंतजार कर रहा है और बाद में फिर चल सकता है।

केवल पहली अवस्था को सुरक्षित रूप से «कुछ नहीं हुआ» कहा जा सकता है। बाकी चार के लिए dead client process के बाहर के records चाहिए।

एक ठोस उदाहरण लें। Server को build deploy करने वाली tool call मिलती है। वह request remote build API को भेज देता है और API के processing के दौरान client crash हो जाता है। Server को टूटी हुई stdout pipe दिखती है। अगर वह तुरंत exit करता है, तो build फिर भी चल सकती है। अगर idempotency mechanism के बिना retry करता है, तो दूसरी build शुरू हो सकती है। अगर वह long-lived credential के साथ जीवित रहता है, तो मूल client के जाने के बाद polling, retry या follow-up work शुरू कर सकता है।

समस्या यह नहीं कि server जीवित रह गया। समस्या यह मानना है कि transport बंद होना authorization, cancellation और remote state के बारे में पूरी जानकारी देता है।

हर action channel के लिए सीमा साफ तय करें:

- कौन-सी घटना server में नया काम आना रोकती है?
- बाहर भेजे गए काम को query या cancel करने के लिए कौन-सा identifier है?
- क्या remote operation idempotency या request token का समर्थन करती है?
- Client गायब होने के बाद credential किस प्रक्रिया के पास रहता है?
- JSON-RPC response न भेज पाने पर final result कहां record होता है?

अगर high-impact tool के लिए इन सवालों के जवाब नहीं हैं, तो केवल stdio इस्तेमाल होने के कारण उसे safe न मानें।

यही नियम SSH पर भी लागू होता है। Interactive दिखने वाली command remote host पर background process शुरू कर सकती है। Local client बंद होने से local channel बंद हो सकता है, जबकि shell से अलग हुई remote command चलती रह सकती है। Server को ऐसा command design चाहिए जिसमें remote work दिखाई दे और cancel किया जा सके। Terminal disconnect पर भरोसा काफी नहीं है।

## साबित करें कि बची प्रक्रिया अब भी कार्रवाई कर सकती है या नहीं

प्रक्रिया का मौजूद होना authority के बराबर नहीं है। वास्तविक कार्रवाई किए बिना action path की जांच करनी होगी।

पहले पता करें कि authority कहां रहती है। जो server अपने environment से API token पढ़ता है, उसका risk profile उस server से अलग है जो हर कार्रवाई के लिए अलग broker से अनुरोध करता है। जिस server के पास file में SSH private key है, उसका profile उस server से अलग है जो short-lived local helper के जरिए delegation करता है। जांचे जा रहे server के लिए जवाब लिखकर रखें।

फिर देखें कि प्रक्रिया के पास अब भी क्या खुला और reachable है। `lsof` एक शुरुआत देता है, लेकिन हर in-memory credential या authenticated session नहीं दिखाता। इसे अपने action logs और, जहां उपलब्ध हों, external system के audit trail के साथ मिलाएं।

एक उपयोगी जांच क्रम इस तरह दिखता है:

1. Candidate PID, command, PPID, process group और खुले network या Unix sockets record करें।
2. उस प्रक्रिया या session से जुड़ी सबसे हाल की बाहरी कार्रवाई खोजें।
3. देखें कि client crash के समय के बाद कोई नई कार्रवाई हुई या नहीं।
4. प्रक्रिया के authorization path को revoke या disable करें।
5. देखें कि प्रक्रिया फिर कोई कार्रवाई करने की कोशिश करती है या नहीं और gateway उसे reject करता है या नहीं।

Orphaned server से harmless दिखने वाला write करवाकर जांच न करें। बहुत-से systems में सचमुच harmless write नहीं होती। Test call rate limits बदल सकती है, audit noise बना सकती है, state rotate कर सकती है या automation शुरू कर सकती है। अगर design में read-only health या identity endpoint है, तो उसे प्राथमिकता दें। इससे भी अच्छा है कि revocation के बाद gateway या credential broker से denial की पुष्टि करें।

एक कठिन फर्क यहां है: network access वाली प्रक्रिया जरूरी नहीं कि कार्रवाई कर सके, और दिखाई देने वाला network connection न रखने वाली प्रक्रिया बाद में कार्रवाई करने में सक्षम हो सकती है। DNS resolution, proxy, local helper, queued timer या child process रास्ता फिर खोल सकते हैं। इसलिए revocation test sockets के snapshot से अधिक महत्वपूर्ण है।

अगर server के पास सीधे reusable secret है, तो cleanup scope में secret rotation या revocation भी शामिल होनी चाहिए। प्रक्रिया बंद करने से memory में उसकी एक copy हटती है, लेकिन attacker, dumped process image या remote service उस secret से क्या कर सकते हैं, यह नहीं बदलता। Teams अक्सर rotation से बचती हैं क्योंकि इसमें काम बढ़ता है। यह समझ में आता है, लेकिन security argument नहीं है।

Gateway model जांच बदल देता है। Server untrusted process के रूप में जीवित रह सकता है, फिर भी नई protected call नहीं कर पाता, क्योंकि उसके पास credential कभी था ही नहीं और अब approved live session भी नहीं है। Sallyport API और SSH secrets अपने encrypted vault में रखता है और कार्रवाई खुद करता है, इसलिए agent को secret plaintext या किसी substitute value के रूप में नहीं मिलता। इससे भटकी हुई stdio प्रक्रिया का संभावित नुकसान घटता है, लेकिन suspect run को फिर भी revoke करें। यह न मानें कि process crash ने अपने आप उसे revoke कर दिया।

व्यावहारिक मानक सरल है: session या action path revoke करने के बाद protected call को deny होना चाहिए और denial record होना चाहिए। अगर आप इसे दिखा नहीं सकते, तो containment की पुष्टि नहीं हुई है।

## Cleanup कहानी बदलने से पहले आखिरी calls record करें

Incident record में memory से बनाई गई कहानी नहीं, स्थिर identifiers होने चाहिए। PID अस्थायी होता है और दोबारा इस्तेमाल हो सकता है। Update के बाद process command बदल सकती है। Session identifier और छेड़छाड़ का पता लगाने वाला audit record कहीं बेहतर anchor देते हैं।

हर संदिग्ध प्रक्रिया के लिए कम से कम एक row बनाएं, जिसमें ये fields हों:

```text
Observed at:
Client process PID and command:
MCP server PID and command:
Parent PID and process group:
Session identifier:
Authorization state:
Last successful action time:
Last attempted action time:
Action target and operation:
Result or remote job identifier:
Revocation time and operator:
Termination signal and exit result:
Follow-up required:
```

सबसे महत्वपूर्ण जोड़ी session identifier और last attempted action है। कई teams केवल सफल calls record करती हैं, जिससे crash जांच का सबसे खुलासा करने वाला क्षण छिप जाता है: वह request जो local machine से निकल गई, लेकिन जिसका result कभी नहीं आया।

तीन timestamps अलग रखें। Record करें कि client कब unavailable हुआ, server की आखिरी ज्ञात कार्रवाई कब शुरू हुई और authority कब revoke की गई। केवल एक «incident time» रखने पर यह पता नहीं चलेगा कि call crash से पहले हुई, अनिश्चित अवधि में हुई या containment लागू हो जाने के बाद।

Action target भी सुरक्षित रखें। «cloud API को call किया» पर्याप्त नहीं है। Account या endpoint class, method या SSH command category और remote service खोजने योग्य request या job identifier दर्ज करें। General incident note में sensitive request bodies न रखें। Side effect का मिलान करने के लिए पर्याप्त जानकारी चाहिए, हर secret और customer record की दूसरी copy नहीं।

अलग session और activity journals वाले gateway में दोनों का उपयोग करें। Session record बताता है कि agent process किसने चलाया और वह run अब भी authorized है या नहीं। Activity record बताता है कि कौन-सी individual calls हुईं और किस क्रम में हुईं। ये अलग सवाल हैं। उन्हें एक broad event stream में मिला देने से दबाव के समय दोनों का जवाब ढूंढना कठिन होता है।

Sallyport दोनों journals को एक encrypted, hash-chained audit log से project करता है। संबंधित session और activity entries capture करने के बाद offline integrity check चलाएं:

```sh
sp audit verify
```

Command का exact result incident record के साथ सुरक्षित करें। Verification यह तय नहीं करती कि कार्रवाई authorized या समझदारी भरी थी। यह बताती है कि जिस audit chain पर आप भरोसा कर रहे हैं, वह vault तक पहुंच के बिना अब भी verify होती है या नहीं। यह अलगाव तब उपयोगी है जब incident review करने वाले व्यक्ति को history जांचने के लिए credentials नहीं मिलने चाहिए।

इस recordkeeping का अभ्यास formal security incident तक न टालें। Routine crash review में missing session IDs, अस्पष्ट commands और dead machine पर ही मौजूद logs मिलते हैं। शांत cleanup के दौरान इन gaps को ठीक करना production write के बाद उन्हें खोजने से बहुत सस्ता है।

## प्रक्रिया समाप्त करने से पहले authority revoke करें

सबसे सुरक्षित क्रम है: revoke, denial verify, फिर प्रक्रिया रोकें। उल्टा करना तेज लगता है क्योंकि प्रक्रिया तुरंत गायब हो जाती है, लेकिन authorization record जीवित रह सकता है और बाद का correlation कठिन हो सकता है।

सबसे सीमित प्रभावी revocation से शुरुआत करें। अगर system हर agent run के लिए अलग authorization track करता है, तो उसी run को revoke करें। अगर एक credential server के सामने आ गया है, तो उसे disable या rotate करें। अगर remote job के पास cancellation handle है, तो उस job को अलग से cancel करें। ये अलग actions हैं, क्योंकि इनके lifetimes अलग हैं।

यह न मानें कि server exit remote work को cancel कर देता है। प्रक्रिया खत्म हो सकती है, जबकि उसकी request अब भी queue में हो। यह भी न मानें कि remote cancellation server को रोक देती है। Authority बची होने पर प्रक्रिया retry या दूसरी request submit कर सकती है। Containment के लिए local और remote दोनों sides को यह मानना होगा कि run खत्म हो गया है।

Action path revoke करने के बाद अपने setup के अनुसार evidence से state verify करें। यह gateway के activity journal में recorded denial, safe identity endpoint को असफल authenticated request या token disabled होने का remote audit entry हो सकता है। तरीका अलग हो सकता है। सिद्धांत एक है: साबित करें कि बची हुई प्रक्रिया protected call नहीं कर सकती।

इसके बाद पहचाने गए PID को सामान्य termination signal भेजें:

```sh
kill -TERM 48271
sleep 2
ps -p 48271 -o pid=,ppid=,stat=,etime=,command=
```

अगर आखिरी command कोई process row नहीं लौटाती, तो वह result record करें। अगर प्रक्रिया अब भी मौजूद है, तो देखें कि वह shutdown कर रही है, I/O में अटकी है या child work को जीवित रख रही है। आगे बढ़ने से पहले group और children की फिर जांच करें। किसी ज्ञात helper को अलग से terminate करना पड़ सकता है, लेकिन पूरे process group को बिना सोचे kill न करें, जब तक पुष्टि न हो कि हर member उसी failed run का हिस्सा है।

`kill -KILL` तभी इस्तेमाल करें जब सामान्य termination विफल हो और सबूत सुरक्षित हो चुके हों। SIGKILL प्रक्रिया को files बंद करने, काम cancel करने, final log लिखने या temporary state हटाने का मौका नहीं देता। कभी-कभी यह सही tradeoff होता है। इसे सही नाम दें: संभावित अधूरी cleanup के साथ forced containment।

जब command line पर्याप्त न हो, macOS Activity Monitor मदद कर सकता है। Apple सामान्य Quit और Force Quit actions document करता है और processes को hierarchy में दिखा सकता है। Hierarchy view parent और child संबंधों की पुष्टि करने में उपयोगी है। यह incident record का विकल्प नहीं है, क्योंकि GUI list session और action evidence सुरक्षित नहीं रखती जिसे बाद में चाहिए होगा।

## Server shutdown को design requirement बनाएं

Stdio servers को client के गायब होने को exceptional corner case नहीं, first-class event मानना चाहिए। Client crashes सामान्य software behavior हैं। Laptops sleep करते हैं, terminals बंद होते हैं, IDE restart होते हैं, updates processes रोकते हैं और model error के बाद agents abort हो सकते हैं।

Server में चार गुणों वाला explicit shutdown path होना चाहिए। Input बंद होने पर वह नए requests स्वीकार करना बंद करे। हर शुरू की गई कार्रवाई को correlation identifier के साथ track करे। Active work को cancel होने या known state तक पहुंचने का सीमित समय दे। उस सीमा के बाद exit करे, न कि गलती से permanent background service बन जाए।

Graceful shutdown को हमेशा इंतजार करने से न मिलाएं। EOF मिलने के बाद external API का अनिश्चित समय तक इंतजार करने वाला server बेहतर व्यवहार वाला orphan ही है। Cleanup के लिए deadline रखें, unresolved काम log करें और exit करें। Unresolved work उसके external job identifier या action record के जरिए खोजने योग्य होना चाहिए।

Child management भी उतना ही जरूरी है। अगर कोई tool compiler, package manager, SSH helper, browser driver या command wrapper शुरू करता है, तो server को पता होना चाहिए कि server के समाप्त होने पर वह child भी समाप्त होनी चाहिए या नहीं। Process groups जानबूझकर सेट करें। Child PIDs capture करें। Shutdown पर केवल उस request से जुड़ी children को terminate करें और record करें कि वे समाप्त हुईं या नहीं।

जब तक tool contract में durable background work साफ तौर पर न हो, shell के जरिए काम को background में न भेजें। `some-command &` जैसी command दूसरा lifetime बना देती है जिसे आपका MCP server शायद देख ही न पाए। Durable work चाहिए तो ऐसे job system में submit करें जो job ID लौटाता हो, फिर deliberate tools के जरिए status और cancellation उपलब्ध कराएं। छिपा हुआ background work durability नहीं, audit gap है।

जहां remote service समर्थन करती हो, idempotency का उपयोग करें। हर external write को session और tool call से बना request identifier दें और request भेजने से पहले उसे record करें। Client submission के बाद crash हो जाए तो retry करना है या नहीं, इसका अनुमान लगाने के बजाय remote system को query कर सकते हैं। जहां idempotency उपलब्ध नहीं है, ambiguity document करें और write replay करने से पहले operator decision जरूरी बनाएं।

Server को diagnostics stderr पर लिखने चाहिए, stdout पर नहीं। Stdout MCP के newline-delimited JSON-RPC stream के लिए है। एक stray debug line protocol corrupt कर सकती है, client failure शुरू कर सकती है और वही crash pattern पैदा कर सकती है जिसे आप साफ करने की कोशिश कर रहे हैं। यह बात छोटी लगती है, जब तक production server startup पर कोई library warning print न कर दे।

## Ownership को shell history में नहीं, action record में रखें

Shell history तब तक सुविधाजनक है जब तक वह missing, truncated, shared या process मरने के बाद लिखी हुई न हो। Action record में काम के दौरान ही ownership information होनी चाहिए।

हर ऐसी call के साथ जो external system को छू सकती है, इतना context जोड़ें कि बाद में चार सवालों का जवाब मिल सके: किस agent run ने इसे मांगा, किस local process ने submit किया, किस authorization decision ने इसकी अनुमति दी और कौन-सी external operation हुई। इनमें से कोई भी missing हो तो client crash के बाद investigator को अनुमान लगाना पड़ेगा।

हर ownership सवाल का जवाब code-signing identity से देने की कोशिश न करें। इससे पता चलता है कि executable पर किसने sign किया, जो यह तय करने में उपयोगी है कि process approval का योग्य है या नहीं। लेकिन यह individual run, call तक पहुंचाने वाले prompt या remote request की पहचान नहीं बताती। Session start पर trust के लिए code-signing authority का उपयोग करें। Operational traceability के लिए session identifier और per-call records रखें।

यही चेतावनी command lines पर भी लागू होती है। Command बता सकती है कि `sp mcp` या server executable शुरू हुआ। वह भरोसे से नहीं बता सकती कि आखिरी tool call कौन-सी थी, user ने उसे approve किया था या remote system ने उसे स्वीकार किया। Process list को supporting evidence मानें, audit source नहीं।

Crash होने पर इस क्रम में correlate करें:

1. Crash के समय client process का owner agent run खोजें।
2. उस run द्वारा शुरू की गई server process या process group खोजें।
3. उस run की आखिरी activity entries ढूंढें और उनके timestamps को process snapshot से मिलाएं।
4. Request, transaction या job identifier से किसी अधूरी external operation का मिलान करें।
5. Revocation और termination events को आखिरी calls के साथ record करें।

यह क्रम एक आम गलती रोकता है: stale PID ढूंढकर उसे kill करना और बाद में उसकी आखिरी API call को गलत agent run से जोड़ देना, क्योंकि दो sessions ने एक ही server command इस्तेमाल की थी। Commands दोहर सकती हैं। Session records नहीं।

अगर आपका environment आज यह correlation नहीं कर सकता, तो autonomous tools को write access देने से पहले इसे जोड़ें। Imperfect observability read-only प्रयोगों में चल सकती है। Production changes में नहीं।

## Crash drill कम जोखिम में gaps दिखाती है

हर ऐसे MCP server के लिए controlled crash drill चलाएं जो external changes कर सकता है। Test account या read-only action path का उपयोग करें और evidence ऐसे capture करें जैसे वास्तविक incident संभाल रहे हों।

सामान्य client session शुरू करें और ज्ञात correlation identifier के साथ एक action चलाएं। Server सक्रिय रहते हुए client को अचानक terminate करें। फिर server PID, PPID और process group, open descriptors और action journal जांचें। Session revoke करें। पुष्टि करें कि अगली protected call deny होती है। अंत में अगर EOF पर server बंद न हुआ हो तो उसे terminate करें और external action का मिलान करें।

Drill का परिणाम केवल pass या fail label नहीं होना चाहिए। जानना चाहिए कि stdin closure server तक पहुंचती है या नहीं, child processes बचती हैं या नहीं, remote jobs के cancellation handles हैं या नहीं, logs owner की पहचान करते हैं या नहीं और revocation तुरंत behavior बदलती है या नहीं।

Timing पर ध्यान दें। Request dispatch से पहले हुआ crash उस crash से अलग व्यवहार करता है जो remote service द्वारा request स्वीकार किए जाने के बाद हुआ हो। अगर server पर्याप्त instrumentation देता है तो दोनों points पर drill दोहराएं। «sent» और «confirmed» के बीच की खराब सीमा में duplicate writes और false assurances छिपे रहते हैं।

हर server के लिए cleanup expectation लिखें। अच्छी expectation विशिष्ट होती है: input बंद होने के बाद server calls स्वीकार करना बंद करे, configured timeout के भीतर exit करे, उसके स्वामित्व वाली कोई helper process न छोड़े और हर शुरू की गई external action के लिए activity record बनाए। कमजोर expectation है «client सामान्यतः इसे साफ कर देता है»। सामान्य behavior control नहीं है।

कुछ बार ऐसा करने के बाद stale processes रहस्यमय नहीं लगेंगी। वे process snapshot, ownership trail, revocation action और cleanup rule वाली defined failure mode बन जाएंगी। यही मानक होना चाहिए। प्रक्रिया crash कर सकती है। उसकी आखिरी calls को समझाने और सीमित करने की आपकी क्षमता नहीं।
