Merge feature/add_r_execenv: Add R execution support and refactor pod_executor imports

This commit is contained in:
Hans Aschauer 2026-03-05 06:48:17 +01:00
commit 403b48bc8e
21 changed files with 120 additions and 86 deletions

View file

@ -2,7 +2,7 @@
## Overview ## Overview
MCP-Forge is an MCP (Model Context Protocol) server that provides intelligent code execution capabilities for AI agents. It enables agents to execute Python code in isolated containers while having access to other MCP tools, optimizing data processing by keeping large datasets in the execution environment rather than passing them through the LLM context. MCP-Forge is an MCP (Model Context Protocol) server that provides intelligent code execution capabilities for AI agents. It enables agents to execute Python and R code in isolated containers while having access to other MCP tools, optimizing data processing by keeping large datasets in the execution environment rather than passing them through the LLM context.
## Core Concept ## Core Concept
@ -12,7 +12,7 @@ MCP-Forge is an MCP (Model Context Protocol) server that provides intelligent co
- Limited by context window size - Limited by context window size
**Solution:** MCP-Forge provides a code execution environment where: **Solution:** MCP-Forge provides a code execution environment where:
- MCP tools are available as Python functions - MCP tools are available as language-native functions (Python and R)
- Agents can write code to process data locally - Agents can write code to process data locally
- Only relevant results flow back to the agent - Only relevant results flow back to the agent
- The agent already knows tool signatures from its own configuration - The agent already knows tool signatures from its own configuration
@ -58,7 +58,7 @@ MCP-Forge Server
│ └─ mcp-forge/custom:{user-env-name} │ └─ mcp-forge/custom:{user-env-name}
└─ Isolated Execution Containers └─ Isolated Execution Containers
├─ Python Runtime ├─ Python/R Runtime
├─ MCP Client (injected tools) ├─ MCP Client (injected tools)
└─ Session Volumes └─ Session Volumes
``` ```
@ -137,17 +137,19 @@ MCP-Forge Server
#### Tools #### Tools
**`execute_python`** **`execute_code`**
Execute Python code in an isolated container with MCP tools available. Execute Python or R code in an isolated container with MCP tools available.
Parameters: Parameters:
```json ```json
{ {
"code": "string (required) - Python code to execute", "code": "string (required) - Source code to execute",
"language": "string (optional) - 'python' (default) or 'r'",
"mcp_tools": "array (optional) - List of MCP tool names to inject", "mcp_tools": "array (optional) - List of MCP tool names to inject",
"session_id": "string (optional) - Session ID for stateful execution (null = stateless)", "session_id": "string (optional) - Session ID for stateful execution (null = stateless)",
"backend": "string (optional) - 'simple' (default) or 'jupyter'", "backend": "string (optional) - 'simple' (default) or 'jupyter'",
"kernel": "string (optional) - for jupyter backend: 'python' (ipykernel) or 'r' (rkernel)",
"timeout": "integer (optional) - Max execution time in seconds (default: 300)", "timeout": "integer (optional) - Max execution time in seconds (default: 300)",
"volumes": "object (optional) - Volume mount configuration", "volumes": "object (optional) - Volume mount configuration",
"custom_image": "string (optional) - Custom environment name to use", "custom_image": "string (optional) - Custom environment name to use",
@ -1445,4 +1447,29 @@ print(result['result'])
- Advanced security scanning - Advanced security scanning
- Comprehensive monitoring - Comprehensive monitoring
- High availability setup - High availability setup
- Documentation and examples - Documentation and examples
## Multi-language Execution (Python + R)
MCP-Forge supports both Python and R execution modes:
- **Simple backend**
- `language=python` runs python shell execution (existing path)
- `language=r` runs R shell execution (new path)
- Stateless by default
- **Jupyter backend**
- `kernel=python` uses IPython kernel (existing path)
- `kernel=r` uses IRkernel (new path)
- Stateful sessions keyed by `session_id`
### Tool Injection for R
The MCP bridge protocol remains Unix-socket JSON based and language-agnostic.
R injection wrappers mirror Python wrappers at a behavioral level:
- Generate R wrapper functions per allowed MCP tool
- Forward tool calls over mounted bridge socket
- Decode JSON responses to R lists/vectors
This keeps execution containers air-gapped while preserving access to configured MCP tools.

View file

@ -68,6 +68,13 @@
- Phase 5.3: Integration & End-to-End Testing - Phase 5.3: Integration & End-to-End Testing
- Phase 6: Documentation & Deployment - Phase 6: Documentation & Deployment
**New Workstream (2026-03-04): R execution environments**
- Add language selection to execution tooling (`python` + `r`)
- Add simple backend R shell execution path
- Add stateful Jupyter R kernel support (`IRkernel`)
- Add MCP tool injection generator for R wrappers
- Add tests first for R simple/stateful execution and server dispatch
**Test Count:** 387 tests passing **Test Count:** 387 tests passing
**Last Updated:** 2026-02-06 **Last Updated:** 2026-02-06

View file

@ -24,12 +24,12 @@ from typing import Optional
sys.path.insert(0, str(Path(__file__).parent / "src")) sys.path.insert(0, str(Path(__file__).parent / "src"))
from mcp_forge.config.loader import load_config from mcp_forge.config.loader import load_config
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.podman.containers import SecureContainerManager from mcp_forge.podman.containers import SecureContainerManager
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.security.allowlist import AllowlistValidator from mcp_forge.security.allowlist import AllowlistValidator
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
from mcp_forge.execution.simple.backend import SimpleBackend from mcp_forge.adapters import SimpleBackend
from mcp_forge.execution.jupyter.backend import JupyterBackend from mcp_forge.execution.jupyter.backend import JupyterBackend

View file

@ -27,13 +27,13 @@ from pathlib import Path
# Add src to path # Add src to path
sys.path.insert(0, str(Path(__file__).parent / "src")) sys.path.insert(0, str(Path(__file__).parent / "src"))
from mcp_forge.execution.simple.executor import ExecutionResult from pod_executor.simple.executor import ExecutionResult
from mcp_forge.execution.jupyter.kernel import JupyterKernelManager from mcp_forge.execution.jupyter.kernel import JupyterKernelManager
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.podman.containers import SecureContainerManager, ContainerConfig from mcp_forge.podman.containers import SecureContainerManager, ContainerConfig
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.security.allowlist import OperationValidator from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
import time import time
from typing import Optional, Dict from typing import Optional, Dict

View file

@ -6,8 +6,8 @@ import hashlib
from mcp_forge.config.schema import ForgeConfig from mcp_forge.config.schema import ForgeConfig
from mcp_forge.podman.containers import SecureContainerManager from mcp_forge.podman.containers import SecureContainerManager
from mcp_forge.security.audit import AuditLogger, AuditEventType, AuditSeverity from mcp_forge.security.audit import AuditLogger, AuditEventType, AuditSeverity
from mcp_forge.security.resource_limits import ResourceLimits, parse_memory_string from pod_executor.security.resource_limits import ResourceLimits, parse_memory_string
from mcp_forge.execution.simple.executor import ExecutionResult from pod_executor.simple.executor import ExecutionResult
from mcp_forge.execution.jupyter.kernel import JupyterKernelManager from mcp_forge.execution.jupyter.kernel import JupyterKernelManager
from mcp_forge.execution.jupyter.sessions import SessionManager, SessionState, SessionError from mcp_forge.execution.jupyter.sessions import SessionManager, SessionState, SessionError

View file

@ -22,8 +22,8 @@ from jupyter_client.blocking.client import BlockingKernelClient
import zmq import zmq
from mcp_forge.podman.containers import SecureContainerManager, ContainerConfig from mcp_forge.podman.containers import SecureContainerManager, ContainerConfig
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
from mcp_forge.execution.simple.executor import ExecutionResult from pod_executor.simple.executor import ExecutionResult
class KernelError(Exception): class KernelError(Exception):

View file

@ -7,8 +7,8 @@ from datetime import datetime, timedelta
from mcp_forge.execution.jupyter.kernel import JupyterKernelManager from mcp_forge.execution.jupyter.kernel import JupyterKernelManager
from mcp_forge.config.schema import SessionConfig from mcp_forge.config.schema import SessionConfig
from mcp_forge.security.audit import AuditLogger, AuditEventType, AuditSeverity from mcp_forge.security.audit import AuditLogger, AuditEventType, AuditSeverity
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
from mcp_forge.execution.simple.executor import ExecutionResult from pod_executor.simple.executor import ExecutionResult
class SessionError(Exception): class SessionError(Exception):

View file

@ -10,10 +10,10 @@ from typing import Optional, Dict, List
from datetime import datetime, timedelta from datetime import datetime, timedelta
from pathlib import Path from pathlib import Path
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.allowlist import OperationValidator, SecurityError from mcp_forge.security.allowlist import OperationValidator, SecurityError
from mcp_forge.security.audit import AuditLogger, AuditEventType, AuditSeverity from mcp_forge.security.audit import AuditLogger, AuditEventType, AuditSeverity
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
class ContainerConfig: class ContainerConfig:

View file

@ -25,12 +25,12 @@ from pathlib import Path
# Add src to path # Add src to path
sys.path.insert(0, str(Path(__file__).parent / "src")) sys.path.insert(0, str(Path(__file__).parent / "src"))
from mcp_forge.execution.simple.executor import CodeExecutor from pod_executor.simple.executor import CodeExecutor
from mcp_forge.execution.jupyter.kernel import JupyterKernelManager from mcp_forge.execution.jupyter.kernel import JupyterKernelManager
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.podman.containers import SecureContainerManager from mcp_forge.podman.containers import SecureContainerManager
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.security.allowlist import OperationValidator, AllowlistManager from mcp_forge.security.allowlist import OperationValidator
class PassthroughValidator(OperationValidator): class PassthroughValidator(OperationValidator):

View file

@ -12,7 +12,7 @@ from mcp_forge.builder.environment_builder import (
from mcp_forge.builder.package_validator import SecurityError from mcp_forge.builder.package_validator import SecurityError
from mcp_forge.builder.image_builder import BuildResult from mcp_forge.builder.image_builder import BuildResult
from mcp_forge.config.schema import EnvironmentBuilderConfig from mcp_forge.config.schema import EnvironmentBuilderConfig
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger

View file

@ -5,7 +5,7 @@ from pathlib import Path
from unittest.mock import Mock from unittest.mock import Mock
from mcp_forge.builder.image_builder import ImageBuilder, BuildResult from mcp_forge.builder.image_builder import ImageBuilder, BuildResult
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.config.schema import EnvironmentBuilderConfig from mcp_forge.config.schema import EnvironmentBuilderConfig
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger

View file

@ -6,11 +6,11 @@ from pathlib import Path
from mcp_forge.execution.jupyter.backend import JupyterBackend from mcp_forge.execution.jupyter.backend import JupyterBackend
from mcp_forge.execution.jupyter.sessions import SessionManager, Session, SessionState from mcp_forge.execution.jupyter.sessions import SessionManager, Session, SessionState
from mcp_forge.execution.simple.executor import ExecutionResult from pod_executor.simple.executor import ExecutionResult
from mcp_forge.config.schema import ForgeConfig, ExecutionConfig, ImageConfig, SessionConfig from mcp_forge.config.schema import ForgeConfig, ExecutionConfig, ImageConfig, SessionConfig
from mcp_forge.podman.containers import SecureContainerManager from mcp_forge.podman.containers import SecureContainerManager
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
@pytest.fixture @pytest.fixture

View file

@ -11,8 +11,8 @@ from mcp_forge.execution.jupyter.kernel import (
KernelError KernelError
) )
from mcp_forge.podman.containers import SecureContainerManager from mcp_forge.podman.containers import SecureContainerManager
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
from mcp_forge.execution.simple.executor import ExecutionResult from pod_executor.simple.executor import ExecutionResult
@pytest.fixture @pytest.fixture

View file

@ -14,8 +14,8 @@ from mcp_forge.execution.jupyter.sessions import (
from mcp_forge.execution.jupyter.kernel import JupyterKernelManager from mcp_forge.execution.jupyter.kernel import JupyterKernelManager
from mcp_forge.config.schema import SessionConfig from mcp_forge.config.schema import SessionConfig
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
from mcp_forge.execution.simple.executor import ExecutionResult from pod_executor.simple.executor import ExecutionResult
@pytest.fixture @pytest.fixture

View file

@ -4,12 +4,12 @@ import pytest
from unittest.mock import Mock, MagicMock, patch from unittest.mock import Mock, MagicMock, patch
from pathlib import Path from pathlib import Path
from mcp_forge.execution.simple.backend import SimpleBackend from mcp_forge.adapters import SimpleBackend
from mcp_forge.execution.simple.executor import ExecutionResult from pod_executor.simple.executor import ExecutionResult
from mcp_forge.config.schema import ForgeConfig, ExecutionConfig, ImageConfig from mcp_forge.config.schema import ForgeConfig, ExecutionConfig, ImageConfig
from mcp_forge.podman.containers import SecureContainerManager from mcp_forge.podman.containers import SecureContainerManager
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
@pytest.fixture @pytest.fixture

View file

@ -4,9 +4,9 @@ import pytest
from unittest.mock import Mock from unittest.mock import Mock
import json import json
from mcp_forge.execution.simple.executor import CodeExecutor, ExecutionResult from pod_executor.simple.executor import CodeExecutor, ExecutionResult
from mcp_forge.podman.containers import SecureContainerManager from mcp_forge.podman.containers import SecureContainerManager
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
@pytest.fixture @pytest.fixture

View file

@ -15,10 +15,10 @@ from pathlib import Path
def test_create_container_with_valid_params_succeeds(): def test_create_container_with_valid_params_succeeds():
"""Test that container creation with valid params succeeds.""" """Test that container creation with valid params succeeds."""
from mcp_forge.podman.containers import ContainerConfig, SecureContainerManager from mcp_forge.podman.containers import ContainerConfig, SecureContainerManager
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.allowlist import OperationValidator from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.security.audit import AuditLogger, AuditEventType from mcp_forge.security.audit import AuditLogger, AuditEventType
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
# Setup mocks # Setup mocks
@ -51,7 +51,7 @@ def test_create_container_with_valid_params_succeeds():
def test_create_container_with_forbidden_params_raises_security_error(): def test_create_container_with_forbidden_params_raises_security_error():
"""Test that forbidden parameters raise SecurityError.""" """Test that forbidden parameters raise SecurityError."""
from mcp_forge.podman.containers import ContainerConfig, SecureContainerManager from mcp_forge.podman.containers import ContainerConfig, SecureContainerManager
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.allowlist import OperationValidator, SecurityError from mcp_forge.security.allowlist import OperationValidator, SecurityError
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -80,7 +80,7 @@ def test_create_container_with_forbidden_params_raises_security_error():
def test_create_container_with_invalid_image_raises_security_error(): def test_create_container_with_invalid_image_raises_security_error():
"""Test that invalid/disallowed images raise SecurityError.""" """Test that invalid/disallowed images raise SecurityError."""
from mcp_forge.podman.containers import ContainerConfig, SecureContainerManager from mcp_forge.podman.containers import ContainerConfig, SecureContainerManager
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.allowlist import OperationValidator, SecurityError from mcp_forge.security.allowlist import OperationValidator, SecurityError
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -126,7 +126,7 @@ def test_create_container_enforces_required_parameters():
def test_resource_limits_are_applied_correctly(): def test_resource_limits_are_applied_correctly():
"""Test that resource limits are correctly applied.""" """Test that resource limits are correctly applied."""
from mcp_forge.podman.containers import ContainerConfig from mcp_forge.podman.containers import ContainerConfig
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
limits = ResourceLimits( limits = ResourceLimits(
memory="1g", memory="1g",
@ -149,7 +149,7 @@ def test_resource_limits_are_applied_correctly():
def test_volume_mounts_are_validated(): def test_volume_mounts_are_validated():
"""Test that volume mounts are validated against allowlist.""" """Test that volume mounts are validated against allowlist."""
from mcp_forge.podman.containers import ContainerConfig, SecureContainerManager from mcp_forge.podman.containers import ContainerConfig, SecureContainerManager
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.allowlist import OperationValidator, SecurityError from mcp_forge.security.allowlist import OperationValidator, SecurityError
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -190,7 +190,7 @@ def test_volume_mounts_are_validated():
def test_start_container_on_session_container_succeeds(): def test_start_container_on_session_container_succeeds():
"""Test that starting a session container succeeds.""" """Test that starting a session container succeeds."""
from mcp_forge.podman.containers import SecureContainerManager from mcp_forge.podman.containers import SecureContainerManager
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.allowlist import OperationValidator from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -218,7 +218,7 @@ def test_start_container_on_session_container_succeeds():
def test_start_container_on_non_session_container_raises_security_error(): def test_start_container_on_non_session_container_raises_security_error():
"""Test that starting a non-session container raises SecurityError.""" """Test that starting a non-session container raises SecurityError."""
from mcp_forge.podman.containers import SecureContainerManager from mcp_forge.podman.containers import SecureContainerManager
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.allowlist import OperationValidator, SecurityError from mcp_forge.security.allowlist import OperationValidator, SecurityError
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -241,7 +241,7 @@ def test_start_container_on_non_session_container_raises_security_error():
def test_stop_container_works(): def test_stop_container_works():
"""Test that stopping a container works.""" """Test that stopping a container works."""
from mcp_forge.podman.containers import SecureContainerManager from mcp_forge.podman.containers import SecureContainerManager
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.allowlist import OperationValidator from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -269,7 +269,7 @@ def test_stop_container_works():
def test_remove_container_works(): def test_remove_container_works():
"""Test that removing a container works.""" """Test that removing a container works."""
from mcp_forge.podman.containers import SecureContainerManager from mcp_forge.podman.containers import SecureContainerManager
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.allowlist import OperationValidator from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -297,7 +297,7 @@ def test_remove_container_works():
def test_cleanup_old_containers(): def test_cleanup_old_containers():
"""Test cleanup of old containers.""" """Test cleanup of old containers."""
from mcp_forge.podman.containers import SecureContainerManager from mcp_forge.podman.containers import SecureContainerManager
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.allowlist import OperationValidator from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -340,7 +340,7 @@ def test_cleanup_old_containers():
def test_get_container_logs(): def test_get_container_logs():
"""Test getting container logs.""" """Test getting container logs."""
from mcp_forge.podman.containers import SecureContainerManager from mcp_forge.podman.containers import SecureContainerManager
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.allowlist import OperationValidator from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -371,7 +371,7 @@ def test_get_container_logs():
def test_wait_for_container(): def test_wait_for_container():
"""Test waiting for container to exit.""" """Test waiting for container to exit."""
from mcp_forge.podman.containers import SecureContainerManager from mcp_forge.podman.containers import SecureContainerManager
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.allowlist import OperationValidator from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -402,7 +402,7 @@ def test_wait_for_container():
def test_container_config_to_podman_params_includes_all_security_settings(): def test_container_config_to_podman_params_includes_all_security_settings():
"""Test that ContainerConfig.to_podman_params includes all required settings.""" """Test that ContainerConfig.to_podman_params includes all required settings."""
from mcp_forge.podman.containers import ContainerConfig from mcp_forge.podman.containers import ContainerConfig
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
config = ContainerConfig( config = ContainerConfig(
image="mcp-forge/python:3.11", image="mcp-forge/python:3.11",

View file

@ -13,7 +13,7 @@ from unittest.mock import Mock, MagicMock, patch
def test_connection_to_podman_socket_succeeds(tmp_path): def test_connection_to_podman_socket_succeeds(tmp_path):
"""Test that connection to Podman socket succeeds.""" """Test that connection to Podman socket succeeds."""
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.security.allowlist import OperationValidator from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -42,7 +42,7 @@ def test_connection_to_podman_socket_succeeds(tmp_path):
def test_connection_failure_raises_clear_error(tmp_path): def test_connection_failure_raises_clear_error(tmp_path):
"""Test that connection failure raises clear error.""" """Test that connection failure raises clear error."""
from mcp_forge.podman.client import PodmanClient, PodmanConnectionError from pod_executor.containers.client import PodmanClient, PodmanConnectionError
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.security.allowlist import OperationValidator from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -66,7 +66,7 @@ def test_connection_failure_raises_clear_error(tmp_path):
def test_socket_path_validation(tmp_path): def test_socket_path_validation(tmp_path):
"""Test that socket path is validated before connecting.""" """Test that socket path is validated before connecting."""
from mcp_forge.podman.client import PodmanClient, PodmanConnectionError from pod_executor.containers.client import PodmanClient, PodmanConnectionError
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.security.allowlist import OperationValidator from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -89,7 +89,7 @@ def test_socket_path_validation(tmp_path):
def test_socket_permissions_check(tmp_path): def test_socket_permissions_check(tmp_path):
"""Test that socket permissions are checked.""" """Test that socket permissions are checked."""
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.security.allowlist import OperationValidator from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -120,7 +120,7 @@ def test_socket_permissions_check(tmp_path):
def test_api_version_compatibility_check(tmp_path): def test_api_version_compatibility_check(tmp_path):
"""Test that API version is checked.""" """Test that API version is checked."""
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.security.allowlist import OperationValidator from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -154,7 +154,7 @@ def test_api_version_compatibility_check(tmp_path):
def test_ping_health_check(tmp_path): def test_ping_health_check(tmp_path):
"""Test that ping/health check works.""" """Test that ping/health check works."""
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.security.allowlist import OperationValidator from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -184,7 +184,7 @@ def test_ping_health_check(tmp_path):
def test_lazy_connection(tmp_path): def test_lazy_connection(tmp_path):
"""Test that connection is lazy (only connects when needed).""" """Test that connection is lazy (only connects when needed)."""
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.security.allowlist import OperationValidator from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -215,7 +215,7 @@ def test_lazy_connection(tmp_path):
def test_disconnect_cleanup(tmp_path): def test_disconnect_cleanup(tmp_path):
"""Test that disconnect cleans up properly.""" """Test that disconnect cleans up properly."""
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.security.allowlist import OperationValidator from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -246,7 +246,7 @@ def test_disconnect_cleanup(tmp_path):
def test_connection_error_includes_socket_path(tmp_path): def test_connection_error_includes_socket_path(tmp_path):
"""Test that connection errors include the socket path for debugging.""" """Test that connection errors include the socket path for debugging."""
from mcp_forge.podman.client import PodmanClient, PodmanConnectionError from pod_executor.containers.client import PodmanClient, PodmanConnectionError
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.security.allowlist import OperationValidator from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -270,7 +270,7 @@ def test_connection_error_includes_socket_path(tmp_path):
def test_client_property_auto_connects(tmp_path): def test_client_property_auto_connects(tmp_path):
"""Test that accessing client property auto-connects if not connected.""" """Test that accessing client property auto-connects if not connected."""
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.security.allowlist import OperationValidator from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig
@ -304,7 +304,7 @@ def test_client_property_auto_connects(tmp_path):
def test_validator_and_audit_logger_stored(tmp_path): def test_validator_and_audit_logger_stored(tmp_path):
"""Test that validator and audit logger are stored for later use.""" """Test that validator and audit logger are stored for later use."""
from mcp_forge.podman.client import PodmanClient from pod_executor.containers.client import PodmanClient
from mcp_forge.security.audit import AuditLogger from mcp_forge.security.audit import AuditLogger
from mcp_forge.security.allowlist import OperationValidator from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.config.schema import SecurityConfig from mcp_forge.config.schema import SecurityConfig

View file

@ -10,7 +10,7 @@ import pytest
def test_parse_memory_string_megabytes(): def test_parse_memory_string_megabytes():
"""Test parsing memory string with megabytes suffix.""" """Test parsing memory string with megabytes suffix."""
from mcp_forge.security.resource_limits import parse_memory_string from pod_executor.security.resource_limits import parse_memory_string
result = parse_memory_string("512m") result = parse_memory_string("512m")
assert result == 536870912 # 512 * 1024 * 1024 assert result == 536870912 # 512 * 1024 * 1024
@ -18,7 +18,7 @@ def test_parse_memory_string_megabytes():
def test_parse_memory_string_gigabytes(): def test_parse_memory_string_gigabytes():
"""Test parsing memory string with gigabytes suffix.""" """Test parsing memory string with gigabytes suffix."""
from mcp_forge.security.resource_limits import parse_memory_string from pod_executor.security.resource_limits import parse_memory_string
result = parse_memory_string("2g") result = parse_memory_string("2g")
assert result == 2147483648 # 2 * 1024 * 1024 * 1024 assert result == 2147483648 # 2 * 1024 * 1024 * 1024
@ -26,7 +26,7 @@ def test_parse_memory_string_gigabytes():
def test_parse_memory_string_kilobytes(): def test_parse_memory_string_kilobytes():
"""Test parsing memory string with kilobytes suffix.""" """Test parsing memory string with kilobytes suffix."""
from mcp_forge.security.resource_limits import parse_memory_string from pod_executor.security.resource_limits import parse_memory_string
result = parse_memory_string("1024k") result = parse_memory_string("1024k")
assert result == 1048576 # 1024 * 1024 assert result == 1048576 # 1024 * 1024
@ -34,7 +34,7 @@ def test_parse_memory_string_kilobytes():
def test_parse_memory_string_case_insensitive(): def test_parse_memory_string_case_insensitive():
"""Test that memory string parsing is case-insensitive.""" """Test that memory string parsing is case-insensitive."""
from mcp_forge.security.resource_limits import parse_memory_string from pod_executor.security.resource_limits import parse_memory_string
assert parse_memory_string("512M") == 536870912 assert parse_memory_string("512M") == 536870912
assert parse_memory_string("2G") == 2147483648 assert parse_memory_string("2G") == 2147483648
@ -43,7 +43,7 @@ def test_parse_memory_string_case_insensitive():
def test_parse_memory_string_invalid_format_raises_value_error(): def test_parse_memory_string_invalid_format_raises_value_error():
"""Test that invalid format raises ValueError.""" """Test that invalid format raises ValueError."""
from mcp_forge.security.resource_limits import parse_memory_string from pod_executor.security.resource_limits import parse_memory_string
with pytest.raises(ValueError) as exc_info: with pytest.raises(ValueError) as exc_info:
parse_memory_string("invalid") parse_memory_string("invalid")
@ -58,7 +58,7 @@ def test_parse_memory_string_invalid_format_raises_value_error():
def test_parse_memory_string_negative_value_raises_value_error(): def test_parse_memory_string_negative_value_raises_value_error():
"""Test that negative values raise ValueError.""" """Test that negative values raise ValueError."""
from mcp_forge.security.resource_limits import parse_memory_string from pod_executor.security.resource_limits import parse_memory_string
with pytest.raises(ValueError) as exc_info: with pytest.raises(ValueError) as exc_info:
parse_memory_string("-512m") parse_memory_string("-512m")
@ -67,7 +67,7 @@ def test_parse_memory_string_negative_value_raises_value_error():
def test_parse_memory_string_zero_value_raises_value_error(): def test_parse_memory_string_zero_value_raises_value_error():
"""Test that zero value raises ValueError.""" """Test that zero value raises ValueError."""
from mcp_forge.security.resource_limits import parse_memory_string from pod_executor.security.resource_limits import parse_memory_string
with pytest.raises(ValueError) as exc_info: with pytest.raises(ValueError) as exc_info:
parse_memory_string("0m") parse_memory_string("0m")
@ -76,7 +76,7 @@ def test_parse_memory_string_zero_value_raises_value_error():
def test_parse_cpu_quota_valid_value(): def test_parse_cpu_quota_valid_value():
"""Test that valid CPU quota values are accepted.""" """Test that valid CPU quota values are accepted."""
from mcp_forge.security.resource_limits import parse_cpu_quota from pod_executor.security.resource_limits import parse_cpu_quota
result = parse_cpu_quota(50000) result = parse_cpu_quota(50000)
assert result == 50000 assert result == 50000
@ -87,7 +87,7 @@ def test_parse_cpu_quota_valid_value():
def test_parse_cpu_quota_max_limit(): def test_parse_cpu_quota_max_limit():
"""Test that CPU quota has a reasonable maximum (10 cores).""" """Test that CPU quota has a reasonable maximum (10 cores)."""
from mcp_forge.security.resource_limits import parse_cpu_quota from pod_executor.security.resource_limits import parse_cpu_quota
# Should accept up to 1000000 (10 cores) # Should accept up to 1000000 (10 cores)
result = parse_cpu_quota(1000000) result = parse_cpu_quota(1000000)
@ -101,7 +101,7 @@ def test_parse_cpu_quota_max_limit():
def test_parse_cpu_quota_negative_raises_value_error(): def test_parse_cpu_quota_negative_raises_value_error():
"""Test that negative CPU quota raises ValueError.""" """Test that negative CPU quota raises ValueError."""
from mcp_forge.security.resource_limits import parse_cpu_quota from pod_executor.security.resource_limits import parse_cpu_quota
with pytest.raises(ValueError) as exc_info: with pytest.raises(ValueError) as exc_info:
parse_cpu_quota(-1) parse_cpu_quota(-1)
@ -110,7 +110,7 @@ def test_parse_cpu_quota_negative_raises_value_error():
def test_parse_cpu_quota_zero_raises_value_error(): def test_parse_cpu_quota_zero_raises_value_error():
"""Test that zero CPU quota raises ValueError.""" """Test that zero CPU quota raises ValueError."""
from mcp_forge.security.resource_limits import parse_cpu_quota from pod_executor.security.resource_limits import parse_cpu_quota
with pytest.raises(ValueError) as exc_info: with pytest.raises(ValueError) as exc_info:
parse_cpu_quota(0) parse_cpu_quota(0)
@ -119,7 +119,7 @@ def test_parse_cpu_quota_zero_raises_value_error():
def test_parse_storage_string_same_as_memory(): def test_parse_storage_string_same_as_memory():
"""Test that storage parsing works the same as memory parsing.""" """Test that storage parsing works the same as memory parsing."""
from mcp_forge.security.resource_limits import parse_storage_string from pod_executor.security.resource_limits import parse_storage_string
assert parse_storage_string("1g") == 1073741824 assert parse_storage_string("1g") == 1073741824
assert parse_storage_string("512m") == 536870912 assert parse_storage_string("512m") == 536870912
@ -128,7 +128,7 @@ def test_parse_storage_string_same_as_memory():
def test_resource_limits_class_initialization(): def test_resource_limits_class_initialization():
"""Test ResourceLimits class initializes correctly.""" """Test ResourceLimits class initializes correctly."""
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
limits = ResourceLimits( limits = ResourceLimits(
memory="512m", memory="512m",
@ -145,7 +145,7 @@ def test_resource_limits_class_initialization():
def test_resource_limits_validates_memory(): def test_resource_limits_validates_memory():
"""Test that ResourceLimits validates memory string.""" """Test that ResourceLimits validates memory string."""
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
with pytest.raises(ValueError): with pytest.raises(ValueError):
ResourceLimits( ResourceLimits(
@ -158,7 +158,7 @@ def test_resource_limits_validates_memory():
def test_resource_limits_validates_storage(): def test_resource_limits_validates_storage():
"""Test that ResourceLimits validates storage string.""" """Test that ResourceLimits validates storage string."""
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
with pytest.raises(ValueError): with pytest.raises(ValueError):
ResourceLimits( ResourceLimits(
@ -171,7 +171,7 @@ def test_resource_limits_validates_storage():
def test_resource_limits_validates_cpu_quota(): def test_resource_limits_validates_cpu_quota():
"""Test that ResourceLimits validates CPU quota.""" """Test that ResourceLimits validates CPU quota."""
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
with pytest.raises(ValueError): with pytest.raises(ValueError):
ResourceLimits( ResourceLimits(
@ -184,7 +184,7 @@ def test_resource_limits_validates_cpu_quota():
def test_resource_limits_to_podman_params(): def test_resource_limits_to_podman_params():
"""Test conversion to Podman container parameters.""" """Test conversion to Podman container parameters."""
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
limits = ResourceLimits( limits = ResourceLimits(
memory="512m", memory="512m",
@ -205,7 +205,7 @@ def test_resource_limits_to_podman_params():
def test_resource_limits_default_timeout(): def test_resource_limits_default_timeout():
"""Test that ResourceLimits has a default timeout.""" """Test that ResourceLimits has a default timeout."""
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
limits = ResourceLimits( limits = ResourceLimits(
memory="512m", memory="512m",
@ -218,7 +218,7 @@ def test_resource_limits_default_timeout():
def test_parse_memory_string_with_spaces(): def test_parse_memory_string_with_spaces():
"""Test parsing memory strings that have spaces.""" """Test parsing memory strings that have spaces."""
from mcp_forge.security.resource_limits import parse_memory_string from pod_executor.security.resource_limits import parse_memory_string
# Should handle spaces gracefully (strip them) # Should handle spaces gracefully (strip them)
result = parse_memory_string(" 512m ") result = parse_memory_string(" 512m ")
@ -227,7 +227,7 @@ def test_parse_memory_string_with_spaces():
def test_parse_memory_string_bytes_suffix(): def test_parse_memory_string_bytes_suffix():
"""Test parsing memory string with bytes suffix (no multiplier).""" """Test parsing memory string with bytes suffix (no multiplier)."""
from mcp_forge.security.resource_limits import parse_memory_string from pod_executor.security.resource_limits import parse_memory_string
# Just a number (bytes) - should this be supported? # Just a number (bytes) - should this be supported?
# Based on architecture, we support k, m, g suffixes # Based on architecture, we support k, m, g suffixes
@ -238,7 +238,7 @@ def test_parse_memory_string_bytes_suffix():
def test_resource_limits_storage_quota_in_podman_params(): def test_resource_limits_storage_quota_in_podman_params():
"""Test that storage limits are included in Podman params.""" """Test that storage limits are included in Podman params."""
from mcp_forge.security.resource_limits import ResourceLimits from pod_executor.security.resource_limits import ResourceLimits
limits = ResourceLimits( limits = ResourceLimits(
memory="512m", memory="512m",
@ -256,7 +256,7 @@ def test_resource_limits_storage_quota_in_podman_params():
def test_cpu_quota_explanation(): def test_cpu_quota_explanation():
"""Test that CPU quota values have clear meaning.""" """Test that CPU quota values have clear meaning."""
from mcp_forge.security.resource_limits import parse_cpu_quota from pod_executor.security.resource_limits import parse_cpu_quota
# 100000 = 100% of one CPU core # 100000 = 100% of one CPU core
# 50000 = 50% of one CPU core # 50000 = 50% of one CPU core
@ -269,7 +269,7 @@ def test_cpu_quota_explanation():
def test_parse_memory_with_decimal(): def test_parse_memory_with_decimal():
"""Test parsing memory strings with decimal values.""" """Test parsing memory strings with decimal values."""
from mcp_forge.security.resource_limits import parse_memory_string from pod_executor.security.resource_limits import parse_memory_string
# Should handle decimals # Should handle decimals
result = parse_memory_string("1.5g") result = parse_memory_string("1.5g")

View file

@ -6,7 +6,7 @@ from mcp.types import Tool, TextContent
import json import json
from mcp_forge.server.tools.execute_python import ExecutePythonTool from mcp_forge.server.tools.execute_python import ExecutePythonTool
from mcp_forge.execution.simple.backend import ExecutionResult from pod_executor.simple.executor import ExecutionResult
@pytest.fixture @pytest.fixture