Files
nocobase-mcp/README.md
T

13 KiB

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:

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:

export NOCOBASE_MCP_ENV=nb-mcp
export NOCOBASE_MCP_CONFIG=/home/nac/repo/nocobase-mcp/config.json

Local stdio MCP

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:

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:

{
  "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:

{
  "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:

# 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:

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:

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:

Authorization: Bearer <long-random-token>

For a managed user-level systemd service:

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:

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 scripts/mcp_service.sh show-config

Check service state, saved paths, saved nb executable, URL, and local HTTP probe:

bash scripts/mcp_service.sh status

Stop or uninstall:

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.