85 lines
2.8 KiB
Python
85 lines
2.8 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import subprocess
|
|
from collections.abc import Callable
|
|
from typing import Any
|
|
|
|
from .nb_client import NbAuthenticationError, NbCommandError
|
|
|
|
ENABLE_RAW_DATA_ENV = "NOCOBASE_MCP_ENABLE_RAW_DATA"
|
|
|
|
|
|
def safe_tool_result(operation: Callable[[], dict[str, Any]]) -> dict[str, Any]:
|
|
try:
|
|
return operation()
|
|
except NbAuthenticationError as error:
|
|
return tool_error(
|
|
code="nb_auth_failed",
|
|
message=str(error),
|
|
hint=(
|
|
"Ask the local installation Agent to run the secure connect-key flow, then enter your personal "
|
|
"NocoBase API Key only in the private local prompt."
|
|
),
|
|
retryable=False,
|
|
)
|
|
except NbCommandError as error:
|
|
return tool_error(
|
|
code="nb_command_failed",
|
|
message=str(error),
|
|
hint="Check nb env status, NocoBase ACL, collection names, and query parameters.",
|
|
retryable=False,
|
|
)
|
|
except subprocess.TimeoutExpired as error:
|
|
return tool_error(
|
|
code="nb_command_timeout",
|
|
message=str(error),
|
|
hint="The NocoBase CLI call timed out. Retry with a smaller query or check NocoBase responsiveness.",
|
|
retryable=True,
|
|
)
|
|
except FileNotFoundError as error:
|
|
return tool_error(
|
|
code="nb_not_found",
|
|
message=str(error),
|
|
hint="Set NOCOBASE_MCP_NB_BIN to the absolute nb path, then restart with scripts/mcp_service.sh start.",
|
|
retryable=False,
|
|
)
|
|
except Exception as error:
|
|
return tool_error(
|
|
code="tool_error",
|
|
message=str(error),
|
|
hint="Check MCP server logs and the CRM mapping config.",
|
|
retryable=False,
|
|
)
|
|
|
|
|
|
def safe_raw_tool_result(include_raw: bool, operation: Callable[[], dict[str, Any]]) -> dict[str, Any]:
|
|
if include_raw and os.environ.get(ENABLE_RAW_DATA_ENV, "").strip() != "1":
|
|
return tool_error(
|
|
code="raw_data_disabled",
|
|
message="Raw NocoBase rows are disabled for this local MCP installation.",
|
|
hint=f"Keep include_raw=false, or set {ENABLE_RAW_DATA_ENV}=1 after approving the data exposure policy.",
|
|
retryable=False,
|
|
)
|
|
return safe_tool_result(operation)
|
|
|
|
|
|
def tool_error(*, code: str, message: str, hint: str, retryable: bool) -> dict[str, Any]:
|
|
return {"error": {"code": code, "message": message, "hint": hint, "retryable": retryable}}
|
|
|
|
|
|
# Compatibility aliases for internal imports and older tests.
|
|
_safe_tool_result = safe_tool_result
|
|
_safe_raw_tool_result = safe_raw_tool_result
|
|
_tool_error = tool_error
|
|
|
|
__all__ = [
|
|
"ENABLE_RAW_DATA_ENV",
|
|
"safe_tool_result",
|
|
"safe_raw_tool_result",
|
|
"tool_error",
|
|
"_safe_tool_result",
|
|
"_safe_raw_tool_result",
|
|
"_tool_error",
|
|
]
|