twentyhq/twenty · error · RuntimeError

Twenty MCP bridge not available. Missing requests library or

Error message

Twenty MCP bridge not available. Missing requests library or credentials.

What it means

Raised by TwentyMCP.call_tool (the Python helper injected into code-interpreter sandboxes) when self._available is False. Availability is computed once in __init__ as the AND of three conditions: the `requests` Python package imported successfully, TWENTY_SERVER_URL is set and non-empty, and TWENTY_API_TOKEN is set and non-empty. The message names the two credential/env causes; the requests cause is implicit.

Source

Thrown at packages/twenty-server/src/engine/core-modules/tool/tools/code-interpreter-tool/twenty-mcp-helper.const.ts:71

        Catalog tools (find_many_companies, create_one_person, …) are routed
        through execute_tool. MCP-native tools are called directly.
        The execute_tool envelope { success, message, result } is
        unwrapped so you always get the inner tool's result back.

        Args:
            name: Tool name (catalog or MCP-native)
            arguments: Tool arguments as a dictionary

        Returns:
            Tool result as parsed JSON

        Example:
            companies = twenty.call_tool('find_many_companies', {'limit': 5})
            # companies == {'records': [...], 'count': '5'}
        """
        if not self._available:
            raise RuntimeError('Twenty MCP bridge not available. Missing requests library or credentials.')

        if name in self._MCP_NATIVE_TOOLS:
            return self._raw_mcp_call(name, arguments)

        wrapped = self._raw_mcp_call('execute_tool', {
            'toolName': name,
            'arguments': arguments or {},
        })
        # execute_tool returns one of:
        #   success: { success: True,  message, result: {...} }
        #   failure: { success: False, message, error }
        # Raise on failure, unwrap on success, pass through unknown shapes.
        if isinstance(wrapped, dict):
            if wrapped.get('success') is False:
                raise Exception(wrapped.get('error') or wrapped.get('message') or
                                f"execute_tool failed for {name}")
            if 'result' in wrapped:
                return wrapped['result']

View on GitHub (pinned to 1f5dd2bbd2)

Solutions

  1. Set both env vars in the sandbox process: TWENTY_SERVER_URL (e.g. http://localhost:3000) and TWENTY_API_TOKEN (a valid API key).
  2. Ensure the `requests` package is installed in the sandbox Python environment (`pip install requests`).
  3. Guard with `if twenty.available:` before calling tools, and log a clear message when the bridge is off.
  4. Verify the server actually exposes /mcp and the token has scope to call tools before relying on the bridge.

Example fix

# before
companies = twenty.call_tool('find_many_companies', {'limit': 5})   # raises if bridge unconfigured
# after — check availability first, degrade gracefully
if not twenty.available:
    raise RuntimeError('Twenty MCP bridge unavailable: set TWENTY_SERVER_URL and TWENTY_API_TOKEN and install requests')
companies = twenty.call_tool('find_many_companies', {'limit': 5})
Defensive patterns

Strategy: type-guard

Validate before calling

# Run inside the sandbox before any tool call.
if not twenty.available:
    missing = []
    try:
        import requests  # noqa: F401
    except ImportError:
        missing.append('requests package')
    if not os.environ.get('TWENTY_SERVER_URL'):
        missing.append('TWENTY_SERVER_URL')
    if not os.environ.get('TWENTY_API_TOKEN'):
        missing.append('TWENTY_API_TOKEN')
    raise RuntimeError(f'Twenty MCP bridge unavailable; missing: {", ".join(missing)}')

Type guard

# The helper exposes `available` as the canonical guard.
def twenty_ready() -> bool:
    return bool(getattr(twenty, 'available', False))

Try / catch

try:
    result = twenty.call_tool('find_many_companies', {'limit': 5})
except RuntimeError as e:
    if 'not available' in str(e):
        # degrade gracefully: skip the tool call, return empty, log the missing config
        result = {'records': [], 'count': '0', '_bridge_unavailable': True}
    else:
        raise

Prevention

When it happens

Trigger: Running sandboxed code that calls twenty.call_tool(...) when the sandbox environment was not provisioned with TWENTY_SERVER_URL and/or TWENTY_API_TOKEN, or when the `requests` package is not installed in the sandbox's Python. Any call_tool, bulk_upsert, or lookup_by on the global `twenty` instance will trip this on the first invocation.

Common situations: Local/dev runs of the code-interpreter tool without the API token configured; a deployment where the sandbox sidecar does not inherit the server's env vars; a sandbox image stripped of the requests package to reduce size; a token rotation that left the sandbox with an empty value.

Related errors


AI-assisted analysis of twentyhq/twenty@1f5dd2bbd2 (2026-08-12). Data as JSON: /api/errors/8e4213979b66d976. Report an issue: GitHub.