Files
nocobase-mcp/README.md
T

286 lines
16 KiB
Markdown

# NocoBase CRM MCP
Local MCP server for end users who need customer, contact, follow-up, CRM overview, and tightly scoped project write access in NocoBase.
Each user runs this MCP on their own computer and signs in to the remote NocoBase environment with `nb` CLI. Every query and write therefore uses that user's NocoBase identity and ACL. The project does not require a shared remote MCP service or a shared NocoBase service account.
For an agent-guided installation that works across MCP clients, give the installing Agent the immutable Release version of [`INSTALL.md`](INSTALL.md). The guide combines adaptive prompts with an idempotent helper script; it defaults to local stdio and read-only access. Do not distribute a default-branch installation link as a release.
The default stdio server is read-only. Add `--enable-writes` for fixed-schema project create/update tools. Optional local HTTP mode also provides authenticated attachment uploads. Neither mode exposes arbitrary collections, fields, commands, or delete operations.
## Configure
The supported end-user flow uses a personal NocoBase API Key (Authorize Key), not a username/password or a shared service credential. The installing Agent asks the user to create a revocable Key in NocoBase, then starts this helper in a user-controlled local terminal:
```bash
uv run --no-project --python 3.12 python scripts/agent_install.py \
--project-root . connect-key --api-base-url https://nocobase.example --env-name nb-mcp --json
```
The user pastes the Key only into `nb` CLI's private TTY prompt. The helper never accepts the Key as an argument, environment variable, chat value, file, captured output, or return value. Do not copy another user's Key or `nb` configuration directory. The Key must represent that user's normal business identity/role, so NocoBase can apply the intended roles, row-level filters, and field permissions to every operation.
This repository includes a `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
```
To expose the two fixed project write tools using the same user's CLI login:
```bash
uv run nocobase-crm-mcp --config config.json --enable-writes
```
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.
Nested bounded lists are explicit as well: `get_customer` includes `contacts_pagination` and `recent_followups_pagination`, while `get_project` includes `followups_pagination`. Large analytical scans return a `truncation` object with `total`, `returned`, `limit`, and `next_offset`; aggregation queries fail with a narrowing hint instead of silently discarding buckets. Team member resolution paginates through all related users rather than stopping at 100.
## 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`.
Raw rows are a separate data-exposure capability and are disabled at the MCP boundary by default. An administrator must explicitly set `NOCOBASE_MCP_ENABLE_RAW_DATA=1` on that user's local installation after reviewing the configured fields and organizational policy. NocoBase ACL still applies, but enabling raw output can send more permitted business fields to the AI client than compact mode.
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
Local HTTP mode, or stdio with `--enable-writes`, 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.
Attachment upload is disabled by default. After an administrator confirms that the user's NocoBase role may create or update `t_projects`, set `NOCOBASE_MCP_ENABLE_UPLOADS=1` to expose 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 local MCP bearer token.
Every upload first calls NocoBase `roles:check` with the current user's credential. The MCP follows NocoBase's global and collection-specific ACL resolution and requires `t_projects:create` or `t_projects:update` before it calls `attachments:create`. A role without project write permission receives HTTP `403` with `nb_upload_forbidden`, even if that NocoBase installation otherwise allows the generic attachment-create endpoint.
Queries and writes use the saved `nb-mcp` personal Key login. For multipart uploads and attachment associations, the MCP asks `nb env info nb-mcp --json --show-secrets` for the current credential at request time. The credential is held only in memory: it is not placed in `config.json`, the MCP client configuration, logs, or subprocess arguments. If the Key is missing, expired, or revoked, ask the local Agent to rerun the secure `connect-key` flow and enter a newly issued personal Key in the private prompt.
`scripts/mcp_service.sh start` creates `.runtime/nocobase-crm-mcp.env` with mode `0600`, its parent directory with mode `0700`, a random local MCP bearer token, and only non-secret runtime settings. The legacy `NOCOBASE_MCP_API_BASE_URL` / `NOCOBASE_MCP_API_TOKEN` override remains accepted for existing operator deployments, but it is not part of the end-user installation path.
First-install flow:
```bash
# The Agent first runs connect-key in a user-controlled TTY. The user pastes a
# personal NocoBase API Key only into the hidden prompt; it never enters argv.
uv run --no-project --python 3.12 python scripts/agent_install.py \
--project-root . connect-key --api-base-url https://nocobase.example --env-name nb-mcp --json
# Create and start the private, loopback-only user service.
bash scripts/mcp_service.sh start
# Optional: after ACL approval, change this generated 0600 file to
# NOCOBASE_MCP_ENABLE_UPLOADS=1, then restart the service.
${EDITOR:-vi} .runtime/nocobase-crm-mcp.env
bash scripts/mcp_service.sh start
curl -X POST -H "Authorization: Bearer <mcp-token>" -F "file=@quote.pdf" http://127.0.0.1:10087/uploads
```
The NocoBase login belongs to the user's own `nb` environment, not `.runtime/nocobase-crm-mcp.env`. The MCP bearer token protects only the loopback HTTP endpoint. An authorized upload that is not yet associated with a project is intentionally retained; this release does not auto-clean orphan attachments. Enable uploads only for approved project writers and monitor unassociated attachments. The MCP provides no delete operation for projects or attachments.
## Permissions
The logged-in user's NocoBase roles need 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 either write-enabled mode, the logged-in user additionally needs:
- `t_projects`: create and update
- `attachments`: upload/create
- `t_projects.f_projects_attachments`: add and remove attachment relations
Keep `NOCOBASE_MCP_ENABLE_UPLOADS=0` for read-only users and project roles without create/update access. Before enabling uploads, test the exact role with both `roles:check` and a real denied/allowed upload case. Do not grant project or attachment delete permissions solely for MCP use; this server does not expose deletes, and operators should handle any retained orphan attachments through an audited administrative cleanup process.
## Local HTTP MCP (optional)
HTTP mode uses streamable HTTP with a separate local bearer token for MCP access:
```bash
export NOCOBASE_MCP_TOKEN='<long-random-token>'
export NOCOBASE_MCP_ENABLE_UPLOADS=0
uv run nocobase-crm-mcp --transport streamable-http --config config.json
```
Default bind is `127.0.0.1:10087` with path `/mcp`. The supported end-user deployment is loopback-only; do not expose this port to a LAN or the public internet.
Local 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. NocoBase access comes from the same operating-system user's saved `nb-mcp` personal Key. If the MCP reports `nb_auth_failed` or `nb_upload_unauthorized`, ask the local Agent to rerun the secure `connect-key` flow; never paste the Key into chat or a command argument.
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.