Files
nocobase-mcp/nocobase_crm_mcp/server_tools_core.py
T

140 lines
5.6 KiB
Python

from __future__ import annotations
from collections.abc import Callable
from typing import Annotated, Any
from pydantic import Field
SafeResult = Callable[[Callable[[], dict[str, Any]]], dict[str, Any]]
SafeRawResult = Callable[[bool, Callable[[], dict[str, Any]]], dict[str, Any]]
def register_core_tools(server: Any, service: Any, safe_result: SafeResult, *, enable_config_tools: bool) -> None:
@server.tool()
def crm_health() -> dict[str, Any]:
"""Check NocoBase CLI reachability and the configured CRM collection mapping."""
return safe_result(service.crm_health)
@server.tool()
def crm_schema(
include_fields: Annotated[
bool,
Field(description="When true, try to include NocoBase collection and field metadata."),
] = False,
) -> dict[str, Any]:
"""Return the configured CRM collection and field mapping for this MCP server."""
return safe_result(lambda: service.crm_schema(include_fields=include_fields))
@server.tool()
def crm_overview(
date_from: Annotated[
str | None,
Field(
description="Optional lower bound for follow-up date, in YYYY-MM-DD or an accepted NocoBase date shortcut."
),
] = None,
date_to: Annotated[
str | None,
Field(
description="Optional upper bound for follow-up date, in YYYY-MM-DD or an accepted NocoBase date shortcut."
),
] = None,
owner: Annotated[
str | None,
Field(description="Optional user to filter customer ownership and follow-up participation."),
] = None,
) -> dict[str, Any]:
"""Return CRM totals, common customer distributions, and recent follow-ups."""
return safe_result(lambda: service.crm_overview(date_from=date_from, date_to=date_to, owner=owner))
@server.tool()
def crm_filter_options(
owner_query: Annotated[
str | None,
Field(description="Optional owner search text. Returns matching user candidates for owner filters."),
] = None,
) -> dict[str, Any]:
"""Return available customer filter values and optional owner candidates."""
return safe_result(lambda: service.crm_filter_options(owner_query=owner_query))
@server.tool()
def resolve_owner(
query: Annotated[str, Field(description="Owner username, nickname, email, or English name to resolve.")],
) -> dict[str, Any]:
"""Return candidate NocoBase users for an owner filter before querying CRM data."""
return safe_result(lambda: service.resolve_owner(query))
@server.tool()
def list_teams(
query: Annotated[str | None, Field(description="Optional team name search.")] = None,
include_members: Annotated[bool, Field(description="Include resolved team members.")] = False,
formal_members_only: Annotated[
bool,
Field(description="When including members, keep only formal employees."),
] = True,
limit: Annotated[int, Field(description="Maximum teams to return.")] = 20,
offset: Annotated[int, Field(description="Zero-based result offset.")] = 0,
) -> dict[str, Any]:
"""List configured CRM teams and optionally their formal members."""
return safe_result(
lambda: service.list_teams(
query,
include_members=include_members,
formal_members_only=formal_members_only,
limit=limit,
offset=offset,
)
)
@server.tool()
def resolve_team(
query: Annotated[str, Field(description="Team name to resolve.")],
) -> dict[str, Any]:
"""Resolve a team name before using it as a CRM filter."""
return safe_result(lambda: service.resolve_team(query))
@server.tool()
def get_team(
team_id: Annotated[Any, Field(description="NocoBase team ID.")],
include_members: Annotated[bool, Field(description="Include team members.")] = True,
formal_members_only: Annotated[
bool,
Field(description="Return only formal employees when members are included."),
] = True,
) -> dict[str, Any]:
"""Get one CRM team, its managers, and optionally its members."""
return safe_result(
lambda: service.get_team(
team_id,
include_members=include_members,
formal_members_only=formal_members_only,
)
)
@server.tool()
def team_crm_overview(
team: Annotated[str, Field(description="Exact or uniquely matching CRM team name.")],
date_from: Annotated[str | None, Field(description="Optional lower follow-up date bound.")] = None,
date_to: Annotated[str | None, Field(description="Optional upper follow-up date bound.")] = None,
include_member_breakdown: Annotated[
bool,
Field(description="Include per-member customer ownership and follow-up contribution."),
] = True,
) -> dict[str, Any]:
"""Return team totals and member contributions using formal CRM team membership."""
return safe_result(
lambda: service.team_crm_overview(
team,
date_from=date_from,
date_to=date_to,
include_member_breakdown=include_member_breakdown,
)
)
if enable_config_tools:
@server.tool()
def crm_config_template() -> dict[str, Any]:
"""Infer a config.json template from collection field metadata without reading business records."""
return safe_result(service.crm_config_template)