fix: ruff

This commit is contained in:
youhuanghe
2026-03-22 07:35:38 +00:00
committed by WangCham
parent b64a23f9ac
commit 9e0fa375e9
6 changed files with 314 additions and 155 deletions
@@ -18,13 +18,12 @@ import shutil
import socket import socket
import subprocess import subprocess
from types import SimpleNamespace from types import SimpleNamespace
from unittest.mock import Mock
import pytest import pytest
from langbot.pkg.box.backend import BaseSandboxBackend from langbot.pkg.box.backend import BaseSandboxBackend
from langbot.pkg.box.client import ActionRPCBoxClient from langbot.pkg.box.client import ActionRPCBoxClient
from langbot.pkg.box.errors import BoxBackendUnavailableError, BoxRuntimeUnavailableError from langbot.pkg.box.errors import BoxBackendUnavailableError
from langbot.pkg.box.models import BoxExecutionStatus, BoxNetworkMode, BoxSpec from langbot.pkg.box.models import BoxExecutionStatus, BoxNetworkMode, BoxSpec
from langbot.pkg.box.runtime import BoxRuntime from langbot.pkg.box.runtime import BoxRuntime
from langbot.pkg.box.server import BoxServerHandler from langbot.pkg.box.server import BoxServerHandler
@@ -166,20 +165,24 @@ async def test_session_persists_files(box_client: ActionRPCBoxClient):
"""Write a file in one exec, read it back in a second exec on the same session.""" """Write a file in one exec, read it back in a second exec on the same session."""
sid = 'int-persist' sid = 'int-persist'
write_result = await box_client.execute(BoxSpec( write_result = await box_client.execute(
cmd='echo "hello from file" > /tmp/testfile.txt', BoxSpec(
session_id=sid, cmd='echo "hello from file" > /tmp/testfile.txt',
workdir='/tmp', session_id=sid,
image=_TEST_IMAGE, workdir='/tmp',
)) image=_TEST_IMAGE,
)
)
assert write_result.exit_code == 0 assert write_result.exit_code == 0
read_result = await box_client.execute(BoxSpec( read_result = await box_client.execute(
cmd='cat /tmp/testfile.txt', BoxSpec(
session_id=sid, cmd='cat /tmp/testfile.txt',
workdir='/tmp', session_id=sid,
image=_TEST_IMAGE, workdir='/tmp',
)) image=_TEST_IMAGE,
)
)
assert read_result.exit_code == 0 assert read_result.exit_code == 0
assert 'hello from file' in read_result.stdout assert 'hello from file' in read_result.stdout
@@ -20,7 +20,6 @@ import subprocess
import aiohttp import aiohttp
import pytest import pytest
from aiohttp import web
from aiohttp.test_utils import TestServer from aiohttp.test_utils import TestServer
from langbot.pkg.box.client import ActionRPCBoxClient from langbot.pkg.box.client import ActionRPCBoxClient
+1 -2
View File
@@ -1,13 +1,12 @@
from __future__ import annotations from __future__ import annotations
from types import SimpleNamespace from types import SimpleNamespace
from unittest.mock import AsyncMock, Mock, patch from unittest.mock import Mock
import pytest import pytest
from langbot_plugin.box.client import ActionRPCBoxClient from langbot_plugin.box.client import ActionRPCBoxClient
from langbot.pkg.box.connector import BoxRuntimeConnector from langbot.pkg.box.connector import BoxRuntimeConnector
from langbot_plugin.box.errors import BoxRuntimeUnavailableError
def make_app(logger: Mock, runtime_url: str = ''): def make_app(logger: Mock, runtime_url: str = ''):
+15 -7
View File
@@ -12,7 +12,12 @@ import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
from langbot_plugin.box.backend import BaseSandboxBackend from langbot_plugin.box.backend import BaseSandboxBackend
from langbot_plugin.box.client import BoxRuntimeClient, ActionRPCBoxClient from langbot_plugin.box.client import BoxRuntimeClient, ActionRPCBoxClient
from langbot_plugin.box.errors import BoxBackendUnavailableError, BoxSessionConflictError, BoxSessionNotFoundError, BoxValidationError from langbot_plugin.box.errors import (
BoxBackendUnavailableError,
BoxSessionConflictError,
BoxSessionNotFoundError,
BoxValidationError,
)
from langbot_plugin.box.models import ( from langbot_plugin.box.models import (
BUILTIN_PROFILES, BUILTIN_PROFILES,
BoxExecutionResult, BoxExecutionResult,
@@ -20,7 +25,6 @@ from langbot_plugin.box.models import (
BoxHostMountMode, BoxHostMountMode,
BoxManagedProcessSpec, BoxManagedProcessSpec,
BoxNetworkMode, BoxNetworkMode,
BoxProfile,
BoxSessionInfo, BoxSessionInfo,
BoxSpec, BoxSpec,
) )
@@ -70,7 +74,6 @@ class _InProcessBoxRuntimeClient(BoxRuntimeClient):
return self._runtime.get_session(session_id) return self._runtime.get_session(session_id)
class FakeBackend(BaseSandboxBackend): class FakeBackend(BaseSandboxBackend):
def __init__(self, logger: Mock, available: bool = True): def __init__(self, logger: Mock, available: bool = True):
super().__init__(logger) super().__init__(logger)
@@ -520,7 +523,9 @@ async def test_profile_locked_field_cannot_be_overridden():
logger = Mock() logger = Mock()
backend = FakeBackend(logger) backend = FakeBackend(logger)
runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300)
service = BoxService(make_app(logger, profile='offline_readonly'), client=_InProcessBoxRuntimeClient(logger, runtime)) service = BoxService(
make_app(logger, profile='offline_readonly'), client=_InProcessBoxRuntimeClient(logger, runtime)
)
await service.initialize() await service.initialize()
result = await service.execute_sandbox_tool( result = await service.execute_sandbox_tool(
@@ -631,7 +636,9 @@ async def test_profile_offline_readonly_locks_read_only_rootfs():
logger = Mock() logger = Mock()
backend = FakeBackend(logger) backend = FakeBackend(logger)
runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300)
service = BoxService(make_app(logger, profile='offline_readonly'), client=_InProcessBoxRuntimeClient(logger, runtime)) service = BoxService(
make_app(logger, profile='offline_readonly'), client=_InProcessBoxRuntimeClient(logger, runtime)
)
await service.initialize() await service.initialize()
await service.execute_sandbox_tool( await service.execute_sandbox_tool(
@@ -649,7 +656,9 @@ async def test_profile_network_extended_has_relaxed_limits():
logger = Mock() logger = Mock()
backend = FakeBackend(logger) backend = FakeBackend(logger)
runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300)
service = BoxService(make_app(logger, profile='network_extended'), client=_InProcessBoxRuntimeClient(logger, runtime)) service = BoxService(
make_app(logger, profile='network_extended'), client=_InProcessBoxRuntimeClient(logger, runtime)
)
await service.initialize() await service.initialize()
await service.execute_sandbox_tool({'cmd': 'echo hi'}, make_query(42)) await service.execute_sandbox_tool({'cmd': 'echo hi'}, make_query(42))
@@ -1028,4 +1037,3 @@ class TestBoxHostMountModeNone:
host_path_mode=BoxHostMountMode.READ_ONLY, host_path_mode=BoxHostMountMode.READ_ONLY,
workdir='/opt/custom', workdir='/opt/custom',
) )
@@ -43,14 +43,7 @@ class RecordingProvider:
function=provider_message.FunctionCall( function=provider_message.FunctionCall(
name='sandbox_exec', name='sandbox_exec',
arguments=json.dumps( arguments=json.dumps(
{ {'cmd': ("python - <<'PY'\nnums = [1, 2, 3, 4]\nprint(sum(nums) / len(nums))\nPY")}
'cmd': (
"python - <<'PY'\n"
"nums = [1, 2, 3, 4]\n"
'print(sum(nums) / len(nums))\n'
'PY'
)
}
), ),
), ),
) )
@@ -60,7 +53,7 @@ class RecordingProvider:
tool_result = json.loads(messages[-1].content) tool_result = json.loads(messages[-1].content)
return provider_message.Message( return provider_message.Message(
role='assistant', role='assistant',
content=f"The average is {tool_result['stdout']}.", content=f'The average is {tool_result["stdout"]}.',
) )
@@ -192,7 +185,7 @@ async def test_localagent_uses_sandbox_exec_for_exact_calculation():
tool_manager.execute_func_call.assert_awaited_once() tool_manager.execute_func_call.assert_awaited_once()
tool_name, tool_parameters = tool_manager.execute_func_call.await_args.args[:2] tool_name, tool_parameters = tool_manager.execute_func_call.await_args.args[:2]
assert tool_name == 'sandbox_exec' assert tool_name == 'sandbox_exec'
assert "print(sum(nums) / len(nums))" in tool_parameters['cmd'] assert 'print(sum(nums) / len(nums))' in tool_parameters['cmd']
first_request = provider.requests[0] first_request = provider.requests[0]
assert any( assert any(
@@ -3,6 +3,7 @@
Uses importlib.util.spec_from_file_location to load mcp.py directly without Uses importlib.util.spec_from_file_location to load mcp.py directly without
triggering the circular import chain through the app module. triggering the circular import chain through the app module.
""" """
from __future__ import annotations from __future__ import annotations
import importlib import importlib
@@ -20,6 +21,7 @@ import pytest
# Load mcp.py directly from file path, with stub dependencies # Load mcp.py directly from file path, with stub dependencies
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def _stub_module(fqn: str, attrs: dict | None = None, is_package: bool = False): def _stub_module(fqn: str, attrs: dict | None = None, is_package: bool = False):
"""Create or return a stub module and register it in sys.modules.""" """Create or return a stub module and register it in sys.modules."""
if fqn in sys.modules: if fqn in sys.modules:
@@ -59,9 +61,12 @@ def mcp_module():
_save_and_stub('langbot_plugin.api.entities.events.pipeline_query', {}) _save_and_stub('langbot_plugin.api.entities.events.pipeline_query', {})
_save_and_stub('langbot_plugin.api.entities.builtin', is_package=True) _save_and_stub('langbot_plugin.api.entities.builtin', is_package=True)
_save_and_stub('langbot_plugin.api.entities.builtin.resource', is_package=True) _save_and_stub('langbot_plugin.api.entities.builtin.resource', is_package=True)
_save_and_stub('langbot_plugin.api.entities.builtin.resource.tool', { _save_and_stub(
'LLMTool': type('LLMTool', (), {}), 'langbot_plugin.api.entities.builtin.resource.tool',
}) {
'LLMTool': type('LLMTool', (), {}),
},
)
_save_and_stub('langbot_plugin.api.entities.builtin.provider', is_package=True) _save_and_stub('langbot_plugin.api.entities.builtin.provider', is_package=True)
_save_and_stub('langbot_plugin.api.entities.builtin.provider.message', {}) _save_and_stub('langbot_plugin.api.entities.builtin.provider.message', {})
_save_and_stub('sqlalchemy', {'select': Mock()}) _save_and_stub('sqlalchemy', {'select': Mock()})
@@ -78,9 +83,12 @@ def mcp_module():
_save_and_stub('langbot.pkg', is_package=True) _save_and_stub('langbot.pkg', is_package=True)
_save_and_stub('langbot.pkg.provider', is_package=True) _save_and_stub('langbot.pkg.provider', is_package=True)
_save_and_stub('langbot.pkg.provider.tools', is_package=True) _save_and_stub('langbot.pkg.provider.tools', is_package=True)
_save_and_stub('langbot.pkg.provider.tools.loader', { _save_and_stub(
'ToolLoader': type('ToolLoader', (), {'__init__': lambda self, ap: None}), 'langbot.pkg.provider.tools.loader',
}) {
'ToolLoader': type('ToolLoader', (), {'__init__': lambda self, ap: None}),
},
)
_save_and_stub('langbot.pkg.provider.tools.loaders', is_package=True) _save_and_stub('langbot.pkg.provider.tools.loaders', is_package=True)
_save_and_stub('langbot.pkg.core', is_package=True) _save_and_stub('langbot.pkg.core', is_package=True)
_save_and_stub('langbot.pkg.core.app', {'Application': type('Application', (), {})}) _save_and_stub('langbot.pkg.core.app', {'Application': type('Application', (), {})})
@@ -90,9 +98,11 @@ def mcp_module():
# box models # box models
import enum as _enum import enum as _enum
class _BPS(str, _enum.Enum): class _BPS(str, _enum.Enum):
RUNNING = 'running' RUNNING = 'running'
EXITED = 'exited' EXITED = 'exited'
_save_and_stub('langbot_plugin.box', is_package=True) _save_and_stub('langbot_plugin.box', is_package=True)
_save_and_stub('langbot_plugin.box.models', {'BoxManagedProcessStatus': _BPS}) _save_and_stub('langbot_plugin.box.models', {'BoxManagedProcessStatus': _BPS})
@@ -100,8 +110,17 @@ def mcp_module():
mod_fqn = 'langbot.pkg.provider.tools.loaders.mcp' mod_fqn = 'langbot.pkg.provider.tools.loaders.mcp'
sys.modules.pop(mod_fqn, None) sys.modules.pop(mod_fqn, None)
mcp_path = os.path.join( mcp_path = os.path.join(
os.path.dirname(__file__), '..', '..', '..', os.path.dirname(__file__),
'src', 'langbot', 'pkg', 'provider', 'tools', 'loaders', 'mcp.py', '..',
'..',
'..',
'src',
'langbot',
'pkg',
'provider',
'tools',
'loaders',
'mcp.py',
) )
mcp_path = os.path.normpath(mcp_path) mcp_path = os.path.normpath(mcp_path)
spec = importlib.util.spec_from_file_location(mod_fqn, mcp_path) spec = importlib.util.spec_from_file_location(mod_fqn, mcp_path)
@@ -124,6 +143,7 @@ def mcp_module():
# Helpers # Helpers
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def _make_ap(): def _make_ap():
ap = Mock() ap = Mock()
ap.logger = Mock() ap.logger = Mock()
@@ -160,28 +180,32 @@ class TestMCPServerBoxConfig:
assert cfg.read_only_rootfs is None assert cfg.read_only_rootfs is None
def test_custom_values(self, mcp_module): def test_custom_values(self, mcp_module):
cfg = mcp_module.MCPServerBoxConfig.model_validate({ cfg = mcp_module.MCPServerBoxConfig.model_validate(
'image': 'node:20', {
'network': 'on', 'image': 'node:20',
'host_path': '/home/user/mcp', 'network': 'on',
'host_path_mode': 'rw', 'host_path': '/home/user/mcp',
'env': {'FOO': 'bar'}, 'host_path_mode': 'rw',
'startup_timeout_sec': 60, 'env': {'FOO': 'bar'},
'cpus': 2.0, 'startup_timeout_sec': 60,
'memory_mb': 1024, 'cpus': 2.0,
'pids_limit': 256, 'memory_mb': 1024,
'read_only_rootfs': False, 'pids_limit': 256,
}) 'read_only_rootfs': False,
}
)
assert cfg.image == 'node:20' assert cfg.image == 'node:20'
assert cfg.network == 'on' assert cfg.network == 'on'
assert cfg.cpus == 2.0 assert cfg.cpus == 2.0
assert cfg.memory_mb == 1024 assert cfg.memory_mb == 1024
def test_extra_fields_ignored(self, mcp_module): def test_extra_fields_ignored(self, mcp_module):
cfg = mcp_module.MCPServerBoxConfig.model_validate({ cfg = mcp_module.MCPServerBoxConfig.model_validate(
'image': 'node:20', {
'unknown_field': 'whatever', 'image': 'node:20',
}) 'unknown_field': 'whatever',
}
)
assert cfg.image == 'node:20' assert cfg.image == 'node:20'
assert not hasattr(cfg, 'unknown_field') assert not hasattr(cfg, 'unknown_field')
@@ -191,56 +215,98 @@ class TestMCPServerBoxConfig:
class TestRewritePath: class TestRewritePath:
def test_no_host_path_returns_unchanged(self, mcp_module): def test_no_host_path_returns_unchanged(self, mcp_module):
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'u1', 'mode': 'sse', mcp_module,
'command': 'python', 'args': [], {
}) 'name': 'test',
'uuid': 'u1',
'mode': 'sse',
'command': 'python',
'args': [],
},
)
assert s._rewrite_path('/some/path', None) == '/some/path' assert s._rewrite_path('/some/path', None) == '/some/path'
def test_empty_path_returns_empty(self, mcp_module): def test_empty_path_returns_empty(self, mcp_module):
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'u1', 'mode': 'sse', mcp_module,
'command': 'python', 'args': [], {
}) 'name': 'test',
'uuid': 'u1',
'mode': 'sse',
'command': 'python',
'args': [],
},
)
assert s._rewrite_path('', '/home/user/mcp') == '' assert s._rewrite_path('', '/home/user/mcp') == ''
def test_prefix_match_rewrites(self, mcp_module): def test_prefix_match_rewrites(self, mcp_module):
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'u1', 'mode': 'sse', mcp_module,
'command': 'python', 'args': [], {
}) 'name': 'test',
'uuid': 'u1',
'mode': 'sse',
'command': 'python',
'args': [],
},
)
result = s._rewrite_path('/home/user/mcp/server.py', '/home/user/mcp') result = s._rewrite_path('/home/user/mcp/server.py', '/home/user/mcp')
assert result == '/workspace/server.py' assert result == '/workspace/server.py'
def test_exact_match_rewrites_to_workspace(self, mcp_module): def test_exact_match_rewrites_to_workspace(self, mcp_module):
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'u1', 'mode': 'sse', mcp_module,
'command': 'python', 'args': [], {
}) 'name': 'test',
'uuid': 'u1',
'mode': 'sse',
'command': 'python',
'args': [],
},
)
result = s._rewrite_path('/home/user/mcp', '/home/user/mcp') result = s._rewrite_path('/home/user/mcp', '/home/user/mcp')
assert result == '/workspace' assert result == '/workspace'
def test_non_matching_path_unchanged(self, mcp_module): def test_non_matching_path_unchanged(self, mcp_module):
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'u1', 'mode': 'sse', mcp_module,
'command': 'python', 'args': [], {
}) 'name': 'test',
'uuid': 'u1',
'mode': 'sse',
'command': 'python',
'args': [],
},
)
result = s._rewrite_path('/opt/other/server.py', '/home/user/mcp') result = s._rewrite_path('/opt/other/server.py', '/home/user/mcp')
assert result == '/opt/other/server.py' assert result == '/opt/other/server.py'
def test_similar_prefix_not_rewritten(self, mcp_module): def test_similar_prefix_not_rewritten(self, mcp_module):
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'u1', 'mode': 'sse', mcp_module,
'command': 'python', 'args': [], {
}) 'name': 'test',
'uuid': 'u1',
'mode': 'sse',
'command': 'python',
'args': [],
},
)
result = s._rewrite_path('/home/user/mcp-other/file.py', '/home/user/mcp') result = s._rewrite_path('/home/user/mcp-other/file.py', '/home/user/mcp')
assert result == '/home/user/mcp-other/file.py' assert result == '/home/user/mcp-other/file.py'
def test_nested_subpath_rewrites(self, mcp_module): def test_nested_subpath_rewrites(self, mcp_module):
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'u1', 'mode': 'sse', mcp_module,
'command': 'python', 'args': [], {
}) 'name': 'test',
'uuid': 'u1',
'mode': 'sse',
'command': 'python',
'args': [],
},
)
result = s._rewrite_path('/home/user/mcp/src/lib/main.py', '/home/user/mcp') result = s._rewrite_path('/home/user/mcp/src/lib/main.py', '/home/user/mcp')
assert result == '/workspace/src/lib/main.py' assert result == '/workspace/src/lib/main.py'
@@ -250,25 +316,43 @@ class TestRewritePath:
class TestInferHostPath: class TestInferHostPath:
def test_no_absolute_paths_returns_none(self, mcp_module): def test_no_absolute_paths_returns_none(self, mcp_module):
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'u1', 'mode': 'sse', mcp_module,
'command': 'python', 'args': ['server.py'], {
}) 'name': 'test',
'uuid': 'u1',
'mode': 'sse',
'command': 'python',
'args': ['server.py'],
},
)
assert s._infer_host_path() is None assert s._infer_host_path() is None
def test_nonexistent_path_returns_none(self, mcp_module): def test_nonexistent_path_returns_none(self, mcp_module):
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'u1', 'mode': 'sse', mcp_module,
'command': '/nonexistent/path/to/python', 'args': [], {
}) 'name': 'test',
'uuid': 'u1',
'mode': 'sse',
'command': '/nonexistent/path/to/python',
'args': [],
},
)
assert s._infer_host_path() is None assert s._infer_host_path() is None
def test_existing_absolute_path_infers_directory(self, mcp_module): def test_existing_absolute_path_infers_directory(self, mcp_module):
with tempfile.NamedTemporaryFile(suffix='.py') as f: with tempfile.NamedTemporaryFile(suffix='.py') as f:
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'u1', 'mode': 'sse', mcp_module,
'command': 'python', 'args': [f.name], {
}) 'name': 'test',
'uuid': 'u1',
'mode': 'sse',
'command': 'python',
'args': [f.name],
},
)
result = s._infer_host_path() result = s._infer_host_path()
assert result is not None assert result is not None
assert result == os.path.dirname(os.path.realpath(f.name)) assert result == os.path.dirname(os.path.realpath(f.name))
@@ -279,10 +363,16 @@ class TestInferHostPath:
class TestBuildBoxSessionPayload: class TestBuildBoxSessionPayload:
def test_minimal_config(self, mcp_module): def test_minimal_config(self, mcp_module):
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'u1', 'mode': 'sse', mcp_module,
'command': 'python', 'args': [], {
}) 'name': 'test',
'uuid': 'u1',
'mode': 'sse',
'command': 'python',
'args': [],
},
)
payload = s._build_box_session_payload('session-123') payload = s._build_box_session_payload('session-123')
assert payload['session_id'] == 'session-123' assert payload['session_id'] == 'session-123'
assert payload['workdir'] == '/workspace' assert payload['workdir'] == '/workspace'
@@ -290,21 +380,33 @@ class TestBuildBoxSessionPayload:
assert 'host_path' not in payload assert 'host_path' not in payload
def test_with_host_path(self, mcp_module): def test_with_host_path(self, mcp_module):
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'u1', 'mode': 'sse', mcp_module,
'command': 'python', 'args': [], {
'box': {'host_path': '/home/user/mcp', 'host_path_mode': 'ro'}, 'name': 'test',
}) 'uuid': 'u1',
'mode': 'sse',
'command': 'python',
'args': [],
'box': {'host_path': '/home/user/mcp', 'host_path_mode': 'ro'},
},
)
payload = s._build_box_session_payload('session-123') payload = s._build_box_session_payload('session-123')
assert payload['host_path'] == '/home/user/mcp' assert payload['host_path'] == '/home/user/mcp'
assert payload['host_path_mode'] == 'ro' assert payload['host_path_mode'] == 'ro'
def test_optional_fields_included_when_set(self, mcp_module): def test_optional_fields_included_when_set(self, mcp_module):
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'u1', 'mode': 'sse', mcp_module,
'command': 'python', 'args': [], {
'box': {'image': 'node:20', 'cpus': 2.0, 'memory_mb': 1024, 'pids_limit': 256}, 'name': 'test',
}) 'uuid': 'u1',
'mode': 'sse',
'command': 'python',
'args': [],
'box': {'image': 'node:20', 'cpus': 2.0, 'memory_mb': 1024, 'pids_limit': 256},
},
)
payload = s._build_box_session_payload('session-123') payload = s._build_box_session_payload('session-123')
assert payload['image'] == 'node:20' assert payload['image'] == 'node:20'
assert payload['cpus'] == 2.0 assert payload['cpus'] == 2.0
@@ -312,10 +414,16 @@ class TestBuildBoxSessionPayload:
assert payload['pids_limit'] == 256 assert payload['pids_limit'] == 256
def test_none_fields_excluded(self, mcp_module): def test_none_fields_excluded(self, mcp_module):
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'u1', 'mode': 'sse', mcp_module,
'command': 'python', 'args': [], {
}) 'name': 'test',
'uuid': 'u1',
'mode': 'sse',
'command': 'python',
'args': [],
},
)
payload = s._build_box_session_payload('session-123') payload = s._build_box_session_payload('session-123')
assert 'image' not in payload assert 'image' not in payload
assert 'cpus' not in payload assert 'cpus' not in payload
@@ -326,10 +434,17 @@ class TestBuildBoxSessionPayload:
class TestBuildBoxProcessPayload: class TestBuildBoxProcessPayload:
def test_basic_payload(self, mcp_module): def test_basic_payload(self, mcp_module):
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'u1', 'mode': 'sse', mcp_module,
'command': 'python', 'args': ['server.py'], 'env': {'KEY': 'val'}, {
}) 'name': 'test',
'uuid': 'u1',
'mode': 'sse',
'command': 'python',
'args': ['server.py'],
'env': {'KEY': 'val'},
},
)
payload = s._build_box_process_payload() payload = s._build_box_process_payload()
assert payload['command'] == 'python' assert payload['command'] == 'python'
assert payload['args'] == ['server.py'] assert payload['args'] == ['server.py']
@@ -337,26 +452,36 @@ class TestBuildBoxProcessPayload:
assert payload['cwd'] == '/workspace' assert payload['cwd'] == '/workspace'
def test_path_rewriting_applied(self, mcp_module): def test_path_rewriting_applied(self, mcp_module):
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'u1', 'mode': 'sse', mcp_module,
'command': '/home/user/mcp/venv/bin/python', {
'args': ['/home/user/mcp/server.py', '--config', '/home/user/mcp/config.json'], 'name': 'test',
'env': {}, 'uuid': 'u1',
'box': {'host_path': '/home/user/mcp'}, 'mode': 'sse',
}) 'command': '/home/user/mcp/venv/bin/python',
'args': ['/home/user/mcp/server.py', '--config', '/home/user/mcp/config.json'],
'env': {},
'box': {'host_path': '/home/user/mcp'},
},
)
payload = s._build_box_process_payload() payload = s._build_box_process_payload()
# venv python is replaced with plain 'python' (deps installed in-container) # venv python is replaced with plain 'python' (deps installed in-container)
assert payload['command'] == 'python' assert payload['command'] == 'python'
assert payload['args'] == ['/workspace/server.py', '--config', '/workspace/config.json'] assert payload['args'] == ['/workspace/server.py', '--config', '/workspace/config.json']
def test_non_matching_args_not_rewritten(self, mcp_module): def test_non_matching_args_not_rewritten(self, mcp_module):
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'u1', 'mode': 'sse', mcp_module,
'command': 'python', {
'args': ['/opt/other/server.py', '--flag'], 'name': 'test',
'env': {}, 'uuid': 'u1',
'box': {'host_path': '/home/user/mcp'}, 'mode': 'sse',
}) 'command': 'python',
'args': ['/opt/other/server.py', '--flag'],
'env': {},
'box': {'host_path': '/home/user/mcp'},
},
)
payload = s._build_box_process_payload() payload = s._build_box_process_payload()
assert payload['command'] == 'python' assert payload['command'] == 'python'
assert payload['args'] == ['/opt/other/server.py', '--flag'] assert payload['args'] == ['/opt/other/server.py', '--flag']
@@ -367,10 +492,16 @@ class TestBuildBoxProcessPayload:
class TestGetRuntimeInfoDict: class TestGetRuntimeInfoDict:
def test_non_stdio_session(self, mcp_module): def test_non_stdio_session(self, mcp_module):
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'test-uuid', 'mode': 'sse', mcp_module,
'command': 'python', 'args': [], {
}) 'name': 'test',
'uuid': 'test-uuid',
'mode': 'sse',
'command': 'python',
'args': [],
},
)
info = s.get_runtime_info_dict() info = s.get_runtime_info_dict()
assert info['status'] == 'connecting' assert info['status'] == 'connecting'
assert 'box_session_id' not in info assert 'box_session_id' not in info
@@ -378,10 +509,17 @@ class TestGetRuntimeInfoDict:
def test_stdio_session_includes_box_info(self, mcp_module): def test_stdio_session_includes_box_info(self, mcp_module):
ap = _make_ap() ap = _make_ap()
ap.box_service.available = True ap.box_service.available = True
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'test-uuid', 'mode': 'stdio', mcp_module,
'command': 'python', 'args': [], {
}, ap=ap) 'name': 'test',
'uuid': 'test-uuid',
'mode': 'stdio',
'command': 'python',
'args': [],
},
ap=ap,
)
info = s.get_runtime_info_dict() info = s.get_runtime_info_dict()
assert info['box_session_id'] == 'mcp-test-uuid' assert info['box_session_id'] == 'mcp-test-uuid'
assert info['box_enabled'] is True assert info['box_enabled'] is True
@@ -389,10 +527,17 @@ class TestGetRuntimeInfoDict:
def test_stdio_session_without_box_runtime(self, mcp_module): def test_stdio_session_without_box_runtime(self, mcp_module):
ap = _make_ap() ap = _make_ap()
ap.box_service.available = False ap.box_service.available = False
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'test-uuid', 'mode': 'stdio', mcp_module,
'command': 'python', 'args': [], {
}, ap=ap) 'name': 'test',
'uuid': 'test-uuid',
'mode': 'stdio',
'command': 'python',
'args': [],
},
ap=ap,
)
info = s.get_runtime_info_dict() info = s.get_runtime_info_dict()
assert 'box_session_id' not in info assert 'box_session_id' not in info
@@ -402,20 +547,32 @@ class TestGetRuntimeInfoDict:
class TestBoxConfigParsing: class TestBoxConfigParsing:
def test_box_config_parsed_from_server_config(self, mcp_module): def test_box_config_parsed_from_server_config(self, mcp_module):
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'u1', 'mode': 'sse', mcp_module,
'command': 'python', 'args': [], {
'box': {'image': 'node:20', 'host_path': '/home/user/mcp'}, 'name': 'test',
}) 'uuid': 'u1',
'mode': 'sse',
'command': 'python',
'args': [],
'box': {'image': 'node:20', 'host_path': '/home/user/mcp'},
},
)
assert isinstance(s.box_config, mcp_module.MCPServerBoxConfig) assert isinstance(s.box_config, mcp_module.MCPServerBoxConfig)
assert s.box_config.image == 'node:20' assert s.box_config.image == 'node:20'
assert s.box_config.host_path == '/home/user/mcp' assert s.box_config.host_path == '/home/user/mcp'
def test_missing_box_key_uses_defaults(self, mcp_module): def test_missing_box_key_uses_defaults(self, mcp_module):
s = _make_session(mcp_module, { s = _make_session(
'name': 'test', 'uuid': 'u1', 'mode': 'sse', mcp_module,
'command': 'python', 'args': [], {
}) 'name': 'test',
'uuid': 'u1',
'mode': 'sse',
'command': 'python',
'args': [],
},
)
assert isinstance(s.box_config, mcp_module.MCPServerBoxConfig) assert isinstance(s.box_config, mcp_module.MCPServerBoxConfig)
assert s.box_config.image is None assert s.box_config.image is None
assert s.box_config.host_path_mode == 'ro' assert s.box_config.host_path_mode == 'ro'