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

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:

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

To expose the two fixed project write tools using the same user's CLI login:

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:

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:

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

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:

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

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:

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:

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. 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 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.

S
Description
Local TypeScript MCP for NocoBase CRM using each user's personal Authorize Key
Readme
624 KiB
v0.3.1
Latest
2026-08-15 14:04:55 +08:00
Languages
TypeScript 99.5%
JavaScript 0.5%