Files
nocobase-mcp/nocobase_crm_mcp/server_tools_records.py
T

162 lines
7.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_record_tools(server: Any, service: Any, safe_result: SafeResult, safe_raw_result: SafeRawResult) -> None:
@server.tool()
def list_customers(
query: Annotated[str | None, Field(description="Optional customer keyword search.")] = None,
status: Annotated[str | None, Field(description="Optional exact customer status.")] = None,
owner: Annotated[
str | None, Field(description="Optional customer owner name, username, email, or English name.")
] = None,
team: Annotated[str | None, Field(description="Optional CRM team name.")] = None,
source: Annotated[str | None, Field(description="Optional exact customer source.")] = None,
limit: Annotated[int, Field(description="Maximum rows to return. Defaults to 20.")] = 20,
offset: Annotated[int, Field(description="Zero-based result offset. Defaults to 0.")] = 0,
include_raw: Annotated[
bool, Field(description="When true, include the original NocoBase row under raw. Defaults to false.")
] = False,
) -> dict[str, Any]:
"""List CRM customers with compact rows and pagination metadata.
Rows are {id, name, english_name, territorial_name, status, source,
owners: [{id, name}], managers, createdAt, updatedAt}. Empty values are
omitted. A name shown as "#123" means that customer has no name recorded.
"""
return safe_raw_result(
include_raw,
lambda: service.list_customers(
query=query,
status=status,
owner=owner,
team=team,
source=source,
limit=limit,
offset=offset,
include_raw=include_raw,
),
)
@server.tool()
def get_customer(
customer_id: Annotated[Any, Field(description="Customer primary key value.")],
include_contacts: Annotated[bool, Field(description="Include related contacts.")] = True,
include_recent_followups: Annotated[bool, Field(description="Include recent related follow-ups.")] = True,
followup_limit: Annotated[int, Field(description="Maximum related follow-ups to return.")] = 10,
include_raw: Annotated[
bool,
Field(description="When true, include original NocoBase rows under raw. Defaults to false."),
] = False,
) -> dict[str, Any]:
"""Get one customer with its contacts and recent follow-ups.
Returns {customer, contacts, recent_followups, timezone}. The customer is
{id, name, english_name, territorial_name, status, source, owners, managers,
address, intro, tags, ...} using readable keys, never NocoBase field codes.
Contacts are {id, name, title} without phone or email -- call list_contacts
for those. Follow-ups use the same row shape as list_followups. Empty values
are omitted, so a missing key means empty rather than unknown.
"""
return safe_raw_result(
include_raw,
lambda: service.get_customer(
customer_id,
include_contacts=include_contacts,
include_recent_followups=include_recent_followups,
followup_limit=followup_limit,
include_raw=include_raw,
),
)
@server.tool()
def list_contacts(
customer_id: Annotated[
Any | None, Field(description="Optional customer primary key for relation filtering.")
] = None,
query: Annotated[str | None, Field(description="Optional contact keyword search.")] = None,
limit: Annotated[int, Field(description="Maximum rows to return. Defaults to 20.")] = 20,
offset: Annotated[int, Field(description="Zero-based result offset. Defaults to 0.")] = 0,
include_details: Annotated[
bool,
Field(description="Include contact phone and email. Defaults to true."),
] = True,
include_raw: Annotated[
bool, Field(description="When true, include the original NocoBase row under raw. Defaults to false.")
] = False,
) -> dict[str, Any]:
"""List CRM contacts as compact rows, optionally filtered by customer.
Rows are {id, name, title, phone, email, customers: [{id, name}],
createdAt, updatedAt}. Set include_details=false to omit phone and email.
Empty values are omitted.
"""
return safe_raw_result(
include_raw,
lambda: service.list_contacts(
customer_id=customer_id,
query=query,
limit=limit,
offset=offset,
include_details=include_details,
include_raw=include_raw,
),
)
@server.tool()
def list_followups(
customer_id: Annotated[
Any | None, Field(description="Optional customer primary key for relation filtering.")
] = None,
contact_id: Annotated[
Any | None, Field(description="Optional contact primary key for relation filtering.")
] = None,
owner: Annotated[
str | None, Field(description="Optional follow-up participant name, username, email, or English name.")
] = None,
team: Annotated[str | None, Field(description="Optional CRM team name.")] = None,
project_id: Annotated[Any | None, Field(description="Optional CRM project ID.")] = None,
followup_type: Annotated[str | None, Field(description="Optional exact follow-up type value.")] = None,
record_status: Annotated[str | None, Field(description="Optional exact follow-up record status value.")] = None,
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,
limit: Annotated[int, Field(description="Maximum rows to return. Defaults to 20.")] = 20,
offset: Annotated[int, Field(description="Zero-based result offset. Defaults to 0.")] = 0,
include_raw: Annotated[
bool, Field(description="When true, include the original NocoBase row under raw. Defaults to false.")
] = False,
) -> dict[str, Any]:
"""List customer follow-up records as compact rows with pagination metadata.
Rows are {id, followedAt, customers: [{id, name}], contacts, participants,
projects, type, status, location, content}. Content is the full follow-up
text, never truncated. type and status are the display labels; use
crm_filter_options for the codes the followup_type and record_status
filters expect. creator appears only when the submitter is not also a
participant. Empty values are omitted.
"""
return safe_raw_result(
include_raw,
lambda: service.list_followups(
customer_id=customer_id,
contact_id=contact_id,
owner=owner,
team=team,
project_id=project_id,
followup_type=followup_type,
record_status=record_status,
date_from=date_from,
date_to=date_to,
limit=limit,
offset=offset,
include_raw=include_raw,
),
)