- Created adapters/ module with SimpleBackend and JupyterBackend wrappers - Adapters map ForgeConfig to pod_executor explicit parameters - Updated server.py to use pod_executor components: - PodmanClient and SecureContainerManager from pod_executor - SimpleFileAuditLogger and BasicValidator from pod_executor - Removed old execution/ and podman/ imports - Updated all tool files: - execute_python.py: imports from adapters - document_state.py: uses jupyter_backend instead of session_manager - resources.py: updated session references - Updated builder files to import from pod_executor: - image_builder.py: PodmanClient, parse_memory_string - environment_builder.py: PodmanClient - Fixed test: test_resource_limits_storage_quota_in_podman_params - Storage is tracked internally but not in Podman params - All 40 pod_executor tests now passing Key architectural change: - pod_executor is now the execution engine - mcp_forge adapters provide ForgeConfig compatibility layer - Separation of concerns: execution vs MCP protocol
108 lines
3.5 KiB
Python
108 lines
3.5 KiB
Python
"""Jupyter backend adapter for MCP-Forge.
|
|
|
|
Wraps pod_executor.JupyterBackend with MCP-Forge configuration.
|
|
"""
|
|
|
|
from typing import Optional, List, Dict, Any
|
|
import logging
|
|
|
|
from pod_executor import JupyterBackend as PodJupyterBackend, ExecutionResult
|
|
from pod_executor.containers.manager import SecureContainerManager
|
|
from pod_executor.jupyter.sessions import SessionState, SessionError
|
|
|
|
from ..config.schema import ForgeConfig
|
|
from ..security.audit import AuditLogger
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# Re-export for compatibility
|
|
__all__ = ["JupyterBackend", "SessionState", "SessionError"]
|
|
|
|
|
|
class JupyterBackend:
|
|
"""Adapter for stateful Python code execution using Jupyter kernels.
|
|
|
|
This wraps pod_executor.JupyterBackend and adapts it to MCP-Forge's
|
|
configuration system.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
container_manager: SecureContainerManager,
|
|
audit_logger: AuditLogger,
|
|
config: ForgeConfig
|
|
):
|
|
"""
|
|
Initialize Jupyter backend adapter.
|
|
|
|
Args:
|
|
container_manager: Container lifecycle manager
|
|
audit_logger: Audit logging instance
|
|
config: MCP-Forge configuration
|
|
"""
|
|
self.container_manager = container_manager
|
|
self.audit_logger = audit_logger
|
|
self.config = config
|
|
|
|
# Create pod_executor backend with config parameters
|
|
self.backend = PodJupyterBackend(
|
|
container_manager=container_manager,
|
|
image=config.images.jupyter,
|
|
default_timeout=config.execution.default_timeout,
|
|
default_memory=config.execution.default_memory,
|
|
default_cpu_quota=config.execution.default_cpu_quota,
|
|
max_timeout=config.execution.max_timeout,
|
|
max_memory=config.execution.max_memory,
|
|
max_cpu_quota=config.execution.max_cpu_quota,
|
|
max_sessions=config.sessions.max_concurrent,
|
|
idle_timeout=config.sessions.idle_timeout,
|
|
audit_logger=audit_logger
|
|
)
|
|
|
|
logger.debug(f"JupyterBackend initialized with image={config.images.jupyter}")
|
|
|
|
def execute(
|
|
self,
|
|
code: str,
|
|
session_id: str,
|
|
timeout: Optional[int] = None,
|
|
memory: Optional[str] = None,
|
|
cpu_quota: Optional[int] = None
|
|
) -> ExecutionResult:
|
|
"""
|
|
Execute code in a stateful Jupyter session.
|
|
|
|
Args:
|
|
code: Python code to execute
|
|
session_id: Session identifier
|
|
timeout: Optional timeout override (seconds)
|
|
memory: Optional memory limit override
|
|
cpu_quota: Optional CPU quota override
|
|
|
|
Returns:
|
|
ExecutionResult with stdout, stderr, result, etc.
|
|
"""
|
|
return self.backend.execute(
|
|
code=code,
|
|
session_id=session_id,
|
|
timeout=timeout,
|
|
memory=memory,
|
|
cpu_quota=cpu_quota
|
|
)
|
|
|
|
def list_sessions(self) -> List[Dict[str, Any]]:
|
|
"""List all active sessions."""
|
|
return self.backend.list_sessions()
|
|
|
|
def get_session(self, session_id: str) -> Optional[Dict[str, Any]]:
|
|
"""Get session information."""
|
|
return self.backend.get_session(session_id)
|
|
|
|
def destroy_session(self, session_id: str) -> bool:
|
|
"""Destroy a session."""
|
|
return self.backend.destroy_session(session_id)
|
|
|
|
def cleanup_idle_sessions(self) -> int:
|
|
"""Clean up idle sessions."""
|
|
return self.backend.cleanup_idle_sessions()
|