mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-17 03:04:20 +00:00
fix(box): restore sandbox config and shared mcp runtime
This commit is contained in:
@@ -20,7 +20,7 @@ from ....core import app
|
||||
import langbot_plugin.api.entities.builtin.resource.tool as resource_tool
|
||||
import langbot_plugin.api.entities.builtin.provider.message as provider_message
|
||||
from ....entity.persistence import mcp as persistence_mcp
|
||||
from .mcp_stdio import BoxStdioSessionRuntime, MCPServerBoxConfig, MCPSessionErrorPhase
|
||||
from .mcp_stdio import BoxStdioSessionRuntime, MCPServerBoxConfig as MCPServerBoxConfig, MCPSessionErrorPhase
|
||||
|
||||
|
||||
class MCPSessionStatus(enum.Enum):
|
||||
@@ -349,7 +349,7 @@ class RuntimeMCPSession:
|
||||
return self._box_stdio_runtime.uses_box_stdio()
|
||||
|
||||
def _build_box_session_id(self) -> str:
|
||||
return f'mcp-{self.server_uuid}'
|
||||
return 'mcp-shared'
|
||||
|
||||
def _rewrite_path(self, path: str, host_path: str | None) -> str:
|
||||
return self._box_stdio_runtime.rewrite_path(path, host_path)
|
||||
|
||||
@@ -81,8 +81,14 @@ class BoxStdioSessionRuntime:
|
||||
cpus=self.config.cpus,
|
||||
memory_mb=self.config.memory_mb,
|
||||
pids_limit=self.config.pids_limit,
|
||||
persistent=True,
|
||||
)
|
||||
|
||||
@property
|
||||
def process_id(self) -> str:
|
||||
"""Each MCP server gets a unique process_id within the shared session."""
|
||||
return self.owner.server_uuid
|
||||
|
||||
def uses_box_stdio(self) -> bool:
|
||||
if self.server_config.get('mode') != 'stdio':
|
||||
return False
|
||||
@@ -104,7 +110,9 @@ class BoxStdioSessionRuntime:
|
||||
if host_path:
|
||||
install_cmd = self.owner._detect_install_command(host_path)
|
||||
if install_cmd:
|
||||
self.ap.logger.info(f'MCP server {self.server_name}: installing dependencies in Box with: {install_cmd}')
|
||||
self.ap.logger.info(
|
||||
f'MCP server {self.server_name}: installing dependencies in Box with: {install_cmd}'
|
||||
)
|
||||
try:
|
||||
result = await workspace.execute_raw(
|
||||
install_cmd,
|
||||
@@ -122,6 +130,7 @@ class BoxStdioSessionRuntime:
|
||||
await workspace.start_managed_process(
|
||||
self.server_config['command'],
|
||||
self.server_config.get('args', []),
|
||||
process_id=self.process_id,
|
||||
env=self.server_config.get('env', {}),
|
||||
)
|
||||
except Exception:
|
||||
@@ -129,10 +138,12 @@ class BoxStdioSessionRuntime:
|
||||
raise
|
||||
|
||||
try:
|
||||
websocket_url = workspace.get_managed_process_websocket_url()
|
||||
websocket_url = workspace.get_managed_process_websocket_url(self.process_id)
|
||||
transport = await self.owner.exit_stack.enter_async_context(websocket_client(websocket_url))
|
||||
read_stream, write_stream = transport
|
||||
self.owner.session = await self.owner.exit_stack.enter_async_context(ClientSession(read_stream, write_stream))
|
||||
self.owner.session = await self.owner.exit_stack.enter_async_context(
|
||||
ClientSession(read_stream, write_stream)
|
||||
)
|
||||
except Exception:
|
||||
self.owner.error_phase = MCPSessionErrorPhase.RELAY_CONNECT
|
||||
raise
|
||||
@@ -150,7 +161,7 @@ class BoxStdioSessionRuntime:
|
||||
consecutive_errors = 0
|
||||
while not self.owner._shutdown_event.is_set():
|
||||
try:
|
||||
info = await workspace.get_managed_process()
|
||||
info = await workspace.get_managed_process(self.process_id)
|
||||
if isinstance(info, dict):
|
||||
status = info.get('status', '')
|
||||
else:
|
||||
@@ -173,10 +184,13 @@ class BoxStdioSessionRuntime:
|
||||
if not self.uses_box_stdio():
|
||||
return
|
||||
|
||||
try:
|
||||
await self._build_workspace().cleanup()
|
||||
except Exception as exc:
|
||||
self.ap.logger.warning(f'Failed to cleanup Box session for MCP server {self.server_name}: {exc}')
|
||||
# In the shared-session model, we do not delete the session itself.
|
||||
# The managed process exits independently; deleting the session would
|
||||
# kill other MCP servers sharing the same container.
|
||||
self.ap.logger.info(
|
||||
f'MCP server {self.server_name}: process_id={self.process_id} cleanup complete '
|
||||
f'(shared session {self.owner._build_box_session_id()} kept alive)'
|
||||
)
|
||||
|
||||
def rewrite_path(self, path: str, host_path: str | None) -> str:
|
||||
return rewrite_mounted_path(path, host_path)
|
||||
|
||||
@@ -121,9 +121,7 @@ class NativeToolLoader(loader.ToolLoader):
|
||||
)
|
||||
|
||||
box_service = self.ap.box_service
|
||||
host_root = (
|
||||
selected_skill.get('package_root') if selected_skill is not None else box_service.default_host_workspace
|
||||
)
|
||||
host_root = selected_skill.get('package_root') if selected_skill is not None else box_service.default_workspace
|
||||
if not host_root:
|
||||
raise ValueError('No host workspace configured for file operations.')
|
||||
|
||||
|
||||
@@ -183,9 +183,9 @@ class SkillAuthoringToolLoader(loader.ToolLoader):
|
||||
|
||||
def _resolve_workspace_directory(self, sandbox_path: str) -> str:
|
||||
box_service = getattr(self.ap, 'box_service', None)
|
||||
workspace_root = getattr(box_service, 'default_host_workspace', None)
|
||||
workspace_root = getattr(box_service, 'default_workspace', None)
|
||||
if not workspace_root:
|
||||
raise ValueError('No default host workspace configured for importing skills')
|
||||
raise ValueError('No default workspace configured for importing skills')
|
||||
|
||||
normalized_path = str(sandbox_path).strip() or '/workspace'
|
||||
if not normalized_path.startswith('/workspace'):
|
||||
|
||||
Reference in New Issue
Block a user