char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
20455a4ad
char/aliceai-t5-moe
1#!/usr/bin/env python3 2""" 3MCP server (NDJSON JSON-RPC over stdio) that spawns a long-lived grandchild which inherits 4this process's stdin/stdout/stderr and keeps them open. 5 6This reproduces the reader-teardown deadlock: killing the direct MCP child (SIGKILL, which is 7all subprocess_terminate() does) does NOT close the stdout/stderr pipe write ends, because the 8grandchild still holds them. A server that reads those pipes with a blocking read would then 9wait forever for an EOF that never arrives, hanging teardown (both warmup shutdown at startup 10and process shutdown). The polled, running-aware reader must exit regardless. 11""" 12import json 13import os 14import subprocess 15import sys 16 17# Spawn a grandchild that inherits our std handles (fds 0/1/2 = the MCP pipes) and lives well 18# past any teardown in the tests. We do NOT redirect its stdio, so it keeps the pipe write ends 19# open even after this process is killed. 20subprocess .Popen ([sys .executable ,"-c" ,"import time; time.sleep(30)" ]) 21 22TOOLS = [ 23 { 24"name" :"echo" , 25"description" :"Echo back the input message" , 26"inputSchema" : { 27"type" :"object" , 28"properties" : {"message" : {"type" :"string" ,"description" :"Message to echo" }}, 29"required" : ["message" ], 30 }, 31 } 32] 33 34 35def handle_initialize (params ,req_id ): 36return { 37"jsonrpc" :"2.0" , 38"id" :req_id , 39"result" : { 40"protocolVersion" :"2024-11-05" , 41"capabilities" : {"tools" : {}}, 42"serverInfo" : {"name" :"grandchild-test" ,"version" :"1.0" }, 43 }, 44 } 45 46 47def handle_tools_list (params ,req_id ): 48return {"jsonrpc" :"2.0" ,"id" :req_id ,"result" : {"tools" :TOOLS }} 49 50 51def handle_tools_call (params ,req_id ): 52if params .get ("name" )== "echo" : 53message = params .get ("arguments" , {}).get ("message" ,"" ) 54return { 55"jsonrpc" :"2.0" , 56"id" :req_id , 57"result" : {"content" : [{"type" :"text" ,"text" :f"echo: { message } " }]}, 58 } 59return {"jsonrpc" :"2.0" ,"id" :req_id ,"error" : {"code" :- 32602 ,"message" :"Unknown tool" }} 60 61 62HANDLERS = { 63"initialize" :handle_initialize , 64"tools/list" :handle_tools_list , 65"tools/call" :handle_tools_call , 66} 67 68 69def main (): 70sys .stdout = os .fdopen (sys .stdout .fileno (),"w" ,buffering = 1 ) 71sys .stderr = os .fdopen (sys .stderr .fileno (),"w" ,buffering = 1 ) 72 73for line in sys .stdin : 74line = line .strip () 75if not line : 76continue 77try : 78request = json .loads (line ) 79except json .JSONDecodeError : 80continue 81 82method = request .get ("method" ) 83req_id = request .get ("id" ) 84params = request .get ("params" , {}) 85 86if req_id is None : 87continue # notification, no response 88 89handler = HANDLERS .get (method ) 90if handler : 91response = handler (params ,req_id ) 92else : 93response = {"jsonrpc" :"2.0" ,"id" :req_id ,"error" : {"code" :- 32601 ,"message" :f"Method not found: { method } " }} 94 95sys .stdout .write (json .dumps (response )+ "\n" ) 96sys .stdout .flush () 97 98 99if __name__ == "__main__" : 100main ()