twentyhq/twenty · error · Exception

MCP Error: {result['error'].get('message', 'Unknown error')}

Error message

MCP Error: {result['error'].get('message', 'Unknown error')}

What it means

Raised by TwentyMCP._raw_mcp_call when the raw JSON-RPC response from POST {TWENTY_SERVER_URL}/mcp contains a top-level `error` object. This is the JSON-RPC 2.0 error channel (distinct from execute_tool's success envelope) and fires for protocol/transport-level failures: method not found, invalid params, parse errors, or server-internal errors at the MCP layer. The message uses error.message, defaulting to 'Unknown error'.

Source

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

    def _raw_mcp_call(self, name: str, arguments: dict = None):
        """Low-level: issue a tools/call against the MCP surface verbatim."""
        response = requests.post(
            f"{self.url}/mcp",
            headers={"Authorization": f"Bearer {self.token}"},
            json={
                "jsonrpc": "2.0",
                "id": 1,
                "method": "tools/call",
                "params": {"name": name, "arguments": arguments or {}}
            },
            timeout=30
        )
        response.raise_for_status()
        result = response.json()

        if "error" in result:
            raise Exception(f"MCP Error: {result['error'].get('message', 'Unknown error')}")

        content = result.get("result", {}).get("content", [])
        if content and content[0].get("type") == "text":
            return json.loads(content[0]["text"])
        return result.get("result")

# --------------------------------------------------------------------------
# \`twenty\` is a pre-built instance of the TwentyMCP class above. It is
# already bound in this module scope — DO NOT \`import twenty\`. There is
# no Python package by that name. Just use it directly, e.g.:
#     companies = twenty.call_tool('find_many_companies', {'limit': 10})
# --------------------------------------------------------------------------
twenty = TwentyMCP()
`;

View on GitHub (pinned to 1f5dd2bbd2)

Solutions

  1. Read the embedded message — it is the server's JSON-RPC error detail.
  2. If the message indicates auth (401/403), rotate/regenerate TWENTY_API_TOKEN and confirm it is current.
  3. Confirm the tool name exists by calling `learn_tools` (an MCP-native tool) and inspecting the catalog before calling catalog tools.
  4. Check response.status_code by enriching the helper to log it — raise_for_status runs first, so a JSON-RPC error here means HTTP 200 with an error body, typical of MCP protocol-level failures.
  5. Verify version alignment between the code-interpreter helper and the Twenty server release.

Example fix

# before — only the message string is surfaced, hard to triage transport vs logic
if "error" in result:
    raise Exception(f"MCP Error: {result['error'].get('message', 'Unknown error')}")
# after — include the JSON-RPC error code and HTTP status for faster diagnosis
if "error" in result:
    err = result['error']
    raise Exception(
        f"MCP Error (code={err.get('code')}, http={response.status_code}): "
        f"{err.get('message', 'Unknown error')}"
    )
Defensive patterns

Strategy: try-catch

Validate before calling

# Cheap pre-check: confirm the /mcp endpoint is reachable and the token is accepted before user code calls tools.
import requests, os

def mcp_endpoint_ok() -> bool:
    url = os.environ.get('TWENTY_SERVER_URL', '')
    token = os.environ.get('TWENTY_API_TOKEN', '')
    if not (url and token):
        return False
    try:
        r = requests.post(
            f'{url}/mcp',
            headers={'Authorization': f'Bearer {token}'},
            json={'jsonrpc': '2.0', 'id': 1, 'method': 'tools/list', 'params': {}},
            timeout=5,
        )
        return r.status_code == 200 and 'error' not in r.json()
    except requests.RequestException:
        return False

Type guard

from typing import Any

def is_jsonrpc_error(response: Any) -> bool:
    return isinstance(response, dict) and 'error' in response

Try / catch

try:
    result = twenty.call_tool('find_many_companies', {'limit': 5})
except Exception as e:
    msg = str(e)
    if msg.startswith('MCP Error:'):
        # JSON-RPC level failure — auth, method-not-found, or server error
        if 'unauthorized' in msg.lower() or 'forbidden' in msg.lower():
            raise RuntimeError('TWENTY_API_TOKEN rejected by /mcp; rotate the token.') from e
        if 'method not found' in msg.lower() or 'not found' in msg.lower():
            raise RuntimeError('Tool name unknown to this server version; call learn_tools to refresh the catalog.') from e
        raise  # other server-side JSON-RPC errors
    raise

Prevention

When it happens

Trigger: Calling a tool name the /mcp endpoint does not expose (method/params rejected); the server is up but the MCP surface returned a JSON-RPC error (e.g. auth rejected at the gateway, malformed jsonrpc payload); a server-side exception inside the MCP handler that the framework serializes as a JSON-RPC error rather than an execute_tool failure envelope.

Common situations: Token is expired or revoked (auth middleware rejects before reaching the tool); calling an MCP-native tool name that was renamed or removed in a newer server version; a network proxy returning its own JSON body that happens to contain `error`; version skew between the helper's expected method names and the deployed server.

Related errors


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