mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-09 20:50:58 +00:00
feat(tenancy): establish cloud isolation foundations
This commit is contained in:
@@ -32,13 +32,8 @@ from ....workspace.errors import WorkspaceError, WorkspaceInvariantError
|
||||
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 as MCPServerBoxConfig, # noqa: F401 - public re-export
|
||||
MCPSessionErrorPhase,
|
||||
_ColdStartRetry,
|
||||
_get_default_memory_mb,
|
||||
) # noqa: F401
|
||||
from .mcp_stdio import BoxStdioSessionRuntime, MCPServerBoxConfig, MCPSessionErrorPhase, _ColdStartRetry # noqa: F401
|
||||
from .mcp_policy import require_stdio_mcp_enabled, stdio_mcp_enabled
|
||||
|
||||
# Synthesized LLM tools for MCP resources (not from server tools/list).
|
||||
# Dispatched in MCPLoader.invoke_tool; placeholder func on LLMTool is never used.
|
||||
@@ -367,6 +362,11 @@ class RuntimeMCPSession:
|
||||
)
|
||||
|
||||
async def _init_stdio_python_server(self):
|
||||
# Final transport gate: this must run before both the Box branch and
|
||||
# the backwards-compatible host-stdio branch. Service/UI checks are
|
||||
# usability guards; this is the execution security boundary.
|
||||
require_stdio_mcp_enabled(self.ap, self.server_config)
|
||||
|
||||
if self._uses_box_stdio():
|
||||
await self._box_stdio_runtime.initialize()
|
||||
return
|
||||
@@ -1502,6 +1502,12 @@ class MCPLoader(loader.ToolLoader):
|
||||
|
||||
for server in servers:
|
||||
config = self.ap.persistence_mgr.serialize_model(persistence_mcp.MCPServer, server)
|
||||
if config.get('mode') == 'stdio' and not stdio_mcp_enabled(self.ap):
|
||||
self.ap.logger.info(
|
||||
f'Skipping disabled stdio MCP server {server.uuid}; '
|
||||
'the persisted configuration is retained but no process is launched'
|
||||
)
|
||||
continue
|
||||
try:
|
||||
binding = await self.ap.workspace_service.get_execution_binding(server.workspace_uuid)
|
||||
execution_context = ExecutionContext(
|
||||
@@ -1577,6 +1583,7 @@ class MCPLoader(loader.ToolLoader):
|
||||
"""
|
||||
execution_context = await self._assert_execution_active(context)
|
||||
server_config = dict(server_config)
|
||||
require_stdio_mcp_enabled(self.ap, server_config)
|
||||
configured_workspace = str(server_config.get('workspace_uuid') or '').strip()
|
||||
if configured_workspace and configured_workspace != execution_context.workspace_uuid:
|
||||
raise ValueError('MCP server configuration belongs to another Workspace')
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
MCP_STDIO_DISABLED_CODE = 'mcp_stdio_disabled'
|
||||
MCP_STDIO_DISABLED_MESSAGE = 'Stdio MCP is disabled by instance policy'
|
||||
|
||||
|
||||
class MCPStdioDisabledError(RuntimeError):
|
||||
"""Raised when an instance-level policy refuses stdio MCP execution."""
|
||||
|
||||
code = MCP_STDIO_DISABLED_CODE
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(MCP_STDIO_DISABLED_MESSAGE)
|
||||
|
||||
|
||||
def stdio_mcp_enabled(ap: Any) -> bool:
|
||||
"""Return the independent instance-level stdio MCP feature gate.
|
||||
|
||||
The open-source default remains enabled for backwards compatibility. A
|
||||
deployment can disable it with ``mcp.stdio.enabled: false`` (or
|
||||
``MCP__STDIO__ENABLED=false``). Invalid values fail closed instead of
|
||||
accidentally enabling local process execution.
|
||||
"""
|
||||
|
||||
instance_config = getattr(ap, 'instance_config', None)
|
||||
config = getattr(instance_config, 'data', None)
|
||||
if not isinstance(config, dict):
|
||||
return False
|
||||
mcp_config = config.get('mcp', {})
|
||||
if not isinstance(mcp_config, dict):
|
||||
return False
|
||||
stdio_config = mcp_config.get('stdio', {})
|
||||
if not isinstance(stdio_config, dict):
|
||||
return False
|
||||
enabled = stdio_config.get('enabled', True)
|
||||
return enabled if isinstance(enabled, bool) else False
|
||||
|
||||
|
||||
def is_stdio_server(server_config: dict[str, Any] | None) -> bool:
|
||||
return isinstance(server_config, dict) and str(server_config.get('mode') or '').strip().lower() == 'stdio'
|
||||
|
||||
|
||||
def require_stdio_mcp_enabled(ap: Any, server_config: dict[str, Any] | None) -> None:
|
||||
"""Fail closed for a stdio server before choosing Box or host transport."""
|
||||
|
||||
if is_stdio_server(server_config) and not stdio_mcp_enabled(ap):
|
||||
raise MCPStdioDisabledError()
|
||||
Reference in New Issue
Block a user