263 lines
13 KiB
Markdown
263 lines
13 KiB
Markdown
# NocoBase CRM MCP
|
|
|
|
MCP server for remote agents that need customer, contact, follow-up, CRM overview, and tightly scoped project write access in NocoBase.
|
|
|
|
The default stdio server remains read-only. Its managed HTTP mode adds only fixed-schema project create/update tools and authenticated attachment uploads; it never exposes arbitrary collections, fields, commands, or delete operations.
|
|
|
|
## Configure
|
|
|
|
This repository already includes a local `config.json` generated for the current CRM. To regenerate it from the example:
|
|
|
|
```bash
|
|
cp config.example.json config.json
|
|
```
|
|
|
|
The known CRM collection names in this environment are:
|
|
|
|
- customers: `t_nt3pjadelqi` (`客户`)
|
|
- contacts: `t_j77ftx1rf84` (`客户联系人`)
|
|
- followups: `t_07667a5evtr` (`跟进记录`)
|
|
|
|
The checked-in example config uses the confirmed mapping:
|
|
|
|
- customer owner: `kehu_followup_person` (`跟进人`)
|
|
- customer status/progress: `kh_progress` (`进度`)
|
|
- customer source: `f_225h6n6v654` (`客户来源`)
|
|
- contact-to-customer relation: `kehulianxiren_client`
|
|
- follow-up-to-customer relation: `gjjl_client`
|
|
- follow-up-to-contact relation: `gjjl_client_contacts`
|
|
- actual follow-up participants: `gjjl_followup_person`
|
|
- follow-up record submitter: `createdBy` (audit only; not treated as the actual follower)
|
|
- follow-up date/content: `f_srysmjkwfq1` and `f_19iplxz0nds`
|
|
- teams: `t_team_management`, with `tdgl_manager` and `tdgl_team_members`
|
|
- formal employee status: `users.yh_status = nkluuumb1r1`
|
|
|
|
The default NocoBase CLI env is `nb-mcp`. Override it with:
|
|
|
|
```bash
|
|
export NOCOBASE_MCP_ENV=nb-mcp
|
|
export NOCOBASE_MCP_CONFIG=/home/nac/repo/nocobase-mcp/config.json
|
|
```
|
|
|
|
## Local stdio MCP
|
|
|
|
```bash
|
|
uv run nocobase-crm-mcp --config config.json
|
|
```
|
|
|
|
Exposed tools:
|
|
|
|
- `crm_health`
|
|
- `crm_schema`
|
|
- `crm_overview`
|
|
- `crm_filter_options`
|
|
- `resolve_owner`
|
|
- `list_teams`
|
|
- `resolve_team`
|
|
- `get_team`
|
|
- `team_crm_overview`
|
|
- `list_customers`
|
|
- `get_customer`
|
|
- `list_contacts`
|
|
- `list_followups`
|
|
- `list_projects`
|
|
- `get_project`
|
|
- `get_project_timeline`
|
|
- `list_stale_customers`
|
|
|
|
`crm_config_template` is hidden by default because it needs NocoBase data-modeling metadata permissions. Enable it only during configuration work:
|
|
|
|
```bash
|
|
NOCOBASE_MCP_ENABLE_CONFIG_TOOLS=1 uv run nocobase-crm-mcp --config config.json
|
|
```
|
|
|
|
The `owner` parameter on `crm_overview`, `list_customers`, and `list_followups` accepts a user's username, nickname, email, or English name. The server resolves that value through the `users` resource, then filters CRM relation fields by user id. Customer ownership uses `kehu_followup_person`; follow-up contribution uses `gjjl_followup_person` only. `createdBy` remains visible as the record submitter but is not counted as the actual follower. If a value is missing or ambiguous, the tool returns a structured `error` object with candidate users instead of throwing an MCP error.
|
|
|
|
Team-aware tools resolve `t_team_management.f_team_name`, then use only members whose `users.yh_status` is the confirmed formal-employee value. Team totals count each follow-up record once; member contribution counts each participant in `gjjl_followup_person`. Team managers, customer owners, participants, and record submitters remain separate in compact results.
|
|
|
|
Use `crm_filter_options` to discover status/source values and owner candidates before constructing filtered queries. Status/source options are sorted by count descending, with blank source values last.
|
|
|
|
List tools clamp `limit` to `1..100`, normalize negative offsets to `0`, and implement exact arbitrary offsets even when an offset is not divisible by the limit. Responses include `has_more` and `next_offset`. Compact date-time values use `Asia/Shanghai` and include an explicit `+08:00` offset; date-only project fields stay unchanged.
|
|
|
|
## Response shapes
|
|
|
|
Every tool returns readable keys, never NocoBase field codes. Relations always
|
|
resolve to the same `{"id": ..., "name": ...}` object, so the same customer looks
|
|
identical whether it appears in a follow-up row, a contact row, or a customer
|
|
detail. Keys whose value is empty are omitted — **an absent key means empty, not
|
|
unknown**. Pass `include_raw=true` on any of these tools when the original
|
|
NocoBase row is genuinely needed; it is then attached under `raw`.
|
|
|
|
A `list_followups` row:
|
|
|
|
```json
|
|
{
|
|
"id": 40,
|
|
"followedAt": "2026-07-08T17:00:00+08:00",
|
|
"customers": [{"id": 4249, "name": "Zomato(Eternal)"}],
|
|
"contacts": [{"id": 8, "name": "张三"}],
|
|
"participants": [{"id": 54, "name": "王小明"}],
|
|
"projects": [{"id": 41, "name": "EV inverter"}],
|
|
"type": "拜访/请客",
|
|
"status": "OK",
|
|
"location": "佛山",
|
|
"content": "full follow-up text, never truncated"
|
|
}
|
|
```
|
|
|
|
`content` is always the complete follow-up text. `type` and `status` are display
|
|
labels; `crm_filter_options` publishes the raw codes that the `followup_type` and
|
|
`record_status` filters expect. `creator` appears only when the record submitter
|
|
is not also one of the participants, since a self-submitted record would
|
|
otherwise name the same person twice.
|
|
|
|
`get_customer` returns `{customer, contacts, recent_followups, timezone}`. The
|
|
customer carries `id`, `name`, `english_name`, `territorial_name`, `status`,
|
|
`source`, `owners`, `managers`, plus the readable detail fields configured under
|
|
`detailFields` (`address`, `intro`, `tags`, `followup_interval_days`). Its
|
|
embedded contacts are `{id, name, title}` — phone and email are deliberately
|
|
withheld here; call `list_contacts` when they are actually needed, or
|
|
`include_details=false` there to omit them.
|
|
|
|
A customer name rendered as `#4249` means that record has no value in any of its
|
|
configured `nameFields`, so only its id could be shown. That is a data-quality
|
|
signal, not a name — fix the source record. The server never renders a bare id as
|
|
if it were a name.
|
|
|
|
`nameFields` (customers, contacts, projects) and `labelFields` (users) are
|
|
ordered: the first non-empty field wins. Add every field that can hold a name —
|
|
omitting one is how a customer ends up displayed as `#4249`.
|
|
|
|
The current NocoBase model has no structured next-step owner, due date, or completion status. The server therefore does not expose action-item, overdue-action, or generated daily/weekly report tools.
|
|
|
|
Tool runtime failures are returned as structured errors:
|
|
|
|
```json
|
|
{
|
|
"error": {
|
|
"code": "nb_command_failed",
|
|
"message": "...",
|
|
"hint": "...",
|
|
"retryable": false
|
|
}
|
|
}
|
|
```
|
|
|
|
## Project writes and uploads
|
|
|
|
Managed HTTP mode exposes two additional MCP tools:
|
|
|
|
- `create_project(name, customer_id?, inquiry_date?, progress?, applications?, part_numbers?, period_usage?, details?, attachment_ids?)`
|
|
- `update_project(project_id, name?, customer_id?, inquiry_date?, progress?, applications?, part_numbers?, period_usage?, details?, add_attachment_ids?, remove_attachment_ids?)`
|
|
|
|
They target only `t_projects` and the fixed field mapping in `projects` in `config.json`. A project name is required for creation; `inquiry_date` uses `YYYY-MM-DD`; progress accepts only `询价阶段`, `已报价`, `送样`, `已定点`, `SOP`, or `终止`. Attachments are added or removed incrementally—there is no replacement attachment-list operation.
|
|
|
|
The same HTTP service exposes authenticated `POST /uploads`. Submit exactly one multipart field named `file`; the default single-file limit is 25 MiB (`NOCOBASE_MCP_UPLOAD_MAX_BYTES=26214400`). A successful upload returns an `upload_id`, which callers can pass to either project tool. The upload route and `/mcp` use the same MCP bearer token.
|
|
|
|
HTTP write mode also requires one server-side NocoBase API key. The same key is supplied explicitly to every `nb api` CRM query and every direct HTTP upload/association request, so the service does not depend on the saved `nb-mcp` basic login session. Do not put these values in `config.json`, pass them to MCP clients, or log them:
|
|
|
|
- `NOCOBASE_MCP_API_BASE_URL` (for example `https://nocobase.example/api`)
|
|
- `NOCOBASE_MCP_API_TOKEN` (a scoped NocoBase API key)
|
|
|
|
Use a dedicated NocoBase account and role with only the permissions listed below. Choose an appropriate API-key validity period and rotate the key deliberately; if NocoBase rejects the key, MCP tools return `nb_auth_failed` or `nb_upload_unauthorized` instead of attempting a password login or retry loop.
|
|
|
|
`scripts/mcp_service.sh start` creates `.runtime/nocobase-crm-mcp.env` with mode `0600`, its parent directory with mode `0700`, and only the non-secret upload size setting. HTTP write mode fails at startup with the missing variable name when either credential is absent. For migration, the legacy pair `NOCOBASE_MCP_UPLOAD_API_BASE_URL` / `NOCOBASE_MCP_UPLOAD_JWT` remains accepted when both are present; conflicting canonical and legacy values are rejected.
|
|
|
|
First-install flow:
|
|
|
|
```bash
|
|
# Creates the private env file; the service is expected to fail until credentials are added.
|
|
bash scripts/mcp_service.sh start
|
|
|
|
# Add the server-side NocoBase API key to the systemd EnvironmentFile.
|
|
cat >> .runtime/nocobase-crm-mcp.env <<'EOF'
|
|
NOCOBASE_MCP_API_BASE_URL=https://nocobase.example/api
|
|
NOCOBASE_MCP_API_TOKEN=<scoped-api-key>
|
|
EOF
|
|
|
|
# Start the service with the newly configured private env file.
|
|
bash scripts/mcp_service.sh start
|
|
curl -X POST -H "Authorization: Bearer <mcp-token>" -F "file=@quote.pdf" http://<host>:10087/uploads
|
|
```
|
|
|
|
The NocoBase credentials belong in `.runtime/nocobase-crm-mcp.env`, not in shell-local variables or client configuration. MCP bearer-token rotation preserves these operator-managed env lines. `scripts/mcp_service.sh status` reports whether canonical or legacy NocoBase credentials are configured without printing their values. An uploaded attachment that is not yet associated with a project is intentionally retained; this release does not auto-clean orphan attachments. It also provides no delete operation for projects or attachments.
|
|
|
|
## Permissions
|
|
|
|
Runtime tools are read-only, but the role behind `nb-mcp` needs these read permissions:
|
|
|
|
- CRM collections: `t_nt3pjadelqi`, `t_j77ftx1rf84`, `t_07667a5evtr`
|
|
- user lookup collection: `users`, at least `id`, `username`, `nickname`, `email`, `f_yh_english_name`
|
|
|
|
`crm_schema(include_fields=true)` and the opt-in `crm_config_template` are configuration helpers. They use this read-only NocoBase data-modeling command:
|
|
|
|
```bash
|
|
nb api data-modeling collections fields list --collection-name <collection> --env nb-mcp --yes
|
|
```
|
|
|
|
Use a super-admin or configuration-capable role for this one-time discovery step. The remote Agent does not need data-modeling access after `config.json` is generated.
|
|
|
|
For the read-only stdio/default server, do not grant write permissions such as collection apply/destroy, fields apply, or business data create/update/destroy.
|
|
|
|
For the managed HTTP write mode only, the NocoBase JWT role additionally needs:
|
|
|
|
- `t_projects`: create and update
|
|
- `attachments`: upload/create
|
|
- `t_projects.f_projects_attachments`: add and remove attachment relations
|
|
|
|
Do not grant project or attachment delete permissions. This server does not offer deletes, and it retains uploaded but unassociated attachments.
|
|
|
|
## Remote HTTP MCP
|
|
|
|
HTTP mode uses streamable HTTP with a separate bearer token for MCP access:
|
|
|
|
```bash
|
|
export NOCOBASE_MCP_TOKEN='<long-random-token>'
|
|
uv run nocobase-crm-mcp --transport streamable-http --config config.json
|
|
```
|
|
|
|
Default bind is `0.0.0.0:10087` with path `/mcp`.
|
|
|
|
Remote clients must send:
|
|
|
|
```http
|
|
Authorization: Bearer <long-random-token>
|
|
```
|
|
|
|
For a managed user-level systemd service:
|
|
|
|
```bash
|
|
bash scripts/mcp_service.sh start
|
|
```
|
|
|
|
The first `start` creates `.runtime/nocobase-crm-mcp.env` with a random bearer token automatically. It also discovers the current `nb` executable and saves it as `NOCOBASE_MCP_NB_BIN`, because the systemd user service does not inherit an interactive shell or nvm PATH. Use `new-token` only when rotating the token later.
|
|
|
|
If `nb` is installed somewhere unusual, set its absolute path before starting:
|
|
|
|
```bash
|
|
NOCOBASE_MCP_NB_BIN=/absolute/path/to/nb bash scripts/mcp_service.sh start
|
|
```
|
|
|
|
The generated env file also includes the non-secret 25 MiB upload limit. The service fails at startup until the operator adds `NOCOBASE_MCP_API_BASE_URL` and `NOCOBASE_MCP_API_TOKEN` to that private file. Run `start` again after adding them; `new-token` preserves those operator-managed credential lines.
|
|
|
|
Show the saved URL/header/client config:
|
|
|
|
```bash
|
|
bash scripts/mcp_service.sh show-config
|
|
```
|
|
|
|
Check service state, saved paths, saved `nb` executable, URL, and local HTTP probe:
|
|
|
|
```bash
|
|
bash scripts/mcp_service.sh status
|
|
```
|
|
|
|
Stop or uninstall:
|
|
|
|
```bash
|
|
bash scripts/mcp_service.sh stop
|
|
bash scripts/mcp_service.sh uninstall
|
|
```
|
|
|
|
The systemd unit references `.runtime/nocobase-crm-mcp.env`; it does not embed the bearer token. The env file is written with `0600` permissions and `.runtime/` is ignored.
|