Skip to main content
Glama
kernel_daemon.py8.68 kB
#!/usr/bin/env python3 """ Kernel daemon that maintains a persistent Jupyter kernel """ import jupyter_client import json import socket import threading import sys import os import signal import time from pathlib import Path class KernelDaemon: def __init__(self, port=9999): self.port = port self.kernel_manager = None self.kernel_client = None self.socket_server = None self.running = False # Use absolute paths in the script directory self.script_dir = Path(__file__).parent.absolute() self.lock_file = self.script_dir / '.kernel_daemon.lock' self.connection_file = self.script_dir / '.kernel_connection.json' def start_kernel(self): """Start the Jupyter kernel""" print("Starting Jupyter kernel...") # Check for uv venv in current directory or parent directories cwd = Path.cwd() venv_path = None # Search for .venv in current and parent directories for path in [cwd] + list(cwd.parents): potential_venv = path / '.venv' if potential_venv.exists(): venv_path = potential_venv break if venv_path: # Use uv venv Python python_path = venv_path / 'bin' / 'python' if not python_path.exists(): # Windows path python_path = venv_path / 'Scripts' / 'python.exe' if python_path.exists(): print(f"Using uv venv Python: {python_path}") # Set up environment for venv kernel_env = os.environ.copy() kernel_env['VIRTUAL_ENV'] = str(venv_path) kernel_env['PATH'] = f"{venv_path / 'bin'}:{kernel_env.get('PATH', '')}" # Start kernel with venv Python self.kernel_manager = jupyter_client.KernelManager(kernel_name='python3') self.kernel_manager.kernel_cmd = [ str(python_path), '-m', 'ipykernel_launcher', '-f', '{connection_file}' ] self.kernel_manager.env = kernel_env else: print("Using system Python") self.kernel_manager = jupyter_client.KernelManager(kernel_name='python3') else: print("No venv found, using system Python") self.kernel_manager = jupyter_client.KernelManager(kernel_name='python3') self.kernel_manager.start_kernel() self.kernel_client = self.kernel_manager.client() self.kernel_client.start_channels() self.kernel_client.wait_for_ready() # Save connection info with open(self.connection_file, 'w') as f: json.dump({ 'connection_file': self.kernel_manager.connection_file, 'port': self.port }, f) print(f"✅ Kernel started and ready on port {self.port}") def execute_code(self, code): """Execute code in the kernel and return outputs""" msg_id = self.kernel_client.execute(code) outputs = [] while True: try: msg = self.kernel_client.get_iopub_msg(timeout=10) msg_type = msg['header']['msg_type'] content = msg['content'] if msg_type == 'stream': outputs.append({ 'type': 'stream', 'name': content['name'], 'text': content['text'] }) elif msg_type == 'execute_result': outputs.append({ 'type': 'execute_result', 'data': content['data'], 'execution_count': content['execution_count'] }) elif msg_type == 'error': outputs.append({ 'type': 'error', 'ename': content['ename'], 'evalue': content['evalue'], 'traceback': content['traceback'] }) elif msg_type == 'display_data': outputs.append({ 'type': 'display_data', 'data': content['data'] }) elif msg_type == 'status' and content['execution_state'] == 'idle': break except: break return outputs def handle_client(self, client_socket): """Handle client requests""" try: # Receive data data = b'' while True: chunk = client_socket.recv(4096) if not chunk: break data += chunk if b'\n\n' in data: # Double newline as message terminator break request = json.loads(data.decode()) if request['action'] == 'execute': outputs = self.execute_code(request['code']) response = {'status': 'success', 'outputs': outputs} elif request['action'] == 'ping': response = {'status': 'alive'} elif request['action'] == 'shutdown': response = {'status': 'shutting_down'} self.running = False else: response = {'status': 'error', 'message': 'Unknown action'} # Send response client_socket.send(json.dumps(response).encode() + b'\n\n') except Exception as e: error_response = {'status': 'error', 'message': str(e)} client_socket.send(json.dumps(error_response).encode() + b'\n\n') finally: client_socket.close() def start_server(self): """Start the socket server""" self.socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket_server.bind(('localhost', self.port)) self.socket_server.listen(5) self.running = True # Write lock file with PID with open(self.lock_file, 'w') as f: json.dump({'pid': os.getpid(), 'port': self.port}, f) print(f"✅ Daemon listening on port {self.port}") while self.running: try: self.socket_server.settimeout(1.0) client_socket, addr = self.socket_server.accept() # Handle each client in a new thread thread = threading.Thread(target=self.handle_client, args=(client_socket,)) thread.start() except socket.timeout: continue except: break def cleanup(self): """Clean up resources""" print("Shutting down daemon...") self.running = False if self.kernel_manager: self.kernel_manager.shutdown_kernel() if self.socket_server: self.socket_server.close() if self.lock_file.exists(): self.lock_file.unlink() if self.connection_file.exists(): self.connection_file.unlink() print("✅ Daemon shut down") def run(self): """Main daemon loop""" # Set up signal handlers signal.signal(signal.SIGINT, lambda s, f: self.cleanup()) signal.signal(signal.SIGTERM, lambda s, f: self.cleanup()) try: self.start_kernel() self.start_server() finally: self.cleanup() def is_daemon_running(): """Check if daemon is already running""" script_dir = Path(__file__).parent.absolute() lock_file = script_dir / '.kernel_daemon.lock' if not lock_file.exists(): return False try: with open(lock_file, 'r') as f: info = json.load(f) # Check if process is still running try: os.kill(info['pid'], 0) return True except OSError: # Process is dead, clean up lock file lock_file.unlink() return False except: return False if __name__ == '__main__': if is_daemon_running(): print("❌ Daemon is already running") sys.exit(1) daemon = KernelDaemon() daemon.run()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mayank-ketkar-sf/ClaudeJupy'

If you have feedback or need assistance with the MCP directory API, please join our Discord server